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.util.Argument; 019import org.opengion.fukurou.util.FileUtil; 020import org.opengion.fukurou.util.FileString; 021import org.opengion.fukurou.util.Closer ; 022import org.opengion.fukurou.util.StringUtil ; 023import org.opengion.fukurou.util.LogWriter; 024 025import java.util.Map ; 026import java.util.LinkedHashMap ; 027import java.util.List ; 028import java.util.ArrayList ; 029import java.util.Locale ; // 5.7.3.2 (2014/02/28) ignoreCase が実装されていなかった。 030import java.util.regex.Pattern; // 5.7.3.2 (2014/02/28) regexを利用する場合 031import java.util.regex.Matcher; // 5.7.3.2 (2014/02/28) regexを利用する場合 032 033import java.io.File; 034import java.io.PrintWriter; 035import java.io.BufferedReader; 036import java.io.IOException; 037 038/** 039 * Process_GrepChange は、上流から受け取った FileLineModelから、語句を 040 * 置換する、ChainProcess インターフェースの実装クラスです。 041 * 042 * Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在 043 * しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが 044 * 出来ないことです。 045 * 046 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、 047 * 対象とする語句を置換します。 048 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、 049 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。 050 * 置換文字(値)は、\t の特殊文字が使用できます。 051 * この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード 052 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。 053 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、 054 * 置き換えた結果も、同じファイルにセーブします。 055 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。 056 * -inEncode は、入力ファイルのエンコード指定になります。 057 * -outEncode は、出力ファイルのエンコードや、キーワードファイルの 058 * エンコード指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。) 059 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で 060 * 求まる値を使用します。 061 * 062 * 5.7.3.2 (2014/02/28) 063 * -regex=true で、キーワードに正規表現を利用できます。具体的には、String#replaceAll(String,String) 064 * を利用して置換します。 065 * 通常の置換処理は、indexOf で見つけて、StringBuilder#replace(int,int,String) を繰り返して処理しています。 066 * -ignoreCase=true で、検索キーワードに大文字小文字を区別しない処理が可能です。 067 * 068 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト 069 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを 070 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し 071 * できれば、使用可能です。 072 * 073 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 074 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 075 * 繋げてください。 076 * 077 * Process_GrepChange -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8 078 * 079 * -keywordFile=キーワード :置換する語句を含むキーと値のペアー(タブ区切り) 080 * [-ignoreCase=[false/true] ] :検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する]) 081 * [-regex=[false/true] ] :キーワードに正規表現を利用する(true)かどうか(初期値:false[利用しない]) 082 * [-isChange=置換可否 ] :置換処理を実施する(true)かどうか(初期値:置換する[true]) 083 * [-inEncode=入力エンコード ] :入力ファイルのエンコードタイプ 084 * [-outEncode=出力エンコード] :出力ファイルやキーワードファイルのエンコードタイプ 085 * [-display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 086 * [-debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 087 * 088 * @version 4.0 089 * @author Kazuhiko Hasegawa 090 * @since JDK5.0, 091 */ 092public class Process_GrepChange extends AbstractProcess implements ChainProcess { 093 private String[] keyword = null; 094 private String[] change = null; 095 private Pattern[] pattern = null; // 5.7.3.2 (2014/02/28) キーワードに正規表現を利用する場合 096 private boolean ignoreCase = false; 097 private boolean regex = false; // 5.7.3.2 (2014/02/28) キーワードに正規表現を利用するかどうか 098 private boolean isChange = true; // 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする 099 private String inEncode = null; 100 private String outEncode = null; 101 private boolean display = false; // 表示しない 102 private boolean debug = false; // 5.7.3.0 (2014/02/07) デバッグ情報 103 104 private int inCount = 0; 105 private int findCount = 0; 106 private int cngCount = 0; 107 108 private static final Map<String,String> mustProparty ; // [プロパティ]必須チェック用 Map 109 private static final Map<String,String> usableProparty ; // [プロパティ]整合性チェック Map 110 111 static { 112 mustProparty = new LinkedHashMap<String,String>(); 113 mustProparty.put( "keywordFile", "置換する語句を含むキーと値のペアー(タブ区切り)(必須)" ); 114 115 usableProparty = new LinkedHashMap<String,String>(); 116 usableProparty.put( "ignoreCase", "検索時に大文字小文字を区別しない(true)かどうか。" + 117 CR + "(初期値:区別する[false])" ); 118 usableProparty.put( "regex", "キーワードに正規表現を利用する(true)かどうか。" + 119 CR + "(初期値:利用しない[false])" ); // 5.7.3.2 (2014/02/28) 120 usableProparty.put( "isChange", "置換処理を実施する(true)かどうか" + 121 CR + "(初期値:置換する[true])" ); 122 usableProparty.put( "inEncode", "入力ファイルのエンコードタイプ" ); 123 usableProparty.put( "outEncode", "出力ファイルやキーワードファイルのエンコードタイプ" ); 124 usableProparty.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 125 CR + "(初期値:false:表示しない)" ); 126 usableProparty.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 127 CR + "(初期値:false:表示しない)" ); // 5.7.3.0 (2014/02/07) デバッグ情報 128 } 129 130 /** 131 * デフォルトコンストラクター。 132 * このクラスは、動的作成されます。デフォルトコンストラクターで、 133 * super クラスに対して、必要な初期化を行っておきます。 134 * 135 */ 136 public Process_GrepChange() { 137 super( "org.opengion.fukurou.process.Process_GrepChange",mustProparty,usableProparty ); 138 } 139 140 /** 141 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 142 * 初期処理(ファイルオープン、DBオープン等)に使用します。 143 * 144 * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加 145 * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、キーワードの \t の使用、trim() 廃止、ignoreCase の実装、regex の追加 146 * 147 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 148 */ 149 public void init( final ParamProcess paramProcess ) { 150 Argument arg = getArgument(); 151 152 String keywordFile = arg.getProparty("keywordFile" ); 153 ignoreCase = arg.getProparty("ignoreCase",ignoreCase); 154 regex = arg.getProparty("regex",regex); // 5.7.3.2 (2014/02/28) 155 isChange = arg.getProparty("isChange",isChange); // 5.1.2.0 (2010/01/01) 156 inEncode = arg.getProparty("inEncode",System.getProperty("file.encoding")); 157 outEncode = arg.getProparty("outEncode",System.getProperty("file.encoding")); 158 display = arg.getProparty("display",display); 159 debug = arg.getProparty("debug",debug); // 5.7.3.0 (2014/02/07) デバッグ情報 160// if( debug ) { println( arg.toString() ); } // 5.7.3.0 (2014/02/07) デバッグ情報 161 162 FileString fs = new FileString(); 163 fs.setFilename( keywordFile ); 164 fs.setEncode( outEncode ); 165// String[] lines = fs.getValue( "\n" ); 166 String[] lines = fs.getValue( CR ); // 5.7.3.2 (2014/02/28) \n でなく、CR とします。 167 int len = lines.length; 168 if( len == 0 ) { 169 String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ; 170 throw new RuntimeException( errMsg ); 171 } 172 173 println( "keywordFile を、" + len + "件読み取りました。" ); 174 List<String> keyList = new ArrayList<String>( len ); 175 List<String> cngList = new ArrayList<String>( len ); 176 177 for( int i=0; i<len; i++ ) { 178 // String line = lines[i].trim(); 179 String line = lines[i]; 180 int indx = line.indexOf( '\t' ); 181 if( indx <= 0 ) { continue ; } // TAB が先頭や、存在しない行は読み飛ばす。 182 // 5.7.3.2 (2014/02/28) debug の表示と、キーワードの \t の使用、trim() 廃止 183// keyList.add( line.substring( 0,indx ).trim() ); 184// String cng = line.substring( indx+1 ).trim(); 185 String key = line.substring( 0,indx ); 186 String cng = line.substring( indx+1 ); 187 188 if( ignoreCase ) { key = key.toUpperCase(Locale.JAPAN); } // 5.7.3.2 (2014/02/28) ignoreCase の実装漏れ 189 190 if( debug ) { println( "[" + key + "]⇒[" + cng + "]" ); } 191 192// key = StringUtil.replace( key,"\\n",CR ); // 5.7.3.2 (2014/02/28) キーワードに \n は使えない。 193 key = StringUtil.replace( key,"\\t","\t" ); 194 195// cng = StringUtil.replace( cng,"\\n",CR ); // 5.7.3.2 (2014/02/28) キーワードに \n は使えない。 196 cng = StringUtil.replace( cng,"\\t","\t" ); 197 198 keyList.add( key ); 199 cngList.add( cng ); 200 } 201 keyword = keyList.toArray( new String[keyList.size()] ); 202 change = cngList.toArray( new String[cngList.size()] ); 203 204 // 5.7.3.2 (2014/02/28) regex=true の場合の処理 205 if( regex ) { 206 pattern = new Pattern[keyword.length]; 207 for( int i=0; i<keyword.length; i++ ) { 208 pattern[i] = (ignoreCase) ? Pattern.compile( keyword[i],Pattern.CASE_INSENSITIVE ) 209 : Pattern.compile( keyword[i] ) ; 210 } 211 } 212 } 213 214 /** 215 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 216 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 217 * 218 * @param isOK トータルで、OKだったかどうか[true:成功/false:失敗] 219 */ 220 public void end( final boolean isOK ) { 221 // ここでは処理を行いません。 222 } 223 224 /** 225 * 引数の LineModel を処理するメソッドです。 226 * 変換処理後の LineModel を返します。 227 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 228 * null データを返します。つまり、null データは、後続処理を行わない 229 * フラグの代わりにも使用しています。 230 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 231 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 232 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 233 * 各処理ごとに自分でコピー(クローン)して下さい。 234 * 235 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。 236 * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加 237 * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 238 * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ignoreCase の実装 239 * 240 * @param data オリジナルのLineModel 241 * 242 * @return 処理変換後のLineModel 243 */ 244 public LineModel action( final LineModel data ) { 245 inCount++ ; 246 final FileLineModel fileData ; 247 if( data instanceof FileLineModel ) { 248 fileData = (FileLineModel)data ; 249 } 250 else { 251 String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ; 252 throw new RuntimeException( errMsg ); 253 } 254 255 256 File org = fileData.getFile() ; 257 String orgName = org.getPath(); 258 if( ! org.isFile() ) { return data; } 259 260 if( debug ) { println( "File:" + org ); } // 5.1.2.0 (2010/01/01) display の条件変更 261 262 BufferedReader reader = FileUtil.getBufferedReader( org,inEncode ); 263// File tempFile = new File( org.getPath() + "_temp" ); 264// PrintWriter tempWrt = FileUtil.getPrintWriter( tempFile,outEncode ); 265 File tempFile = null; 266 PrintWriter tempWrt = null; 267 268 // 5.1.2.0 (2010/01/01) 置換する場合の前処理 269 if( isChange ) { 270 tempFile = new File( orgName + "_temp" ); 271 tempWrt = FileUtil.getPrintWriter( tempFile,outEncode ); 272 } 273 274 boolean nextFlag = false; 275 276 try { 277 String line ; 278 int lineNo = 0; 279 while((line = reader.readLine()) != null) { 280 lineNo++ ; 281 // 5.7.3.2 (2014/02/28) regex 対応 282 if( regex ) { 283 for( int i=0; i<pattern.length; i++ ) { 284 Matcher mt = pattern[i].matcher( line ); 285 nextFlag = mt.matches(); 286 if( nextFlag ) { 287 findCount++ ; 288 if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); } 289 if( isChange ) { 290 line = mt.replaceAll( change[i] ); 291 cngCount++ ; 292 } 293 } 294 } 295 } 296 else { 297 StringBuilder buf = new StringBuilder( line ); 298 // boolean foundFlag = false; // 行単位に初期化する。 299 for( int i=0; i<keyword.length; i++ ) { 300 // 5.7.3.2 (2014/02/28) ignoreCase 対応。 301 // int indx = buf.indexOf( keyword[i] ); 302 int indx = (ignoreCase) ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i] ) 303 : buf.indexOf( keyword[i] ) ; 304 305 // 置換対象発見。行出力用に見つかれば、true にする。 306 if( indx >= 0 ) { 307 // foundFlag = true; 308 nextFlag = true; // 1度でも見つかれば、true にセット 309 if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); } 310 findCount++ ; 311 } 312 // 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。 313 if( isChange ) { 314 while( indx >= 0 ) { 315 buf.replace( indx,indx+keyword[i].length(),change[i] ); 316 // 5.7.3.2 (2014/02/28) ignoreCase 対応。 317 // indx = buf.indexOf( keyword[i],indx+change[i].length() ); 318 int nxt = indx+change[i].length(); 319 indx = (ignoreCase) ? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt ) 320 : buf.indexOf( keyword[i],nxt ); 321 322 // nextFlag = true; // キーワードが存在したファイル。 323 cngCount++ ; 324// findCount++ ; 325 } 326 } 327 } 328 line = buf.toString(); 329 } 330 // 5.1.2.0 (2010/01/01) 置換する場合の処理 331 if( isChange ) { 332// tempWrt.println( buf.toString() ); 333 tempWrt.println( line ); // 5.7.3.2 (2014/02/28) regexで出力を共有する為。 334 } 335 } 336 } 337 catch ( IOException ex ) { 338 String errMsg = "処理中にエラーが発生しました。[" + data.getRowNo() + "]件目" + CR 339// + data.toString() ; 340 + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 341 throw new RuntimeException( errMsg,ex ); 342 } 343 finally { 344 Closer.ioClose( reader ); 345 Closer.ioClose( tempWrt ); 346 } 347 348 // 5.1.2.0 (2010/01/01) 置換する場合の処理 349 if( isChange ) { 350 if( nextFlag ) { 351 if( !org.delete() ) { 352 String errMsg = "所定のファイルを削除できませんでした。[" + org + "]" + CR 353 + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 354 throw new RuntimeException( errMsg ); 355 } 356 if( !tempFile.renameTo( org ) ) { 357 String errMsg = "所定のファイルをリネームできませんでした。[" + tempFile + "]" + CR 358 + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 359 throw new RuntimeException( errMsg ); 360 } 361 } 362 else { 363 if( !tempFile.delete() ) { 364 String errMsg = "所定のファイルを削除できませんでした。[" + tempFile + "]" + CR 365 + "data=[" + data.dataLine() + "]" + CR ; // 5.7.2.2 (2014/01/24) エラー時にデータも出力します。 366 throw new RuntimeException( errMsg ); 367 } 368 } 369 } 370 371 return (nextFlag) ? data : null ; 372 } 373 374 /** 375 * プロセスの処理結果のレポート表現を返します。 376 * 処理プログラム名、入力件数、出力件数などの情報です。 377 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 378 * 形式で出してください。 379 * 380 * @return 処理結果のレポート 381 */ 382 public String report() { 383 String report = "[" + getClass().getName() + "]" + CR 384 + TAB + "Search File Count : " + inCount + CR 385 + TAB + "Key Find Count : " + findCount + CR 386 + TAB + "Key Change Count : " + cngCount ; 387 388 return report ; 389 } 390 391 /** 392 * このクラスの使用方法を返します。 393 * 394 * @return このクラスの使用方法 395 */ 396 public String usage() { 397 StringBuilder buf = new StringBuilder(); 398 399 buf.append( "Process_GrepChange は、上流から受け取った FileLineModelから、語句を" ).append( CR ); 400 buf.append( "置換する、ChainProcess インターフェースの実装クラスです。" ).append( CR ); 401 buf.append( "Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在" ).append( CR ); 402 buf.append( "しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが" ).append( CR ); 403 buf.append( "出来ないことです。" ).append( CR ); 404 buf.append( CR ); 405 buf.append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、" ).append( CR ); 406 buf.append( "対象とする語句を置換します。" ).append( CR ); 407 buf.append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、" ).append( CR ); 408 buf.append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。" ).append( CR ); 409 buf.append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース" ).append( CR ); 410 buf.append( "が前後に存在している場合は、ご注意ください。" ).append( CR ); 411 buf.append( "置換文字(値)は、\t と \n の特殊文字が使用できます。" ).append( CR ); 412 buf.append( "この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード" ).append( CR ); 413 buf.append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。" ).append( CR ); 414 buf.append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、" ).append( CR ); 415 buf.append( "置き換えた結果も、同じファイルにセーブします。" ).append( CR ); 416 buf.append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。" ).append( CR ); 417 buf.append( "-inEncode は、入力ファイルのエンコード指定になります。" ).append( CR ); 418 buf.append( "-outEncode は、出力ファイルのエンコードや、キーワードファイルのエンコード" ).append( CR ); 419 buf.append( "指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)" ).append( CR ); 420 buf.append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") " ).append( CR ); 421 buf.append( "で求まる値を使用します。" ).append( CR ); 422 buf.append( CR ); 423 buf.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト" ).append( CR ); 424 buf.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを" ).append( CR ); 425 buf.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し" ).append( CR ); 426 buf.append( "できれば、使用可能です。" ).append( CR ); 427 buf.append( CR ); 428 buf.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ); 429 buf.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ); 430 buf.append( "繋げてください。" ).append( CR ); 431 buf.append( CR ).append( CR ); 432 433 buf.append( getArgument().usage() ).append( CR ); 434 435 return buf.toString(); 436 } 437 438 /** 439 * このクラスは、main メソッドから実行できません。 440 * 441 * @param args コマンド引数配列 442 */ 443 public static void main( final String[] args ) { 444 LogWriter.log( new Process_GrepChange().usage() ); 445 } 446}