001    /*
002     * Copyright (c) 2009 The openGion Project.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013     * either express or implied. See the License for the specific language
014     * governing permissions and limitations under the License.
015     */
016    package org.opengion.fukurou.process;
017    
018    import org.opengion.fukurou.util.Argument;
019    import org.opengion.fukurou.util.StringUtil;
020    import org.opengion.fukurou.util.FileUtil;
021    import org.opengion.fukurou.util.Closer ;
022    import org.opengion.fukurou.util.LogWriter;
023    
024    import java.util.Map ;
025    import java.util.HashMap ;
026    import java.util.LinkedHashMap ;
027    
028    import java.io.File;
029    import java.io.BufferedReader;
030    import java.io.IOException;
031    
032    /**
033     * Process_TableDiffは、ファイルから読み取った?容を?LineModel に設定後?
034     * 下流に渡す?FirstProcess インターフェースの実?ラスです?
035     *
036     * DBTableModel 形式?ファイルを読み取って、各行を LineModel にセ?して?
037     * 下?プロセスチェインの??タは上流から下流に渡されます?)に渡します?
038     *
039     * 引数??中にスペ?スを含??合?、ダブルコー??ション("") で括って下さ??
040     * 引数??の ?』?前後には、スペ?スは挟めません。??key=value の様に
041     * 繋げてください?
042     *
043     * @og.formSample
044     *  Process_TableDiff -infile1=INFILE -infile2=INFILE2 -action=DIFF1 -encode=UTF-8 -columns=AA,BB,CC
045     *
046     *    -infile1=入力ファイル?    ??力ファイル?
047     *    -infile2=入力ファイル?    ??力ファイル?
048     *    -action=比?果の方?     ?ONLY,DIFF,INTERSEC
049     *   [-sep1=セパレータ??     ] ?区???(初期値:タ?
050     *   [-sep2=セパレータ??     ] ?区???(初期値:タ?
051     *   [-encode1=?エンコー?  ] ??力ファイルのエンコードタイ?
052     *   [-encode2=?エンコー?  ] ??力ファイルのエンコードタイ?
053     *   [-columns=読み取りカラ? ] ??力カラ?(カンマ区?)
054     *   [-keyClms=比?るカラ? ] ?比?る?の基準カラ?(カンマ区?)
055     *   [-diffClms=比?るカラ?] ?比?るカラ?(カンマ区?)
056     *   [-display=false|true       ] ?結果を標準?力に表示する(true)かしな?false)?初期値:false[表示しない])
057     *   [-debug=false|true         ] ?デバッグ??を標準?力に表示する(true)かしな?false)?初期値:false[表示しない])
058     *
059     * @og.rev 4.2.3.0 (2008/05/26) 新規作?
060     *
061     * @version  4.0
062     * @author   Kazuhiko Hasegawa
063     * @since    JDK5.0,
064     */
065    public class Process_TableDiff extends AbstractProcess implements FirstProcess {
066            private static final String ENCODE = System.getProperty("file.encoding");
067    
068            private String                  separator1      = TAB;  // ?区???
069            private String                  separator2      = TAB;  // ?区???
070            private String                  infile1         = null;
071            private String                  infile2         = null;
072            private BufferedReader  reader1         = null;
073    //      private BufferedReader  reader2         = null;
074            private LineModel               model           = null;
075            private String                  line            = null;
076            private int[]                   clmNos          = null;         // ファイルのヘッ??のカラ?号
077            private int[]                   keyClmNos       = null;         // 比?る?の基準カラ?のカラ?号
078            private int[]                   diffClmNos      = null;         // 比?るカラ?のカラ?号
079    //      private String                  action          = null;
080            private String                  actCmnd         = null;         // action から名称変更
081            private boolean                 display         = false;        // 表示しな?
082            private boolean                 debug           = false;        // 表示しな?
083            private boolean                 nameNull        = false;        // ?件??タ?true
084    
085            private final Map<String,String> file2Map = new HashMap<String,String>();   // 4.3.1.1 (2008/08/23) final?
086    
087            private int                             inCount1        = 0;
088            private int                             inCount2        = 0;
089            private int                             outCount        = 0;
090    
091            private static final Map<String,String> mustProparty   ;          // ?プロパティ???チェ?用 Map
092            private static final Map<String,String> usableProparty ;          // ?プロパティ?整合?チェ? Map
093    
094            static {
095                    mustProparty = new LinkedHashMap<String,String>();
096                    mustProparty.put( "infile1",    "入力ファイル? (??)" );
097                    mustProparty.put( "infile2",    "入力ファイル? (??)" );
098                    mustProparty.put( "action",             "(??)ONLY,DIFF,INTERSEC" );
099                    mustProparty.put( "keyClms",    "比?る?の基準カラ?(??)(カンマ区?)" );
100                    mustProparty.put( "diffClms",   "比?るカラ?(??)(カンマ区?)" );
101    
102                    usableProparty = new LinkedHashMap<String,String>();
103                    usableProparty.put( "sep1",                     "区??? (初期値:タ?" );
104                    usableProparty.put( "sep2",                     "区??? (初期値:タ?" );
105                    usableProparty.put( "encode1",          "入力ファイルのエンコードタイ?" );
106                    usableProparty.put( "encode2",          "入力ファイルのエンコードタイ?" );
107                    usableProparty.put( "columns",          "入力カラ?(カンマ区?)" );
108                    usableProparty.put( "display",          "結果を標準?力に表示する(true)かしな?false)? +
109                                                                                            CR + " (初期値:false:表示しな?" );
110                    usableProparty.put( "debug",            "????を標準?力に表示する(true)かしな?false)? +
111                                                                                            CR + " (初期値:false:表示しな?" );
112            }
113    
114            /**
115             * ?ォルトコンストラクター?
116             * こ?クラスは、動??されます??ォルトコンストラクターで?
117             * super クラスに対して、?な初期化を行っておきます?
118             *
119             */
120            public Process_TableDiff() {
121                    super( "org.opengion.fukurou.process.Process_TableDiff",mustProparty,usableProparty );
122            }
123    
124            /**
125             * プロセスの初期化を行います?初めに??、呼び出されます?
126             * 初期処?ファイルオープン??オープン?に使用します?
127             *
128             * @param   paramProcess ??タベ?スの接続???などを持って?オブジェク?
129             */
130            public void init( final ParamProcess paramProcess ) {
131                    Argument arg = getArgument();
132    
133                    infile1                         = arg.getProparty( "infile1" );
134                    infile2                         = arg.getProparty( "infile2" );
135                    actCmnd                         = arg.getProparty( "action"  );
136                    String  encode1         = arg.getProparty( "encode1",ENCODE );
137                    String  encode2         = arg.getProparty( "encode2",ENCODE );
138                    separator1                      = arg.getProparty( "sep1",separator1 );
139                    separator2                      = arg.getProparty( "sep2",separator2 );
140                    String  clms            = arg.getProparty( "columns"  );
141                    String  keyClms         = arg.getProparty( "keyClms"  );
142                    String  diffClms        = arg.getProparty( "diffClms" );
143                    display                         = arg.getProparty( "display",display );
144                    debug                           = arg.getProparty( "debug"  ,debug );
145    
146                    if( infile1 == null || infile2 == null ) {
147                            String errMsg = "ファイル名が?されて?せん?
148                                                    + "File1=[" + infile1 + "] , File2=[" + infile2 + "]" ;
149                            throw new RuntimeException( errMsg );
150                    }
151    
152                    File file1 = new File( infile1 );
153                    File file2 = new File( infile2 );
154    
155                    if( ! file1.exists() || ! file2.exists() ) {
156                            // 4.3.1.1 (2008/08/23) Avoid if (x != y) ..; else ..;
157                            String errMsg = "ファイルが存在しません?
158                                                    + ((file1.exists()) ? "" : "File1=[" + file1 + "] " )
159                                                    + ((file2.exists()) ? "" : "File2=[" + file2 + "]" );
160                            throw new RuntimeException( errMsg );
161                    }
162    
163                    if( ! file1.isFile() || ! file2.isFile() ) {
164                            // 4.3.1.1 (2008/08/23) Avoid if (x != y) ..; else ..;
165                            String errMsg = "フォル???できません。ファイル名を?してください?
166                                                    + ((file1.isFile()) ? "" : "File1=[" + file1 + "] " )
167                                                    + ((file2.isFile()) ? "" : "File2=[" + file2 + "]" );
168                            throw new RuntimeException( errMsg );
169                    }
170    
171                    reader1 = FileUtil.getBufferedReader( file1,encode1 );
172    //              reader2 = FileUtil.getBufferedReader( file2,encode2 );
173    
174                    final String[] names ;
175                    if( clms != null ) {
176                            names = StringUtil.csv2Array( clms );   // ??カラ?配?
177                    }
178                    else {
179                            String[] clmNames = readName( reader1 );                // ファイルのカラ?配?
180                            if( clmNames == null || clmNames.length == 0 ) { nameNull = true; return ; }
181                            names = clmNames;
182                    }
183    
184                    model = new LineModel();
185                    model.init( names );
186    
187                    if( display ) { println( model.nameLine() ); }
188    
189                    // 入力カラ?のカラ?号
190                    clmNos = new int[names.length];
191                    for( int i=0; i<names.length; i++ ) {
192                            clmNos[i] = i+1;                                                // 行番号??1しておく?
193    //                      int no = model.getColumnNo( names[i] );
194    //                      if( no >= 0 ) { clmNos[no] = i+1; }          // 行番号??1しておく?
195                    }
196    
197                    // 比?る?の基準カラ?
198                    if( debug ) { println( "DEBUG:\tkeyClms=" + keyClms ); }
199                    final String[] keyClmNms = StringUtil.csv2Array( keyClms );
200                    keyClmNos = new int[keyClmNms.length];
201                    for( int i=0; i<keyClmNms.length; i++ ) {
202                            keyClmNos[i] = model.getColumnNo( keyClmNms[i] );
203            //              if( debug ) { println( "DEBUG:" + keyClmNms[i] + ":[" + keyClmNos[i] + "]" ); }
204            //              int no = model.getColumnNo( keyClmNms[i] );
205            //              if( no >= 0 ) { keyClmNos[no] = i+1; }               // 行番号??1しておく?
206                    }
207    
208                    // 比?るカラ?
209                    if( debug ) { println( "DEBUG:\tdiffClms=" + diffClms ); }
210                    final String[] diffClmNms = StringUtil.csv2Array( diffClms );
211                    diffClmNos = new int[diffClmNms.length];
212                    for( int i=0; i<diffClmNms.length; i++ ) {
213                            diffClmNos[i] = model.getColumnNo( diffClmNms[i] );
214            //              if( debug ) { println( "DEBUG:" + diffClmNms[i] + ":[" + diffClmNos[i] + "]" ); }
215            //              int no = model.getColumnNo( diffClmNms[i] );
216            //              if( no >= 0 ) { diffClmNos[no] = i+1; }              // 行番号??1しておく?
217                    }
218    
219                    readF2Data( file2,encode2 );
220            }
221    
222            /**
223             * プロセスの終?行います??に??、呼び出されます?
224             * 終???ファイルクローズ??クローズ?に使用します?
225             *
226             * @param   isOK ト?タルで、OK?たかど?[true:成功/false:失敗]
227             */
228            public void end( final boolean isOK ) {
229                    Closer.ioClose( reader1 );
230                    reader1 = null;
231            }
232    
233            /**
234             * こ???タの処?おいて、次の処?出来るかど?を問?わせます?
235             * こ?呼び出し1回毎に、次の??タを取得する準備を行います?
236             *
237             * @return      処?きる:true / 処?きな?false
238             */
239            public boolean next() {
240                    if( nameNull ) { return false; }
241    
242                    boolean flag = false;
243                    try {
244                            while((line = reader1.readLine()) != null) {
245                                    inCount1++ ;
246                                    if( line.length() == 0 || line.charAt( 0 ) == '#' ) { continue; }
247                                    else {
248                                            flag = true;
249                                            break;
250                                    }
251                            }
252                    }
253                    catch (IOException ex) {
254                            String errMsg = "ファイル読込みエラー[" + infile1 + "]:(" + inCount1 + ")"  ;
255                            throw new RuntimeException( errMsg,ex );
256                    }
257                    return flag;
258            }
259    
260            /**
261             * ??に?行データである LineModel を作?しま?
262             * FirstProcess は、次?処?チェインして???の行データ?
263             * 作?して、後続? ChainProcess クラスに処?ータを渡します?
264             *
265             * ファイルより読み込んだ?行???タ???ブルモ?に
266             * セ?するように?しま?
267             * なお?読込みは?NAME??読み込みます???タ件数が少な??合??
268             * "" をセ?しておきます?
269             *
270             * @param       rowNo   処?の行番号
271             *
272             * @return      処?換後?LineModel
273             */
274            public LineModel makeLineModel( final int rowNo ) {
275                    outCount++ ;
276                    String[] vals = StringUtil.csv2Array( line ,separator1.charAt(0) );
277    
278                    int len = vals.length;
279                    for( int clmNo=0; clmNo<model.size(); clmNo++ ) {
280                            int no = clmNos[clmNo];
281                            if( len > no ) {
282                                    model.setValue( clmNo,vals[no] );
283                            }
284                            else {
285                                    // EXCEL が?終端TABを削除してしま?め?少な??合?埋める?
286                                    model.setValue( clmNo,"" );
287                            }
288                    }
289                    model.setRowNo( rowNo ) ;
290    
291    //              if( display ) { println( model.dataLine() ); }          // 5.1.2.0 (2010/01/01) display の条件変更
292    
293                    return action( model );
294            }
295    
296            /**
297             * キーと、DIFF設定?を比?、action に応じ?LineModel を返します?
298             * action には、ONLY,DIFF,INTERSEC が指定できます?
299             *   ONLY      inFile1 のみに存在する行?場合?inFile1 のレコードを返します?
300             *   DIFF      inFile1 と inFile2 に存在し?かつ、DIFF値が異なる?inFile1 のレコードを返します?
301             *   INTERSEC  inFile1 と inFile2 に存在し?かつ、DIFF値も同じ?inFile1 のレコードを返します?
302             * inFile2 側をキャ?ュします?で、inFile2 側の??タ量が少な?に選んでください?
303             *
304             * @param       model LineModelオブジェク?
305             *
306             * @return      実行後?LineModel
307             */
308            private LineModel action( final LineModel model ) {
309                    LineModel rtn = null;
310                    Object[] obj = model.getValues();
311    
312                    // キーのカラ?合?します?
313                    StringBuilder keys = new StringBuilder();
314                    for( int i=0; i<keyClmNos.length; i++ ) {
315                            keys.append( obj[keyClmNos[i]] ).append( "," );
316                    }
317    
318                    String data = file2Map.get( keys.toString() );
319            //      if( debug ) { println( "DEBUG:" + keys.toString() + ":" + data ); }
320    
321                    if( "ONLY".equalsIgnoreCase( actCmnd ) && data == null ) {
322                            if( debug ) { println( "DEBUG:ONLY\t" + keys.toString() ); }
323                            rtn = model;
324                    }
325                    else {
326                            // DIFF値のカラ?合?します?
327                            StringBuilder vals = new StringBuilder();
328                            for( int i=0; i<diffClmNos.length; i++ ) {
329                                    vals.append( obj[diffClmNos[i]] ).append( "," );
330                            }
331    
332                            boolean eq = ( vals.toString() ).equals( data );
333    
334                            if( "DIFF".equalsIgnoreCase( actCmnd ) && ! eq ) {
335                                    if( debug ) { println( "DEBUG:DIFF\t" + keys.toString() + "\t" + data + "\t" + vals.toString() ); }
336                                    rtn = model;
337                            }
338                            else if( "INTERSEC".equalsIgnoreCase( actCmnd ) && eq ) {
339                                    if( debug ) { println( "DEBUG:INTERSEC\t" + keys.toString() + "\t" + data ); }
340                                    rtn = model;
341                            }
342                    }
343                    if( display && rtn != null ) { println( rtn.dataLine() ); }
344                    return rtn;
345            }
346    
347            /**
348             * BufferedReader より?NAME 行??名情報を読み取ります?
349             * ??タカラ?り前に??目名情報を示?"#Name" が存在する仮定で取り込みます?
350             * こ?行?、ファイルの形式に無関係に、TAB で区?れて?す?
351             *
352             * @param       reader PrintWriterオブジェク?
353             *
354             * @return      カラ?配?(存在しな??合?、サイズ??配?)
355             */
356            private String[] readName( final BufferedReader reader ) {
357                    try {
358                            // 4.0.0 (2005/01/31) line 変数名変更
359                            String line1;
360                            while((line1 = reader.readLine()) != null) {
361                                    inCount1++ ;
362                                    if( line1.length() == 0 ) { continue; }
363                                    if( line1.charAt(0) == '#' ) {
364                                            String key = line1.substring( 0,5 );
365                                            if( key.equalsIgnoreCase( "#NAME" ) ) {
366                                                    // ?レギュラー処???の TAB 以前???無視する?
367                                                    String line2 = line1.substring( line1.indexOf( TAB )+1 );
368                                                    return StringUtil.csv2Array( line2 ,TAB.charAt(0) );
369                                            }
370                                            else  { continue; }
371                                    }
372                                    else {
373                                            String errMsg = "#NAME が見つかる前に??タが見つかりました?;
374                                            throw new RuntimeException( errMsg );
375                                    }
376                            }
377                    }
378                    catch (IOException ex) {
379                            String errMsg = "ファイル読込みエラー[" + infile1 + "]:(" + inCount1 + ")"  ;
380                            throw new RuntimeException( errMsg,ex );
381                    }
382                    return new String[0];
383            }
384    
385            /**
386             * ファイル属?を読取り、キー??を作?し??メモリマップにキャ?ュします?
387             * こ?マップをもとに、inFile1 の??タを?次読み取って、??進めます?
388             *
389             * @param       file2 読取り??ファイル
390             * @param       encode2 ファイルのエンコー?
391             */
392            private void readF2Data( final File file2, final String encode2 ) {
393                    BufferedReader reader2 = null;
394                    try {
395                            if( debug ) { println( "DEBUG:\tFile2="+ file2 + " 初期処? ); }
396                            reader2 = FileUtil.getBufferedReader( file2,encode2 );
397                            // 4.0.0 (2005/01/31) line 変数名変更
398                            String line1;
399                            char sep2 = separator2.charAt(0);
400                            while((line1 = reader2.readLine()) != null) {
401                                    inCount2++ ;
402                                    if( line1.length() == 0 ) { continue; }
403                                    if( line1.charAt(0) == '#' ) { continue; }
404                                    else {
405                                            // ?レギュラー処???の TAB 以前???無視する?
406                                            String line2 = line1.substring( line1.indexOf( separator2 )+1 );
407                                            Object[] obj = StringUtil.csv2Array( line2 , sep2 );
408    
409                                            // キーのカラ?合?します?
410                                            StringBuilder keys = new StringBuilder();
411                                            for( int i=0; i<keyClmNos.length; i++ ) {
412                                                    keys.append( obj[keyClmNos[i]] ).append( "," );
413                                            }
414    
415                                            // DIFF値のカラ?合?します?
416                                            StringBuilder vals = new StringBuilder();
417                                            for( int i=0; i<diffClmNos.length; i++ ) {
418                                                    vals.append( obj[diffClmNos[i]] ).append( "," );
419                                            }
420    
421                                            if( debug ) { println( "DEBUG:\t" + keys.toString() + "\t" + vals.toString() ); }
422    
423                                            file2Map.put( keys.toString(), vals.toString() );
424                                    }
425                            }
426                            if( debug ) { println( "DEBUG:\t======初期処??=====" ); }
427                    }
428                    catch (IOException ex) {
429                            String errMsg = "ファイル読込みエラー[" + infile2 + "]:(" + inCount2 + ")"  ;
430                            throw new RuntimeException( errMsg,ex );
431                    }
432                    finally {
433                            Closer.ioClose( reader2 );
434                    }
435            }
436    
437            /**
438             * プロセスの処?果のレポ?ト表現を返します?
439             * 処??ログラ?、?力件数、?力件数などの??です?
440             * こ???をそのまま、標準?力に出すことで、結果レポ?トと出来るよ?
441             * 形式で出してください?
442             *
443             * @return   処?果のレポ??
444             */
445            public String report() {
446                    String report = "[" + getClass().getName() + "]" + CR
447                                    + TAB + "Input  File1  : " + infile1    + CR
448                                    + TAB + "Input  File2  : " + infile2    + CR
449                                    + TAB + "Input  Count1 : " + inCount1   + CR
450                                    + TAB + "Input  Count2 : " + inCount2   + CR
451                                    + TAB + "Output Count  : " + outCount ;
452    
453                    return report ;
454            }
455    
456            /**
457             * こ?クラスの使用方法を返します?
458             *
459             * @return      こ?クラスの使用方?
460             */
461            public String usage() {
462                    StringBuilder buf = new StringBuilder();
463    
464                    buf.append( "Process_TableDiffは、ファイルから読み取った?容を?LineModel に設定後?"         ).append( CR );
465                    buf.append( "下流に渡す?FirstProcess インターフェースの実?ラスです?"                                    ).append( CR );
466                    buf.append( CR );
467                    buf.append( "DBTableModel 形式?ファイルを読み取って、各行を LineModel にセ?して?          ).append( CR );
468                    buf.append( "下?プロセスチェインの??タは上流から下流に渡されます?)に渡します?"              ).append( CR );
469                    buf.append( CR );
470                    buf.append( "引数??中に空白を含??合?、ダブルコー??ション(\"\") で括って下さ??" ).append( CR );
471                    buf.append( "引数??の ?』?前後には、空白は挟めません。??key=value の様に"             ).append( CR );
472                    buf.append( "繋げてください?                                                                                                                              ).append( CR );
473                    buf.append( CR ).append( CR );
474    
475                    buf.append( getArgument().usage() ).append( CR );
476    
477                    return buf.toString();
478            }
479    
480            /**
481             * こ?クラスは、main メソ?から実行できません?
482             *
483             * @param       args    コマンド引数配?
484             */
485            public static void main( final String[] args ) {
486                    LogWriter.log( new Process_TableDiff().usage() );
487            }
488    }