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         * 初期処理(ファイルオープン、DBオープン等)に使用します。
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         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
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        public LineModel action( final LineModel data ) {
282                inCount++ ;
283                final FileLineModel fileData ;
284                if( data instanceof FileLineModel ) {
285                        fileData = (FileLineModel)data ;
286                }
287                else {
288                        // これは、プログラマーの問題なので、errAbend 対象外
289                        final String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
290                        throw new OgRuntimeException( errMsg );
291                }
292
293                final File org = fileData.getFile() ;
294                if( ! org.isFile() ) { return data; }
295
296                if( debug ) { println( "File:" + org ); }               // 5.1.2.0 (2010/01/01) display の条件変更
297
298                File            tempFile  = null;
299                PrintWriter     tempWrt   = null;
300
301                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
302                final String orgName = org.getPath();
303
304                // 5.1.2.0 (2010/01/01) 置換する場合の前処理
305                if( isChange ) {
306                        // 6.3.1.1 (2015/07/10) 出力が同じ場合は、従来通り temp出力して、置き換える。
307                        if( isEquals ) {
308                                tempFile  = new File( orgName + "_temp" );
309                        }
310                        else {
311                                // 入出力が異なる場合
312                                tempFile  = new File( outPath, org.getAbsolutePath().substring( inPathLen ) );
313                                fileData.setFile( tempFile );   // tempFile は、出力ファイルの事。
314                                // 出力先のフォルダが無ければ作成
315                                final File parent = tempFile.getParentFile();
316                                if( parent != null && ! parent.exists() && !parent.mkdirs() ) {
317                                        final String errMsg = "所定のフォルダが作成できませんでした。[" + parent + "]" + CR
318                                                                + " inCount=[" + inCount + "]件" + CR
319                                                                + " data=[" + data.dataLine() + "]" + CR ;              // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
320                                        throwException( errMsg,errAbend );
321                                        return null;    // ログだけの場合は、以下の処理は打ち切り。
322                                }
323                        }
324
325                        tempWrt = FileUtil.getPrintWriter( tempFile,outEncode );
326                }
327
328                boolean nextFlag = false;
329
330                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
331                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
332
333                final BufferedReader reader = FileUtil.getBufferedReader( org,inEncode );
334                try {
335                        String line ;
336                        int    lineNo = 0;
337                        while((line = reader.readLine()) != null) {
338                                lineNo++ ;
339                                // 5.7.3.2 (2014/02/28) regex 対応
340                                if( regex ) {
341                                        for( int i=0; i<pattern.length; i++ ) {
342                                                final Matcher mt = pattern[i].matcher( line );
343                                                if( mt.matches() ) {
344                                                        nextFlag = true;                        // 1度でも見つかれば、true にセット
345                                                        findCount++ ;
346                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
347                                                        if( isChange ) {
348                                                                // 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
349                                                                line = mt.replaceAll( "$1" + change[i] + "$2" );        // 前方参照
350                                                                cngCount++ ;
351                                                        }
352                                                }
353                                        }
354                                }
355                                else {
356                                        final String line2 = ignoreCase ? line.toUpperCase(Locale.JAPAN) : line ;               //  6.4.1.1 (2016/01/16) 先に、必要な処理を行う。
357                                        buf.setLength(0);                               // new StringBuilder の代わり。
358                                        buf.append( line );
359                                        for( int i=0; i<keyword.length; i++ ) {
360                                                int indx = line2.indexOf( keyword[i] );
361                                                // 置換対象発見。行出力用に見つかれば、true にする。
362                                                if( indx >= 0 ) {
363                                                        // 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
364                                                        // 検索結果の前後の文字が、IN_PTN に含まれている場合のみ、見つかったことにする。
365                                                        if( useWordUnit ) {
366                                                                // 見つかった場所のひとつ前の文字が、IN_PTN に存在しなければ、見つからなかった。
367                                                                if( indx > 0 && IN_PTN.indexOf( line.charAt( indx-1 ) ) < 0 ||
368                                                                        // 見つかった場所のひとつ後ろの文字が、IN_PTN に存在しなければ、見つからなかった。
369                                                                        line.length() < (indx+1) && IN_PTN.indexOf( line.charAt( indx+1 ) ) < 0 ) {
370                                                                        // 対象外になったキーワードと行を表示します。
371                                                                        if( display ) { println( "NoChange:" + orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
372                                                                        continue;
373                                                                }
374                                                        }
375                                                        nextFlag  = true;                               // 1度でも見つかれば、true にセット
376                                                        if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
377                                                        findCount++ ;
378
379                                                        // 6.4.1.1 (2016/01/16) 見つかったときだけ、置換処理を実施するように変更。
380                                                        // 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。
381                                                        if( isChange ) {
382                                                                while( indx >= 0 ) {
383                                                                        buf.replace( indx,indx+keyword[i].length(),change[i] );
384                                                                        // 5.7.3.2 (2014/02/28) ignoreCase 対応。
385                                                                        final int nxt = indx+change[i].length();
386                                                                        indx = ignoreCase       ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt )
387                                                                                                                : buf.indexOf( keyword[i],nxt );
388
389                                                                        cngCount++ ;
390                                                                }
391                                                        }
392                                                }
393                                        }
394                                        line = buf.toString();
395                                }
396                                // 5.1.2.0 (2010/01/01) 置換する場合の処理
397                                if( isChange ) {
398                                        tempWrt.println( line );                                // 5.7.3.2 (2014/02/28) regexで出力を共有する為。
399                                }
400                        }
401                }
402                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
403                catch( final CharacterCodingException ex ) {
404                        final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
405                                                                +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
406                                                                +       " [" + org + "] , Encode=[" + inEncode + "]" ;
407                        throwException( errMsg,ex,errAbend );
408                        return null;    // ログだけの場合は、以下の処理は打ち切り。
409                }
410                catch( final IOException ex ) {
411                        final String errMsg = "処理中にエラーが発生しました。" + CR
412                                                + " [" + org + "] , Encode=[" + inEncode + "]" + CR
413                                                + " [" + data.getRowNo() + "]件目" + CR
414                                                + " data=[" + data.dataLine() + "]" + CR ;              // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
415                        throwException( errMsg,ex,errAbend );
416                        return null;    // ログだけの場合は、以下の処理は打ち切り。
417                }
418                finally {
419                        Closer.ioClose( reader );
420                        Closer.ioClose( tempWrt );
421                }
422
423                // 5.1.2.0 (2010/01/01) 置換する場合の処理
424                if( isChange && isEquals ) {            // 6.3.1.1 (2015/07/10) 出力が同じ場合の時のみ、後処理が必要。
425                        if( nextFlag ) {
426                                if( !org.delete() ) {
427                                        final String errMsg = "所定のファイルを削除できませんでした。[" + org + "]" + CR
428                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
429                                        throwException( errMsg,errAbend );
430                                }
431                                if( !tempFile.renameTo( org ) ) {
432                                        final String errMsg = "所定のファイルをリネームできませんでした。[" + tempFile + "]" + CR
433                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
434                                        throwException( errMsg,errAbend );
435                                }
436                        }
437                        else {
438                                if( !tempFile.delete() ) {
439                                        final String errMsg = "所定のファイルを削除できませんでした。[" + tempFile + "]" + CR
440                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
441                                        throwException( errMsg,errAbend );
442                                }
443                        }
444                }
445
446                return nextFlag ? data : null ;
447        }
448
449        /**
450         * プロセスの処理結果のレポート表現を返します。
451         * 処理プログラム名、入力件数、出力件数などの情報です。
452         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
453         * 形式で出してください。
454         *
455         * @return   処理結果のレポート
456         */
457        public String report() {
458                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
459                return "[" + getClass().getName() + "]" + CR
460//              final String report = "[" + getClass().getName() + "]" + CR
461                                + TAB + "Search File Count : " + inCount    + CR
462                                + TAB + "Key Find    Count : " + findCount  + CR
463                                + TAB + "Key Change  Count : " + cngCount ;
464
465//              return report ;
466        }
467
468        /**
469         * このクラスの使用方法を返します。
470         *
471         * @return      このクラスの使用方法
472         * @og.rtnNotNull
473         */
474        public String usage() {
475                final StringBuilder buf = new StringBuilder( 1200 )
476                        .append( "Process_GrepChange は、上流から受け取った FileLineModelから、語句を"                   ).append( CR )
477                        .append( "置換する、ChainProcess インターフェースの実装クラスです。"                                  ).append( CR )
478                        .append( "Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在"          ).append( CR )
479                        .append( "しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが"        ).append( CR )
480                        .append( "出来ないことです。"                                                                                                                    ).append( CR )
481                        .append( CR )
482                        .append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、" ).append( CR )
483                        .append( "対象とする語句を置換します。"                                                                                                       ).append( CR )
484                        .append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、"            ).append( CR )
485                        .append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。"                   ).append( CR )
486                        .append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース"   ).append( CR )
487                        .append( "が前後に存在している場合は、ご注意ください。"                                                                       ).append( CR )
488                        .append( "置換文字(値)は、\t と \n の特殊文字が使用できます。"                                                       ).append( CR )
489                        .append( "この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード" ).append( CR )
490                        .append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。" ).append( CR )
491                        .append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、" ).append( CR )
492                        .append( "置き換えた結果も、同じファイルにセーブします。"                                                              ).append( CR )
493                        .append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。"        ).append( CR )
494                        .append( "-inEncode は、入力ファイルのエンコード指定になります。"                                             ).append( CR )
495                        .append( "-outEncode は、出力ファイルのエンコードや、キーワードファイルのエンコード"   ).append( CR )
496                        .append( "指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)"         ).append( CR )
497                        .append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") "     ).append( CR )
498                        .append( "で求まる値を使用します。"                                                                                                         ).append( CR )
499                        .append( CR )
500                        .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"  ).append( CR )
501                        .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"                ).append( CR )
502                        .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"      ).append( CR )
503                        .append( "できれば、使用可能です。"                                                                                                         ).append( CR )
504                        .append( CR )
505                        .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"    ).append( CR )
506                        .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"           ).append( CR )
507                        .append( "繋げてください。"                                                                                                                             ).append( CR )
508                        .append( CR ).append( CR )
509                        .append( getArgument().usage() ).append( CR );
510
511                return buf.toString();
512        }
513
514        /**
515         * このクラスは、main メソッドから実行できません。
516         *
517         * @param       args    コマンド引数配列
518         */
519        public static void main( final String[] args ) {
520                LogWriter.log( new Process_GrepChange().usage() );
521        }
522}