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 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. 344 return notEquals ^ isFind ? data : null ; 345 } 346 347 /** 348 * キーワードが存在しているかどうかをチェックします。 349 * ここでは、1行づつ読み取りながら、最初に見つかった時点で制御を返します。 350 * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな 351 * ファイル等での検索には、効率的です。 352 * 353 * @og.rev 4.0.1.0 (2007/12/14) 新規追加 354 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 355 * @og.rev 6.3.1.0 (2015/06/28) errAbend属性追加。 356 * @og.rev 6.3.1.1 (2015/07/10) useOmitCmnt、useAllFind 機能追加 357 * @og.rev 6.4.0.2 (2015/12/11) CommentLineParser 改造。 358 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 359 * 360 * @param file 検索元のファイルオブジェクト 361 * 362 * @return 最初に見つかった行番号(見つからなければ、-1 を返す) 363 */ 364 private int findKeyword( final File file ) { 365 366 int firstLineNo = -1; 367 final BufferedReader reader = FileUtil.getBufferedReader( file,inEncode ); 368 369 // 6.4.0.2 (2015/12/11) CommentLineParser 改造 370 final CommentLineParser clp = useOmitCmnt ? new CommentLineParser( FileInfo.getSUFIX( file ) ) : null; 371 try { 372 String line ; 373 int lineNo = 0; 374 while((line = reader.readLine()) != null) { 375 lineNo++ ; // 注意:ここで返す行数は、コメント行を含む行数とする。 376 377 // 6.3.1.1 (2015/07/10) useOmitCmnt 機能。コメント行を削除する処理を入れる。 378 if( useOmitCmnt ) { 379 line = clp.line( line ); 380 if( line == null ) { continue; } // 戻り値が null の場合は、行として不成立 381 } 382 final Matcher mach = pattern.matcher( line ); 383 if( mach.find() ) { 384 if( debug ) { 385 final String buf = "DEBUG:\t" + file.getPath() + "(" + lineNo + "): " + line ; 386 println( buf ); 387 } 388 // 6.3.1.1 (2015/07/10) useAllFind 機能。最後まで検索を続けます。 389 if( useAllFind ) { 390 final String msg = file.getAbsolutePath() + '(' + lineNo + "):" + line ; 391 println( msg ); 392 } 393 else { 394 firstLineNo = lineNo; 395 break; 396 } 397 } 398 } 399 } 400 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 401 catch( final CharacterCodingException ex ) { 402 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 403 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 404 + " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ; 405 // 呼出元で、errAbend処理するので、そのまま、throw しておく。 406 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 407 } 408 catch( final IOException ex ) { 409 final String errMsg = "キーワードファイル読取エラーが発生しました。" + CR 410 + " [" + file.getPath() + "] , Encode=[" + inEncode + "]" ; 411 // 呼出元で、errAbend処理するので、そのまま、throw しておく。 412 throw new OgRuntimeException( errMsg,ex ); 413 } 414 finally { 415 Closer.ioClose( reader ); 416 } 417 418 return firstLineNo; 419 } 420 421 /** 422 * キーワードが存在しているかどうかをチェックします。 423 * ここでは、ファイルをすべて読み取ってから、チェックします。 424 * よって、複数行にまたがる keyword でのマッチングが可能です。 425 * 426 * @og.rev 4.0.1.0 (2007/12/14) 新規追加 427 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 428 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 429 * 430 * @param file 検索元のファイルオブジェクト 431 * 432 * @return 検索元のファイルの文字列化情報(ただし、見つからなければ、null) 433 */ 434 private String findKeywordAsBulk( final File file ) { 435 436 boolean isFind = false; 437 438 // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 439 final String line = FileUtil.getValue( file.getPath() , inEncode ); // 6.4.5.2 (2016/05/06) 440 441 final Matcher mach = pattern.matcher( line ); 442 if( mach.find() ) { 443 if( debug ) { println( "DEBUG:\t" + file.getPath() ); } 444 isFind = true; 445 } 446 447 return isFind ? line : null; 448 } 449 450 /** 451 * キーワードを指定の文字列に置き換えます。 452 * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、 453 * オリジナル_backup という名称に変わります。 454 * ここでは、1行づつ読み取りながら、変換処理を行います。 455 * よって、複数行にまたがる keyword でのマッチングは出来ませんが、大きな 456 * ファイル等での置換でも、メモリの使用量は抑えられます。 457 * 458 * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。 459 * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 460 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 461 * @og.rev 6.5.0.1 (2016/10/21) CharacterCodingException は、OgCharacterException に変換する。 462 * 463 * @param inFile 検索元の入力ファイルオブジェクト 464 * @param outFile 変換後の出力ファイルオブジェクト 465 * @param firstLineNo キーワードが存在した場合の最初の行番号 466 */ 467 private void changeKeyword( final File inFile,final File outFile,final int firstLineNo ) { 468 469 final BufferedReader reader = FileUtil.getBufferedReader( inFile,inEncode ); 470 final PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode ); 471 472 String line = null; 473 try { 474 // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 475 if( "HEAD".equals( insert ) ) { 476 writer.println( change ); 477 } 478 479 int lineNo = 0; 480 while((line = reader.readLine()) != null) { 481 lineNo++ ; 482 if( lineNo <= skipRowCount ) { continue; } // 6.2.4.0 (2015/05/15) 483 484 if( lineNo >= firstLineNo ) { 485 final Matcher mach = pattern.matcher( line ); 486 487 String chnStr = null; 488 if( "CHANGE".equals( insert ) ) { 489 chnStr = strChange( mach ); 490 } 491 else if( "BEFORE".equals( insert ) ) { 492 chnStr = strBefore( line,mach ); 493 } 494 else if( "AFTER".equals( insert ) ) { 495 chnStr = strAfter( line,mach ); 496 } 497 498 if( chnStr != null ) { 499 line = chnStr; 500 cngCount++ ; // 変換されれば カウント 501 } 502 } 503 writer.println( line ); // readLine() してるので、最後に改行が必要。 504 } 505 506 // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 507 if( "TAIL".equals( insert ) ) { 508 writer.println( change ); 509 } 510 } 511 // 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 512 catch( final CharacterCodingException ex ) { 513 final String errMsg = "文字のエンコード・エラーが発生しました。" + CR 514 + " ファイルのエンコードが指定のエンコードと異なります。" + CR 515 + " [" + inFile + "] , Encode=[" + inEncode + "]" ; 516 // 呼出元で、errAbend処理するので、そのまま、throw しておく。 517 throw new OgCharacterException( errMsg,ex ); // 6.5.0.1 (2016/10/21) 518 } 519 catch( final IOException ex ) { 520 final String errMsg = "処理中にエラーが発生しました。[" + line + "]" + CR 521 + " [" + inFile + "] , Encode=[" + inEncode + "]" ; 522 // 呼出元で、errAbend処理するので、そのまま、throw しておく。 523 throw new OgRuntimeException( errMsg,ex ); 524 } 525 finally { 526 Closer.ioClose( reader ); 527 Closer.ioClose( writer ); 528 } 529 } 530 /** 531 * キーワードを指定の文字列に置き換えます。 532 * useBackup 属性に true を指定した場合、置き換え後の、backup ファイルは、 533 * オリジナル_backup という名称に変わります。 534 * ここでは、ファイルをすべて読み取ってから、チェックします。 535 * よって、複数行にまたがる keyword でのマッチングが可能です。 536 * 537 * @og.rev 4.0.1.0 (2007/12/14) 置換処理を独立させます。 538 * @og.rev 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 539 * 540 * @param fileLine 検索元の行文字列 541 * @param outFile 出力ファイルオブジェクト 542 */ 543 private void changeKeywordAsBulk( final String fileLine,final File outFile ) { 544 final PrintWriter writer = FileUtil.getPrintWriter( outFile,outEncode ); 545 546 String line = fileLine ; 547 try { 548 // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 549 if( "HEAD".equals( insert ) ) { 550 writer.println( change ); 551 } 552 553 final Matcher mach = pattern.matcher( line ); 554 555 String chnStr = null; 556 if( "CHANGE".equals( insert ) ) { 557 chnStr = strChange( mach ); 558 } 559 else if( "BEFORE".equals( insert ) ) { 560 chnStr = strBefore( line,mach ); 561 } 562 else if( "AFTER".equals( insert ) ) { 563 chnStr = strAfter( line,mach ); 564 } 565 566 if( chnStr != null ) { 567 line = chnStr; 568 cngCount++ ; // 変換されれば カウント 569 } 570 571 writer.print( line ); // 注意:改行コードは、不要 572 573 // 6.2.4.0 (2015/05/15) HEAD,TAIL 追加 574 if( "TAIL".equals( insert ) ) { 575 writer.println( change ); 576 } 577 } 578 catch( final RuntimeException ex ) { 579 final String errMsg = "処理中にエラーが発生しました。[" + outFile.getPath() + "]" ; 580 // 呼出元で、errAbend処理するので、そのまま、throw しておく。 581 throw new OgRuntimeException( errMsg,ex ); 582 } 583 finally { 584 Closer.ioClose( writer ); 585 } 586 } 587 588 /** 589 * insert が、"CHANGE" の場合の処理結果を求めます。 590 * 変換しなかった場合は、null を返します。 591 * これは、変換カウントを算出する為のフラグ代わりに使用しています。 592 * 593 * @param mach キーワードの正規表現 594 * 595 * @return 変換結果(対象行で無い場合は、null) 596 */ 597 private String strChange( final Matcher mach ) { 598 String line = null; 599 if( mach.find() ) { 600 line = mach.replaceAll( change ); 601 } 602 return line ; 603 } 604 605 /** 606 * insert が、"BEFORE" の場合の処理結果を求めます。 607 * 変換しなかった場合は、null を返します。 608 * これは、変換カウントを算出する為のフラグ代わりに使用しています。 609 * 610 * @param line 検索行 611 * @param mach キーワードの正規表現 612 * 613 * @return 変換結果(対象行で無い場合は、null) 614 */ 615 private String strBefore( final String line , final Matcher mach ) { 616 boolean isChng = false; 617 final StringBuilder buf = new StringBuilder( line.length() ); 618 int indx = 0; 619 while( mach.find() ) { 620 isChng = true; 621 final int strt = mach.start() + insOffset; 622 buf.append( line.substring( indx,strt ) ); 623 buf.append( change ); 624 indx = strt; 625 } 626 627 String rtn = null; 628 if( isChng ) { 629 buf.append( line.substring( indx ) ); 630 rtn = buf.toString(); 631 } 632 633 return rtn ; 634 } 635 636 /** 637 * insert が、"AFTER" の場合の処理結果を求めます。 638 * 変換しなかった場合は、null を返します。 639 * これは、変換カウントを算出する為のフラグ代わりに使用しています。 640 * 641 * @param line 検索行 642 * @param mach キーワードの正規表現 643 * 644 * @return 変換結果(対象行で無い場合は、null) 645 */ 646 private String strAfter( final String line , final Matcher mach ) { 647 boolean isChng = false; 648 final StringBuilder buf = new StringBuilder( line.length() ); 649 int indx = 0; 650 while( mach.find() ) { 651 isChng = true; 652 final int end = mach.end() + insOffset; 653 buf.append( line.substring( indx,end ) ); 654 buf.append( change ); 655 indx = end; 656 } 657 String rtn = null; 658 if( isChng ) { 659 buf.append( line.substring( indx ) ); 660 rtn = buf.toString(); 661 } 662 663 return rtn ; 664 } 665 666 /** 667 * プロセスの処理結果のレポート表現を返します。 668 * 処理プログラム名、入力件数、出力件数などの情報です。 669 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 670 * 形式で出してください。 671 * 672 * @return 処理結果のレポート 673 */ 674 public String report() { 675 if( findCount < cngCount ) { findCount = cngCount; } 676 677 final String report = "[" + getClass().getName() + "]" + CR 678 + TAB + "Search Keyword : " + keyword + CR 679 + TAB + "Search File Count : " + inCount + CR 680 + TAB + "Key Find Count : " + findCount + CR 681 + TAB + "Key Change Count : " + cngCount ; 682 683 return report ; 684 } 685 686 /** 687 * このクラスの使用方法を返します。 688 * 689 * @return このクラスの使用方法 690 * @og.rtnNotNull 691 */ 692 public String usage() { 693 final StringBuilder buf = new StringBuilder( 1400 ) 694 .append( "Process_Grep は、上流から受け取った FileLineModelから、文字列を見つけ出す" ).append( CR ) 695 .append( "ChainProcess インターフェースの実装クラスです。" ).append( CR ) 696 .append( CR ) 697 .append( "正規表現の keyword を上流から受け取った FileLineModel から検索します。" ).append( CR ) 698 .append( "見つかった対象ファイルから、指定の文字列を置換する場合は、-change か" ).append( CR ) 699 .append( "-changeFile で、keyword を置換する文字列を指定して下さい。" ).append( CR ) 700 .append( "置換する文字列には、\t と \n の特殊文字が使用できます。" ).append( CR ) 701 .append( CR ) 702 .append( "処理対象は、通常は、1行づつ読み取りながら処理を行います。存在チェックの場合は、" ).append( CR ) 703 .append( "見つかった時点で処理を中止します。これは、該当箇所をピックアップするのではなく、" ).append( CR ) 704 .append( "存在しているかどうかを判断して、あれば、下流に流すというのが目的だからです。" ).append( CR ) 705 .append( "keyword を、改行を含む正規表現で、検索・置換する場合は、-useBulkRead 属性を" ).append( CR ) 706 .append( "true に設定してください。これは、入力ファイルを一括して読み込みます。" ).append( CR ) 707 .append( "-ignoreCase は、検索時にキーの大文字小文字を無視するように指定します。" ).append( CR ) 708 .append( "-notEquals は、結果(見つかればtrue)を反転(見つからなければtrue)します。" ).append( CR ) 709 .append( "これは、行単位ではなく、ファイル単位に判定しますので、change 指定した場合" ).append( CR ) 710 .append( "でも、対象行は、見つかった行です。ただし、下流に対して、見つからない" ).append( CR ) 711 .append( "場合だけ処理を継続させます。" ).append( CR ) 712 .append( "-inEncode は、入力ファイルのエンコード指定になります。" ).append( CR ) 713 .append( "-outEncode は、出力ファイルのエンコードや、changeFileで指定の置換文字列" ).append( CR ) 714 .append( "ファイルのエンコード指定になります。(changeFile は、必ず 出力ファイルと)" ).append( CR ) 715 .append( "同じエンコードです。" ).append( CR ) 716 .append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") " ).append( CR ) 717 .append( "で求まる値を使用します。" ).append( CR ) 718 .append( "-changeFile を使用することで、複数行の文字列に置換することが可能です。" ).append( CR ) 719 .append( CR ) 720 .append( "上流(プロセスチェインのデータは上流から渡されます。)からのLineModel の" ).append( CR ) 721 .append( "ファイルオブジェクトより、指定の文字列が含まれているか検索します。" ).append( CR ) 722 .append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト" ).append( CR ) 723 .append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを" ).append( CR ) 724 .append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し" ).append( CR ) 725 .append( "できれば、使用可能です。" ).append( CR ) 726 .append( CR ) 727 .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 728 .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 729 .append( "繋げてください。" ).append( CR ) 730 .append( CR ).append( CR ) 731 .append( getArgument().usage() ).append( CR ); 732 733 return buf.toString(); 734 } 735 736 /** 737 * このクラスは、main メソッドから実行できません。 738 * 739 * @param args コマンド引数配列 740 */ 741 public static void main( final String[] args ) { 742 LogWriter.log( new Process_Grep().usage() ); 743 } 744}