    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.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    019import org.opengion.fukurou.system.Closer ;
    020import org.opengion.fukurou.system.LogWriter;
    021import org.opengion.fukurou.util.Argument;
    022import org.opengion.fukurou.util.FileUtil;
    023import org.opengion.fukurou.util.StringUtil ;
    024
    025import java.util.Map ;
    026import java.util.LinkedHashMap ;
    027import java.util.List ;
    028import java.util.ArrayList ;
    029import java.util.Locale ;                               // 5.7.3.2 (2014/02/28) ignoreCase が実装されていなかった。
    030import java.util.regex.Pattern;                 // 5.7.3.2 (2014/02/28) regexを利用する場合
    031import java.util.regex.Matcher;                 // 5.7.3.2 (2014/02/28) regexを利用する場合
    032
    033import java.io.File;
    034import java.io.PrintWriter;
    035import java.io.BufferedReader;
    036import java.io.IOException;
    037import java.nio.charset.CharacterCodingException;                                       // 6.3.1.0 (2015/06/28)
    038
    039/**
    040 * Process_GrepChange は、上流から受け取った FileLineModelから、語句を
    041 * 置換する、ChainProcess インターフェースの実装クラスです。
    042 *
    043 * Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在
    044 * しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが
    045 * 出来ないことです。
    046 *
    047 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、
    048 * 対象とする語句を置換します。
    049 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、
    050 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。
    051 * 置換文字(値)は、\t の特殊文字が使用できます。
    052 * この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード
    053 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。
    054 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、
    055 * <del>6.3.1.1 (2015/07/10) 置き換えた結果も、同じファイルにセーブします。</del>
    056 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。
    057 * -inEncode は、入力ファイルのエンコード指定になります。
    058 * -outEncode は、出力ファイルのエンコードや、キーワードファイルの
    059 * エンコード指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)
    060 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で
    061 * 求まる値を使用します。
    062 *
    063 * 5.7.3.2 (2014/02/28)
    064 * -regex=true で、ｷｰﾜｰﾄﾞに正規表現を利用できます。具体的には、String#replaceAll(String,String)
    065 * を利用して置換します。
    066 * 通常の置換処理は、indexOf で見つけて、StringBuilder#replace(int,int,String) を繰り返して処理しています。
    067 * -ignoreCase=true で、検索ｷｰﾜｰﾄﾞに大文字小文字を区別しない処理が可能です。
    068 *
    069 * 6.3.1.1 (2015/07/10)
    070 * ※ 出力ファイルを別フォルダにコピー置換する機能を追加します。
    071 *    方法は、Process_FileCopy と同様、inPath と outPath を指定します。
    072 * ※ useWordUnit="true" を指定すると、出来るだけ、Grep対象を、単語単位で置換しようとします。
    073 *    具体的には、ｷｰﾜｰﾄﾞ文字列の前後に、""(ﾀﾞﾌﾞﾙｸｵｰﾄ)、''(ｼﾝｸﾞﾙｸｵｰﾄ)、＞＜(ﾀｸﾞ記号)、空白、改行を
    074 *    付加して、それらを含めてﾏｯﾁした場合のみ置換する方法を取ります。
    075 *
    076 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
    077 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
    078 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
    079 * できれば、使用可能です。
    080 *
    081 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
    082 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
    083 * 繋げてください。
    084 *
    085 *  Process_GrepChange -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
    086 *
    087 *    -keywordFile=キーワード      ：置換する語句を含むキーと値のペアー(タブ区切り)
    088 *   [-ignoreCase=[false/true]   ] ：検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する])
    089 *   [-regex=[false/true]        ] ：ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか(初期値:false[利用しない])
    090 *   [-isChange=置換可否         ] ：置換処理を実施する(true)かどうか(初期値:置換する[true])
    091 *   [-inPath=入力共通パス       ] ：上流で検索されたファイルパスの共通部分
    092 *   [-inEncode=入力エンコード   ] ：入力ファイルのエンコードタイプ
    093 *   [-outEncode=出力エンコード  ] ：出力ファイルやキーワードファイルのエンコードタイプ
    094 *   [-outPath=出力共通パス      ] ：出力するファイルパスの共通部分
    095 *   [-useWordUnit=単語単位置換  ] ：出来るだけ、Grep対象を、単語単位で置換しようとします(初期値:false[部分置換])
    096 *   [-errAbend=[true/false]     ] ：異常発生時に、処理を中断(true)するか、継続(false)するかを指定する(初期値:true[中断する])
    097 *   [-display=[false/true]      ] ：結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    098 *   [-debug=[false/true]        ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
    099 *
    100 * @version  4.0
    101 * @author   Kazuhiko Hasegawa
    102 * @since    JDK5.0,
    103 */
    104public class Process_GrepChange extends AbstractProcess implements ChainProcess {
    105        // 6.3.1.1 (2015/07/10) useWordUnit="true" 時に、使用することになります。
    106        private static final String IN_PTN = "([\"'><\\\t\\\n 　])";     // 6.3.1.1 (2015/07/10) ほとんど同じなので、共有する。
    107
    108        private String[]        keyword ;
    109        private String[]        change  ;
    110        private Pattern[]       pattern ;                               // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用する場合
    111        private boolean         ignoreCase      ;
    112        private boolean         regex           ;                       // 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用するかどうか
    113        private boolean         isChange        = true;         // 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする
    114        private String          inEncode        ;
    115        private String          outPath         ;                       // 6.3.1.1 (2015/07/10)
    116        private String          outEncode       ;
    117        private boolean         useWordUnit     ;                       // 6.3.1.1 (2015/07/10) 部分置換
    118        private boolean         errAbend        = true;         // 6.3.1.1 (2015/07/10) 中断する
    119        private boolean         display         ;                       // false:表示しない
    120        private boolean         debug           ;                       // 5.7.3.0 (2014/02/07) デバッグ情報
    121
    122        private int             inPathLen               ;                       // 6.3.1.1 (2015/07/10)
    123        private boolean isEquals                ;                       // 6.3.1.1 (2015/07/10)
    124
    125        private int             inCount         ;
    126        private int             findCount       ;
    127        private int             cngCount        ;
    128
    129        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    130        private static final Map<String,String> MUST_PROPARTY   ;               // ［プロパティ］必須チェック用 Map
    131        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
    132        private static final Map<String,String> USABLE_PROPARTY ;               // ［プロパティ］整合性チェック Map
    133
    134        static {
    135                MUST_PROPARTY = new LinkedHashMap<>();
    136                MUST_PROPARTY.put( "keywordFile",       "置換する語句を含むキーと値のペアー(タブ区切り)(必須)" );
    137
    138                USABLE_PROPARTY = new LinkedHashMap<>();
    139                USABLE_PROPARTY.put( "ignoreCase",      "検索時に大文字小文字を区別しない(true)かどうか。" +
    140                                                                                CR + "(初期値:区別する[false])" );
    141                USABLE_PROPARTY.put( "regex",           "ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか。" +
    142                                                                                CR + "(初期値:利用しない[false])" );    // 5.7.3.2 (2014/02/28)
    143                USABLE_PROPARTY.put( "isChange",                "置換処理を実施する(true)かどうか" +
    144                                                                                CR + "(初期値:置換する[true])" );
    145                USABLE_PROPARTY.put( "inPath",          "入力するファイルパスの共通部分" );    // 6.3.1.1 (2015/07/10)
    146                USABLE_PROPARTY.put( "inEncode",                "入力ファイルのエンコードタイプ" );
    147                USABLE_PROPARTY.put( "outPath",         "出力するファイルパスの共通部分" );    // 6.3.1.1 (2015/07/10)
    148                USABLE_PROPARTY.put( "outEncode",       "出力ファイルやキーワードファイルのエンコードタイプ" );
    149                USABLE_PROPARTY.put( "useWordUnit",     "出来るだけ、Grep対象を、単語単位で置換" +
    150                                                                                CR + "(初期値:false:部分置換)" );                      // 6.3.1.1 (2015/07/10)
    151                USABLE_PROPARTY.put( "errAbend",                "異常発生時に、処理を中断(true)するか、継続(false)するか" +
    152                                                                                CR + "(初期値:true:中断する)" );                       // 6.3.1.0 (2015/06/28)
    153                USABLE_PROPARTY.put( "display",         "結果を標準出力に表示する(true)かしない(false)か" +
    154                                                                                CR + "(初期値:false:表示しない)" );
    155                USABLE_PROPARTY.put( "debug",   "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    156                                                                                CR + "(初期値:false:表示しない)" );             // 5.7.3.0 (2014/02/07) デバッグ情報
    157        }
    158
    159        /**
    160         * デフォルトコンストラクター。
    161         * このクラスは、動的作成されます。デフォルトコンストラクターで、
    162         * super クラスに対して、必要な初期化を行っておきます。
    163         *
    164         */
    165        public Process_GrepChange() {
    166                super( "org.opengion.fukurou.process.Process_GrepChange",MUST_PROPARTY,USABLE_PROPARTY );
    167        }
    168
    169        /**
    170         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
    171         * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
    172         *
    173         * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
    174         * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止、ignoreCase の実装、regex の追加
    175         * @og.rev 6.3.1.1 (2015/07/10) 出力ファイルを別フォルダにコピー置換する機能を追加
    176         * @og.rev 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
    177         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
    178         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
    179         *
    180         * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
    181         */
    182        public void init( final ParamProcess paramProcess ) {
    183                final Argument arg = getArgument();
    184
    185                final String keywordFile = arg.getProparty( "keywordFile" );
    186                ignoreCase      = arg.getProparty( "ignoreCase" , ignoreCase            );
    187                regex           = arg.getProparty( "regex"              , regex                         );              // 5.7.3.2 (2014/02/28)
    188                isChange        = arg.getProparty( "isChange"   , isChange                      );              // 5.1.2.0 (2010/01/01)
    189                inEncode        = arg.getProparty( "inEncode"   , System.getProperty("file.encoding"));
    190                outPath         = arg.getProparty( "outPath"    , null                          );              // 6.3.1.1 (2015/07/10)
    191                outEncode       = arg.getProparty( "outEncode"  , System.getProperty("file.encoding"));
    192                useWordUnit     = arg.getProparty( "useWordUnit", useWordUnit           );              // 6.3.1.1 (2015/07/10)
    193                errAbend        = arg.getProparty( "errAbend"   , errAbend                      );              // 6.3.1.1 (2015/07/10)
    194                display         = arg.getProparty( "display"    , display                       );
    195                debug           = arg.getProparty( "debug"              , debug                         );              // 5.7.3.0 (2014/02/07) デバッグ情報
    196
    197                // 6.3.1.1 (2015/07/10) 入力と出力が同じか？
    198                final String inPath = arg.getProparty( "inPath" , null );       // 6.3.4.0 (2015/08/01)
    199                isEquals  = outPath == null || inPath == null || inPath.equalsIgnoreCase( outPath );
    200                inPathLen = inPath == null ? 0 : inPath.length();
    201
    202                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
    203                final List<String> list = FileUtil.getLineList( keywordFile , outEncode );      // 6.4.5.2 (2016/05/06)
    204                final int len = list.size();                                                            // 6.4.5.2 (2016/05/06)
    205                if( len == 0 ) {
    206                        // これは、初期情報取込み処理なので、errAbend 対象外
    207                        final String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ;
    208                        throw new OgRuntimeException( errMsg );
    209                }
    210
    211                println( "keywordFile を、" + len + "件読み取りました。" );
    212                final List<String> keyList = new ArrayList<>( len );
    213                final List<String> cngList = new ArrayList<>( len );
    214
    215                for( final String line : list ) {
    216        //              String line = lines[i].trim();
    217                        final int indx = line.indexOf( '\t' );
    218                        if( indx <= 0 ) { continue ; }  // TAB が先頭や、存在しない行は読み飛ばす。
    219                        // 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止
    220                        String key = line.substring( 0,indx );
    221                        String cng = line.substring( indx+1 );
    222
    223                        if( ignoreCase ) { key = key.toUpperCase(Locale.JAPAN); }       // 5.7.3.2 (2014/02/28) ignoreCase の実装漏れ
    224
    225                        if( debug ) { println( "[" + key + "]⇒[" + cng + "]" ); }
    226
    227                        key = StringUtil.replace( key,"\\t","\t" );
    228                        cng = StringUtil.replace( cng,"\\t","\t" );
    229
    230                        keyList.add( key );
    231                        cngList.add( cng );
    232                }
    233                keyword = keyList.toArray( new String[keyList.size()] );
    234                change  = cngList.toArray( new String[cngList.size()] );
    235
    236                // 5.7.3.2 (2014/02/28) regex=true の場合の処理
    237                if( regex ) {
    238                        pattern = new Pattern[keyword.length];
    239                        for( int i=0; i<keyword.length; i++ ) {
    240                                // 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
    241                                final String keywd = useWordUnit ? IN_PTN + keyword[i] + IN_PTN : keyword[i] ;
    242                                pattern[i] = ignoreCase ? Pattern.compile( keywd , Pattern.CASE_INSENSITIVE )
    243                                                                                : Pattern.compile( keywd ) ;
    244                        }
    245                }
    246        }
    247
    248        /**
    249         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
    250         * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
    251         *
    252         * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
    253         */
    254        public void end( final boolean isOK ) {
    255                // ここでは処理を行いません。
    256        }
    257
    258        /**
    259         * 引数の LineModel を処理するメソッドです。
    260         * 変換処理後の LineModel を返します。
    261         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
    262         * null データを返します。つまり、null データは、後続処理を行わない
    263         * フラグの代わりにも使用しています。
    264         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
    265         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
    266         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
    267         * 各処理ごとに自分でコピー(クローン)して下さい。
    268         *
    269         * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
    270         * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
    271         * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    272         * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ignoreCase の実装
    273         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
    274         * @og.rev 6.3.1.1 (2015/07/10) 出力ファイルを別フォルダにコピー置換する機能を追加
    275         * @og.rev 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
    276         *
    277         * @param       data    オリジナルのLineModel
    278         *
    279         * @return      処理変換後のLineModel
    280         */
    281        @Override       // ChainProcess
    282        public LineModel action( final LineModel data ) {
    283                inCount++ ;
    284                final FileLineModel fileData ;
    285                if( data instanceof FileLineModel ) {
    286                        fileData = (FileLineModel)data ;
    287                }
    288                else {
    289                        // これは、プログラマーの問題なので、errAbend 対象外
    290                        final String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
    291                        throw new OgRuntimeException( errMsg );
    292                }
    293
    294                final File org = fileData.getFile() ;
    295                if( ! org.isFile() ) { return data; }
    296
    297                if( debug ) { println( "File:" + org ); }               // 5.1.2.0 (2010/01/01) display の条件変更
    298
    299                File            tempFile  = null;
    300                PrintWriter     tempWrt   = null;
    301
    302                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
    303                final String orgName = org.getPath();
    304
    305                // 5.1.2.0 (2010/01/01) 置換する場合の前処理
    306                if( isChange ) {
    307                        // 6.3.1.1 (2015/07/10) 出力が同じ場合は、従来通り temp出力して、置き換える。
    308                        if( isEquals ) {
    309                                tempFile  = new File( orgName + "_temp" );
    310                        }
    311                        else {
    312                                // 入出力が異なる場合
    313                                tempFile  = new File( outPath, org.getAbsolutePath().substring( inPathLen ) );
    314                                fileData.setFile( tempFile );   // tempFile は、出力ファイルの事。
    315                                // 出力先のフォルダが無ければ作成
    316                                final File parent = tempFile.getParentFile();
    317                                if( parent != null && ! parent.exists() && !parent.mkdirs() ) {
    318                                        final String errMsg = "所定のフォルダが作成できませんでした。[" + parent + "]" + CR
    319                                                                + " inCount=[" + inCount + "]件" + CR
    320                                                                + " data=[" + data.dataLine() + "]" + CR ;              // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    321                                        throwException( errMsg,errAbend );
    322                                        return null;    // ログだけの場合は、以下の処理は打ち切り。
    323                                }
    324                        }
    325
    326                        tempWrt = FileUtil.getPrintWriter( tempFile,outEncode );
    327                }
    328
    329                boolean nextFlag = false;
    330
    331                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
    332                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    333
    334                final BufferedReader reader = FileUtil.getBufferedReader( org,inEncode );
    335                try {
    336                        String line ;
    337                        int    lineNo = 0;
    338                        while((line = reader.readLine()) != null) {
    339                                lineNo++ ;
    340                                // 5.7.3.2 (2014/02/28) regex 対応
    341                                if( regex ) {
    342                                        for( int i=0; i<pattern.length; i++ ) {
    343                                                final Matcher mt = pattern[i].matcher( line );
    344                                                if( mt.matches() ) {
    345                                                        nextFlag = true;                        // １度でも見つかれば、true にセット
    346                                                        findCount++ ;
    347                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
    348                                                        if( isChange ) {
    349                                                                // 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
    350                                                                line = mt.replaceAll( "$1" + change[i] + "$2" );        // 前方参照
    351                                                                cngCount++ ;
    352                                                        }
    353                                                }
    354                                        }
    355                                }
    356                                else {
    357                                        final String line2 = ignoreCase ? line.toUpperCase(Locale.JAPAN) : line ;               //  6.4.1.1 (2016/01/16) 先に、必要な処理を行う。
    358                                        buf.setLength(0);                               // new StringBuilder の代わり。
    359                                        buf.append( line );
    360                                        for( int i=0; i<keyword.length; i++ ) {
    361                                                int indx = line2.indexOf( keyword[i] );
    362                                                // 置換対象発見。行出力用に見つかれば、true にする。
    363                                                if( indx >= 0 ) {
    364                                                        // 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
    365                                                        // 検索結果の前後の文字が、IN_PTN に含まれている場合のみ、見つかったことにする。
    366                                                        if( useWordUnit ) {
    367                                                                // 見つかった場所のひとつ前の文字が、IN_PTN に存在しなければ、見つからなかった。
    368                                                                if( indx > 0 && IN_PTN.indexOf( line.charAt( indx-1 ) ) < 0 ||
    369                                                                        // 見つかった場所のひとつ後ろの文字が、IN_PTN に存在しなければ、見つからなかった。
    370                                                                        line.length() < (indx+1) && IN_PTN.indexOf( line.charAt( indx+1 ) ) < 0 ) {
    371                                                                        // 対象外になったキーワードと行を表示します。
    372                                                                        if( display ) { println( "NoChange:" + orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
    373                                                                        continue;
    374                                                                }
    375                                                        }
    376                                                        nextFlag  = true;                               // １度でも見つかれば、true にセット
    377                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
    378                                                        findCount++ ;
    379
    380                                                        // 6.4.1.1 (2016/01/16) 見つかったときだけ、置換処理を実施するように変更。
    381                                                        // 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。
    382                                                        if( isChange ) {
    383                                                                while( indx >= 0 ) {
    384                                                                        buf.replace( indx,indx+keyword[i].length(),change[i] );
    385                                                                        // 5.7.3.2 (2014/02/28) ignoreCase 対応。
    386                                                                        final int nxt = indx+change[i].length();
    387                                                                        indx = ignoreCase       ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt )
    388                                                                                                                : buf.indexOf( keyword[i],nxt );
    389
    390                                                                        cngCount++ ;
    391                                                                }
    392                                                        }
    393                                                }
    394                                        }
    395                                        line = buf.toString();
    396                                }
    397                                // 5.1.2.0 (2010/01/01) 置換する場合の処理
    398                                if( isChange ) {
    399                                        tempWrt.println( line );                                // 5.7.3.2 (2014/02/28) regexで出力を共有する為。
    400                                }
    401                        }
    402                }
    403                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
    404                catch( final CharacterCodingException ex ) {
    405                        final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
    406                                                                +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
    407                                                                +       " [" + org + "] , Encode=[" + inEncode + "]" ;
    408                        throwException( errMsg,ex,errAbend );
    409                        return null;    // ログだけの場合は、以下の処理は打ち切り。
    410                }
    411                catch( final IOException ex ) {
    412                        final String errMsg = "処理中にエラーが発生しました。" + CR
    413                                                + " [" + org + "] , Encode=[" + inEncode + "]" + CR
    414                                                + " [" + data.getRowNo() + "]件目" + CR
    415                                                + " data=[" + data.dataLine() + "]" + CR ;              // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    416                        throwException( errMsg,ex,errAbend );
    417                        return null;    // ログだけの場合は、以下の処理は打ち切り。
    418                }
    419                finally {
    420                        Closer.ioClose( reader );
    421                        Closer.ioClose( tempWrt );
    422                }
    423
    424                // 5.1.2.0 (2010/01/01) 置換する場合の処理
    425                if( isChange && isEquals ) {            // 6.3.1.1 (2015/07/10) 出力が同じ場合の時のみ、後処理が必要。
    426                        if( nextFlag ) {
    427                                if( !org.delete() ) {
    428                                        final String errMsg = "所定のファイルを削除できませんでした。[" + org + "]" + CR
    429                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    430                                        throwException( errMsg,errAbend );
    431                                }
    432                                if( !tempFile.renameTo( org ) ) {
    433                                        final String errMsg = "所定のファイルをリネームできませんでした。[" + tempFile + "]" + CR
    434                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    435                                        throwException( errMsg,errAbend );
    436                                }
    437                        }
    438                        else {
    439                                if( !tempFile.delete() ) {
    440                                        final String errMsg = "所定のファイルを削除できませんでした。[" + tempFile + "]" + CR
    441                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
    442                                        throwException( errMsg,errAbend );
    443                                }
    444                        }
    445                }
    446
    447                return nextFlag ? data : null ;
    448        }
    449
    450        /**
    451         * プロセスの処理結果のレポート表現を返します。
    452         * 処理プログラム名、入力件数、出力件数などの情報です。
    453         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
    454         * 形式で出してください。
    455         *
    456         * @return   処理結果のレポート
    457         */
    458        public String report() {
    459                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
    460                return "[" + getClass().getName() + "]" + CR
    461//              final String report = "[" + getClass().getName() + "]" + CR
    462                                + TAB + "Search File Count : " + inCount    + CR
    463                                + TAB + "Key Find    Count : " + findCount  + CR
    464                                + TAB + "Key Change  Count : " + cngCount ;
    465
    466//              return report ;
    467        }
    468
    469        /**
    470         * このクラスの使用方法を返します。
    471         *
    472         * @return      このクラスの使用方法
    473         * @og.rtnNotNull
    474         */
    475        public String usage() {
    476                final StringBuilder buf = new StringBuilder( 1200 )
    477                        .append( "Process_GrepChange は、上流から受け取った FileLineModelから、語句を"                   ).append( CR )
    478                        .append( "置換する、ChainProcess インターフェースの実装クラスです。"                                  ).append( CR )
    479                        .append( "Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在"          ).append( CR )
    480                        .append( "しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが"        ).append( CR )
    481                        .append( "出来ないことです。"                                                                                                                    ).append( CR )
    482                        .append( CR )
    483                        .append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、" ).append( CR )
    484                        .append( "対象とする語句を置換します。"                                                                                                       ).append( CR )
    485                        .append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、"            ).append( CR )
    486                        .append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。"                   ).append( CR )
    487                        .append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース"   ).append( CR )
    488                        .append( "が前後に存在している場合は、ご注意ください。"                                                                       ).append( CR )
    489                        .append( "置換文字(値)は、\t と \n の特殊文字が使用できます。"                                                       ).append( CR )
    490                        .append( "この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード" ).append( CR )
    491                        .append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。" ).append( CR )
    492                        .append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、" ).append( CR )
    493                        .append( "置き換えた結果も、同じファイルにセーブします。"                                                              ).append( CR )
    494                        .append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。"        ).append( CR )
    495                        .append( "-inEncode は、入力ファイルのエンコード指定になります。"                                             ).append( CR )
    496                        .append( "-outEncode は、出力ファイルのエンコードや、キーワードファイルのエンコード"   ).append( CR )
    497                        .append( "指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)"         ).append( CR )
    498                        .append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") "     ).append( CR )
    499                        .append( "で求まる値を使用します。"                                                                                                         ).append( CR )
    500                        .append( CR )
    501                        .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"  ).append( CR )
    502                        .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"                ).append( CR )
    503                        .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"      ).append( CR )
    504                        .append( "できれば、使用可能です。"                                                                                                         ).append( CR )
    505                        .append( CR )
    506                        .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
    507                        .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"           ).append( CR )
    508                        .append( "繋げてください。"                                                                                                                             ).append( CR )
    509                        .append( CR ).append( CR )
    510                        .append( getArgument().usage() ).append( CR );
    511
    512                return buf.toString();
    513        }
    514
    515        /**
    516         * このクラスは、main メソッドから実行できません。
    517         *
    518         * @param       args    コマンド引数配列
    519         */
    520        public static void main( final String[] args ) {
    521                LogWriter.log( new Process_GrepChange().usage() );
    522        }
    523}