/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.process;

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.system.Closer ;
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.util.FileUtil;
// import org.opengion.fukurou.util.FileString;
import org.opengion.fukurou.util.StringUtil ;

import java.util.Map ;
import java.util.LinkedHashMap ;
import java.util.List ;
import java.util.ArrayList ;
import java.util.Locale ;				// 5.7.3.2 (2014/02/28) ignoreCase が実装されていなかった。
import java.util.regex.Pattern;			// 5.7.3.2 (2014/02/28) regexを利用する場合
import java.util.regex.Matcher;			// 5.7.3.2 (2014/02/28) regexを利用する場合

import java.io.File;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.CharacterCodingException;					// 6.3.1.0 (2015/06/28)

/**
 * Process_GrepChange は、上流から受け取った FileLineModelから、語句を
 * 置換する、ChainProcess インターフェースの実装クラスです。
 *
 * Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在
 * しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが
 * 出来ないことです。
 *
 * keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、
 * 対象とする語句を置換します。
 * keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、
 * その行を読み飛ばします。また、区切りタブは何個存在しても構いません。
 * 置換文字(値)は、\t の特殊文字が使用できます。
 * この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード
 * や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用してください。
 * このプログラムでは、上流から受け取った FileLineModel のファイルに対して、
 * <del>6.3.1.1 (2015/07/10) 置き換えた結果も、同じファイルにセーブします。</del>
 * 元のファイルを保存したい場合は、予めバックアップを取得しておいてください。
 * -inEncode は、入力ファイルのエンコード指定になります。
 * -outEncode は、出力ファイルのエンコードや、キーワードファイルの
 * エンコード指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)
 * これらのエンコードが無指定の場合は、System.getProperty("file.encoding") で
 * 求まる値を使用します。
 *
 * 5.7.3.2 (2014/02/28)
 * -regex=true で、ｷｰﾜｰﾄﾞに正規表現を利用できます。具体的には、String#replaceAll(String,String) 
 * を利用して置換します。
 * 通常の置換処理は、indexOf で見つけて、StringBuilder#replace(int,int,String) を繰り返して処理しています。
 * -ignoreCase=true で、検索ｷｰﾜｰﾄﾞに大文字小文字を区別しない処理が可能です。
 *
 * 6.3.1.1 (2015/07/10)
 * ※ 出力ファイルを別フォルダにコピー置換する機能を追加します。
 *    方法は、Process_FileCopy と同様、inPath と outPath を指定します。
 * ※ useWordUnit="true" を指定すると、出来るだけ、Grep対象を、単語単位で置換しようとします。
 *    具体的には、ｷｰﾜｰﾄﾞ文字列の前後に、""(ﾀﾞﾌﾞﾙｸｵｰﾄ)、''(ｼﾝｸﾞﾙｸｵｰﾄ)、><(ﾀｸﾞ記号)、空白、改行を
 *    付加して、それらを含めてﾏｯﾁした場合のみ置換する方法を取ります。
 *
 * 上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト
 * である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを
 * 使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し
 * できれば、使用可能です。
 *
 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
 * 繋げてください。
 *
 *  Process_GrepChange -keyword=検索文字列 -ignoreCase=true -outfile=OUTFILE -encode=UTF-8
 *
 *    -keywordFile=キーワード      ：置換する語句を含むキーと値のペアー(タブ区切り)
 *   [-ignoreCase=[false/true]   ] ：検索時に大文字小文字を区別しない(true)かどうか(初期値:false[区別する])
 *   [-regex=[false/true]        ] ：ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか(初期値:false[利用しない])
 *   [-isChange=置換可否         ] ：置換処理を実施する(true)かどうか(初期値:置換する[true])
 *   [-inPath=入力共通パス       ] ：上流で検索されたファイルパスの共通部分
 *   [-inEncode=入力エンコード   ] ：入力ファイルのエンコードタイプ
 *   [-outEncode=出力エンコード  ] ：出力ファイルやキーワードファイルのエンコードタイプ
 *   [-outPath=出力共通パス      ] ：出力するファイルパスの共通部分
 *   [-useWordUnit=単語単位置換  ] ：出来るだけ、Grep対象を、単語単位で置換しようとします(初期値:false[部分置換])
 *   [-errAbend=[true/false]     ] ：異常発生時に、処理を中断(true)するか、継続(false)するかを指定する(初期値:true[中断する])
 *   [-display=[false/true]      ] ：結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *   [-debug=[false/true]        ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Process_GrepChange extends AbstractProcess implements ChainProcess {
	// 6.3.1.1 (2015/07/10) useWordUnit="true" 時に、使用することになります。
	private static final String IN_PTN = "([\"'><\\\t\\\n 　])";	// 6.3.1.1 (2015/07/10) ほとんど同じなので、共有する。

	private String[]	keyword	;
	private String[]	change	;
	private Pattern[]	pattern	;				// 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用する場合
	private boolean		ignoreCase	;
	private boolean		regex		;			// 5.7.3.2 (2014/02/28) ｷｰﾜｰﾄﾞに正規表現を利用するかどうか
	private boolean		isChange	= true;		// 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする
//	private String		inPath		;			// 6.3.1.1 (2015/07/10)
	private String		inEncode	;
	private String		outPath		;			// 6.3.1.1 (2015/07/10)
	private String		outEncode	;
	private boolean		useWordUnit	;			// 6.3.1.1 (2015/07/10) 部分置換
	private boolean		errAbend	= true;		// 6.3.1.1 (2015/07/10) 中断する
	private boolean		display		;			// false:表示しない
	private boolean		debug		;			// 5.7.3.0 (2014/02/07) デバッグ情報

	private int		inPathLen		;			// 6.3.1.1 (2015/07/10)
	private boolean	isEquals		;			// 6.3.1.1 (2015/07/10)

	private int		inCount		;
	private int		findCount	;
	private int		cngCount	;

	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> MUST_PROPARTY   ;		// ［プロパティ］必須チェック用 Map
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> USABLE_PROPARTY ;		// ［プロパティ］整合性チェック Map

	static {
		MUST_PROPARTY = new LinkedHashMap<>();
		MUST_PROPARTY.put( "keywordFile",	"置換する語句を含むキーと値のペアー(タブ区切り)(必須)" );

		USABLE_PROPARTY = new LinkedHashMap<>();
		USABLE_PROPARTY.put( "ignoreCase",	"検索時に大文字小文字を区別しない(true)かどうか。" +
										CR + "(初期値:区別する[false])" );
		USABLE_PROPARTY.put( "regex",		"ｷｰﾜｰﾄﾞに正規表現を利用する(true)かどうか。" +
										CR + "(初期値:利用しない[false])" );	// 5.7.3.2 (2014/02/28)
		USABLE_PROPARTY.put( "isChange",		"置換処理を実施する(true)かどうか" +
										CR + "(初期値:置換する[true])" );
		USABLE_PROPARTY.put( "inPath",		"入力するファイルパスの共通部分" );	// 6.3.1.1 (2015/07/10)
		USABLE_PROPARTY.put( "inEncode",		"入力ファイルのエンコードタイプ" );
		USABLE_PROPARTY.put( "outPath",		"出力するファイルパスの共通部分" );	// 6.3.1.1 (2015/07/10)
		USABLE_PROPARTY.put( "outEncode",	"出力ファイルやキーワードファイルのエンコードタイプ" );
		USABLE_PROPARTY.put( "useWordUnit",	"出来るだけ、Grep対象を、単語単位で置換" +
										CR + "(初期値:false:部分置換)" );			// 6.3.1.1 (2015/07/10)
		USABLE_PROPARTY.put( "errAbend",		"異常発生時に、処理を中断(true)するか、継続(false)するか" +
										CR + "(初期値:true:中断する)" );			// 6.3.1.0 (2015/06/28)
		USABLE_PROPARTY.put( "display",		"結果を標準出力に表示する(true)かしない(false)か" +
										CR + "(初期値:false:表示しない)" );
		USABLE_PROPARTY.put( "debug",	"デバッグ情報を標準出力に表示する(true)かしない(false)か" +
										CR + "(初期値:false:表示しない)" );		// 5.7.3.0 (2014/02/07) デバッグ情報
	}

	/**
	 * デフォルトコンストラクター。
	 * このクラスは、動的作成されます。デフォルトコンストラクターで、
	 * super クラスに対して、必要な初期化を行っておきます。
	 *
	 */
	public Process_GrepChange() {
		super( "org.opengion.fukurou.process.Process_GrepChange",MUST_PROPARTY,USABLE_PROPARTY );
	}

	/**
	 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
	 * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
	 *
	 * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
	 * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止、ignoreCase の実装、regex の追加
	 * @og.rev 6.3.1.1 (2015/07/10) 出力ファイルを別フォルダにコピー置換する機能を追加
	 * @og.rev 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
	 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
	 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
	 *
	 * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
	 */
	public void init( final ParamProcess paramProcess ) {
		final Argument arg = getArgument();

		final String keywordFile = arg.getProparty( "keywordFile" );
		ignoreCase	= arg.getProparty( "ignoreCase"	, ignoreCase		);
		regex		= arg.getProparty( "regex"		, regex				);		// 5.7.3.2 (2014/02/28)
		isChange	= arg.getProparty( "isChange"	, isChange			);		// 5.1.2.0 (2010/01/01)
//		inPath		= arg.getProparty( "inPath"		, null				);		// 6.3.1.1 (2015/07/10)
		inEncode	= arg.getProparty( "inEncode"	, System.getProperty("file.encoding"));
		outPath		= arg.getProparty( "outPath"	, null				);		// 6.3.1.1 (2015/07/10)
		outEncode	= arg.getProparty( "outEncode"	, System.getProperty("file.encoding"));
		useWordUnit	= arg.getProparty( "useWordUnit", useWordUnit		);		// 6.3.1.1 (2015/07/10)
		errAbend	= arg.getProparty( "errAbend"	, errAbend			);		// 6.3.1.1 (2015/07/10)
		display		= arg.getProparty( "display"	, display			);
		debug		= arg.getProparty( "debug"		, debug				);		// 5.7.3.0 (2014/02/07) デバッグ情報

		// 6.3.1.1 (2015/07/10) 入力と出力が同じか？
		final String inPath = arg.getProparty( "inPath" , null );	// 6.3.4.0 (2015/08/01)
		isEquals  = outPath == null || inPath == null || inPath.equalsIgnoreCase( outPath );
		inPathLen = inPath == null ? 0 : inPath.length();

		// 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
//		final FileString fs = new FileString();
//		fs.setFilename( keywordFile );
//		fs.setEncode( outEncode );
//		final FileString fs = new FileString( keywordFile , outEncode ) ;
//		final String[] lines = fs.getValue( CR );					// 5.7.3.2 (2014/02/28) \n でなく、CR とします。
//		final String[] lines = fs.getValues();						// CR は、デフォルト行分割記号
//		final List<String> list = fs.getValues();					// 6.4.5.2 (2016/05/06)
		final List<String> list = FileUtil.getLineList( keywordFile , outEncode );	// 6.4.5.2 (2016/05/06)
//		final int len = lines.length;
		final int len = list.size();								// 6.4.5.2 (2016/05/06)
		if( len == 0 ) {
			// これは、初期情報取込み処理なので、errAbend 対象外
			final String errMsg = "keywordFile の内容が 読み取れませんでした。[" + keywordFile + "]" ;
			throw new OgRuntimeException( errMsg );
		}

		println( "keywordFile を、" + len + "件読み取りました。" );
		final List<String> keyList = new ArrayList<>( len );
		final List<String> cngList = new ArrayList<>( len );

		for( final String line : list ) {
//		for( int i=0; i<len; i++ ) {
	//		String line = lines[i].trim();
//			final String line = lines[i];
			final int indx = line.indexOf( '\t' );
			if( indx <= 0 ) { continue ; }	// TAB が先頭や、存在しない行は読み飛ばす。
			// 5.7.3.2 (2014/02/28) debug の表示と、ｷｰﾜｰﾄﾞの \t の使用、trim() 廃止
			String key = line.substring( 0,indx );
			String cng = line.substring( indx+1 );

			if( ignoreCase ) { key = key.toUpperCase(Locale.JAPAN); }	// 5.7.3.2 (2014/02/28) ignoreCase の実装漏れ

			if( debug ) { println( "[" + key + "]⇒[" + cng + "]" ); }

			key = StringUtil.replace( key,"\\t","\t" );
			cng = StringUtil.replace( cng,"\\t","\t" );

			keyList.add( key );
			cngList.add( cng );
		}
		keyword	= keyList.toArray( new String[keyList.size()] );
		change	= cngList.toArray( new String[cngList.size()] );

		// 5.7.3.2 (2014/02/28) regex=true の場合の処理
		if( regex ) {
			pattern = new Pattern[keyword.length];
			for( int i=0; i<keyword.length; i++ ) {
				// 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
//				pattern[i] = ignoreCase	? Pattern.compile( keyword[i] , Pattern.CASE_INSENSITIVE )
//										: Pattern.compile( keyword[i] ) ;
				final String keywd = useWordUnit ? IN_PTN + keyword[i] + IN_PTN : keyword[i] ;
				pattern[i] = ignoreCase	? Pattern.compile( keywd , Pattern.CASE_INSENSITIVE )
										: Pattern.compile( keywd ) ;
			}
		}
	}

	/**
	 * プロセスの終了を行います。最後に一度だけ、呼び出されます。
	 * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
	 *
	 * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
	 */
	public void end( final boolean isOK ) {
		// ここでは処理を行いません。
	}

	/**
	 * 引数の LineModel を処理するメソッドです。
	 * 変換処理後の LineModel を返します。
	 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
	 * null データを返します。つまり、null データは、後続処理を行わない
	 * フラグの代わりにも使用しています。
	 * なお、変換処理後の LineModel と、オリジナルの LineModel が、
	 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
	 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
	 * 各処理ごとに自分でコピー(クローン)して下さい。
	 *
	 * @og.rev 4.0.0.0 (2007/11/28) メソッドの戻り値をチェックします。
	 * @og.rev 5.1.2.0 (2010/01/01) 置換するかどうかを指定可能にする(isChange)属性追加
	 * @og.rev 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
	 * @og.rev 5.7.3.2 (2014/02/28) debug の表示と、ignoreCase の実装
	 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
	 * @og.rev 6.3.1.1 (2015/07/10) 出力ファイルを別フォルダにコピー置換する機能を追加
	 * @og.rev 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
	 *
	 * @param	data	オリジナルのLineModel
	 *
	 * @return	処理変換後のLineModel
	 */
	public LineModel action( final LineModel data ) {
		inCount++ ;
		final FileLineModel fileData ;
		if( data instanceof FileLineModel ) {
			fileData = (FileLineModel)data ;
		}
		else {
			// これは、プログラマーの問題なので、errAbend 対象外
			final String errMsg = "データが FileLineModel オブジェクトではありません。" + CR ;
			throw new OgRuntimeException( errMsg );
		}

		final File org = fileData.getFile() ;
//		final String orgName = org.getPath();
		if( ! org.isFile() ) { return data; }

		if( debug ) { println( "File:" + org ); }		// 5.1.2.0 (2010/01/01) display の条件変更

		File		tempFile  = null;
		PrintWriter	tempWrt   = null;

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		final String orgName = org.getPath();

		// 5.1.2.0 (2010/01/01) 置換する場合の前処理
		if( isChange ) {
			// 6.3.1.1 (2015/07/10) 出力が同じ場合は、従来通り temp出力して、置き換える。
//			tempFile  = new File( orgName + "_temp" );
//			tempWrt   = FileUtil.getPrintWriter( tempFile,outEncode );
			if( isEquals ) {
				tempFile  = new File( orgName + "_temp" );
			}
			else {
				// 入出力が異なる場合
				tempFile  = new File( outPath, org.getAbsolutePath().substring( inPathLen ) );
				fileData.setFile( tempFile );	// tempFile は、出力ファイルの事。
				// 出力先のフォルダが無ければ作成
				final File parent = tempFile.getParentFile();
				if( parent != null && ! parent.exists() && !parent.mkdirs() ) {
					final String errMsg = "所定のフォルダが作成できませんでした。[" + parent + "]" + CR
								+ " inCount=[" + inCount + "]件" + CR
								+ " data=[" + data.dataLine() + "]" + CR ;		// 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
//					throw new OgRuntimeException( errMsg );
					throwException( errMsg,errAbend );
					return null;	// ログだけの場合は、以下の処理は打ち切り。
				}
			}

			tempWrt = FileUtil.getPrintWriter( tempFile,outEncode );
		}

		boolean nextFlag = false;

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );

		final BufferedReader reader = FileUtil.getBufferedReader( org,inEncode );
		try {
			String line ;
			int    lineNo = 0;
			while((line = reader.readLine()) != null) {
				lineNo++ ;
				// 5.7.3.2 (2014/02/28) regex 対応
				if( regex ) {
					for( int i=0; i<pattern.length; i++ ) {
						final Matcher mt = pattern[i].matcher( line );
//						nextFlag = mt.matches();		// 6.4.1.1 (2016/01/16) nextFlag が最後にマッチした値に書き換えられていた。
//						if( nextFlag ) {
						if( mt.matches() ) {
							nextFlag = true;			// １度でも見つかれば、true にセット
							findCount++ ;
							if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
							if( isChange ) {
								// 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
//								line = mt.replaceAll( change[i] );
								line = mt.replaceAll( "$1" + change[i] + "$2" );	// 前方参照
								cngCount++ ;
							}
						}
					}
				}
				else {
					final String line2 = ignoreCase ? line.toUpperCase(Locale.JAPAN) : line ;		//  6.4.1.1 (2016/01/16) 先に、必要な処理を行う。
					buf.setLength(0);				// new StringBuilder の代わり。
					buf.append( line );
					for( int i=0; i<keyword.length; i++ ) {
						int indx = line2.indexOf( keyword[i] );
						// 置換対象発見。行出力用に見つかれば、true にする。
						if( indx >= 0 ) {
							// 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
							// 検索結果の前後の文字が、IN_PTN に含まれている場合のみ、見つかったことにする。
							if( useWordUnit ) {
								// 見つかった場所のひとつ前の文字が、IN_PTN に存在しなければ、見つからなかった。
								if( indx > 0 && IN_PTN.indexOf( line.charAt( indx-1 ) ) < 0 ||
									// 見つかった場所のひとつ後ろの文字が、IN_PTN に存在しなければ、見つからなかった。
									line.length() < (indx+1) && IN_PTN.indexOf( line.charAt( indx+1 ) ) < 0 ) {
									// 対象外になったキーワードと行を表示します。
									if( display ) { println( "NoChange:" + orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
									continue;
								}
							}
							nextFlag  = true;				// １度でも見つかれば、true にセット
							if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
							findCount++ ;

							// 6.4.1.1 (2016/01/16) 見つかったときだけ、置換処理を実施するように変更。
							// 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。
							if( isChange ) {
								while( indx >= 0 ) {
									buf.replace( indx,indx+keyword[i].length(),change[i] );
									// 5.7.3.2 (2014/02/28) ignoreCase 対応。
									final int nxt = indx+change[i].length();
									indx = ignoreCase	? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt )
														: buf.indexOf( keyword[i],nxt );

									cngCount++ ;
								}
							}
						}
					}
//					final StringBuilder buf = new StringBuilder( line );
//	//				boolean foundFlag = false;		// 行単位に初期化する。
//					for( int i=0; i<keyword.length; i++ ) {
//						// 5.7.3.2 (2014/02/28) ignoreCase 対応。
//	//					int indx = buf.indexOf( keyword[i] );
//						int indx = ignoreCase	? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i] )
//												: buf.indexOf( keyword[i] ) ;
//
//						// 置換対象発見。行出力用に見つかれば、true にする。
//						if( indx >= 0 ) {
//							// 6.3.1.1 (2015/07/10) useWordUnit="true" 時は、出来るだけ、Grep対象を、単語単位で置換しようとします
//							// 検索結果の前後の文字が、IN_PTN に含まれている場合のみ、見つかったことにする。
//							if( useWordUnit ) {
//								// 見つかった場所のひとつ前の文字が、IN_PTN に存在しなければ、見つからなかった。
//								if( indx > 0 && IN_PTN.indexOf( buf.charAt( indx-1 ) ) < 0 ||
//								// 見つかった場所のひとつ後ろの文字が、IN_PTN に存在しなければ、見つからなかった。
//									buf.length() < (indx+1) && IN_PTN.indexOf( buf.charAt( indx+1 ) ) < 0 ) {
//									// 対象外になったキーワードと行を表示します。
//									if( display ) { println( "NoChange:" + orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
//									continue;
//								}
//							}
//	//						foundFlag = true;
//							nextFlag  = true;		// １度でも見つかれば、true にセット
//							if( display ) { println( orgName + ":" + lineNo + ":" + keyword[i] + ":" + line ); }
//							findCount++ ;
//						}
//						// 置換対象が見つかっても、isChange=true でなければ、置換処理は行わない。
//						if( isChange ) {
//							while( indx >= 0 ) {
//								buf.replace( indx,indx+keyword[i].length(),change[i] );
//								// 5.7.3.2 (2014/02/28) ignoreCase 対応。
//	//							indx = buf.indexOf( keyword[i],indx+change[i].length() );
//								final int nxt = indx+change[i].length();
//								indx = ignoreCase	? buf.toString().toUpperCase(Locale.JAPAN).indexOf( keyword[i],nxt )
//													: buf.indexOf( keyword[i],nxt );
//
//	//							nextFlag = true;			// キーワードが存在したファイル。
//								cngCount++ ;
//							}
//						}
//					}
					line = buf.toString();
				}
				// 5.1.2.0 (2010/01/01) 置換する場合の処理
				if( isChange ) {
					tempWrt.println( line );				// 5.7.3.2 (2014/02/28) regexで出力を共有する為。
				}
			}
		}
		// 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。
		catch( final CharacterCodingException ex ) {
			final String errMsg = "文字のエンコード・エラーが発生しました。" + CR
								+	"  ファイルのエンコードが指定のエンコードと異なります。" + CR
								+	" [" + org + "] , Encode=[" + inEncode + "]" ;
//			throw new OgRuntimeException( errMsg,ex );
			throwException( errMsg,ex,errAbend );
			return null;	// ログだけの場合は、以下の処理は打ち切り。
		}
		catch( final IOException ex ) {
			final String errMsg = "処理中にエラーが発生しました。" + CR
						+ " [" + org + "] , Encode=[" + inEncode + "]" + CR
						+ " [" + data.getRowNo() + "]件目" + CR
						+ " data=[" + data.dataLine() + "]" + CR ;		// 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
//			throw new OgRuntimeException( errMsg,ex );
			throwException( errMsg,ex,errAbend );
			return null;	// ログだけの場合は、以下の処理は打ち切り。
		}
		finally {
			Closer.ioClose( reader );
			Closer.ioClose( tempWrt );
		}

		// 5.1.2.0 (2010/01/01) 置換する場合の処理
//		if( isChange ) {
		if( isChange && isEquals ) {		// 6.3.1.1 (2015/07/10) 出力が同じ場合の時のみ、後処理が必要。
			if( nextFlag ) {
				if( !org.delete() ) {
					final String errMsg = "所定のファイルを削除できませんでした。[" + org + "]" + CR
							+	"data=[" + data.dataLine() + "]" + CR ;		// 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
//					throw new OgRuntimeException( errMsg );
					throwException( errMsg,errAbend );
				}
				if( !tempFile.renameTo( org ) ) {
					final String errMsg = "所定のファイルをリネームできませんでした。[" + tempFile + "]" + CR
							+	"data=[" + data.dataLine() + "]" + CR ;		// 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
//					throw new OgRuntimeException( errMsg );
					throwException( errMsg,errAbend );
				}
			}
			else {
				if( !tempFile.delete() ) {
					final String errMsg = "所定のファイルを削除できませんでした。[" + tempFile + "]" + CR
							+	"data=[" + data.dataLine() + "]" + CR ;		// 5.7.2.2 (2014/01/24) エラー時にデータも出力します。
//					throw new OgRuntimeException( errMsg );
					throwException( errMsg,errAbend );
				}
			}
		}

		return nextFlag ? data : null ;
	}

	/**
	 * プロセスの処理結果のレポート表現を返します。
	 * 処理プログラム名、入力件数、出力件数などの情報です。
	 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
	 * 形式で出してください。
	 *
	 * @return   処理結果のレポート
	 */
	public String report() {
		final String report = "[" + getClass().getName() + "]" + CR
				+ TAB + "Search File Count : " + inCount    + CR
				+ TAB + "Key Find    Count : " + findCount  + CR
				+ TAB + "Key Change  Count : " + cngCount ;

		return report ;
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 * @og.rtnNotNull
	 */
	public String usage() {
		final StringBuilder buf = new StringBuilder( 1200 )
			.append( "Process_GrepChange は、上流から受け取った FileLineModelから、語句を"			).append( CR )
			.append( "置換する、ChainProcess インターフェースの実装クラスです。"					).append( CR )
			.append( "Process_Grep との違いは、チェックするファイルのコピーを(キーワードが存在"		).append( CR )
			.append( "しなくとも)作成することと、検索キーに正規表現が使えない、複数行置き換えが"	).append( CR )
			.append( "出来ないことです。"															).append( CR )
			.append( CR )
			.append( "keywordFile より、置換する語句を含むキーと値のペアー(タブ区切り)を読取り、"	).append( CR )
			.append( "対象とする語句を置換します。"													).append( CR )
			.append( "keywordFile に、タブが含まれない行や、先頭にタブが存在している場合は、"		).append( CR )
			.append( "その行を読み飛ばします。また、区切りタブは何個存在しても構いません。"			).append( CR )
			.append( "ただし、タブで区切った前(キー)と後ろ(値)は、trim() されますので、スペース"	).append( CR )
			.append( "が前後に存在している場合は、ご注意ください。"									).append( CR )
			.append( "置換文字(値)は、\t と \n の特殊文字が使用できます。" 							).append( CR )
			.append( "この GrepChange では、語句に、正規表現は使用できません。正規表現のキーワード"	).append( CR )
			.append( "や文字列を複数行の文字列と置き換える場合は、Process_Grep を使用して下さい。"	).append( CR )
			.append( "このプログラムでは、上流から受け取った FileLineModel のファイルに対して、"	).append( CR )
			.append( "置き換えた結果も、同じファイルにセーブします。"								).append( CR )
			.append( "元のファイルを保存したい場合は、予めバックアップを取得しておいてください。"	).append( CR )
			.append( "-inEncode は、入力ファイルのエンコード指定になります。" 						).append( CR )
			.append( "-outEncode は、出力ファイルのエンコードや、キーワードファイルのエンコード" 	).append( CR )
			.append( "指定になります。(keywordFile は、必ず 出力ファイルと同じエンコードです。)" 	).append( CR )
			.append( "これらのエンコードが無指定の場合は、System.getProperty(\"file.encoding\") " 	).append( CR )
			.append( "で求まる値を使用します。" 													).append( CR )
			.append( CR )
			.append( "上流プロセスでは、Name 属性として、『File』を持ち、値は、Fileオブジェクト"	).append( CR )
			.append( "である、Process_FileSearch を使用するのが、便利です。それ以外のクラスを"		).append( CR )
			.append( "使用する場合でも、Name属性と、File オブジェクトを持つ LineModel を受け渡し"	).append( CR )
			.append( "できれば、使用可能です。"														).append( CR )
			.append( CR )
			.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"	).append( CR )
			.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"		).append( CR )
			.append( "繋げてください。"																).append( CR )
			.append( CR ).append( CR )
			.append( getArgument().usage() ).append( CR );

		return buf.toString();
	}

	/**
	 * このクラスは、main メソッドから実行できません。
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		LogWriter.log( new Process_GrepChange().usage() );
	}
}
