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