    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         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    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         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    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}