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