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