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 */
016package org.opengion.fukurou.process;
017
018import org.opengion.fukurou.util.Argument;
019import org.opengion.fukurou.util.HybsEntry ;
020import org.opengion.fukurou.util.LogWriter;
021import org.opengion.fukurou.util.StringUtil;            // 5.7.2.3 (2014/01/31)
022
023import java.util.Map ;
024import java.util.LinkedHashMap ;
025
026/**
027 * Process_TableFilter は、上流から受け取ったデータをフィルタする、
028 * ChainProcess インターフェースの実装クラスです。
029 *
030 * 上流(プロセスチェインのデータは上流から下流へと渡されます。)から
031 * 受け取ったLineModel を元に、項目のフィルタリングを行います。
032 * 条件が成立した場合は、下流に流します。複数の条件を指定できますが、
033 * すべて AND で判定されます。
034 * (設定条件すべてを満たす場合のみ、下流にデータを流します。)
035 *
036 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
037 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
038 * 繋げてください。
039 *
040 * @og.formSample
041 *  Process_TableFilter
042 *
043 *   [ -prefix_XXXX=接頭辞    ] :項目名(XXXX)が、指定の接頭辞で始まる場合、条件成立。
044 *   [ -suffix_XXXX=接尾辞    ] :項目名(XXXX)が、指定の接尾辞で終わる場合、条件成立。
045 *   [ -instr_XXXX=部分文字列 ] :項目名(XXXX)が、指定の部分文字列と一致する場合、条件成立。
046 *   [ -equals_XXXX=一致      ] :項目名(XXXX)が、文字列と一致する場合、条件成立。文字列は、大文字小文字は区別しません(equalsIgnoreCase)。
047 *   [ -match_XXXX=正規表現   ] :項目名(XXXX)が、正規表現と一致する場合、条件成立。
048 *   [ -unmatch_XXXX=正規表現 ] :項目名(XXXX)が、正規表現と一致しない場合、条件成立。
049 *   [ -const_XXXX=固定値     ] :-const_FGJ=1
050 *                                     項目名(XXXX)に、固定値を設定します。
051 *   [ -replace_XXXX=固定値   ] :-replace_BIKO="YYYY⇒ZZZZ" (元先指定は、⇒で区切ります。)
052 *                                     項目名(XXXX)の文字列から、YYYY という文字を ZZZZ に置換します。
053 *                                -replace_FGJ="_:0 A:1 B:2"    (元値:新値 のスペース区切り)
054 *                                     区切り文字に ⇒ を使用しない場合は、CASE処理を行います。
055 *                                     項目名(XXXX)の値を、元値 から 新値 に置換します。(完全一致)
056 *   [ -display=[false/true]  ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
057 *   [ -debug=[false/true]    ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
058 *
059 * @version  4.0
060 * @author   Kazuhiko Hasegawa
061 * @since    JDK5.0,
062 */
063public class Process_TableFilter extends AbstractProcess implements ChainProcess {
064        /** replace_ で使用する区切り記号  {@value} */
065        public static final char REP_SEP                = '⇒'   ;       // 4.3.1.1 (2008/08/24)
066
067        private static final String PREFIX_KEY  = "prefix_"     ;
068        private static final String SUFFIX_KEY  = "suffix_"     ;
069        private static final String INSTR_KEY   = "instr_"      ;
070        private static final String EQUALS_KEY  = "equals_"     ;
071        private static final String MATCH_KEY   = "match_"      ;
072        private static final String UNMATCH_KEY = "unmatch_";
073        private static final String CONST_KEY   = "const_"      ;
074        private static final String REPLACE_KEY = "replace_" ;          // 4.3.1.1 (2008/08/24)
075
076        private final LineModelFilter filter = new LineModelFilter();
077
078        private boolean         display         = false;        // 表示しない
079        private boolean         debug           = false;        // 5.7.3.0 (2014/02/07) デバッグ情報
080
081        private String[]        cnstClm         = null;         // 固定値を設定するカラム名
082        private int[]           cnstClmNos      = null;         // 固定値を設定するカラム番号
083        private String[]        constVal        = null;         // カラム番号に対応した固定値
084
085        // 4.3.1.1 (2008/08/24) replace 置換関係に必要なデータ
086        private String[]        repClm          = null;         // 置換を設定するカラム名
087        private int[]           repClmNos       = null;         // 置換を設定するカラム番号
088        private String[]        repValFrom      = null;         // カラム番号に対応した置換元文字列
089        private String[]        repValTo        = null;         // カラム番号に対応した置換後文字列
090
091        // 5.7.2.3 (2014/01/31) replace 置換の case処理関係に必要なデータ
092        private String[]        caseVals        = null;         // カラム番号に対応した置換後文字列
093
094        private boolean         firstRow        = true;         // 最初の一行目
095        private int                     count           = 0;
096
097        private static final Map<String,String> mustProparty   ;          // [プロパティ]必須チェック用 Map
098        private static final Map<String,String> usableProparty ;          // [プロパティ]整合性チェック Map
099
100        static {
101                mustProparty = new LinkedHashMap<String,String>();
102
103                usableProparty = new LinkedHashMap<String,String>();
104                usableProparty.put( PREFIX_KEY  ,       "項目名(XXXX)が、指定の接頭辞で始まる場合、条件成立。" );
105                usableProparty.put( SUFFIX_KEY  ,       "項目名(XXXX)が、指定の接尾辞で終わる場合、条件成立。" );
106                usableProparty.put( INSTR_KEY   ,       "項目名(XXXX)が、指定の部分文字列と一致する場合、条件成立。" );
107                usableProparty.put( EQUALS_KEY  ,       "項目名(XXXX)が、文字列と一致する場合、条件成立。" +
108                                                                                CR + "(大文字小文字は区別しない)" );
109                usableProparty.put( MATCH_KEY   ,       "項目名(XXXX)が、正規表現と一致する場合、条件成立。" );
110                usableProparty.put( UNMATCH_KEY ,       "項目名(XXXX)が、正規表現と一致しない場合、条件成立。" );
111                usableProparty.put( CONST_KEY   ,       "項目名(XXXX)に、固定値を設定します。" );
112                // 4.3.1.1 (2008/08/24) replace 置換関係
113                usableProparty.put( REPLACE_KEY ,       "項目名(XXXX)の文字列から、YYYY⇒ZZZZ で部分置換します。" +
114                                                                                CR + "項目名(XXXX)の文字列から、_:0 A:1 B:2でCASE置換します。" );
115                usableProparty.put( "display"   ,       "結果を標準出力に表示する(true)かしない(false)か" +
116                                                                                CR + "(初期値:false:表示しない)" );
117                usableProparty.put( "debug",    "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
118                                                                                CR + "(初期値:false:表示しない)" );             // 5.7.3.0 (2014/02/07) デバッグ情報
119        }
120
121        /**
122         * デフォルトコンストラクター。
123         * このクラスは、動的作成されます。デフォルトコンストラクターで、
124         * super クラスに対して、必要な初期化を行っておきます。
125         *
126         */
127        public Process_TableFilter() {
128                super( "org.opengion.fukurou.process.Process_TableFilter",mustProparty,usableProparty );
129        }
130
131        /**
132         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
133         * 初期処理(ファイルオープン、DBオープン等)に使用します。
134         *
135         * @og.rev 4.3.1.1 (2008/08/24) 置換関係対応
136         * @og.rev 5.7.2.3 (2014/01/31) replace 置換の case処理。
137         *
138         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
139         */
140        public void init( final ParamProcess paramProcess ) {
141                Argument arg = getArgument();
142
143                display = arg.getProparty( "display",display );
144                debug   = arg.getProparty("debug",debug);                               // 5.7.3.0 (2014/02/07) デバッグ情報
145//              if( debug ) { println( arg.toString() ); }                      // 5.7.3.0 (2014/02/07) デバッグ情報
146
147                HybsEntry[] entry = arg.getEntrys( PREFIX_KEY );
148                for( int i=0; i<entry.length; i++ ) {
149                        filter.add( FilterOperation.PREFIX, entry[i].getKey(), entry[i].getValue() );
150                }
151
152                entry = arg.getEntrys( SUFFIX_KEY );
153                for( int i=0; i<entry.length; i++ ) {
154                        filter.add( FilterOperation.SUFFIX, entry[i].getKey(), entry[i].getValue() );
155                }
156
157                entry = arg.getEntrys( INSTR_KEY );
158                for( int i=0; i<entry.length; i++ ) {
159                        filter.add( FilterOperation.INSTR, entry[i].getKey(), entry[i].getValue() );
160                }
161
162                entry = arg.getEntrys( EQUALS_KEY );
163                for( int i=0; i<entry.length; i++ ) {
164                        filter.add( FilterOperation.EQUALS, entry[i].getKey(), entry[i].getValue() );
165                }
166
167                entry = arg.getEntrys( MATCH_KEY );
168                for( int i=0; i<entry.length; i++ ) {
169                        filter.add( FilterOperation.MATCH, entry[i].getKey(), entry[i].getValue() );
170                }
171
172                entry = arg.getEntrys( UNMATCH_KEY );
173                for( int i=0; i<entry.length; i++ ) {
174                        filter.add( FilterOperation.UNMATCH, entry[i].getKey(), entry[i].getValue() );
175                }
176
177                HybsEntry[] cnstKey = arg.getEntrys( CONST_KEY );
178                int csize       = cnstKey.length;
179                cnstClm         = new String[csize];
180                constVal        = new String[csize];
181                for( int i=0; i<csize; i++ ) {
182                        cnstClm[i]      = cnstKey[i].getKey();
183                        constVal[i]     = cnstKey[i].getValue();
184                }
185
186                // 4.3.1.1 (2008/08/24) replace 置換関係
187                HybsEntry[] repKey = arg.getEntrys( REPLACE_KEY );
188                int rsize       = repKey.length;
189                repClm          = new String[rsize];
190                repValFrom      = new String[rsize];
191                repValTo        = new String[rsize];
192                caseVals        = new String[rsize];                            // 5.7.2.3 (2014/01/31) replace 置換の case処理
193                for( int i=0; i<rsize; i++ ) {
194                        repClm[i]       = repKey[i].getKey();
195                        String val      = repKey[i].getValue();                 // val は、YYYY⇒ZZZZ の形式
196                        if( val != null ) {
197                                int ad = val.indexOf( REP_SEP );                // REP_SEP は、'⇒'
198                                if( ad >= 0 ) {
199                                        repValFrom[i]   = val.substring( 0,ad );
200                                        repValTo[i]             = val.substring( ad+1 );
201                                }
202                                else {
203                                        // 5.7.2.3 (2014/01/31) replace 置換の case処理
204//                                      repValFrom[i]   = val;                          // 昔は、⇒ が無い場合は、引数をクリアする処理だった。
205//                                      repValTo[i]             = "";                           // 
206                                        caseVals[i]             = val;                          // 5.7.2.3 (2014/01/31) replace 置換の case処理
207                                }
208                        }
209                }
210        }
211
212        /**
213         * 引数の LineModel を処理するメソッドです。
214         * 変換処理後の LineModel を返します。
215         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
216         * null データを返します。つまり、null データは、後続処理を行わない
217         * フラグの代わりにも使用しています。
218         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
219         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
220         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
221         * 各処理ごとに自分でコピー(クローン)して下さい。
222         *
223         * @og.rev 4.3.1.1 (2008/08/24) 置換関係対応
224         * @og.rev 5.7.2.3 (2014/01/31) replace 置換の case処理。
225         *
226         * @param   data        オリジナルのLineModel
227         *
228         * @return      処理変換後のLineModel
229         */
230        public LineModel action( final LineModel data ) {
231                count++ ;
232
233//              if( display ) { println( data.dataLine() ); }
234
235                if( !filter.filter( data ) ) {
236                        return null;            // 不一致
237                }
238
239                if( firstRow ) {
240                        int csize       = cnstClm.length;
241                        cnstClmNos      = new int[csize];
242                        for( int i=0; i<csize; i++ ) {
243                                cnstClmNos[i] = data.getColumnNo( cnstClm[i] );
244                        }
245
246                        // 4.3.1.1 (2008/08/24) 置換関係対応
247                        int rsize       = repClm.length;
248                        repClmNos       = new int[rsize];
249                        for( int i=0; i<rsize; i++ ) {
250                                repClmNos[i] = data.getColumnNo( repClm[i] );
251                        }
252
253                        firstRow = false;
254                        if( display ) { println( data.nameLine() ); }           // 5.7.3.0 (2014/02/07) デバッグ情報
255                }
256
257                if( debug ) { println( "Before:" + data.dataLine() ); }         // 5.1.2.0 (2010/01/01) display の条件変更
258
259                for( int i=0; i<cnstClm.length; i++ ) {
260                        data.setValue( cnstClmNos[i],constVal[i] );
261                }
262
263                // 4.3.1.1 (2008/08/24) 置換関係対応
264                for( int i=0; i<repClm.length; i++ ) {
265                        // 5.7.2.3 (2014/01/31) Object が null の時の処理がおかしかったので修正
266//                      String val = String.valueOf( data.getValue( repClmNos[i] ) );
267//                      if( val != null ) {
268                        String val = "";
269                        Object obj = data.getValue( repClmNos[i] );
270                        if( obj != null ) { val = String.valueOf( obj ); }
271
272                        if( caseVals[i] == null ) {             // 従来からのリプレース処理
273                                val = val.replaceAll( repValFrom[i],repValTo[i] );
274                        }
275                        else {
276                                // 5.7.2.3 (2014/01/31) replace 置換の case処理。
277                                val = StringUtil.caseReplace( val , caseVals[i] , false );
278                        }
279                        data.setValue( repClmNos[i],val );
280                }
281
282                if( debug ) { println( "After :" + data.dataLine() ); }         // 5.1.2.0 (2010/01/01) display の条件変更
283                else if( display ) { println( data.dataLine() ); }              // 5.1.2.0 (2010/01/01) display の条件変更
284                return data;
285        }
286
287        /**
288         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
289         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
290         *
291         * @og.rev 4.3.1.1 (2008/08/24) 置換関係対応
292         * @og.rev 5.7.2.3 (2014/01/31) replace 置換の case処理。
293         *
294         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
295         */
296        public void end( final boolean isOK ) {
297                cnstClm         = null;         // 固定値を設定するカラム名
298                cnstClmNos      = null;         // 固定値を設定するカラム番号
299                constVal        = null;         // カラム番号に対応した固定値
300
301                repClm          = null;         // 置換を設定するカラム名
302                repClmNos       = null;         // 置換を設定するカラム番号
303                repValFrom      = null;         // カラム番号に対応した置換元文字列
304                repValTo        = null;         // カラム番号に対応した置換後文字列
305                caseVals        = null;         // 5.7.2.3 (2014/01/31) replace 置換の case処理
306        }
307
308        /**
309         * プロセスの処理結果のレポート表現を返します。
310         * 処理プログラム名、入力件数、出力件数などの情報です。
311         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
312         * 形式で出してください。
313         *
314         * @return   処理結果のレポート
315         */
316        public String report() {
317                String report = "[" + getClass().getName() + "]" + CR
318                                + TAB + "Model Filter : " + filter + CR
319                                + TAB + "Output Count : " + count ;
320
321                return report ;
322        }
323
324        /**
325         * このクラスの使用方法を返します。
326         *
327         * @return      このクラスの使用方法
328         */
329        public String usage() {
330                StringBuilder buf = new StringBuilder();
331
332                buf.append( "Process_TableFilter は、上流から受け取ったデータをフィルタする、"                                ).append( CR );
333                buf.append( "ChainProcess インターフェースの実装クラスです。"                                                            ).append( CR );
334                buf.append( CR );
335                buf.append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から"                 ).append( CR );
336                buf.append( "受け取ったLineModel を元に、項目のフィルタリングを行います。"                                       ).append( CR );
337                buf.append( "条件が成立した場合は、下流に流します。複数の条件を指定できますが、"                 ).append( CR );
338                buf.append( "すべて AND で判定されます。"                                                                                                  ).append( CR );
339                buf.append( "(設定条件すべてを満たす場合のみ、下流にデータを流します。)"                                    ).append( CR );
340                buf.append( CR );
341                buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR );
342                buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"                ).append( CR );
343                buf.append( "繋げてください。"                                                                                                                          ).append( CR );
344                buf.append( CR ).append( CR );
345                buf.append( getArgument().usage() ).append( CR );
346
347                return buf.toString();
348        }
349
350        /**
351         * このクラスは、main メソッドから実行できません。
352         *
353         * @param       args    コマンド引数配列
354         */
355        public static void main( final String[] args ) {
356                LogWriter.log( new Process_TableFilter().usage() );
357        }
358}