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    //              if( debug ) { println( arg.toString() ); }                      // 5.7.3.0 (2014/02/07) ????
146    
147                    if( infile1 == null || infile2 == null ) {
148                            String errMsg = "ファイル名が?されて?せん?
149                                                    + "File1=[" + infile1 + "] , File2=[" + infile2 + "]" ;
150                            throw new RuntimeException( errMsg );
151                    }
152    
153                    File file1 = new File( infile1 );
154                    File file2 = new File( infile2 );
155    
156                    if( ! file1.exists() || ! file2.exists() ) {
157                            // 4.3.1.1 (2008/08/23) Avoid if (x != y) ..; else ..;
158                            String errMsg = "ファイルが存在しません?
159                                                    + ((file1.exists()) ? "" : "File1=[" + file1 + "] " )
160                                                    + ((file2.exists()) ? "" : "File2=[" + file2 + "]" );
161                            throw new RuntimeException( errMsg );
162                    }
163    
164                    if( ! file1.isFile() || ! file2.isFile() ) {
165                            // 4.3.1.1 (2008/08/23) Avoid if (x != y) ..; else ..;
166                            String errMsg = "フォル???できません。ファイル名を?してください?
167                                                    + ((file1.isFile()) ? "" : "File1=[" + file1 + "] " )
168                                                    + ((file2.isFile()) ? "" : "File2=[" + file2 + "]" );
169                            throw new RuntimeException( errMsg );
170                    }
171    
172                    reader1 = FileUtil.getBufferedReader( file1,encode1 );
173    //              reader2 = FileUtil.getBufferedReader( file2,encode2 );
174    
175                    final String[] names ;
176                    if( clms != null ) {
177                            names = StringUtil.csv2Array( clms );   // ??カラ?配?
178                    }
179                    else {
180                            String[] clmNames = readName( reader1 );                // ファイルのカラ?配?
181                            if( clmNames == null || clmNames.length == 0 ) { nameNull = true; return ; }
182                            names = clmNames;
183                    }
184    
185                    model = new LineModel();
186                    model.init( names );
187    
188                    if( display ) { println( model.nameLine() ); }
189    
190                    // 入力カラ?のカラ?号
191                    clmNos = new int[names.length];
192                    for( int i=0; i<names.length; i++ ) {
193                            clmNos[i] = i+1;                                                // 行番号??1しておく?
194    //                      int no = model.getColumnNo( names[i] );
195    //                      if( no >= 0 ) { clmNos[no] = i+1; }          // 行番号??1しておく?
196                    }
197    
198                    // 比?る?の基準カラ?
199                    if( debug ) { println( "DEBUG:\tkeyClms=" + keyClms ); }
200                    final String[] keyClmNms = StringUtil.csv2Array( keyClms );
201                    keyClmNos = new int[keyClmNms.length];
202                    for( int i=0; i<keyClmNms.length; i++ ) {
203                            keyClmNos[i] = model.getColumnNo( keyClmNms[i] );
204            //              if( debug ) { println( "DEBUG:" + keyClmNms[i] + ":[" + keyClmNos[i] + "]" ); }
205            //              int no = model.getColumnNo( keyClmNms[i] );
206            //              if( no >= 0 ) { keyClmNos[no] = i+1; }               // 行番号??1しておく?
207                    }
208    
209                    // 比?るカラ?
210                    if( debug ) { println( "DEBUG:\tdiffClms=" + diffClms ); }
211                    final String[] diffClmNms = StringUtil.csv2Array( diffClms );
212                    diffClmNos = new int[diffClmNms.length];
213                    for( int i=0; i<diffClmNms.length; i++ ) {
214                            diffClmNos[i] = model.getColumnNo( diffClmNms[i] );
215            //              if( debug ) { println( "DEBUG:" + diffClmNms[i] + ":[" + diffClmNos[i] + "]" ); }
216            //              int no = model.getColumnNo( diffClmNms[i] );
217            //              if( no >= 0 ) { diffClmNos[no] = i+1; }              // 行番号??1しておく?
218                    }
219    
220                    readF2Data( file2,encode2 );
221            }
222    
223            /**
224             * プロセスの終?行います??に??、呼び出されます?
225             * 終???ファイルクローズ??クローズ?に使用します?
226             *
227             * @param   isOK ト?タルで、OK?たかど?[true:成功/false:失敗]
228             */
229            public void end( final boolean isOK ) {
230                    Closer.ioClose( reader1 );
231                    reader1 = null;
232            }
233    
234            /**
235             * こ???タの処?おいて、次の処?出来るかど?を問?わせます?
236             * こ?呼び出し1回毎に、次の??タを取得する準備を行います?
237             *
238             * @return      処?きる:true / 処?きな?false
239             */
240            public boolean next() {
241                    if( nameNull ) { return false; }
242    
243                    boolean flag = false;
244                    try {
245                            while((line = reader1.readLine()) != null) {
246                                    inCount1++ ;
247                                    if( line.length() == 0 || line.charAt( 0 ) == '#' ) { continue; }
248                                    else {
249                                            flag = true;
250                                            break;
251                                    }
252                            }
253                    }
254                    catch (IOException ex) {
255                            String errMsg = "ファイル読込みエラー[" + infile1 + "]:(" + inCount1 + ")"  ;
256                            throw new RuntimeException( errMsg,ex );
257                    }
258                    return flag;
259            }
260    
261            /**
262             * ??に?行データである LineModel を作?しま?
263             * FirstProcess は、次?処?チェインして???の行データ?
264             * 作?して、後続? ChainProcess クラスに処?ータを渡します?
265             *
266             * ファイルより読み込んだ?行???タ???ブルモ?に
267             * セ?するように?しま?
268             * なお?読込みは?NAME??読み込みます???タ件数が少な??合??
269             * "" をセ?しておきます?
270             *
271             * @param       rowNo   処?の行番号
272             *
273             * @return      処?換後?LineModel
274             */
275            public LineModel makeLineModel( final int rowNo ) {
276                    outCount++ ;
277                    String[] vals = StringUtil.csv2Array( line ,separator1.charAt(0) );
278    
279                    int len = vals.length;
280                    for( int clmNo=0; clmNo<model.size(); clmNo++ ) {
281                            int no = clmNos[clmNo];
282                            if( len > no ) {
283                                    model.setValue( clmNo,vals[no] );
284                            }
285                            else {
286                                    // EXCEL が?終端TABを削除してしま?め?少な??合?埋める?
287                                    model.setValue( clmNo,"" );
288                            }
289                    }
290                    model.setRowNo( rowNo ) ;
291    
292    //              if( display ) { println( model.dataLine() ); }          // 5.1.2.0 (2010/01/01) display の条件変更
293    
294                    return action( model );
295            }
296    
297            /**
298             * キーと、DIFF設定?を比?、action に応じ?LineModel を返します?
299             * action には、ONLY,DIFF,INTERSEC が指定できます?
300             *   ONLY      inFile1 のみに存在する行?場合?inFile1 のレコードを返します?
301             *   DIFF      inFile1 と inFile2 に存在し?かつ、DIFF値が異なる?inFile1 のレコードを返します?
302             *   INTERSEC  inFile1 と inFile2 に存在し?かつ、DIFF値も同じ?inFile1 のレコードを返します?
303             * inFile2 側をキャ?ュします?で、inFile2 側の??タ量が少な?に選んでください?
304             *
305             * @param       model LineModelオブジェク?
306             *
307             * @return      実行後?LineModel
308             */
309            private LineModel action( final LineModel model ) {
310                    LineModel rtn = null;
311                    Object[] obj = model.getValues();
312    
313                    // キーのカラ?合?します?
314                    StringBuilder keys = new StringBuilder();
315                    for( int i=0; i<keyClmNos.length; i++ ) {
316                            keys.append( obj[keyClmNos[i]] ).append( "," );
317                    }
318    
319                    String data = file2Map.get( keys.toString() );
320            //      if( debug ) { println( "DEBUG:" + keys.toString() + ":" + data ); }
321    
322                    if( "ONLY".equalsIgnoreCase( actCmnd ) && data == null ) {
323                            if( debug ) { println( "DEBUG:ONLY\t" + keys.toString() ); }
324                            rtn = model;
325                    }
326                    else {
327                            // DIFF値のカラ?合?します?
328                            StringBuilder vals = new StringBuilder();
329                            for( int i=0; i<diffClmNos.length; i++ ) {
330                                    vals.append( obj[diffClmNos[i]] ).append( "," );
331                            }
332    
333                            boolean eq = ( vals.toString() ).equals( data );
334    
335                            if( "DIFF".equalsIgnoreCase( actCmnd ) && ! eq ) {
336                                    if( debug ) { println( "DEBUG:DIFF\t" + keys.toString() + "\t" + data + "\t" + vals.toString() ); }
337                                    rtn = model;
338                            }
339                            else if( "INTERSEC".equalsIgnoreCase( actCmnd ) && eq ) {
340                                    if( debug ) { println( "DEBUG:INTERSEC\t" + keys.toString() + "\t" + data ); }
341                                    rtn = model;
342                            }
343                    }
344                    if( display && rtn != null ) { println( rtn.dataLine() ); }
345                    return rtn;
346            }
347    
348            /**
349             * BufferedReader より?NAME 行??名情報を読み取ります?
350             * ??タカラ?り前に??目名情報を示?"#Name" が存在する仮定で取り込みます?
351             * こ?行?、ファイルの形式に無関係に、TAB で区?れて?す?
352             *
353             * @param       reader PrintWriterオブジェク?
354             *
355             * @return      カラ?配?(存在しな??合?、サイズ??配?)
356             */
357            private String[] readName( final BufferedReader reader ) {
358                    try {
359                            // 4.0.0 (2005/01/31) line 変数名変更
360                            String line1;
361                            while((line1 = reader.readLine()) != null) {
362                                    inCount1++ ;
363                                    if( line1.length() == 0 ) { continue; }
364                                    if( line1.charAt(0) == '#' ) {
365                                            String key = line1.substring( 0,5 );
366                                            if( key.equalsIgnoreCase( "#NAME" ) ) {
367                                                    // ?レギュラー処???の TAB 以前???無視する?
368                                                    String line2 = line1.substring( line1.indexOf( TAB )+1 );
369                                                    return StringUtil.csv2Array( line2 ,TAB.charAt(0) );
370                                            }
371                                            else  { continue; }
372                                    }
373                                    else {
374                                            String errMsg = "#NAME が見つかる前に??タが見つかりました?;
375                                            throw new RuntimeException( errMsg );
376                                    }
377                            }
378                    }
379                    catch (IOException ex) {
380                            String errMsg = "ファイル読込みエラー[" + infile1 + "]:(" + inCount1 + ")"  ;
381                            throw new RuntimeException( errMsg,ex );
382                    }
383                    return new String[0];
384            }
385    
386            /**
387             * ファイル属?を読取り、キー??を作?し??メモリマップにキャ?ュします?
388             * こ?マップをもとに、inFile1 の??タを?次読み取って、??進めます?
389             *
390             * @param       file2 読取り??ファイル
391             * @param       encode2 ファイルのエンコー?
392             */
393            private void readF2Data( final File file2, final String encode2 ) {
394                    BufferedReader reader2 = null;
395                    try {
396                            if( debug ) { println( "DEBUG:\tFile2="+ file2 + " 初期処? ); }
397                            reader2 = FileUtil.getBufferedReader( file2,encode2 );
398                            // 4.0.0 (2005/01/31) line 変数名変更
399                            String line1;
400                            char sep2 = separator2.charAt(0);
401                            while((line1 = reader2.readLine()) != null) {
402                                    inCount2++ ;
403                                    if( line1.length() == 0 ) { continue; }
404                                    if( line1.charAt(0) == '#' ) { continue; }
405                                    else {
406                                            // ?レギュラー処???の TAB 以前???無視する?
407                                            String line2 = line1.substring( line1.indexOf( separator2 )+1 );
408                                            Object[] obj = StringUtil.csv2Array( line2 , sep2 );
409    
410                                            // キーのカラ?合?します?
411                                            StringBuilder keys = new StringBuilder();
412                                            for( int i=0; i<keyClmNos.length; i++ ) {
413                                                    keys.append( obj[keyClmNos[i]] ).append( "," );
414                                            }
415    
416                                            // DIFF値のカラ?合?します?
417                                            StringBuilder vals = new StringBuilder();
418                                            for( int i=0; i<diffClmNos.length; i++ ) {
419                                                    vals.append( obj[diffClmNos[i]] ).append( "," );
420                                            }
421    
422                                            if( debug ) { println( "DEBUG:\t" + keys.toString() + "\t" + vals.toString() ); }
423    
424                                            file2Map.put( keys.toString(), vals.toString() );
425                                    }
426                            }
427                            if( debug ) { println( "DEBUG:\t======初期処??=====" ); }
428                    }
429                    catch (IOException ex) {
430                            String errMsg = "ファイル読込みエラー[" + infile2 + "]:(" + inCount2 + ")"  ;
431                            throw new RuntimeException( errMsg,ex );
432                    }
433                    finally {
434                            Closer.ioClose( reader2 );
435                    }
436            }
437    
438            /**
439             * プロセスの処?果のレポ?ト表現を返します?
440             * 処??ログラ?、?力件数、?力件数などの??です?
441             * こ???をそのまま、標準?力に出すことで、結果レポ?トと出来るよ?
442             * 形式で出してください?
443             *
444             * @return   処?果のレポ??
445             */
446            public String report() {
447                    String report = "[" + getClass().getName() + "]" + CR
448                                    + TAB + "Input  File1  : " + infile1    + CR
449                                    + TAB + "Input  File2  : " + infile2    + CR
450                                    + TAB + "Input  Count1 : " + inCount1   + CR
451                                    + TAB + "Input  Count2 : " + inCount2   + CR
452                                    + TAB + "Output Count  : " + outCount ;
453    
454                    return report ;
455            }
456    
457            /**
458             * こ?クラスの使用方法を返します?
459             *
460             * @return      こ?クラスの使用方?
461             */
462            public String usage() {
463                    StringBuilder buf = new StringBuilder();
464    
465                    buf.append( "Process_TableDiffは、ファイルから読み取った?容を?LineModel に設定後?"         ).append( CR );
466                    buf.append( "下流に渡す?FirstProcess インターフェースの実?ラスです?"                                    ).append( CR );
467                    buf.append( CR );
468                    buf.append( "DBTableModel 形式?ファイルを読み取って、各行を LineModel にセ?して?          ).append( CR );
469                    buf.append( "下?プロセスチェインの??タは上流から下流に渡されます?)に渡します?"              ).append( CR );
470                    buf.append( CR );
471                    buf.append( "引数??中に空白を含??合?、ダブルコー??ション(\"\") で括って下さ??" ).append( CR );
472                    buf.append( "引数??の ?』?前後には、空白は挟めません。??key=value の様に"             ).append( CR );
473                    buf.append( "繋げてください?                                                                                                                              ).append( CR );
474                    buf.append( CR ).append( CR );
475    
476                    buf.append( getArgument().usage() ).append( CR );
477    
478                    return buf.toString();
479            }
480    
481            /**
482             * こ?クラスは、main メソ?から実行できません?
483             *
484             * @param       args    コマンド引数配?
485             */
486            public static void main( final String[] args ) {
487                    LogWriter.log( new Process_TableDiff().usage() );
488            }
489    }