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 java.util.Arrays;
019import java.util.Enumeration;                                                                                                   // 8.1.3.0 (2022/06/03)
020import java.util.jar.JarFile;                                                                                                   // 8.1.3.0 (2022/06/03)
021import java.util.jar.JarEntry;                                                                                                  // 8.1.3.0 (2022/06/03)
022import java.util.LinkedHashMap;
023import java.util.Locale;                                                                                                                // 8.1.3.0 (2022/06/03)
024import java.util.Map;
025import java.util.regex.Pattern;
026import java.util.regex.Matcher;
027
028import java.io.File;
029import java.io.PrintWriter;
030import java.io.BufferedReader;
031import java.io.IOException;
032import java.io.InputStreamReader;                                                                                               // 8.1.3.0 (2022/06/03)
033import java.io.InputStream;                                                                                                             // 8.1.3.0 (2022/06/03)
034import java.nio.charset.CharacterCodingException;                                                               // 6.3.1.0 (2015/06/28)
035
036import org.opengion.fukurou.system.OgRuntimeException;                                                  // 6.4.2.0 (2016/01/29)
037import org.opengion.fukurou.system.OgCharacterException;                                                // 6.5.0.1 (2016/10/21)
038import org.opengion.fukurou.system.Closer;
039import org.opengion.fukurou.system.LogWriter;
040import org.opengion.fukurou.util.Argument;
041import org.opengion.fukurou.util.FileUtil;
042import org.opengion.fukurou.util.StringUtil;
043import org.opengion.fukurou.util.CommentLineParser;                                                             // 6.3.1.1 (2015/07/10)
044import org.opengion.fukurou.util.FileInfo;                                                                              // 6.4.0.2 (2015/12/11)
045import org.opengion.hayabusa.common.HybsSystem;                                                                 // 8.1.3.0 (2022/06/03)
046
047/**
048 * Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す
049 * ChainProcess インターフェースの実装クラスです。
050 *
051 * 正規表現の keyword を上流から受け取った FileLineModel から検索します。
052 * 見つかった対象ファイルから、指定の文字列を置換する場合は、-change か
053 * -changeFile で、keyword を置換する文字列を指定して下さい。
054 * 置換する文字列には、\t と \n の特殊文字が使用できます。
055 *
056 * 処理対象は、通常は、1行づつ読み取りながら処理を行います。存在チェックの場合は、
057 * 見つかった時点で処理を中止します。これは、該当箇所をピックアップするのではなく、
058 * 存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです。
059 * keyword を、改行を含む正規表現で、検索・置換する場合は、-useBulkRead 属性を
060 * true に設定してください。これは、入力ファイルを一括して読み込みます。
061 * -ignoreCase は、検索時にキーの大文字小文字を無視するように指定します。
062 * -notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します。
063 * これは、行単位ではなく、ファイル単位に判定しますので、change 指定した場合
064 * でも、対象行は、見つかった行です。ただし、下流に対して、見つからない
065 * 場合だけ処理を継続させます。
066 * -inEncode は、入力ファイルのエンコード指定になります。
067 * -outEncode は、出力ファイルのエンコードや、changeFileで指定の置換文字列ファイルの
068 * エンコード指定になります。(changeFile は、必ず 出力ファイルと同じエンコードです。)
069 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で
070 * 求まる値を使用します。
071 * -changeFile を使用することで、複数行の文字列に置換することが可能です。
072 * -outfile では、処理を行ったファイル名一覧をセーブします。
073 *
074 * 上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の
075 * ファイルオブジェクトより、指定の文字列が含まれているか検索します。
076 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
077 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
078 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
079 * できれば、使用可能です。
080 *
081 * ※ 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
082 *
083 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。
084 * 引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に
085 * 繋げてください。
086 *
087 * ※ 8.1.3.0 (2022/06/03) jarPrefix、jarSuffix、jarInstr、useRegexp、saveFile 追加
088 *    jar ファイルの中身も検索します。その際、jarファイルに圧縮されているファイル名での
089 *    絞り込みができるように、指定できる属性を追加します。
090 *    ただし、jarファイル内の検索は、useAllFind=true(置換ではなく検索だけ最後まで行う)のみです。
091 *    上流から jar ファイルが指定された場合は、常に検索対象になります。
092 *
093 * @og.formSample
094 *  Process_Grep -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
095 *
096 *    -keyword=キーワード        :検索する語句
097 *   [-ignoreCase=大文字小文字 ] :検索時に大文字小文字を区別しない(true)かどうか(初期値:区別する[false])
098 *   [-notEquals=判定結果の反転] :判定結果を反転させる(true)かどうか(初期値:反転させない[false])
099 *   [-inEncode=入力エンコード     ] :入力ファイルのエンコードタイプ
100 *   [-outEncode=出力エンコード    ] :出力ファイルや置換ファイルのエンコードタイプ
101 *   [-change=置換文字列       ] :-change="ABCD" \t や \n などの特殊文字が使用できます。
102 *   [-changeFile=置換ファイル     ] :-changeFile=change.txt このファイルの記述すべてと置換します。
103 *                                     -change と、-changeFile は、同時に指定できません。
104 *                                     置換機能使用時は、必ず、_backup というファイルが作成されます。
105 *   [-insert=[HEAD/CHANGE/BEFORE/AFTER/TAIL]   ]
106 *                               : 置換でなく挿入する場合の位置を指定します(初期値:CHANGE)
107 *                                 スペースで区切って数字を記述すると、挿入位置にオフセットできます。
108 *   [-delete=[false/true]     ] : 置換でなく削除します(初期値:false)
109 *   [-skipRowCount=スキップ行数  ] : 先頭行から、スキップする行数を指定します(useBulkRead時には使用されません)
110 *   [-useBackup=[false/true]  ] :trueは、backupファイルを作成します(初期値:false)
111 *   [-useBulkRead=[false/true]] :trueは、入力ファイルを一括読込します(初期値:false)
112 *   [-useAllFind=[false/true] ] :置換ではなく検索だけ最後まで行う場合、trueを指定します(初期値:false)
113 *   [-useOmitCmnt=[false/true]] :コメント部分を削除したファイルでgrep処理を行うかどうかを指定(初期値:false)
114 *   [-errAbend=[true/false]   ] :異常発生時に、処理を中断(true)するか、継続(false)するかを指定する(初期値:true[中断する])
115 *   [-jarPrefix=接頭辞        ] :File・・・・,View・・・・,など、指定の接頭辞で始まるjarファイルを中を検索 8.1.3.0 (2022/06/03)
116 *   [-jarSuffix=接尾辞        ] :.txt|.java|.jsp.... など、指定の接尾辞で終わるjarファイルを中を検索 8.1.3.0 (2022/06/03)
117 *   [-jarInstr=部分文字列     ] :jarファイルを中と一致する部分文字列を指定 8.1.3.0 (2022/06/03)
118 *   [-useRegexp=[false/true]  ] :trueは、正規表現で検索します(初期値:false) 8.1.3.0 (2022/06/03)
119 *   [-saveFile=保存ファイル       ] :検索結果を指定ファイルに保存します 8.1.3.0 (2022/06/03)
120 *   [-display=[false/true]    ] :trueは、検索状況を表示します(初期値:false)
121 *   [-debug=[false/true]      ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
122 *
123 * @version  4.0
124 * @author   Kazuhiko Hasegawa
125 * @since    JDK5.0,
126 */
127public class Process_Grep extends AbstractProcess implements ChainProcess {
128        private static final String[] INSERT_LIST = { "HEAD","CHANGE","BEFORE","AFTER","TAIL" };        // 6.2.4.0 (2015/05/15)
129        private static final String ENCODE              = "UTF-8";                                                                                      // 8.1.3.0 (2022/06/03)
130        private static final String AUTO_ENCODE = "autoEncode";                                                                         // 8.1.3.0 (2022/06/03)
131
132        /** 8.1.3.0 (2022/06/03) 拡張子をエンコードに変換するMap */
133        private static final Map<String,String> EXT2ENC = Map.ofEntries(
134                Map.entry("bat"  , "Windows-31J"),      Map.entry("ken"  , "Windows-31J"),
135                Map.entry("sql"  , "Windows-31J"),      Map.entry("vbs"  , "Windows-31J"),
136                Map.entry("css"  , "UTF-8"),            Map.entry("html" , "UTF-8"),
137                Map.entry("java" , "UTF-8"),            Map.entry("js"   , "UTF-8"),
138                Map.entry("jsp"  , "UTF-8"),            Map.entry("xml"  , "UTF-8"),
139                Map.entry("jar"  , "UTF-8")                                                                                             // 除外されない為に追記
140        );
141
142        private Pattern pattern;
143        private String  keyword;
144        private boolean ignoreCase;
145        private boolean notEquals;
146        private String  inEncode;
147        private String  outEncode;
148        private String  change;
149        private String  insert          = "CHANGE";                                                                             // "HEAD","CHANGE","BEFORE","AFTER","TAIL" のどれか
150        private int             insOffset;                                                                                                      // "BEFORE","AFTER" 時のオフセット
151        private boolean useBackup;
152        private boolean useBulkRead;                                                                                            // 4.0.1.0 (2007/12/14) 一括読込
153        private boolean delete;
154        private boolean useAllFind;                                                                                                     // 6.3.1.1 (2015/07/10) 最後まで検索
155        private boolean useOmitCmnt;                                                                                            // 6.3.1.1 (2015/07/10) コメント除外
156        private boolean errAbend        = true;                                                                                 // 6.3.1.0 (2015/06/28) 中断する
157        private String  jarPrefix;                                                                                                      // 8.1.3.0 (2022/06/03)
158        private String  jarSuffix;                                                                                                      // 8.1.3.0 (2022/06/03)
159        private String  jarInstr;                                                                                                       // 8.1.3.0 (2022/06/03)
160        private boolean useRegexp;                                                                                                      // 8.1.3.0 (2022/06/03)
161        private String  saveFile;                                                                                                       // 8.1.3.0 (2022/06/03)
162        private boolean display;
163        private boolean debug;                                                                                                          // 5.1.2.0 (2010/01/01)
164
165        private int             inCount;
166        private int             findCount;
167        private int             cngCount;
168        private int             skipRowCount;                                                                                           // 6.2.4.0 (2015/05/15) 行スキップ
169        private PrintWriter             outWriter;                                                                                      // 8.1.3.0 (2022/06/03) PrintWriterオブジェクト
170        private final String    fileURL = HybsSystem.sys( "FILE_URL" );                         // 8.1.3.0 (2022/06/03) ファイルURL
171
172        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
173        private static final Map<String,String> MUST_PROPARTY   ;               // [プロパティ]必須チェック用 Map
174        /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
175        private static final Map<String,String> USABLE_PROPARTY ;               // [プロパティ]整合性チェック Map
176
177        static {
178                MUST_PROPARTY = new LinkedHashMap<>();
179                MUST_PROPARTY.put( "keyword",   "検索する語句(必須)" );
180
181                USABLE_PROPARTY = new LinkedHashMap<>();
182                USABLE_PROPARTY.put( "ignoreCase",      "検索時に大文字小文字を区別しない(true)かどうか。" +
183                                                                                CR + "(初期値:区別する[false])" );
184                USABLE_PROPARTY.put( "notEquals",       "検索時に判定結果を反転させる(true)かどうか。" +
185                                                                                CR + "(初期値:反転させない[false])" );
186                USABLE_PROPARTY.put( "inEncode",        "入力ファイルのエンコードタイプ" );
187                USABLE_PROPARTY.put( "outEncode",       "出力ファイルや置換ファイルのエンコードタイプ" );
188                USABLE_PROPARTY.put( "change",          "置換文字列 例: -change=\"ABCD\" \\t や \\n などの特殊文字が使用できます。" );
189                USABLE_PROPARTY.put( "changeFile",      "置換文字列ファイル 例: -changeFile=change.txt" +
190                                                                                CR + "-change と、-changeFile は、同時に指定できません。" +
191                                                                                CR + "置換機能使用時は、必ず、_backup というファイルが作成されます。" );
192                USABLE_PROPARTY.put( "insert",          "[HEAD/CHANGE/BEFORE/AFTER/TAIL]:置換でなく挿入する場合の位置を指定します(初期値:CHANGE)"  +
193                                                                                CR + "スペースで区切って数字を記述すると、挿入位置にオフセットできます。" );
194                USABLE_PROPARTY.put( "delete",          "[false/true]:trueは、置換でなく削除します(初期値:false)" );
195                USABLE_PROPARTY.put( "skipRowCount","先頭行から、スキップする行数を指定します。" );                                                                 // 6.2.4.0 (2015/05/15)
196                USABLE_PROPARTY.put( "useBackup",       "[false/true]:trueは、backupファイルを作成します(初期値:false)" );
197                USABLE_PROPARTY.put( "useBulkRead",     "[false/true]:trueは、入力ファイルを一括読込します(初期値:false)" );
198                USABLE_PROPARTY.put( "useAllFind",      "置換ではなく検索だけ最後まで行う場合、trueを指定します(初期値:false)" );   // 6.3.1.1 (2015/07/10)
199                USABLE_PROPARTY.put( "useOmitCmnt",     "コメント部分を削除したファイルでgrep処理を行うかどうかを指定(初期値:false)" );                // 6.3.1.1 (2015/07/10)
200                USABLE_PROPARTY.put( "jarPrefix",       "File・・・・,View・・・・,など、指定の接頭辞で始まるjarファイルを中を検索" );                        // 8.1.3.0 (2022/06/03)
201                USABLE_PROPARTY.put( "jarSuffix",       ".txt|.java|.jsp.... など、指定の接尾辞で終わるjarファイルを中を検索" );              // 8.1.3.0 (2022/06/03)
202                USABLE_PROPARTY.put( "jarInstr",        "jarファイルを中と一致する部分文字列を指定" );                                                                     // 8.1.3.0 (2022/06/03)
203                USABLE_PROPARTY.put( "useRegexp",       "[false/true]:trueは、正規表現で検索します(初期値:false)" );                           // 8.1.3.0 (2022/06/03)
204                USABLE_PROPARTY.put( "errAbend",        "異常発生時に、処理を中断(true)するか、継続(false)するか" +
205                                                                                CR + "(初期値:true:中断する)" );                                                                                               // 6.3.1.0 (2015/06/28)
206                USABLE_PROPARTY.put( "saveFile",        "検索結果を指定ファイルに保存します" );                                                                                  // 8.1.3.0 (2022/06/03)
207                USABLE_PROPARTY.put( "display",         "[false/true]:trueは、検索状況を表示します(初期値:false)" );
208                USABLE_PROPARTY.put( "debug",           "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
209                                                                                        CR + "(初期値:false:表示しない)" );
210        }
211
212        /**
213         * デフォルトコンストラクター。
214         * このクラスは、動的作成されます。デフォルトコンストラクターで、
215         * super クラスに対して、必要な初期化を行っておきます。
216         *
217         */
218        public Process_Grep() {
219                super( "org.opengion.fukurou.process.Process_Grep",MUST_PROPARTY,USABLE_PROPARTY );
220        }
221
222        /**
223         * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
224         * 初期処理(ファイルオープン、DBオープン等)に使用します。
225         *
226         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加。
227         * @og.rev 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
228         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
229         *
230         * @param       paramProcess    データベースの接続先情報などを持っているオブジェクト
231         */
232        public void init( final ParamProcess paramProcess ) {
233                final Argument arg = getArgument();
234
235                keyword                 = arg.getProparty( "keyword");
236                ignoreCase              = arg.getProparty( "ignoreCase" ,ignoreCase     );
237                notEquals               = arg.getProparty( "notEquals"  ,notEquals      );
238                inEncode                = arg.getProparty( "inEncode"   ,System.getProperty("file.encoding"));
239                outEncode               = arg.getProparty( "outEncode"  ,System.getProperty("file.encoding"));
240                useBackup               = arg.getProparty( "useBackup"  ,useBackup      );
241                useBulkRead             = arg.getProparty( "useBulkRead",useBulkRead);          // 4.0.1.0 (2007/12/14)
242                delete                  = arg.getProparty( "delete"             ,delete         );
243                insert                  = arg.getProparty( "insert"             ,insert         );
244                change                  = arg.getFileProparty( "change" ,"changeFile",outEncode,false );
245                skipRowCount    = arg.getProparty( "skipRowCount",0                     );              // 6.2.4.0 (2015/05/15)
246                useAllFind              = arg.getProparty( "useAllFind" ,useAllFind);           // 6.3.1.1 (2015/07/10)
247                useOmitCmnt             = arg.getProparty( "useOmitCmnt",useOmitCmnt);          // 6.3.1.1 (2015/07/10)
248                errAbend                = arg.getProparty( "errAbend"   ,errAbend       );              // 6.3.1.0 (2015/06/28) errAbend属性追加
249                jarPrefix               = arg.getProparty( "jarPrefix"  ,jarPrefix      );              // 8.1.3.0 (2022/06/03)
250                jarSuffix               = arg.getProparty( "jarSuffix"  ,jarSuffix      );              // 8.1.3.0 (2022/06/03)
251                jarInstr                = arg.getProparty( "jarInstr"   ,jarInstr       );              // 8.1.3.0 (2022/06/03)
252                useRegexp               = arg.getProparty( "useRegexp"  ,useRegexp      );              // 8.1.3.0 (2022/06/03)
253                saveFile                = arg.getProparty( "saveFile"   ,saveFile       );              // 8.1.3.0 (2022/06/03)
254                display                 = arg.getProparty( "display"    ,display        );
255                debug                   = arg.getProparty( "debug"              ,debug          );              // 5.1.2.0 (2010/01/01)
256
257                if( change != null ) {
258                        final int adrs = insert.indexOf( ' ' ); // オフセット数字の有無
259                        if( adrs > 0 ) {
260                                insOffset = Integer.parseInt( insert.substring( adrs+1 ) );
261                                insert    = insert.substring( 0,adrs );
262                        }
263
264                        boolean isOK = false;
265                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
266//                      for( int i=0; i<INSERT_LIST.length; i++ ) {
267//                              if( insert.equalsIgnoreCase( INSERT_LIST[i] ) ) {
268                        for( final String insClm : INSERT_LIST ) {
269                                if( insert.equalsIgnoreCase( insClm ) ) {
270                                        isOK = true; break;
271                                }
272                        }
273                        if( !isOK ) {
274                                // 実行時エラーではないので、errAbend 対象外
275                                final String errMsg = "insert は、" + Arrays.toString( INSERT_LIST )
276                                                                        + " から指定してください。" + CR
277                                                                        + "-insert=[" + insert + "]" ;
278                                throw new OgRuntimeException( errMsg );
279                        }
280
281                        change = StringUtil.replace( change,"\\n",CR );
282                        change = StringUtil.replace( change,"\\t","\t" );
283                }
284
285                if( delete ) { change = ""; }   // 削除は、"" 文字列と置換します。
286
287                // 8.1.3.0 (2022/06/03) Modify
288//              if( ignoreCase ) {
289//                      pattern = Pattern.compile( keyword,Pattern.CASE_INSENSITIVE );
290//              }
291//              else {
292//                      pattern = Pattern.compile( keyword );
293//              }
294                // 大文字小文字を区別しない
295                if( ignoreCase ) {
296                        if( useRegexp ) { pattern = Pattern.compile( keyword,Pattern.CASE_INSENSITIVE ); }      // 正規表現を使用する
297                        else { keyword = keyword.toLowerCase( Locale.JAPAN ); }
298                }
299                // 大文字小文字を区別する
300                else {
301                        if( useRegexp ) { pattern = Pattern.compile( keyword ); }                                                       // 正規表現を使用しない
302                }
303
304                // jarファイルの接頭辞/接尾辞/部分文字列
305                if( StringUtil.isNotNull(jarPrefix) ){ jarPrefix = jarPrefix.toLowerCase( Locale.JAPAN ); }
306                if( StringUtil.isNotNull(jarSuffix) ){ jarSuffix = jarSuffix.toLowerCase( Locale.JAPAN ); }
307                if( StringUtil.isNotNull(jarInstr)  ){ jarInstr = jarInstr.toLowerCase( Locale.JAPAN ); }
308
309                // 8.1.3.0 (2022/06/03) 保存ファイル指定有り
310                if( StringUtil.isNotNull(saveFile) ){
311                        final String filename = HybsSystem.url2dir( StringUtil.urlAppend( fileURL, saveFile ) );
312                        outWriter = FileUtil.getPrintWriter( new File( filename ), ENCODE, true );
313                }
314        }
315
316        /**
317         * プロセスの終了を行います。最後に一度だけ、呼び出されます。
318         * 終了処理(ファイルクローズ、DBクローズ等)に使用します。
319         *
320         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
321         *
322         * @param       isOK    トータルで、OKだったかどうか[true:成功/false:失敗]
323         */
324        public void end( final boolean isOK ) {
325                // 8.1.3.0 (2022/06/03) 保存ファイル指定有り
326                if( StringUtil.isNotNull(saveFile) ){
327                        Closer.ioClose( outWriter );
328                }
329        }
330
331        /**
332         * 引数の LineModel を処理するメソッドです。
333         * 変換処理後の LineModel を返します。
334         * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
335         * null データを返します。つまり、null データは、後続処理を行わない
336         * フラグの代わりにも使用しています。
337         * なお、変換処理後の LineModel と、オリジナルの LineModel が、
338         * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
339         * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
340         * 各処理ごとに自分でコピー(クローン)して下さい。
341         *
342         * @og.rev 4.0.1.0 (2007/12/14) ファイルの一括処理対応。
343         * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
344         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加。
345         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
346         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
347         *
348         * @param       data    オリジナルのLineModel
349         *
350         * @return      処理変換後のLineModel
351         */
352        @Override       // ChainProcess
353        public LineModel action( final LineModel data ) {
354                inCount++ ;
355
356                final FileLineModel fileData ;
357                if( data instanceof FileLineModel ) {
358                        fileData = (FileLineModel)data ;
359                }
360                else {
361                        // これは、プログラマーの問題なので、errAbend 対象外
362                        final String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
363                        throw new OgRuntimeException( errMsg );
364                }
365
366                final File file = fileData.getFile() ;
367                if( !file.isFile() ) {
368                        if( display ) { println( data.dataLine() ); }           // 5.1.2.0 (2010/01/01) display の条件変更
369                        return data;
370                }
371
372                boolean isFind = false ;        // 6.3.1.0 (2015/06/28) errAbend属性追加に伴う、初期化漏れ対応
373
374                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
375                String errMsg = null;
376                try {
377                        String fileLine = null;
378                        int firstLineNo = -1;
379
380                        // 8.1.3.0 (2022/06/03) 拡張子によるエンコード指定
381                        final String mapEnc = getEncode( file );
382                        // 8.1.3.0 (2022/06/03) 該当するエンコード無し
383                        if( !"-".equals(mapEnc) ) {
384                                // 8.1.3.0 (2022/06/03) jarファイル内の検索
385//                              if( useBulkRead ) { fileLine    = findKeywordAsBulk( file ); }
386//                              else                      { firstLineNo = findKeyword( file ); }
387                                if( useBulkRead ) { fileLine = findKeywordAsBulk( file,mapEnc ); }
388                                else {
389                                        if( file.getName().endsWith( ".jar" ) ) { findJarKeyword( file ); }             // firstLineNo は、常に -1
390                                        else                                                                    { firstLineNo = findKeyword( file,mapEnc ); }
391                                }
392                        }
393
394                        isFind = fileLine != null || firstLineNo >= 0 ;
395
396                        // 置換処理 ただし、見つかったときのみ実行
397                        if( change != null && isFind ) {
398                                // 入力ファイルは、オリジナル_backup ファイルとする。過去のファイルを削除
399                                final File inFile = new File( file.getPath() + "_backup" );
400                                if( inFile.exists() && !inFile.delete() ) {
401                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
402//                                      final String errMsg = "過去のBKUPファイルを削除できませんでした。[" + inFile + "]" + CR
403                                        errMsg = "過去のBKUPファイルを削除できませんでした。[" + inFile + "]" + CR
404                                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
405                                        // try の中から throw するのは、行儀がよくないが、catch ブロックで errAbend処理する。
406//                                      throw new OgRuntimeException( errMsg );
407                                }
408
409                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
410                                if( errMsg == null ) {
411                                        // オリジナルのファイルを、_backup ファイル名に先に変換する。
412                                        final File fromFile = new File( file.getPath() );
413                                        if( !fromFile.renameTo( inFile ) ) {
414                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
415//                                              final String errMsg = "所定のファイルをリネームできませんでした。[" + fromFile + "]" + CR
416                                                errMsg = "所定のファイルをリネームできませんでした。[" + fromFile + "]" + CR
417                                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
418                                                // try の中から throw するのは、行儀がよくないが、catch ブロックで errAbend処理する。
419//                                              throw new OgRuntimeException( errMsg );
420                                        }
421                                }
422
423                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
424                                if( errMsg == null ) {
425                                        // 変換処理 本体
426                                        if( useBulkRead ) { changeKeywordAsBulk( fileLine,file ); }
427//                                      else                      { changeKeyword( inFile,file,firstLineNo ); }
428                                        else                      { changeKeyword( inFile,file,firstLineNo,mapEnc ); }  // 8.1.3.0 (2022/06/03)
429
430                                        // backup を使わない場合は、削除する。
431                                        // 4.0.0.0 (2007/11/29) 入れ子if の統合
432                                        if( !useBackup && !inFile.delete() ) {
433                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
434//                                              final String errMsg = "所定のファイルを削除できませんでした。[" + inFile + "]" + CR
435                                                errMsg = "所定のファイルを削除できませんでした。[" + inFile + "]" + CR
436                                                                        +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
437                                                // try の中から throw するのは、行儀がよくないが、catch ブロックで errAbend処理する。
438//                                              throw new OgRuntimeException( errMsg );
439                                        }
440                                }
441                        }
442                }
443                catch( final RuntimeException ex ) {
444                        final String errMsg2 = "処理中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR
445                                                +       "data=[" + data.dataLine() + "]" + CR ;         // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
446                        // 6.3.1.0 (2015/06/28) errAbend属性追加。
447                        throwException( errMsg2,ex,errAbend );
448                }
449
450                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
451                if( errMsg != null ) {
452                        throw new OgRuntimeException( errMsg );
453                }
454
455//              if( display && ( notEquals ^ isFind ) ) { println( data.dataLine() ); }         // 5.1.2.0 (2010/01/01) display の条件変更
456                if( notEquals ^ isFind && display ) { println( data.dataLine() ); }                     // 5.1.2.0 (2010/01/01) display の条件変更   // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
457                return notEquals ^ isFind ? data : null ;
458        }
459
460        /**
461         * キーワードが存在しているかどうかをチェックします。
462         * ここでは、1行づつ読み取りながら、すべてのキーワードをピックアップします。
463         *
464         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
465         * @og.rev 8.5.2.0 (2023/07/14) -change=置換文字列 の指定がなくても findCount がセットされるように対応
466         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
467         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
468         *
469         * @param       file    検索元のファイルオブジェクト
470         */
471        private void findJarKeyword( final File file ) {
472                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
473//              JarFile jarFile = null;
474
475//              try {
476//                      jarFile = new JarFile( file );
477
478                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
479                String errMsg = null;
480
481                try ( JarFile jarFile = new JarFile( file ) ) {
482                        final Enumeration<JarEntry> flEnum = jarFile.entries() ;                        // Generics警告対応
483                        while( flEnum.hasMoreElements() ) {
484                                final JarEntry ent = flEnum.nextElement();                                              // Generics警告対応
485                                if( ent.isDirectory() ) { continue; }
486
487                                final String fileName = ent.getName();                                                  // jarファイル内のファイル
488
489                                // 拡張子によるエンコード指定
490                                final String mapEnc = getEncode( fileName );
491                                // 該当するエンコード無し
492                                if( "-".equals(mapEnc) ) { continue; }
493
494                                final String lowName = fileName.toLowerCase( Locale.JAPAN );
495                                if( ( jarPrefix == null || lowName.startsWith( jarPrefix ) )    &&
496                                        ( jarSuffix == null || lowName.endsWith( jarSuffix )   )        &&
497                                        ( jarInstr  == null || lowName.contains( jarInstr )    ) ) {
498
499                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
500                                        InputStream stream = null;
501                                        BufferedReader reader = null;
502                                        try {
503                                                stream = jarFile.getInputStream( ent ) ;
504//                                      try ( InputStream stream = jarFile.getInputStream( ent ) ;
505//                                                BufferedReader reader = new BufferedReader( new InputStreamReader( stream, mapEnc ) ) ) {
506                                                reader = new BufferedReader( new InputStreamReader( stream, mapEnc ) );
507
508                                                final CommentLineParser clp = useOmitCmnt ? new CommentLineParser( FileInfo.getSUFIX( fileName ) ) : null;
509//                                              try {
510                                                        String line ;
511                                                        int lineNo = 0;
512                                                        while((line = reader.readLine()) != null) {
513                                                                lineNo++ ;                                                                              // 注意:ここで返す行数は、コメント行を含む行数とする
514
515                                                                // useOmitCmnt 機能(コメント行を削除する処理を入れる)
516                                                                if( useOmitCmnt ) {
517                                                                        line = clp.line( line );
518                                                                        if( line == null ) { continue; }                        // 戻り値が null の場合は、行として不成立
519                                                                }
520
521                                                                // キーワードを含むかどうか判定
522                                                                if( isKeyword( line ) ) {
523                                                                        final String msg = file.getPath() + "\\" + fileName + '(' + lineNo + "):" + line ;
524                                                                        if( debug ) {
525                                                                                final String buf = "DEBUG:\t" + msg ;
526                                                                                println( buf );
527                                                                        }
528                                                                        // useAllFind=true 相当の処理のみ行う
529                                                                        println( msg );
530                                                                        findCount++ ;                                                           // 8.5.2.0 (2023/07/14) Add
531                                                                        // 保存ファイル指定有り
532                                                                        if( StringUtil.isNotNull(saveFile) ){ outWriter.println( msg ); }
533
534                                                                        // useAllFind 機能(最後まで検索を続ける)
535                                                                        if( !useAllFind ) { break; }
536                                                                }
537                                                        }
538//                                              }
539                                        }
540                                        // nioを使用すると UTF-8とShuft-JISで、エラーになる
541                                        catch( final CharacterCodingException ex ) {
542                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
543//                                              final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
544                                                errMsg = "文字のエンコード・エラーが発生しました。" + CR
545                                                                        +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
546                                                                        +       ex.getMessage()                                                                                 + CR
547                                                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
548//                                                                      +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ;
549                                                                        +       pathEncode( file, mapEnc );
550//                                              // 呼出元で errAbend 処理するので、そのまま throw しておく
551//                                              throw new OgCharacterException( errMsg,ex );
552                                                break;
553                                        }
554                                        catch( final IOException ex ) {
555                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
556//                                              final String errMsg = "キーワードファイル読取エラーが発生しました。" + CR
557                                                errMsg = "キーワードファイル読取エラーが発生しました。" + CR
558                                                                        +       ex.getMessage()                                                 + CR
559                                                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
560//                                                                      +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ;
561                                                                        +       pathEncode( file, mapEnc );
562//                                              // 呼出元で errAbend 処理するので、そのまま throw しておく
563//                                              throw new OgRuntimeException( errMsg,ex );
564                                                break;
565                                        }
566//                                              finally {
567//                                                      Closer.ioClose( reader );
568//                                              }
569//                                      }
570                                        finally {
571                                                Closer.ioClose( reader );
572                                                Closer.ioClose( stream );
573                                        }
574                                }
575                        }
576                }
577                catch( final IOException ex ) {
578                        final String errMsg2 = "キーワードファイル読取エラーが発生しました。" + CR
579                                                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
580//                                                                      +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
581                                                                        +       pathEncode( file, inEncode );
582                        // 呼出元で errAbend 処理するので、そのまま throw しておく
583                        throw new OgRuntimeException( errMsg2,ex );
584                }
585//              finally {
586//                      Closer.zipClose( jarFile );
587//              }
588
589                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
590                if( errMsg != null ) {
591                        throw new OgRuntimeException( errMsg );
592                }
593        }
594
595        /**
596         * 拡張子をエンコードに変換します。
597         *
598         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
599         *
600         * @param       file    検索元のファイル
601         * @return      エンコード
602         */
603        private String getEncode( final File file ) {
604                return getEncode( file.getName() );
605        }
606
607        /**
608         * 拡張子をエンコードに変換します。
609         *
610         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
611         *
612         * @param       fileName        検索元のファイル名
613         * @return      エンコード
614         */
615        private String getEncode( final String fileName ) {
616                if( AUTO_ENCODE.equalsIgnoreCase( inEncode ) ) {                        // 自動判定
617                        final String sufix = FileInfo.getSUFIX( fileName );             // 拡張子取得
618                        return StringUtil.nval( EXT2ENC.get( sufix ), "-" );
619                }
620                else {
621                        return StringUtil.nval( inEncode, "-" );
622                }
623        }
624
625        /**
626         * 該当の行にキーワードを含むかどうか判定します。
627         *
628         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
629         *
630         * @param       line    検索元のファイルの行
631         * @return      キーワードを含むかどうか[true/false]
632         */
633        private boolean isKeyword( final String line ) {
634                if( useRegexp ){                // 正規表現を使用する
635                        final Matcher mach = pattern.matcher( line );
636                        return mach.find();
637                }
638
639                // 8.5.5.1 (2024/02/29) PMD 7.0.0 OnlyOneReturn メソッドには終了ポイントが 1 つだけ必要
640                return ignoreCase
641                                ? line.toLowerCase( Locale.JAPAN ).contains( keyword )          // 大文字小文字を区別しない
642                                : line.contains( keyword );                                                                     // 大文字小文字を区別する
643
644//              // 正規表現を使用しない
645//              else {
646//                      // 大文字小文字を区別しない
647//                      if( ignoreCase ) {
648//                              return line.toLowerCase( Locale.JAPAN ).contains( keyword );
649//                      }
650//                      // 大文字小文字を区別する
651//                      else {
652//                              return line.contains( keyword );
653//                      }
654//              }
655        }
656
657        /**
658         * キーワードが存在しているかどうかをチェックします。
659         * ここでは、1行づつ読み取りながら、最初に見つかった時点で制御を返します。
660         * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな
661         * ファイル等での検索には、効率的です。
662         *
663         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
664         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
665         * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加。
666         * @og.rev 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加
667         * @og.rev 6.4.0.2 (2015/12/11) CommentLineParser 改造。
668         * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。
669         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
670         * @og.rev 8.5.2.0 (2023/07/14) -change=置換文字列 の指定がなくても findCount がセットされるように対応
671         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
672         *
673         * @param       file    検索元のファイルオブジェクト
674         * @param       mapEnc  検索元のファイルエンコード
675         *
676         * @return      最初に見つかった行番号(見つからなければ、-1 を返す)
677         */
678//      private int findKeyword( final File file ) {
679        private int findKeyword( final File file ,final String mapEnc ) {                               // 8.1.3.0 (2022/06/03)
680                int firstLineNo = -1;
681//              final BufferedReader reader = FileUtil.getBufferedReader( file,inEncode );
682                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
683//              final BufferedReader reader = FileUtil.getBufferedReader( file,mapEnc );        // 8.1.3.0 (2022/06/03)
684
685                // 6.4.0.2 (2015/12/11) CommentLineParser 改造
686                final CommentLineParser clp = useOmitCmnt ? new CommentLineParser( FileInfo.getSUFIX( file ) ) : null;
687//              try {
688                try ( BufferedReader reader = FileUtil.getBufferedReader( file,mapEnc ) ) {     // 8.1.3.0 (2022/06/03)
689                        String line ;
690                        int lineNo = 0;
691                        while((line = reader.readLine()) != null) {
692                                lineNo++ ;              // 注意:ここで返す行数は、コメント行を含む行数とする。
693
694                                // 6.3.1.1 (2015/07/10) useOmitCmnt 機能。コメント行を削除する処理を入れる。
695                                if( useOmitCmnt ) {
696                                        line = clp.line( line );
697                                        if( line == null ) { continue; }        // 戻り値が null の場合は、行として不成立
698                                }
699                                // 8.1.3.0 (2022/06/03) Modify
700//                              final Matcher mach = pattern.matcher( line );
701//                              if( mach.find() ) {
702                                // キーワードを含むかどうか判定
703                                if( isKeyword( line ) ) {
704                                        if( debug ) {
705                                                final String buf = "DEBUG:\t" + file.getPath() + "(" + lineNo + "): " + line ;
706                                                println( buf );
707                                        }
708
709                                        // 6.3.1.1 (2015/07/10) useAllFind 機能。最後まで検索を続けます。
710                                        if( useAllFind ) {
711                                                final String msg = file.getAbsolutePath() + '(' + lineNo + "):" + line ;
712                                                println( msg );
713                                                findCount++ ;                                                                                   // 8.5.2.0 (2023/07/14) Add
714                                                // 8.1.3.0 (2022/06/03) 保存ファイル指定有り
715                                                if( StringUtil.isNotNull(saveFile) ){ outWriter.println( msg ); }
716                                        }
717                                        else {
718                                                firstLineNo = lineNo;
719                                                break;
720                                        }
721                                }
722                        }
723                }
724                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
725                catch( final CharacterCodingException ex ) {
726                        final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
727                                                                +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
728//                                                              +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
729                                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
730//                                                              +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
731                                                                +       pathEncode( file, mapEnc );
732                        // 呼出元で、errAbend処理するので、そのまま、throw しておく。
733                        throw new OgCharacterException( errMsg,ex );    // 6.5.0.1 (2016/10/21)
734                }
735                catch( final IOException ex ) {
736                        final String errMsg = "キーワードファイル読取エラーが発生しました。" + CR
737//                                                              +       " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ;
738                                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
739//                                                              +       " [" + file.getPath() + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
740                                                                +       pathEncode( file, mapEnc );
741                        // 呼出元で、errAbend処理するので、そのまま、throw しておく。
742                        throw new OgRuntimeException( errMsg,ex );
743                }
744//              finally {
745//                      Closer.ioClose( reader );
746//              }
747
748                return firstLineNo;
749        }
750
751        /**
752         * キーワードが存在しているかどうかをチェックします。
753         * ここでは、ファイルをすべて読み取ってから、チェックします。
754         * よって、複数行にまたがる keyword でのマッチングが可能です。
755         *
756         * @og.rev 4.0.1.0 (2007/12/14) 新規追加
757         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
758         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
759         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
760         * @og.rev 8.5.2.0 (2023/07/14) -change=置換文字列 の指定がなくても findCount がセットされるように対応
761         *
762         * @param       file    検索元のファイルオブジェクト
763         * @param       mapEnc  検索元のファイルエンコード
764         *
765         * @return      検索元のファイルの文字列化情報(ただし、見つからなければ、null)
766         */
767//      private String findKeywordAsBulk( final File file ) {
768        private String findKeywordAsBulk( final File file,final String mapEnc ) {       // 8.1.3.0 (2022/06/03)
769
770                boolean isFind = false;
771
772                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
773//              final String line = FileUtil.getValue( file.getPath() , inEncode );             // 6.4.5.2 (2016/05/06)
774                final String line = FileUtil.getValue( file.getPath() , mapEnc );               // 8.1.3.0 (2022/06/03)
775
776                final Matcher mach = pattern.matcher( line );
777                if( mach.find() ) {
778                        if( debug ) { println( "DEBUG:\t" + file.getPath() ); }
779                        findCount++ ;                                                                                                           // 8.5.2.0 (2023/07/14) Add
780                        isFind = true;
781                }
782
783                return isFind ? line : null;
784        }
785
786        /**
787         * キーワードを指定の文字列に置き換えます。
788         * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、
789         * オリジナル_backup という名称に変わります。
790         * ここでは、1行づつ読み取りながら、変換処理を行います。
791         * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな
792         * ファイル等での置換でも、メモリの使用量は抑えられます。
793         *
794         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。
795         * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
796         * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
797         * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。
798         * @og.rev 8.1.3.0 (2022/06/03) jarファイル内の検索、オートエンコード対応
799         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
800         *
801         * @param       inFile  検索元の入力ファイルオブジェクト
802         * @param       outFile 変換後の出力ファイルオブジェクト
803         * @param       firstLineNo     キーワードが存在した場合の最初の行番号
804         * @param       mapEnc  検索元のファイルエンコード
805         */
806//      private void changeKeyword( final File inFile,final File outFile,final int firstLineNo ) {
807        private void changeKeyword( final File inFile,final File outFile,final int firstLineNo,final String mapEnc ) {  // 8.1.3.0 (2022/06/03)
808
809//              final BufferedReader reader = FileUtil.getBufferedReader( inFile,inEncode );
810                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
811                final BufferedReader reader = FileUtil.getBufferedReader( inFile,mapEnc );      // 8.1.3.0 (2022/06/03)
812                final PrintWriter    writer = FileUtil.getPrintWriter( outFile,outEncode );
813
814                String line = null;
815                try {
816//              try ( BufferedReader reader = FileUtil.getBufferedReader( inFile,mapEnc );      // 8.1.3.0 (2022/06/03)
817//                        PrintWriter    writer = FileUtil.getPrintWriter( outFile,outEncode ) ) {
818                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
819                        if( "HEAD".equals( insert ) ) {
820                                writer.println( change );
821                        }
822
823                        int lineNo = 0;
824                        while((line = reader.readLine()) != null) {
825                                lineNo++ ;
826                                if( lineNo <= skipRowCount ) { continue; }              // 6.2.4.0 (2015/05/15)
827
828                                if( lineNo >= firstLineNo ) {
829                                        final Matcher mach = pattern.matcher( line );
830
831                                        String chnStr = null;
832                                        if( "CHANGE".equals( insert ) ) {
833                                                chnStr = strChange( mach );
834                                        }
835                                        else if( "BEFORE".equals( insert ) ) {
836                                                chnStr = strBefore( line,mach );
837                                        }
838                                        else if( "AFTER".equals( insert ) ) {
839                                                chnStr = strAfter( line,mach );
840                                        }
841
842                                        if( chnStr != null ) {
843                                                line = chnStr;
844                                                cngCount++ ;    // 変換されれば カウント
845                                        }
846                                }
847                                writer.println( line ); // readLine() してるので、最後に改行が必要。
848                        }
849
850                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
851                        if( "TAIL".equals( insert ) ) {
852                                writer.println( change );
853                        }
854                }
855                // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
856                catch( final CharacterCodingException ex ) {
857                        final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
858                                                                +       "  ファイルのエンコードが指定のエンコードと異なります。" + CR
859//                                                              +       " [" + inFile + "] , Encode=[" + inEncode + "]" ;
860                                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
861//                                                              +       " [" + inFile + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
862                                                                +       pathEncode( inFile, mapEnc );
863                        // 呼出元で、errAbend処理するので、そのまま、throw しておく。
864                        throw new OgCharacterException( errMsg,ex );    // 6.5.0.1 (2016/10/21)
865                }
866                catch( final IOException ex ) {
867                        final String errMsg = "処理中にエラーが発生しました。[" + line + "]" + CR
868//                                                              +       " [" + inFile + "] , Encode=[" + inEncode + "]" ;
869                                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
870//                                                              +       " [" + inFile + "] , Encode=[" + mapEnc + "]" ; // 8.1.3.0 (2022/06/03)
871                                                                +       pathEncode( inFile, mapEnc );
872                        // 呼出元で、errAbend処理するので、そのまま、throw しておく。
873                        throw new OgRuntimeException( errMsg,ex );
874                }
875                finally {
876                        Closer.ioClose( reader );
877                        Closer.ioClose( writer );
878                }
879        }
880        /**
881         * キーワードを指定の文字列に置き換えます。
882         * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、
883         * オリジナル_backup という名称に変わります。
884         * ここでは、ファイルをすべて読み取ってから、チェックします。
885         * よって、複数行にまたがる keyword でのマッチングが可能です。
886         *
887         * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。
888         * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
889         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
890         *
891         * @param       fileLine        検索元の行文字列
892         * @param       outFile 出力ファイルオブジェクト
893         */
894        private void changeKeywordAsBulk( final String fileLine,final File outFile ) {
895                // 8.5.4.2 (2024/01/12) PMD 7.0.0 CloseResource 対応
896//              final PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode );
897
898                String line = fileLine ;
899//              try {
900                try ( PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode ) ) {
901                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
902                        if( "HEAD".equals( insert ) ) {
903                                writer.println( change );
904                        }
905
906                        final Matcher mach = pattern.matcher( line );
907
908                        String chnStr = null;
909                        if( "CHANGE".equals( insert ) ) {
910                                chnStr = strChange( mach );
911                        }
912                        else if( "BEFORE".equals( insert ) ) {
913                                chnStr = strBefore( line,mach );
914                        }
915                        else if( "AFTER".equals( insert ) ) {
916                                chnStr = strAfter( line,mach );
917                        }
918
919                        if( chnStr != null ) {
920                                line = chnStr;
921                                cngCount++ ;    // 変換されれば カウント
922                        }
923
924                        writer.print( line );   // 注意:改行コードは、不要
925
926                        // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加
927                        if( "TAIL".equals( insert ) ) {
928                                writer.println( change );
929                        }
930                }
931                catch( final RuntimeException ex ) {
932                        final String errMsg = "処理中にエラーが発生しました。[" + outFile.getPath() + "]" ;
933                        // 呼出元で、errAbend処理するので、そのまま、throw しておく。
934                        throw new OgRuntimeException( errMsg,ex );
935                }
936//              finally {
937//                      Closer.ioClose( writer );
938//              }
939        }
940
941        /**
942         * insert が、"CHANGE" の場合の処理結果を求めます。
943         * 変換しなかった場合は、null を返します。
944         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
945         *
946         * @param       mach    キーワードの正規表現
947         *
948         * @return      変換結果(対象行で無い場合は、null)
949         */
950        private String strChange( final Matcher mach ) {
951                String line = null;
952                if( mach.find() ) {
953                        line = mach.replaceAll( change );
954                }
955                return line ;
956        }
957
958        /**
959         * insert が、"BEFORE" の場合の処理結果を求めます。
960         * 変換しなかった場合は、null を返します。
961         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
962         *
963         * @param       line    検索行
964         * @param       mach    キーワードの正規表現
965         *
966         * @return      変換結果(対象行で無い場合は、null)
967         */
968        private String strBefore( final String line , final Matcher mach ) {
969                boolean isChng = false;
970                final StringBuilder buf = new StringBuilder( line.length() );
971                int indx = 0;
972                while( mach.find() ) {
973                        isChng = true;
974                        final int strt = mach.start() + insOffset;
975                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveAppendsShouldReuse 対応
976                        buf.append( line.substring( indx,strt ) )
977                                .append( change );
978                        indx = strt;
979                }
980
981                String rtn = null;
982                if( isChng ) {
983                        buf.append( line.substring( indx ) );
984                        rtn = buf.toString();
985                }
986
987                return rtn ;
988        }
989
990        /**
991         * insert が、"AFTER" の場合の処理結果を求めます。
992         * 変換しなかった場合は、null を返します。
993         * これは、変換カウントを算出する為のフラグ代わりに使用しています。
994         *
995         * @param       line    検索行
996         * @param       mach    キーワードの正規表現
997         *
998         * @return      変換結果(対象行で無い場合は、null)
999         */
1000        private String strAfter( final String line , final Matcher mach ) {
1001                boolean isChng = false;
1002                final StringBuilder buf = new StringBuilder( line.length() );
1003                int indx = 0;
1004                while( mach.find() ) {
1005                        isChng = true;
1006                        final int end = mach.end() + insOffset;
1007                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ConsecutiveAppendsShouldReuse 対応
1008                        buf.append( line.substring( indx,end ) )
1009                                .append( change );
1010                        indx = end;
1011                }
1012                String rtn = null;
1013                if( isChng ) {
1014                        buf.append( line.substring( indx ) );
1015                        rtn = buf.toString();
1016                }
1017
1018                return rtn ;
1019        }
1020
1021        /**
1022         * 引数の File とエンコードを文字列に変換します。
1023         * エラーメッセージ等に使用することを前提としています。
1024         *
1025         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidDuplicateLiterals 対応
1026         *
1027         * @param       file    検索行
1028         * @param       encode  キーワードの正規表現
1029         *
1030         * @return      状況を表示する文字列に変換する。
1031         */
1032        private String pathEncode( final File file , final String encode ) {
1033                return " [" + file.getPath() + "] , Encode=[" + encode + "]" ;  // 8.1.3.0 (2022/06/03)
1034        }
1035
1036        /**
1037         * プロセスの処理結果のレポート表現を返します。
1038         * 処理プログラム名、入力件数、出力件数などの情報です。
1039         * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
1040         * 形式で出してください。
1041         *
1042         * @return   処理結果のレポート
1043         */
1044        public String report() {
1045                if( findCount < cngCount ) { findCount = cngCount; }
1046
1047                // 7.2.9.5 (2020/11/28) PMD:Consider simply returning the value vs storing it in local variable 'XXXX'
1048                return "[" + getClass().getName() + "]" + CR
1049//              final String report = "[" + getClass().getName() + "]" + CR
1050                                + TAB + "Search Keyword    : " + keyword    + CR
1051                                + TAB + "Search File Count : " + inCount    + CR
1052                                + TAB + "Key Find    Count : " + findCount  + CR
1053                                + TAB + "Key Change  Count : " + cngCount ;
1054
1055//              return report ;
1056        }
1057
1058        /**
1059         * このクラスの使用方法を返します。
1060         *
1061         * @return      このクラスの使用方法
1062         * @og.rtnNotNull
1063         */
1064        public String usage() {
1065                final StringBuilder buf = new StringBuilder( 1400 )
1066                        .append( "Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す"   ).append( CR )
1067                        .append( "ChainProcess インターフェースの実装クラスです。"                                                                                       ).append( CR )
1068                        .append( CR )
1069                        .append( "正規表現の keyword を上流から受け取った FileLineModel から検索します。"              ).append( CR )
1070                        .append( "見つかった対象ファイルから、指定の文字列を置換する場合は、-change か"                               ).append( CR )
1071                        .append( "-changeFile で、keyword を置換する文字列を指定して下さい。"                                      ).append( CR )
1072                        .append( "置換する文字列には、\t と \n の特殊文字が使用できます。"                                              ).append( CR )
1073                        .append( CR )
1074                        .append( "処理対象は、通常は、1行づつ読み取りながら処理を行います。存在チェックの場合は、"     ).append( CR )
1075                        .append( "見つかった時点で処理を中止します。これは、該当箇所をピックアップするのではなく、"   ).append( CR )
1076                        .append( "存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです。"       ).append( CR )
1077                        .append( "keyword を、改行を含む正規表現で、検索・置換する場合は、-useBulkRead 属性を"     ).append( CR )
1078                        .append( "true に設定してください。これは、入力ファイルを一括して読み込みます。"                        ).append( CR )
1079                        .append( "-ignoreCase は、検索時にキーの大文字小文字を無視するように指定します。"                    ).append( CR )
1080                        .append( "-notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します。"              ).append( CR )
1081                        .append( "これは、行単位ではなく、ファイル単位に判定しますので、change 指定した場合"             ).append( CR )
1082                        .append( "でも、対象行は、見つかった行です。ただし、下流に対して、見つからない"                   ).append( CR )
1083                        .append( "場合だけ処理を継続させます。"                                                                                                       ).append( CR )
1084                        .append( "-inEncode は、入力ファイルのエンコード指定になります。"                                                            ).append( CR )
1085                        .append( "-outEncode は、出力ファイルのエンコードや、changeFileで指定の置換文字列"                              ).append( CR )
1086                        .append( "ファイルのエンコード指定になります。(changeFile は、必ず 出力ファイルと)"                                 ).append( CR )
1087                        .append( "同じエンコードです。"                                                                                                                          ).append( CR )
1088                        .append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") "            ).append( CR )
1089                        .append( "で求まる値を使用します。"                                                                                                         ).append( CR )
1090                        .append( "-changeFile を使用することで、複数行の文字列に置換することが可能です。"            ).append( CR )
1091                        .append( CR )
1092                        .append( "上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の"                          ).append( CR )
1093                        .append( "ファイルオブジェクトより、指定の文字列が含まれているか検索します。"                                  ).append( CR )
1094                        .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"                       ).append( CR )
1095                        .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"                        ).append( CR )
1096                        .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"            ).append( CR )
1097                        .append( "できれば、使用可能です。"                                                                                                         ).append( CR )
1098                        .append( CR )
1099//                      .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"                  ).append( CR )
1100//                      .append( "引数文字列の 『=』 の前後には、空白は挟めません。必ず、-key=value の様に"          ).append( CR )
1101//                      .append( "繋げてください。"                                                                                                                             ).append( CR )
1102                        .append( PROCESS_PARAM_USAGE )  // 8.5.6.1 (2024/03/29) 継承元使用
1103                        .append( CR ).append( CR )
1104                        .append( getArgument().usage() ).append( CR );
1105
1106                return buf.toString();
1107        }
1108
1109        /**
1110         * このクラスは、main メソッドから実行できません。
1111         *
1112         * @param       args    コマンド引数配列
1113         */
1114        public static void main( final String[] args ) {
1115                LogWriter.log( new Process_Grep().usage() );
1116        }
1117}