/*
 * 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.security.HybsCryptography;
import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.system.LogWriter;

import java.util.Map ;
import java.util.LinkedHashMap ;

/**
 * Process_StringUtil は、上流から受け取ったデータをStringUtilクラスの特定の
 * メソッドでデータ変換する、CainProcess インターフェースの実装クラスです。
 *
 * 上流(プロセスチェインのデータは上流から下流へと渡されます。)から
 *  LineModel を元に、指定のカラムの文字を、変換します。
 *
 * 現時点で利用できるStringUtil のメソッドは、下記の通りです。
 *    urlEncode        : UTF-8 で、URLエンコードを行う。
 *    rTrim            : 文字列の後ろのスペースを削除
 *    htmlFilter       : HTML上のエスケープ文字を変換
 *    code39           : CODE39 の 文字列を作成(チェックデジット付き)
 *    getUnicodeEscape : HTML のエスケープ記号(&amp;#xZZZZ;)に変換
 *    getReplaceEscape : HTML のエスケープ記号(&amp;#xZZZZ;)を戻す
 *    spanCut          : 引数からspanタグを取り除く
 *
 * HybsCryptography のメソッドも呼び出せます。
 *    getMD5           : MessageDigestにより、MD5 でハッシュした文字に変換
 *    encrypt          : Hybs独自の暗号化を行います(Hybs内部設定の秘密鍵)
 *    decrypt          : Hybs独自の復号化を行います(Hybs内部設定の秘密鍵)
 *
 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
 * 繋げてください。
 *
 * @og.formSample
 *  Process_StringUtil -action=getMD5|encrypt|decrypt|code39|getUnicodeEscape|getReplaceEscape|･･･ -keys=AA,BB,CC
 *
 *     -action=ESC|REV        ：StringUtilクラスの特定のメソッド名を指定します(必須)。
 *                              urlEncode|rTrim|htmlFilter|getMD5|code39|getUnicodeEscape|getReplaceEscape|spanCut
 *     -keys=AA,BB,CC         ：変換するカラムをCSV形式で複数指定できます(必須)。
 *   [ -display=[false/true]] ：結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *   [ -debug=[false/true]  ] ：デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない])
 *
 * @og.rev 5.0.0.2 (2009/09/15) 新規クラス作成
 *
 * @version  0.9.0  2004/02/27
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Process_StringUtil extends AbstractProcess implements ChainProcess {

	private static final String STR_ACTION_BASE = "org.opengion.fukurou.process.Process_StringUtil$SU_" ;

	private String		keys		;		// 変換するカラム名配列のアドレス
	private int[]		clmNos		;		// 変換するカラム名配列のアドレス
	private boolean		display		;		// 表示しない
	private boolean		debug		;		// 5.7.3.0 (2014/02/07) デバッグ情報

	private boolean		firstRow	= true;	// 最初の一行目
	private int			count		;
	private StrAction	stAction	;		// Ver 5.0.0.2 (2009/09/15)

	/** 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( "action",		"StringUtilの特定のメソッドを指定します(必須)" + 
										CR + "urlEncode , rTrim , htmlFilter , getMD5 , encrypt , decrypt , code39 , getUnicodeEscape , getReplaceEscape , spanCut" );

		MUST_PROPARTY.put( "keys",		"変換するカラムをCSV形式で複数指定できます(必須)。" );

		USABLE_PROPARTY = new LinkedHashMap<>();
		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_StringUtil() {
		super( "org.opengion.fukurou.process.Process_StringUtil",MUST_PROPARTY,USABLE_PROPARTY );
	}

	/**
	 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
	 * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
	 *
	 * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
	 */
	public void init( final ParamProcess paramProcess ) {
		final Argument arg = getArgument();

		keys		= arg.getProparty( "keys",keys );
		display		= arg.getProparty( "display",display );
		debug		= arg.getProparty("debug",debug);				// 5.7.3.0 (2014/02/07) デバッグ情報

		final String act	= arg.getProparty( "action" );

		stAction	= (StrAction)StringUtil.newInstance( STR_ACTION_BASE + act );
	}

	/**
	 * 引数の LineModel を処理するメソッドです。
	 * 変換処理後の LineModel を返します。
	 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、
	 * null データを返します。つまり、null データは、後続処理を行わない
	 * フラグの代わりにも使用しています。
	 * なお、変換処理後の LineModel と、オリジナルの LineModel が、
	 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。
	 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、
	 * 各処理ごとに自分でコピー(クローン)して下さい。
	 *
	 * @param   data	オリジナルのLineModel
	 *
	 * @return	処理変換後のLineModel
	 */
	public LineModel action( final LineModel data ) {
		count++ ;
		try {
			if( firstRow ) {
				makeColumnNos( data );
				firstRow = false;
				if( display ) { println( data.nameLine() ); }		// 5.7.3.0 (2014/02/07) デバッグ情報
			}

			if( debug ) { println( "Before:" + data.dataLine() ); }		// 5.1.2.0 (2010/01/01) display の条件変更
			for( int i=0; i<clmNos.length; i++ ) {
				final String val = (String)data.getValue( clmNos[i] ) ;
				data.setValue( clmNos[i],stAction.change( val ) );
			}

			if( debug ) { println( "After :" + data.dataLine() ); }		// 5.1.2.0 (2010/01/01) display の条件変更
			else if( display ) { println( data.dataLine() ); }		// 5.1.2.0 (2010/01/01) display の条件変更
		}
		catch( final Throwable ex ) {
			final String errMsg = "row=[" + count + "]" + CR +
						"    data=[" + data + "]" + CR ;
			throw new OgRuntimeException( errMsg,ex );
		}
		return data;
	}

	/**
	 * プロセスの終了を行います。最後に一度だけ、呼び出されます。
	 * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
	 *
	 * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
	 */
	public void end( final boolean isOK ) {
		keys		= null;		// 変換するカラム名配列のアドレス
		clmNos		= null;		// 変換するカラム名配列のアドレス
	}

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

		return report ;
	}

	/**
	 * カラム番号配列を取得します。
	 * 繰返し処理を行う場合に、事前にアドレスでアクセスできるように処理するカラム番号を
	 * キャッシュしておきます。
	 *
	 * @param	data  LineModelオブジェクト
	 */
	private void makeColumnNos( final LineModel data ) {
		final String[] clms = StringUtil.csv2Array( keys );
		final int size = clms.length;
		clmNos = new int[size];
		for( int i=0; i<size; i++ ) {
			clmNos[i] = data.getColumnNo( clms[i] );
		}
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 * @og.rtnNotNull
	 */
	public String usage() {
		final StringBuilder buf = new StringBuilder( BUFFER_LARGE )
			.append( "Process_StringUtil は、上流から受け取ったデータをStringUtilクラスの特定の" 	).append( CR )
			.append( "メソッドでデータ変換する、CainProcess インターフェースの実装クラスです。" 	).append( CR )
			.append( CR )
			.append( "上流(プロセスチェインのデータは上流から下流へと渡されます。)から"				).append( CR )
			.append( " LineModel を元に、指定のカラムの文字を、変換します。"						).append( CR )
			.append( CR )
			.append( "現時点で利用できるStringUtil のメソッドは、下記の通りです。"					).append( CR )
			.append( "  urlEncode        : UTF-8 で、URLエンコードを行う。"							).append( CR )
			.append( "  rTrim            : 文字列の後ろのスペースを削除" 							).append( CR )
			.append( "  htmlFilter       : HTML上のエスケープ文字を変換" 							).append( CR )
			.append( "  code39           : CODE39 の 文字列を作成(チェックデジット付き)"			).append( CR )
			.append( "  getUnicodeEscape : HTML のエスケープ記号(&amp;#xZZZZ;)に変換"				).append( CR )
			.append( "  getReplaceEscape : HTML のエスケープ記号(&amp;#xZZZZ;)を戻す"				).append( CR )
			.append( "  spanCut          : 引数からspanタグを取り除く"								).append( CR )
			.append( CR )
			.append( "HybsCryptography のメソッドも呼び出せます。"									).append( CR )
			.append( "  getMD5           : MessageDigestにより、MD5 でハッシュした文字に変換"		).append( CR )
			.append( "  encrypt          : Hybs独自の暗号化を行います(Hybs内部設定の秘密鍵)"		).append( CR )
			.append( "  decrypt          : Hybs独自の復号化を行います(Hybs内部設定の秘密鍵)"		).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_StringUtil().usage() );
	}

	/**
	 * インナークラスとして、共通メソッドを定義します(I/Fの代わり)。
	 *
	 * ※ このクラスは継承されるため、final化しません。
	 */
	private static class StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		public String change( final String val ) {
			return val;
		}
	}

	/**
	 * UTF-8 で、URLエンコードを行います。
	 */
	private static final class SU_urlEncode extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.urlEncode( val );
		}
	}

	/**
	 * 文字列の後ろのスペースを削除します。
	 * 注意：'\u0020' (スペース文字) より小さい文字を切り取ります。
	 */
	private static final class SU_rTrim extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.rTrim( val );
		}
	}

	/**
	 * HTML上のエスケープ文字を変換します。
	 */
	private static final class SU_htmlFilter extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.htmlFilter( val );
		}
	}

	/**
	 * CODE39 の 文字列を作成します。(チェックデジット付き)
	 */
	private static final class SU_code39 extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.code39( val,true );
		}
	}

	/**
	 * Unicode文字列の値を HTML のエスケープ記号(&amp;#xZZZZ;)に変換します。
	 */
	private static final class SU_getUnicodeEscape extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.getUnicodeEscape( val );
		}
	}

	/**
	 * HTML のエスケープ記号(&amp;#xZZZZ;)をUnicode文字列に戻します。
	 */
	private static final class SU_getReplaceEscape extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.getReplaceEscape( val );
		}
	}

	/**
	 * 引数からspanタグを取り除いて返します。
	 */
	private static final class SU_spanCut extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return StringUtil.spanCut( val );
		}
	}

	/**
	 * MessageDigestにより、MD5 でハッシュした文字に変換します。
	 *
	 * @og.rev 5.2.2.0 (2010/11/01) util.StringUtil から security.HybsCryptography へ移動
	 *
	 */
	private static final class SU_getMD5 extends StrAction {
		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			return HybsCryptography.getMD5( val );
		}
	}

	/**
	 * Hybs独自の暗号化を行います(Hybs内部設定の秘密鍵)
	 *
	 * @og.rev 5.2.2.0 (2010/11/01) 新規追加
	 */
	private static final class SU_encrypt extends StrAction {
		private HybsCryptography crpt ;

		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			if( crpt == null ) {
				crpt = new HybsCryptography();
			}
			return crpt.encrypt( val );
		}
	}

	/**
	 * Hybs独自の復号化を行います(Hybs内部設定の秘密鍵)
	 *
	 * @og.rev 5.2.2.0 (2010/11/01) 新規追加
	 */
	private static final class SU_decrypt extends StrAction {
		private HybsCryptography crpt ;

		/**
		 * 引数を変換します。
		 *
		 * @param	val		引数
		 * @return	変換された文字列
		 */
		@Override
		public String change( final String val ) {
			if( crpt == null ) {
				crpt = new HybsCryptography();
			}
			return crpt.decrypt( val );
		}
	}
}
