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