    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.FileUtil;
    020import org.opengion.fukurou.util.FileString;
    021import org.opengion.fukurou.util.Closer ;
    022import org.opengion.fukurou.util.StringUtil ;
    023import org.opengion.fukurou.util.LogWriter;
    024
    025import java.util.Arrays;
    026import java.util.Map ;
    027import java.util.LinkedHashMap ;
    028import java.util.regex.Pattern;
    029import java.util.regex.Matcher;
    030
    031import java.io.File;
    032import java.io.PrintWriter;
    033import java.io.BufferedReader;
    034import java.io.IOException;
    035
    036/**
    037 * Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す
    038 * ChainProcess インターフェースの実装クラスです。
    039 *
    040 * 正規表現の keyword を上流から受け取った FileLineModel から検索します。
    041 * 見つかった対象ファイルから、指定の文字列を置換する場合は、-change か
    042 * -changeFile で、keyword を置換する文字列を指定して下さい。
    043 * 置換する文字列には、\t と \n の特殊文字が使用できます。
    044 *
    045 * 処理対象は、通常は、１行づつ読み取りながら処理を行います。存在チェックの場合は、
    046 * 見つかった時点で処理を中止します。これは、該当箇所をピックアップするのではなく、
    047 * 存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです。
    048 * keyword を、改行を含む正規表現で、検索・置換する場合は、-useBulkRead 属性を
    049 * true に設定してください。これは、入力ファイルを一括して読み込みます。
    050 * -ignoreCase は、正規表現の検索時にキーの大文字小文字を無視するように指定します。
    051 * -notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します。
    052 * これは、行単位ではなく、ファイル単位に判定しますので、change 指定した場合
    053 * でも、対象行は、見つかった行です。ただし、下流に対して、見つからない
    054 * 場合だけ処理を継続させます。
    055 * -inEncode は、入力ファイルのエンコード指定になります。
    056 * -outEncode は、出力ファイルのエンコードや、changeFileで指定の置換文字列ファイルの
    057 * エンコード指定になります。(changeFile は、必ず 出力ファイルと同じエンコードです。)
    058 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で
    059 * 求まる値を使用します。
    060 * -changeFile を使用することで、複数行の文字列に置換することが可能です。
    061 * -outfile では、処理を行ったファイル名一覧をセーブします。
    062 *
    063 * 上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の
    064 * ファイルオブジェクトより、指定の文字列が含まれているか検索します。
    065 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
    066 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
    067 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
    068 * できれば、使用可能です。
    069 *
    070 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。
    071 * 引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に
    072 * 繋げてください。
    073 *
    074 * @og.formSample
    075 *  Process_Grep -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
    076 *
    077 *    -keyword=キーワード        ：検索する語句
    078 *   [-ignoreCase=大文字小文字 ] ：検索時に大文字小文字を区別しない(true)かどうか(初期値:区別する[false])
    079 *   [-notEquals=判定結果の反転] ：判定結果を反転させる(true)かどうか(初期値:反転させない[false])
    080 *   [-inEncode=入力エンコード ] ：入力ファイルのエンコードタイプ
    081 *   [-outEncode=出力エンコード] ：出力ファイルや置換ファイルのエンコードタイプ
    082 *   [-change=置換文字列       ] ：-change="ABCD" \t や \n などの特殊文字が使用できます。
    083 *   [-changeFile=置換ファイル ] ：-changeFile=change.txt このファイルの記述すべてと置換します。
    084 *                                     -change と、-changeFile は、同時に指定できません。
    085 *                                     置換機能使用時は、必ず、_backup というファイルが作成されます。
    086 *   [-insert=[CHANGE/BEFORE/AFTER]   ] : 置換でなく挿入する場合の位置を指定します(初期値:CHANGE)
    087 *                                 スペースで区切って数字を記述すると、挿入位置にオフセットできます。
    088 *   [-delete=[false/true]     ] : 置換でなく削除します(初期値:false)
    089 *   [-useBackup=[false/true]  ] ：trueは、backupファイルを作成します(初期値:false)
    090 *   [-useBulkRead=[false/true]] ：trueは、入力ファイルを一括読込します(初期値:false)
    091 *   [-display=[false/true]    ] ：trueは、検索状況を表示します(初期値:false)
    092 *   [-debug=[false/true]      ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    093 *
    094 * @version  4.0
    095 * @author   Kazuhiko Hasegawa
    096 * @since    JDK5.0,
    097 */
    098public class Process_Grep extends AbstractProcess implements ChainProcess {
    099        private static final String[] INSERT_LIST = new String[] { "CHANGE","BEFORE","AFTER" };
    100
    101        private Pattern pattern         = null;
    102        private String  keyword         = null;
    103        private boolean ignoreCase      = false;
    104        private boolean notEquals       = false;
    105        private String  inEncode        = null;
    106        private String  outEncode       = null;
    107        private String  change          = null;
    108        private String  insert          = "CHANGE";             // "CHANGE","BEFORE","AFTER" のどれか
    109        private int             insOffset       = 0;                    // "BEFORE","AFTER" 時のオフセット
    110        private boolean useBackup       = false;
    111        private boolean useBulkRead     = false;                // 4.0.1.0 (2007/12/14)
    112        private boolean delete          = false;
    113        private boolean display         = false;
    114        private boolean debug           = false;                // 5.1.2.0 (2010/01/01)
    115
    116        private int             inCount         = 0;
    117        private int             findCount       = 0;
    118        private int             cngCount        = 0;
    119
    120        private static final Map<String,String> mustProparty   ;          // ［プロパティ］必須チェック用 Map
    121        private static final Map<String,String> usableProparty ;          // ［プロパティ］整合性チェック Map
    122
    123        static {
    124                mustProparty = new LinkedHashMap<String,String>();
    125                mustProparty.put( "keyword",    "検索する語句(必須)" );
    126
    127                usableProparty = new LinkedHashMap<String,String>();
    128                usableProparty.put( "ignoreCase",       "検索時に大文字小文字を区別しない(true)かどうか。" +
    129                                                                                CR + "(初期値:区別する[false])" );
    130                usableProparty.put( "notEquals",        "検索時に判定結果を反転させる(true)かどうか。" +
    131                                                                                CR + "(初期値:反転させない[false])" );
    132                usableProparty.put( "inEncode",         "入力ファイルのエンコードタイプ" );
    133                usableProparty.put( "outEncode",        "出力ファイルや置換ファイルのエンコードタイプ" );
    134                usableProparty.put( "change",           "置換文字列 例: -change=\"ABCD\" \\t や \\n などの特殊文字が使用できます。" );
    135                usableProparty.put( "changeFile",       "置換文字列ファイル 例: -changeFile=change.txt" +
    136                                                                                CR + "-change と、-changeFile は、同時に指定できません。" +
    137                                                                                CR + "置換機能使用時は、必ず、_backup というファイルが作成されます。" );
    138                usableProparty.put( "insert",           "[CHANGE/BEFORE/AFTER]:置換でなく挿入する場合の位置を指定します(初期値:CHANGE)"  +
    139                                                                                CR + "スペースで区切って数字を記述すると、挿入位置にオフセットできます。" );
    140                usableProparty.put( "delete",           "[false/true]:trueは、置換でなく削除します(初期値:false)" );
    141                usableProparty.put( "useBackup",        "[false/true]:trueは、backupファイルを作成します(初期値:false)" );
    142                usableProparty.put( "useBulkRead",      "[false/true]:trueは、入力ファイルを一括読込します(初期値:false)" );
    143                usableProparty.put( "display",          "[false/true]:trueは、検索状況を表示します(初期値:false)" );
    144                usableProparty.put( "debug",            "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    145                                                                                        CR + "(初期値:false:表示しない)" );
    146        }
    147
    148        /**
    149         * デフォルトコンストラクター。
    150         * このクラスは、動的作成されます。デフォルトコンストラクターで、
    151         * super クラスに対して、必要な初期化を行っておきます。
    152         *
    153         */
    154        public Process_Grep() {
    155                super( "org.opengion.fukurou.process.Process_Grep",mustProparty,usableProparty );
    156        }
    157
    158        /**
    159         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
    160         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    161         *
    162         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
    163         */
    164        public void init( final ParamProcess paramProcess ) {
    165                Argument arg = getArgument();
    166
    167                keyword                 = arg.getProparty("keyword");
    168                ignoreCase              = arg.getProparty("ignoreCase"  ,ignoreCase);
    169                notEquals               = arg.getProparty("notEquals"   ,notEquals);
    170                inEncode                = arg.getProparty("inEncode"    ,System.getProperty("file.encoding"));
    171                outEncode               = arg.getProparty("outEncode"   ,System.getProperty("file.encoding"));
    172                useBackup               = arg.getProparty("useBackup"   ,useBackup);
    173                useBulkRead             = arg.getProparty("useBulkRead" ,useBulkRead);  // 4.0.1.0 (2007/12/14)
    174                delete                  = arg.getProparty("delete"              ,delete );
    175                insert                  = arg.getProparty("insert" ,insert );
    176                change                  = arg.getFileProparty( "change","changeFile",outEncode,false );
    177                display                 = arg.getProparty("display"             ,display);
    178                debug                   = arg.getProparty( "debug"  ,debug );           // 5.1.2.0 (2010/01/01)
    179//              if( debug ) { println( arg.toString() ); }                      // 5.7.3.0 (2014/02/07) デバッグ情報
    180
    181                if( change != null ) {
    182                        int adrs = insert.indexOf( ' ' );       // オフセット数字の有無
    183                        if( adrs > 0 ) {
    184                                insOffset = Integer.parseInt( insert.substring( adrs+1 ) );
    185                                insert    = insert.substring( 0,adrs );
    186                        }
    187
    188                        boolean isOK = false;
    189                        for( int i=0; i<INSERT_LIST.length; i++ ) {
    190                                if( insert.equalsIgnoreCase( INSERT_LIST[i] ) ) {
    191                                        isOK = true; break;
    192                                }
    193                        }
    194                        if( !isOK ) {
    195                                String errMsg = "insert は、" + Arrays.toString( INSERT_LIST )
    196                                                                        + " から指定してください。" + CR
    197                                                                        + "-insert=[" + insert + "]" ;
    198                                throw new RuntimeException( errMsg );
    199                        }
    200
    201                        change = StringUtil.replace( change,"\\n",CR );
    202                        change = StringUtil.replace( change,"\\t","\t" );
    203                }
    204
    205                if( delete ) { change = ""; }   // 削除は、"" 文字列と置換します。
    206
    207                if( ignoreCase ) {
    208                        pattern = Pattern.compile( keyword,Pattern.CASE_INSENSITIVE );
    209                }
    210                else {
    211                        pattern = Pattern.compile( keyword );
    212                }
    213        }
    214
    215        /**
    216         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
    217         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    218         *
    219         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
    220         */
    221        public void end( final boolean isOK ) {
    222                // ここでは処理を行いません。
    223        }
    224
    225        /**
    226         * 引数の LineModel を処理するメソッドです。
    227         * 変換処理後の LineModel を返します。
    228         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
    229         * null データを返します。つまり、null データは、後続処理を行わない
    230         * フラグの代わりにも使用しています。
    231         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
    232         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
    233         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
    234         * 各処理ごとに自分でコピー(クローン)して下さい。
    235         *
    236         * @og.rev 4.0.1.0 (2007/12/14) ファイルの一括処理対応。
    237         * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    238         *
    239         * @param       data    オリジナルのLineModel
    240         *
    241         * @return      処理変換後のLineModel
    242         */
    243        public LineModel action( final LineModel data ) {
    244                inCount++ ;
    245
    246                final FileLineModel fileData ;
    247                if( data instanceof FileLineModel ) {
    248                        fileData = (FileLineModel)data ;
    249                }
    250                else {
    251                        String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
    252                        throw new RuntimeException( errMsg );
    253                }
    254
    255                File file = fileData.getFile() ;
    256                if( ! file.isFile() ) {
    257                        if( display ) { println( data.dataLine() ); }           // 5.1.2.0 (2010/01/01) display の条件変更
    258                        return data;
    259                }
    260
    261                final boolean isFind ;
    262                try {
    263                        String fileLine = null;
    264                        int firstLineNo = -1;
    265                        if( useBulkRead ) { fileLine    = findKeywordAsBulk( file ); }
    266                        else                      { firstLineNo = findKeyword( file ); }
    267
    268                        isFind = ( fileLine != null ) || ( firstLineNo >= 0 ) ;
    269
    270                        // 置換処理 ただし、見つかったときのみ実行
    271                        if( change != null && isFind ) {
    272                                // 入力ファイルは、オリジナル_backup ファイルとする。過去のファイルを削除
    273                                File inFile = new File( file.getPath() + "_backup" );
    274                                if( inFile.exists() && !inFile.delete() ) {
    275                                        String errMsg = "過去のBKUPファイルを削除できませんでした。[" + inFile + "]" + CR
    276                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    277                                        throw new RuntimeException( errMsg );
    278                                }
    279
    280                                // オリジナルのファイルを、_backup ファイル名に先に変換する。
    281                                File fromFile = new File( file.getPath() );
    282                                if( !fromFile.renameTo( inFile ) ) {
    283                                        String errMsg = "所定のファイルをリネームできませんでした。[" + fromFile + "]" + CR
    284                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    285                                        throw new RuntimeException( errMsg );
    286                                }
    287
    288                                // 変換処理 本体
    289                                if( useBulkRead ) { changeKeywordAsBulk( fileLine,file ); }
    290                                else                      { changeKeyword( inFile,file,firstLineNo ); }
    291
    292                                // backup を使わない場合は、削除する。
    293                                // 4.0.0.0 (2007/11/29) 入れ子if の統合
    294                                if( ! useBackup && !inFile.delete() ) {
    295                                        String errMsg = "所定のファイルを削除できませんでした。[" + inFile + "]" + CR
    296                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    297                                        throw new RuntimeException( errMsg );
    298                                }
    299                        }
    300                }
    301                catch ( RuntimeException ex ) {
    302                        String errMsg = "処理中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR
    303//                                              + data.toString() ;
    304                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    305                        throw new RuntimeException( errMsg,ex );
    306                }
    307
    308                if( display && ( notEquals ^ isFind ) ) { println( data.dataLine() ); }         // 5.1.2.0 (2010/01/01) display の条件変更
    309                return ( notEquals ^ isFind ) ? data : null ;
    310        }
    311
    312        /**
    313         * キーワードが存在しているかどうかをチェックします。
    314         * ここでは、１行づつ読み取りながら、最初に見つかった時点で制御を返します。
    315         * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな
    316         * ファイル等での検索には、効率的です。
    317         *
    318         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
    319         *
    320         * @param       file    検索元のファイルオブジェクト
    321         *
    322         * @return      最初に見つかった行番号(見つからなければ、-1 を返す)
    323         */
    324        private int findKeyword( final File file ) {
    325                BufferedReader reader = null;
    326
    327                int firstLineNo = -1;
    328                try {
    329                        reader = FileUtil.getBufferedReader( file,inEncode );
    330                        String line ;
    331                        int lineNo = 0;
    332                        while((line = reader.readLine()) != null) {
    333                                lineNo++ ;
    334                                Matcher mach = pattern.matcher( line );
    335                                if( mach.find() ) {
    336                                        if( debug ) {
    337                                                String buf = "DEBUG:\t" + file.getPath() + "(" + lineNo + "): " + line ;
    338                                                println( buf );
    339                                        }
    340                                        firstLineNo = lineNo;
    341                                        break;
    342                                }
    343                        }
    344                }
    345                catch ( IOException ex ) {
    346                        String errMsg = "ファイル読取エラーが発生しました。[" + file.getPath() + "]" ;
    347                        throw new RuntimeException( errMsg,ex );
    348                }
    349                finally {
    350                        Closer.ioClose( reader );
    351                }
    352
    353                return firstLineNo;
    354        }
    355
    356        /**
    357         * キーワードが存在しているかどうかをチェックします。
    358         * ここでは、ファイルをすべて読み取ってから、チェックします。
    359         * よって、複数行にまたがる keyword でのマッチングが可能です。
    360         *
    361         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
    362         *
    363         * @param       file    検索元のファイルオブジェクト
    364         *
    365         * @return      検索元のファイルの文字列化情報(ただし、見つからなければ、null)
    366         */
    367        private String findKeywordAsBulk( final File file ) {
    368
    369                boolean isFind = false;
    370
    371                FileString sf = new FileString();
    372                sf.setFilename( file.getPath() );
    373                sf.setEncode( inEncode );
    374                String line = sf.getValue();
    375
    376                Matcher mach = pattern.matcher( line );
    377                if( mach.find() ) {
    378                        if( debug ) { println( "DEBUG:\t" + file.getPath() ); }
    379                        isFind = true;
    380                }
    381
    382                return ( isFind ) ? line : null;
    383        }
    384
    385        /**
    386         * キーワードを指定の文字列に置き換えます。
    387         * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、
    388         * オリジナル_backup という名称に変わります。
    389         * ここでは、１行づつ読み取りながら、変換処理を行います。
    390         * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな
    391         * ファイル等での置換でも、メモリの使用量は抑えられます。
    392         *
    393         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。
    394         *
    395         * @param       inFile  検索元の入力ファイルオブジェクト
    396         * @param       outFile 変換後の出力ファイルオブジェクト
    397         * @param       firstLineNo     キーワードが存在した場合の最初の行番号
    398         */
    399        private void changeKeyword( final File inFile,final File outFile,final int firstLineNo ) {
    400
    401                BufferedReader reader = FileUtil.getBufferedReader( inFile,inEncode );
    402                PrintWriter    writer = FileUtil.getPrintWriter( outFile,outEncode );
    403
    404                String line = null;
    405                try {
    406                        int lineNo = 0;
    407                        while((line = reader.readLine()) != null) {
    408                                lineNo++ ;
    409                                if( lineNo >= firstLineNo ) {
    410                                        Matcher mach = pattern.matcher( line );
    411
    412                                        String chnStr = null;
    413                                        if( "CHANGE".equals( insert ) ) {
    414                                                chnStr = strChange( mach );
    415                                        }
    416                                        else if( "BEFORE".equals( insert ) ) {
    417                                                chnStr = strBefore( line,mach );
    418                                        }
    419                                        else if( "AFTER".equals( insert ) ) {
    420                                                chnStr = strAfter( line,mach );
    421                                        }
    422
    423                                        if( chnStr != null ) {
    424                                                line = chnStr;
    425                                                cngCount++ ;    // 変換されれば カウント
    426                                        }
    427                                }
    428                                writer.println( line ); // readLine() してるので、最後に改行が必要。
    429                        }
    430                }
    431                catch ( IOException ex ) {
    432                        String errMsg = "処理中にエラーが発生しました。[" + line + "]" ;
    433                        throw new RuntimeException( errMsg,ex );
    434                }
    435                finally {
    436                        Closer.ioClose( reader );
    437                        Closer.ioClose( writer );
    438                }
    439        }
    440        /**
    441         * キーワードを指定の文字列に置き換えます。
    442         * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、
    443         * オリジナル_backup という名称に変わります。
    444         * ここでは、ファイルをすべて読み取ってから、チェックします。
    445         * よって、複数行にまたがる keyword でのマッチングが可能です。
    446         *
    447         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。
    448         *
    449         * @param       fileLine        検索元の行文字列
    450         * @param       outFile 出力ファイルオブジェクト
    451         */
    452        private void changeKeywordAsBulk( final String fileLine,final File outFile ) {
    453                PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode );
    454
    455                String line = fileLine ;
    456                try {
    457                        Matcher mach = pattern.matcher( line );
    458
    459                        String chnStr = null;
    460                        if( "CHANGE".equals( insert ) ) {
    461                                chnStr = strChange( mach );
    462                        }
    463                        else if( "BEFORE".equals( insert ) ) {
    464                                chnStr = strBefore( line,mach );
    465                        }
    466                        else if( "AFTER".equals( insert ) ) {
    467                                chnStr = strAfter( line,mach );
    468                        }
    469
    470                        if( chnStr != null ) {
    471                                line = chnStr;
    472                                cngCount++ ;    // 変換されれば カウント
    473                        }
    474
    475                        writer.print( line );   // 注意：改行コードは、不要
    476                }
    477                catch ( RuntimeException ex ) {
    478                        String errMsg = "処理中にエラーが発生しました。[" + outFile.getPath() + "]" ;
    479                        throw new RuntimeException( errMsg,ex );
    480                }
    481                finally {
    482                        Closer.ioClose( writer );
    483                }
    484        }
    485
    486        /**
    487         * insert が、"CHANGE" の場合の処理結果を求めます。
    488         * 変換しなかった場合は、null を返します。
    489         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
    490         *
    491         * @param       mach    キーワードの正規表現
    492         *
    493         * @return      変換結果(対象行で無い場合は、null)
    494         */
    495        private String strChange( final Matcher mach ) {
    496                String line = null;
    497                if( mach.find() ) {
    498                        line = mach.replaceAll( change );
    499                }
    500                return line ;
    501        }
    502
    503        /**
    504         * insert が、"BEFORE" の場合の処理結果を求めます。
    505         * 変換しなかった場合は、null を返します。
    506         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
    507         *
    508         * @param       line    検索行
    509         * @param       mach    キーワードの正規表現
    510         *
    511         * @return      変換結果(対象行で無い場合は、null)
    512         */
    513        private String strBefore( final String line , final Matcher mach ) {
    514                boolean isChng = false;
    515                StringBuilder buf = new StringBuilder( line.length() );
    516                int indx = 0;
    517                while( mach.find() ) {
    518                        isChng = true;
    519                        int strt = mach.start() + insOffset;
    520                        buf.append( line.substring( indx,strt ) );
    521                        buf.append( change );
    522                        indx = strt;
    523                }
    524
    525                String rtn = null;
    526                if( isChng ) {
    527                        buf.append( line.substring( indx ) );
    528                        rtn = buf.toString();
    529                }
    530
    531                return rtn ;
    532        }
    533
    534        /**
    535         * insert が、"AFTER" の場合の処理結果を求めます。
    536         * 変換しなかった場合は、null を返します。
    537         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
    538         *
    539         * @param       line    検索行
    540         * @param       mach    キーワードの正規表現
    541         *
    542         * @return      変換結果(対象行で無い場合は、null)
    543         */
    544        private String strAfter( final String line , final Matcher mach ) {
    545                boolean isChng = false;
    546                StringBuilder buf = new StringBuilder( line.length() );
    547                int indx = 0;
    548                while( mach.find() ) {
    549                        isChng = true;
    550                        int end = mach.end() + insOffset;
    551                        buf.append( line.substring( indx,end ) );
    552                        buf.append( change );
    553                        indx = end;
    554                }
    555                String rtn = null;
    556                if( isChng ) {
    557                        buf.append( line.substring( indx ) );
    558                        rtn = buf.toString();
    559                }
    560
    561                return rtn ;
    562        }
    563
    564        /**
    565         * プロセスの処理結果のレポート表現を返します。
    566         * 処理プログラム名、入力件数、出力件数などの情報です。
    567         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
    568         * 形式で出してください。
    569         *
    570         * @return   処理結果のレポート
    571         */
    572        public String report() {
    573                if( findCount < cngCount ) { findCount = cngCount; }
    574
    575                String report = "[" + getClass().getName() + "]" + CR
    576                                + TAB + "Search Keyword    : " + keyword    + CR
    577                                + TAB + "Search File Count : " + inCount    + CR
    578                                + TAB + "Key Find    Count : " + findCount  + CR
    579                                + TAB + "Key Change  Count : " + cngCount ;
    580
    581                return report ;
    582        }
    583
    584        /**
    585         * このクラスの使用方法を返します。
    586         *
    587         * @return      このクラスの使用方法
    588         */
    589        public String usage() {
    590                StringBuilder buf = new StringBuilder();
    591
    592                buf.append( "Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す"                ).append( CR );
    593                buf.append( "ChainProcess インターフェースの実装クラスです。"                                                            ).append( CR );
    594                buf.append( CR );
    595                buf.append( "正規表現の keyword を上流から受け取った FileLineModel から検索します。"           ).append( CR );
    596                buf.append( "見つかった対象ファイルから、指定の文字列を置換する場合は、-change か"                    ).append( CR );
    597                buf.append( "-changeFile で、keyword を置換する文字列を指定して下さい。"                                   ).append( CR );
    598                buf.append( "置換する文字列には、\t と \n の特殊文字が使用できます。"                                           ).append( CR );
    599                buf.append( CR );
    600                buf.append( "処理対象は、通常は、１行づつ読み取りながら処理を行います。存在チェックの場合は、"  ).append( CR );
    601                buf.append( "見つかった時点で処理を中止します。これは、該当箇所をピックアップするのではなく、"  ).append( CR );
    602                buf.append( "存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです。"            ).append( CR );
    603                buf.append( "keyword を、改行を含む正規表現で、検索・置換する場合は、-useBulkRead 属性を"          ).append( CR );
    604                buf.append( "true に設定してください。これは、入力ファイルを一括して読み込みます。"                     ).append( CR );
    605                buf.append( "-ignoreCase は、検索時にキーの大文字小文字を無視するように指定します。"         ).append( CR );
    606                buf.append( "-notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します。"           ).append( CR );
    607                buf.append( "これは、行単位ではなく、ファイル単位に判定しますので、change 指定した場合"          ).append( CR );
    608                buf.append( "でも、対象行は、見つかった行です。ただし、下流に対して、見つからない"                        ).append( CR );
    609                buf.append( "場合だけ処理を継続させます。"                                                                                                    ).append( CR );
    610                buf.append( "-inEncode は、入力ファイルのエンコード指定になります。"                                          ).append( CR );
    611                buf.append( "-outEncode は、出力ファイルのエンコードや、changeFileで指定の置換文字列"            ).append( CR );
    612                buf.append( "ファイルのエンコード指定になります。(changeFile は、必ず 出力ファイルと)"               ).append( CR );
    613                buf.append( "同じエンコードです。"                                                                                                                        ).append( CR );
    614                buf.append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") "  ).append( CR );
    615                buf.append( "で求まる値を使用します。"                                                                                                              ).append( CR );
    616                buf.append( "-changeFile を使用することで、複数行の文字列に置換することが可能です。"                 ).append( CR );
    617                buf.append( CR );
    618                buf.append( "上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の"         ).append( CR );
    619                buf.append( "ファイルオブジェクトより、指定の文字列が含まれているか検索します。"                 ).append( CR );
    620                buf.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"               ).append( CR );
    621                buf.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"             ).append( CR );
    622                buf.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"   ).append( CR );
    623                buf.append( "できれば、使用可能です。"                                                                                                              ).append( CR );
    624                buf.append( CR );
    625                buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR );
    626                buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"                ).append( CR );
    627                buf.append( "繋げてください。"                                                                                                                          ).append( CR );
    628                buf.append( CR ).append( CR );
    629
    630                buf.append( getArgument().usage() ).append( CR );
    631
    632                return buf.toString();
    633        }
    634
    635        /**
    636         * このクラスは、main メソッドから実行できません。
    637         *
    638         * @param       args    コマンド引数配列
    639         */
    640        public static void main( final String[] args ) {
    641                LogWriter.log( new Process_Grep().usage() );
    642        }
    643}