/*
 * 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.hayabusa.taglib;

import static org.opengion.fukurou.util.StringUtil.nval;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.model.Formatter;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.resource.ResourceManager;

/**
 * 画面で入力されたデータのチェックを行うためのタグです。
 * 
 * commandがNEWの場合は検索条件等のリクエストパラメータに対してチェックを行います。
 * commandがENTRYの場合は、登録時のDBテーブルモデルに対するチェックを行います。
 * (値の取得は、先に選択された行のみについて、実行されます。)
 * 
 * チェックを行うための定義は、SQL文 又は JavaScriptの式が記述可能です。
 * これらの式はタグのボディー部分に記述します。
 *
 * SQL文によりチェックを行う場合は、必ず件数が返されるように記述して下さい(select count(*) ･･･ 等)
 * このSQL文で取得された件数とexistの属性値とを照合しチェックを行います。
 * いずれの場合も、成立時は、正常とみなします。
 * （｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。）
 * 
 * JavaScript式を記述する場合は、必ずtrue or falseを返す式を指定して下さい。
 * この式を評価した結果falseが返される場合は、エラーとみなします。
 * 式に不等号等を使用する場合は、CDATAセクションで囲うようにして下さい。
 * 
 * また、いずれのチェック方法の場合でも、引数部に[カラム名]を用いたHybs拡張SQL文を
 * 指定することが可能です。
 * メッセージIDの{0},{1}にはそれぞれ[カラム名]指定されたカラム名及びデータがカンマ区切りで
 * 自動的に設定されます。
 *
 * @og.formSample
 * <pre>
 * ●形式：
 *       ・&lt;og:dataCheck
 *                    command       = "{&#064;command}"
 *                    exist         = "[auto|true|false|one|notuse]"
 *                    errRemove     = "[true|false]"
 *                    msg           = "{&#064;msg}"
 *                    msgParamKeys  = "ZY03"      : メッセージリソースのキーをカンマ区切りで指定。{2} 以降にセット
 *                    sqlType       = "{&#064;sqlType}"
 *                    execType      = "INSERT|COPY|UPDATE|MODIFY|DELETE"  : sqlType を含む場合、実行
 *                    conditionKey  = "FGJ"        : 条件判定するカラムＩＤを指定(初期値は columnId )
 *                    conditionList = "0|1|8|9"    : 条件判定する値のリストを、"|"で区切って登録(初期値は、無条件)
 *                    uniqCheckKeys = "CLM,LANG"   : DBTableModel内でのユニークキーチェックを行うためのカラム
 *         &gt;
 *
 * ●body：SQL文 又は JavaScript式
 *
 * ●使用例
 *       ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    exist     = "true"
 *                    msg       = "MSG0001"
 *         &gt;
 *             select count(*) from GEA03 where clm = [CLM]
 *         &lt;/og:dataCheck&gt;
 *
 *          ・exist 属性の値に応じて、チェック方法が異なります。
 *            [ auto , true , false , one , notuse が指定できます。]
 *
 *       ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    msg       = "MSG0001"
 *         &gt;
 *           &lt;![CDATA[
 *             [DYSTART] &lt; [DY] && [DY] &lt [DYEND]
 *           ]]&gt;
 *         &lt;/og:dataCheck&gt;
 *         
 *         ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    msg       = "MSG0001"
 *         &gt;
 *           &lt;![CDATA[
 *             [GOKEI] &lt [TANKA] * [RITU]
 *           ]]&gt;
 *         &lt;/og:dataCheck&gt;
 * </pre>
 *
 * @og.rev 4.1.1.1 (2008/02/22) 新規作成
 * @og.group ＤＢ登録
 *
 * @version  4.0
 * @author	 Hiroki Nakamura
 * @since    JDK5.0,
 */
public class DataCheckTag extends CommonTagSupport {
	private static final String		VERSION				= "4.2.1.0 (2008/04/11)";
	private static final long		serialVersionUID	= 4210;

	/** command 引数に渡す事の出来る コマンド {@value} */
	public static final String		CMD_NEW				= "NEW";

	/** command 引数に渡す事の出来る コマンド {@value} */
	public static final String		CMD_ENTRY			= "ENTRY";

	/** command 引数に渡す事の出来る コマンド リスト  */
	private static final String[]	COMMAND_LIST		= { CMD_ENTRY, CMD_NEW };

	/** 内部変数 */
	private transient DBTableModel	table		= null;
//	private transient ErrorMessage	errMessage	= null;
	private transient boolean		isSql		= false;
	private transient boolean		isUniqCheck	= false; // 4.3.4.0 (2008/12/01) 追加
	private transient ScriptEngine	jsEngine	= null;
	private transient String		bodyStr		= null; // 4.3.4.0 (2008/12/01) 追加

	/** タグで設定する属性 */
	private String		command			= CMD_ENTRY;
	private String		exist			= "auto";
	private String		tableId			= HybsSystem.TBL_MDL_KEY;
	private String		dbid			= null;
	private String		msg				= null;
	private String[]	msgParamKeys	= null;		// 4.2.0.1 (2008/03/27)
	private boolean		errRemove		= false;
	private String		sqlType			= null;		// INSERT,COPY,UPDATE,MODIFY,DELETE
	private String		execType		= null;		// INSERT,COPY,UPDATE,MODIFY,DELETE
	private boolean		isExec			= true;		// 4.1.2.0 (2008/03/12)

	private String		conditionKey	= null;		// 4.2.0.1 (2008/03/27)
	private String		conditionList	= null;		// 4.2.0.1 (2008/03/27)
	private String		from			= null;		// 4.2.0.1 (2008/03/27)
	private String[]	uniqCheckClms	= null;		// 4.3.4.0 (2008/12/01)

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.1.2.0 (2008/03/12) sqlType,execType 判定
	 *
	 * @return	int
	 */
	@Override
	public int doStartTag() {
		isExec = (sqlType == null || execType == null || execType.indexOf( sqlType ) >= 0 );

		if( isExec ) {
			return( EVAL_BODY_BUFFERED );	// Body を評価する
		}
		else {
			return(SKIP_BODY);				// Body を評価しない
		}
	}
	
	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 * 
	 * @og.rev 4.3.4.0 (2008/12/01) 新規追加
	 *
	 * @return  int
	 */
	@Override
	public int doAfterBody() {
		bodyStr = getBodyString().trim();
		return(SKIP_BODY);
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.1.2.0 (2008/03/12) sqlType,execType 判定
	 * @og.rev 4.2.0.1 (2008/03/27) from を取得
	 * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
	 * @og.rev 4.3.4.0 (2008/12/01) ユニークキーチェック対応。bodyContentの取得を#doAfterBody()で行う。
	 *
	 * @return	int
	 */
	@Override
	public int doEndTag() {
		debugPrint();
		int rtnCode = EVAL_PAGE;

		// 4.1.2.0 (2008/03/12) 実行条件 isExec を評価
		if( isExec && check( command, COMMAND_LIST ) ) {
			// exist="notuse"の場合はチェックしない
			if( exist.equalsIgnoreCase( "notuse" ) ) { return rtnCode; }

			// パラメーターから処理のタイプを判別
			checkParam();

			// エラーメッセージを管理するクラスを作成します。
			ErrMessageManager manager = new ErrMessageManager();
			manager.setTitle( "Data Check Error!" );
			manager.setParamKeys( msgParamKeys );
			manager.setResourceManager( getResource() );
			manager.setFrom( from );

			// command="NEW"の場合
			if( CMD_NEW.equals( command ) ) {
				if( isSql ) {
//					checkSql( str, null, null, DBTableModel.UPDATE_TYPE, 0 );
					checkSql( bodyStr, manager, null, 0, DBTableModel.UPDATE_TYPE );
				}
				else {
//					checkJs( str, null, null, 0, jsEngine );
					checkJs( bodyStr, manager, null, 0, jsEngine );
				}
			}
			// command="ENTRY"の場合(テーブルモデルが存在しない場合は処理しない)
			else if( CMD_ENTRY.equals( command ) ) {
				table = (DBTableModel) getObject( tableId );
				if( table != null && table.getRowCount() > 0 ) {
					manager.setDBTableModel( table );
					if( isUniqCheck ) {
						checkUnique( manager );
					}
					else {
						checkRows( bodyStr,manager );
					}
				}
				else {
					System.out.println( "DBTableModel doesn't exist!! need this when command=\"ENTRY\"" );
				}
			}

			// エラーが発生した場合は、エラーメッセージを表示して以降の処理を行わない。
			ErrorMessage errMessage = manager.getErrMessage() ;
			if( errMessage != null && !errMessage.isOK() && !errRemove ) {
				rtnCode = SKIP_PAGE;
				jspPrint( TaglibUtil.makeHTMLErrorTable( errMessage, getResource() ) );
			}
		}

		return( rtnCode );
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.1.2.0 (2008/03/12) sqlType , execType , isExec 追加
	 * @og.rev 4.2.0.1 (2008/03/27) conditionKey , conditionList , msgParamKeys 追加
	 */
	@Override
	protected void release2() {
		super.release2();
		tableId 		= HybsSystem.TBL_MDL_KEY;
		dbid 			= null;
		command 		= CMD_ENTRY;
		table 			= null;
//		errMessage 		= null;
		exist 			= "auto";
		errRemove 		= false;
		msg 			= null;
		msgParamKeys	= null;		// 4.2.0.1 (2008/03/27)
		isSql			= false;
		isUniqCheck		= false;	// 4.3.4.0 (2008/12/01)
		jsEngine		= null;
		sqlType			= null;		// INSERT,COPY,UPDATE,MODIFY,DELETE
		execType		= null;		// INSERT,COPY,UPDATE,MODIFY,DELETE
		isExec			= true;		// 4.1.2.0 (2008/03/12)
		conditionKey	= null;		// 4.2.0.1 (2008/03/27)
		conditionList	= null;		// 4.2.0.1 (2008/03/27)
		from			= null;		// 4.2.0.1 (2008/03/27)
		bodyStr			= null;		// 4.3.4.0 (2008/12/01))
		uniqCheckClms	= null;		// 4.3.4.0 (2008/12/01)
	}
	
	/**
	 * 引数及びボディー部分のチェックを行い、処理のタイプを判別します。
	 * 
	 */
	private void checkParam() {
		isUniqCheck = ( uniqCheckClms != null && uniqCheckClms.length > 0 ) ? true : false;
		if( isUniqCheck ) {
			if( !CMD_ENTRY.equals( command ) ) {
				String errMsg = "ユニークキーチェックは、command=\"ENTRY\"の場合のみ使用可能です。";
				throw new HybsSystemException( errMsg );
			}
		}
		else {
			if( bodyStr == null || bodyStr.length() == 0 ) {
				String errMsg = "Body部分にチェック定義を記述して下さい。";
				throw new HybsSystemException( errMsg );					
			}
			else {
				// SQLチェックかJavaScriptによるチェックかの判定
				String query = bodyStr.toUpperCase( Locale.JAPAN );		// 4.2.0.1 (2008/03/27)
				if( query.indexOf( "SELECT" ) >= 0 ) {
					isSql = true;
					int st = query.indexOf( "FROM" ) ;
					int ed = query.indexOf( "WHERE" ) ;
					if( st > 0 && st < ed ) {
						from = query.substring( st,ed ).trim();
					}
				}
				else {
					jsEngine = new ScriptEngineManager().getEngineByName( "JavaScript" );
				}
			}
		}
	}

	/**
	 * SQLによるデータチェックを行います。
	 * チェック方法は、exist属性の指定に依存します。
	 * autoの場合は、テーブルモデルの改廃Cから自動でチェック方法が決定されます。
	 *
	 * @param str 実行するSQL文
	 * @param manager ErrMessageManager オブジェクト
	 * @param row 行番号
	 * @param modifyType 改廃C
	 * @return 処理の成否
	 *
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
	 */
//	private boolean checkSql( final String str, final String names, final String[] values
//			, final String modifyType, final int row ) {
	private boolean checkSql( final String str, final ErrMessageManager manager, final String[] values
							, final int row, final String modifyType ) {
		int cnt = DBUtil.dbExist( str, values, getApplicationInfo(), dbid );

		boolean okFlag = true;
		String id = null;
		if( ( exist.equalsIgnoreCase( "true" ) || ( exist.equalsIgnoreCase( "auto" ) 
				&& ( DBTableModel.UPDATE_TYPE.equals( modifyType ) || DBTableModel.DELETE_TYPE.equals( modifyType ) ) ) ) && cnt <= 0 ) {
			// ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
			id = ( msg == null ? "ERR0025" : msg );
			okFlag = false;
		}
		else if( ( exist.equalsIgnoreCase( "false" ) || ( exist.equalsIgnoreCase( "auto" )
				&& DBTableModel.INSERT_TYPE.equals( modifyType ) ) ) && cnt > 0 ) {
			// ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
			id = ( msg == null ? "ERR0026" : msg );
			okFlag = false;
		}
		else if( exist.equalsIgnoreCase( "one" ) && cnt > 1 ) {
			// ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
			id = ( msg == null ? "ERR0027" : msg );
			okFlag = false;
		}

		if( !okFlag ) {
			manager.addMessage( row, id, values );

//			String vals = StringUtil.array2csv( values );
//			ResourceManager resource = getResource();
//			String fromLbl = resource.getLabel( from );
//			if( msgParamKeys == null || msgParamKeys.length == 0 ) {
//				errMessage.addMessage( row, ErrorMessage.NG, id, names, vals, fromLbl );
//			}
//			else {
//				int size = msgParamKeys.length;
//				String[] args = new String[size] ;
//				for( int i=0; i<size; i++ ) {
//					String key = msgParamKeys[i] ;
//					if(		 "@KEY".equals( key ) ) { args[i] = names;	}
//					else if( "@VAL".equals( key ) ) { args[i] = vals;	}
//					else if( "@TBL".equals( key ) ) { args[i] = fromLbl;}
//					// 4.2.1.0 (2008/04/11) リクエスト変数対応
//					else if( "[".startsWith( key ) && "]".endsWith( key )  ) {
//						if( table != null ) {
//							args[i] = table.getValue( row,key.substrng( 1,key.length()-1 ) );
//						}
//					}
//					// 4.2.1.0 (2008/04/11) リクエスト変数対応
//					else if( "{#".startsWith( key ) && "}".endsWith( key )  ) {
//						args[i] = resource.getLabel( key.substrng( 2,key.length()-1 ));
//					}
//					else {
//						args[i] = key;
//					}
//				}
//				errMessage.addMessage( row, ErrorMessage.NG, id, args );
//			}
		}
		return okFlag;
	}

	/**
	 * JavaScriptの式を実行します。
	 * 実行した結果がboolean型でない場合はエラーとなります。
	 * 
	 * @param str  実行するSQL文
	 * @param manager ErrMessageManager オブジェクト
	 * @param values 値配列
	 * @param row 行番号
	 * @param engine JavaScriptエンジン
	 * @return 処理の成否
	 * 
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.2.0.1 (2008/03/27) getClass().getName() から、instanceof に変更
	 * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
	 */
//	private boolean checkJs( final String str, final String names, final String[] values
//			, final int row, final ScriptEngine engine ) {
	private boolean checkJs(  final String str, final ErrMessageManager manager, final String[] values
							, final int row, final ScriptEngine engine ) {
		// JavaScriptエンジンによる評価
		Object obj = null;
		try {
			obj = engine.eval( str );
		}
		catch( ScriptException ex ) {
			String errMsg = "JavaScript式のパースに失敗しました。[" + str + "]";
			throw new HybsSystemException( errMsg , ex );
		}

		// 返り値がBoolean型かチェック
		boolean okFlag = false;
		// 4.2.0.1 (2008/03/27) instanceof に変更
//		if( obj != null && "java.lang.Boolean".equals( obj.getClass().getName() ) ) {
//		if( obj != null && obj instanceof Boolean ) {
		if( obj instanceof Boolean ) {	// 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
			okFlag = ((Boolean)obj).booleanValue();
		}
		else {
			String errMsg = "JavaScript式には true 若しくは false が返るように設定して下さい";
			throw new HybsSystemException( errMsg );
		}

		if( !okFlag ) {
			// ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
			String id = ( msg == null ? "ERR0030" : msg );

			manager.addMessage( row, id, values );

//			String vals = StringUtil.array2csv( values );
//			// ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
//			String id = ( msg == null ? "ERR0030" : msg );
//			// 4.2.1.0 (2008/04/11) リクエスト変数対応
//			if( msgParamKeys == null || msgParamKeys.length == 0 ) {
//				errMessage.addMessage( row, ErrorMessage.NG, id, names, vals, str );
//			}
//			else {
//				int size = msgParamKeys.length;
//				String[] args = new String[size] ;
//				for( int i=0; i<size; i++ ) {
//					ResourceManager resource = getResource();
//					String key = msgParamKeys[i] ;
//					// 4.2.1.0 (2008/04/11) リクエスト変数対応
//					if( "{#".startsWith( key ) && "}".endsWith( key )  ) {
//						args[i] = resource.getLabel( key.substrng( 2,key.length()-1 ));
//					}
//					else {
//						args[i] = key;
//					}
//				}
//				errMessage.addMessage( row, ErrorMessage.NG, id, args );
//			}
		}

		return okFlag;
	}

	/**
	 * DBテーブルモデルの各行に対してデータチェックを行います。
	 *
	 * @param str チェック対象の文字列
	 * @param manager ErrMessageManager オブジェクト
	 * 
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
	 * @og.rev 4.2.1.0 (2008/04/11) ErrMessageManager対応
	 */
//	private void checkRows( final String str ) {
	private void checkRows( final String str, final ErrMessageManager manager ) {

		int[] rowNo = getParameterRows(); // 4.0.0 (2005/01/31)
		if( rowNo.length == 0 ) { return; }

		Formatter format = new Formatter( table );
		format.setFormat( str );
		int[] clmNo = format.getClmNos();
		// 4.2.0.1 (2008/03/27) カラム名のメッセージリソース文字列を作成します。
//		String names = getKeysLabel( clmNo );

		manager.setClmNos( clmNo );

		// SQL文の場合のみ[xxx]を?に変換したSQL文を取得(JavaScriptの場合はループ内で各行毎に取得
		String query = null;
		if( isSql ) {
			query = format.getQueryFormatString();
		}

	 	// 4.2.0.1 (2008/03/27) conditionKey,conditionList 対応
		int cndKeyNo = -1;
		if( conditionKey != null && conditionList != null ) {
			cndKeyNo = table.getColumnNo( conditionKey );		// 不正指定はエラー
		}

		List<Integer> list = new ArrayList<Integer>();
		boolean okFlag = false;
		for( int i = 0; i < rowNo.length; i++ ) {
			int row = rowNo[i] ;
			String[] values = getTableModelData( row, clmNo );
			// 4.2.0.1 (2008/03/27) 条件指定がされている場合に、
			// Listに含まれない場合は、実行されない。
			// 4.2.1.0 (2008/04/11) 厳密に処理します。
			if( cndKeyNo >= 0 && conditionList.indexOf( table.getValue( row,cndKeyNo ) ) < 0 ) {
				String conVal = "|" + table.getValue( row,cndKeyNo ) + "|" ;
				if( conditionList.indexOf( conVal ) < 0 ) { continue; }
			}

			if( isSql ) {
//				okFlag = checkSql( query, names, values, table.getModifyType( row ), row );
				okFlag = checkSql( query, manager, values, row, table.getModifyType( row ) );
			}
			else {
				String jsStr = format.getFormatString( row, "\"" );
//				okFlag = checkJs( jsStr, names, values, row, jsEngine );
				okFlag = checkJs( jsStr, manager, values, row, jsEngine );
			}

			if( errRemove && okFlag ) {
				list.add( row );
			}
		}

		if( errRemove ) {
			Integer[] in = list.toArray( new Integer[list.size()] );
			int[] newRowNo = new int[in.length];
			for( int i = 0; i < in.length; i++ ) {
				newRowNo[i] = in[i].intValue();
			}
			setParameterRows( newRowNo );
		}
	}

	/**
	 * DBテーブルモデルの各行にユニークキーのチェックを行います。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規作成
	 *
	 * @param manager ErrMessageManager オブジェクト
	 */
	private void checkUnique( final ErrMessageManager manager ) {
		int[] rowNo = getParameterRows();
		if( rowNo.length == 0 ) { return; }

		int[] clmNo = new int[uniqCheckClms.length];
		for( int i=0; i<clmNo.length; i++ ) {
			clmNo[i] = table.getColumnNo( uniqCheckClms[i] );
		}

		manager.setClmNos( clmNo );

		List<Integer> list = new ArrayList<Integer>();
		Map<String,Integer> map = new HashMap<String,Integer>();
		for( int i = 0; i < rowNo.length; i++ ) {
			int row = rowNo[i] ;
			String[] values = getTableModelData( row, clmNo );
			String key = StringUtil.array2line( values, " + " );
			 
			if( map.get( key ) == null ) {
				map.put( key, 1 );
				if( errRemove ) {
					list.add( row );
				}
			}
			else {
				// ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
				id = ( msg == null ? "ERR0027" : msg );
				manager.addMessage( row, id, values );
			}
		}

		if( errRemove ) {
			Integer[] in = list.toArray( new Integer[list.size()] );
			int[] newRowNo = new int[in.length];
			for( int i = 0; i < in.length; i++ ) {
				newRowNo[i] = in[i].intValue();
			}
			setParameterRows( newRowNo );
		}
	}

	/**
	 * msgParamKeys 属性に対して、実際の値を割り当てます。
	 * 
	 * ここでは、@KEY 、@TBL 、{#XX} 、{@XX} に対して、実際の値と置き換えられます。
	 * @VAL と、[XX] は、DBTableModel の行番号が必要なので、個別処理で判断します。
	 * msgParamKeys が、未指定(null) の場合は、null を返します。
	 *
	 * @param names カラム名
	 * @param fromLbl テーブル名
	 * 
	 * @og.rev 4.2.0.1 (2008/03/27) 新規作成
	 */
//	private String[] makeMsgParamVals( final String names,final String fromLbl ) {
//		if( msgParamKeys == null ) { return null; }
//
//		int size = msgParamKeys.length;
//		String[] args = new String[size] ;
//		ResourceManager resource = getResource();
//
//		for( int i=0; i<size; i++ ) {
//			String key = msgParamKeys[i] ;
//			if(		 "@KEY".equals( key ) ) { args[i] = names;	}
//			else if( "@VAL".equals( key ) ) { args[i] = key;	}	// そのまま返す
//			else if( "@TBL".equals( key ) ) { args[i] = fromLbl;}
//			// 4.2.1.0 (2008/04/11) リクエスト変数対応
//			else if( "[".startsWith( key ) && "]".endsWith( key )  ) {
//				args[i] = key;										// そのまま返す
//			}
//			else if( "{#".startsWith( key ) && "}".endsWith( key )  ) {
//				args[i] = resource.getLabel( key.substrng( 2,key.length()-1 ));
//			}
//			else {
//				args[i] = getRequestParameter( key );
//			}
//		}
//
//		return args ;
//	}

	/**
	 * 【TAG】(通常は使いません)結果をDBTableModelに書き込んで、sessionに登録するときのキーを指定します。
	 *
	 * @og.tag 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @param	id sessionに登録する時の ID
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ), tableId );
	}

	/**
	 * 【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します。
	 *
	 * @og.tag Queryオブジェクトを作成する時のDB接続IDを指定します。
	 *
	 * @param	id データベース接続ID
	 */
	public void setDbid( final String id ) {
		dbid = nval( getRequestParameter( id ), dbid );
	}

	/**
	 * 【TAG】コマンド(NEW or ENTRY)をセットします。
	 *
	 * @og.tag
	 * コマンドは,HTMLから（get/post)指定されますので,CMD_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 *
	 * @param	cmd コマンド（public static final 宣言されている文字列)
	 * @see		<a href="{@docRoot}/constant-values.html#org.opengion.hayabusa.taglib.DataCheckTag.CMD_NEW">コマンド定数</a>
	 */
	public void setCommand( final String cmd ) {
		String cmd2 = getRequestParameter( cmd );
		if( cmd2 != null && cmd2.length() > 0 ) {
			command = cmd2.toUpperCase( Locale.JAPAN );
		}
	}

	/**
	 * 【TAG】データベースのチェック方法(auto/true/false/one/notuse)を指定します(初期値:｢auto:自動｣)。
	 *
	 * @og.tag
	 * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
	 * の値は、いずれの場合も、成立時は、正常とみなします。
	 * ｢auto:自動｣は、DBTableModeleのmodifyType(A,C,D)に応じて、チェックします。
	 * A,C,D は、entryタグにコマンドを渡してデータを作成したときに、内部で作成されます。
	 * (command="NEW"の場合は、trueと同じ動きになります。)
	 * notuse は、チェックを行いません。これは、このタグを共有使用する場合に、外部で
	 * チェックを行うかどうかを指定できるようにするために使用します。
	 * （｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。）
	 * 初期値は、｢auto:自動｣です。
	 *
	 * @param	ext チェック方法（｢auto:自動｣、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、｢notuse:チェックしない｣）
	 */
	public void setExist( final String ext ) {
		exist = nval( getRequestParameter( ext ), exist );
		if( !"auto".equalsIgnoreCase( exist )
				&& !"true".equalsIgnoreCase( exist )
				&& !"false".equalsIgnoreCase( exist )
				&& !"one".equalsIgnoreCase( exist )
				&& !"notuse".equalsIgnoreCase( exist ) ) {
			String errMsg = "exist 属性は、（auto,true,false,one,notuse）を指定してください。 [" + exist + "]" + HybsSystem.CR;
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】エラー時の選択行を取り除いて継続処理を行うかどうかを指定します(初期値:false)。
	 *
	 * @og.tag
	 * exist 属性に指定された 、｢true:存在する｣、｢false:存在しない｣、｢one:ひとつ以下｣、
	 * に対して、エラーが発生した選択行番号を、取り除いて以下の処理を継続するかどうかを
	 * 指定します。
	 * true に設定した場合は、エラーデータを削除し、継続処理を行うことができます。
	 * flase の場合は、エラーデータを表示して、継続処理を停止します。
	 * 初期値は、｢false:エラー時停止｣です。
	 *
	 * @param	flag エラー時の継続処理(true:エラー行番号を取り除き継続処理/false:エラー時停止)
	 */
	public void setErrRemove( final String flag ) {
		errRemove = nval( getRequestParameter( flag ), errRemove );
	}

	/**
	 * 【TAG】メッセージIDを指定します。
	 *
	 * @og.tag メッセージIDを指定します。
	 * 各処理に応じた初期設定のメッセージIDは、以下の通りです。
	 *   exist="true"   ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
	 *   exist="false"  ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
	 *   exist="one"    ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
	 *   JavaScript     ERR0030=入力したデータが不正です。key={0} value={1} 形式={2}
	 * 引数のパラメータには、通常、チェックに使用した実データが、DBTableModel から取得されます。
	 * 引数を変更する場合は、msgParamKeys を使用してください。
	 *
	 * @param id メッセージID
	 * @see    #setMsgParamKeys( String )
	 */
	public void setMsg( final String id ) {
		msg = nval( getRequestParameter( id ), msg );
	}

	/**
	 * 【TAG】メッセージリソースの引数をカンマ区切りで指定します。
	 *
	 * @og.tag
	 * メッセージリソースのキーをカンマ区切りで指定することで、設定します。
	 * メッセージに引数( {0},{1} など ) がある場合、ここで指定した値を
	 * 順番に、{0},{1},{2}・・・ に当てはめていきます。
	 * キーワードは、カンマ区切りで指定し、それを分解後、ラベルリソースで
	 * リソース変換を行います。(つまり、記述された値そのものでは在りません)
	 * PL/SQL では、"{#PN}" などと指定していた分は、同様に "PN" と指定しです。
	 * 内部的に、where 条件に指定されたキーと値は、@KEY と @VAL に、
	 * from と where の間の文字列は、@TBL に対応付けられます。
	 * {@XXXX} 変数も使用できます。実データの値を取出したい場合は、[PN]と
	 * すれば、DBTableModel の PN の値を取出します。
	 * なにも指定しない場合は、キー={0} 、値={1}、from={2} です。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) 新規追加
	 *
	 * @param keys メッセージリソースのキー(CSV)
	 * @see    #setMsg( String )
	 */
	public void setMsgParamKeys( final String keys ) {
		msgParamKeys = getCSVParameter( keys );
	}

	/**
	 * 【TAG】このチェックを行う、SQLタイプ を指定します。
	 *
	 * @og.tag
	 * SQLタイプは、INSERT,COPY,UPDATE,MODIFY,DELETE などの記号を指定します。
	 * 一般には、result 画面から update 画面へ遷移するときの、command と
	 * 同じにしておけばよいでしょう。
	 * これは、execType とマッチした場合のみ、このチェックが処理されます。
	 * 簡易 equals タグの代役に使用できます。
	 * なにも指定しない場合は、チェックは実行されます。
	 *
	 * @og.rev 4.1.2.0 (2008/03/12) 新規追加
	 *
	 * @param	type このチェックを行う、SQLタイプ
	 */
	public void setSqlType( final String type ) {
		sqlType = nval( getRequestParameter( type ),sqlType );
	}

	/**
	 * 【TAG】このチェックを行う、実行タイプ を指定します。
	 *
	 * @og.tag
	 * 実行タイプは、sqlType とマッチした場合のみ、このチェックが処理されます。
	 * 簡易 equals タグの代役に使用できます。
	 * execType は、複数指定が可能です。単純な文字列マッチで、sqlType を
	 * 含めば、実行されます。
	 * 例えば、sqlType={&#064;sqlType} execType="INSERT|COPY" とすれば、
	 * sqlType に、INSERT または、COPY が登録された場合にチェックが掛かります。
	 * なにも指定しない場合は、チェックは実行されます。
	 *
	 * @og.rev 4.1.2.0 (2008/03/12) 新規追加
	 *
	 * @param	type このチェックを行う、実行タイプ
	 */
	public void setExecType( final String type ) {
		execType = nval( getRequestParameter( type ),execType );
	}

	/**
	 * 【TAG】条件判定するカラムＩＤを指定します。
	 *
	 * @og.tag
	 * 指定のカラムＩＤの値と、conditionList の値を比較して、
	 * 存在する場合は、check処理を実行します。
	 * この処理が有効なのは、command="ENTRY" の場合のみです。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) 新規追加
	 *
	 * @param	key String
	 * @see		#setConditionList( String )
	 */
	public void setConditionKey( final String key ) {
		conditionKey = nval( getRequestParameter( key ),null ) ;
	}
	
	/**
	 * 【TAG】条件判定する値のリストを、"|"で区切って登録します(初期値は、無条件)
	 *
	 * @og.tag
	 * conditionKey とペアで指定します。ここには、カラムの設定値のリストを
	 * 指定することで、複数条件(ＯＲ結合)での比較を行い、リストにカラム値が
	 * 存在する場合のみ、check処理を実行します。
	 * この処理が有効なのは、command="ENTRY" の場合のみです。
	 * 設定しない場合は、無条件に実行します。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) 新規追加
	 *
	 * @param	list String
	 * @see		#setConditionKey( String )
	 */
	public void setConditionList( final String list ) {
		conditionList = nval( getRequestParameter( list ),null ) ;
		if( conditionList != null ) {
			conditionList = "|" + conditionList + "|" ;
		}
	}

	/**
	 * 【TAG】指定されたキーに従って、メモリ上のテーブルに対してユニークキーチェックを行います。
	 *
	 * @og.tag
	 * ユニークキーチェックを行うキーを指定します。ここで、指定されたキーに対して、
	 * DBTableModelの値をチェックし、全てのキーに同じ値となっている行が存在すればエラーとなります。
	 * このチェックは、command="ENTRY"の場合のみ有効です。
	 * また、このチェックは他のチェック(DB存在チェックなど)と同時に処理することはできません。
	 * キーが指定され手いる場合は、ボディ部分に記述されている定義は無視されます。
	 * errRemoveの属性がtrueに指定されている場合、重複行は、DBTableModelの並び順から見て、
	 * 最初の行のみ処理され、2つめ以降の重複行は無視されます。
	 * なお、キーはカンマ区切りで複数指定が可能です。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規追加
	 *
	 * @param	clm String
	 */
	public void setUniqCheckClms( final String clm ) {
		String tmp = nval( getRequestParameter( clm ),null );
		uniqCheckClms = StringUtil.csv2Array( tmp );
	}

	/**
	 * 指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
	 *
	 * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
	 * 処理の対象とします。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) row と clm を入れ替えます。(他とあわせます)
	 *
	 * @param	row   行番号
	 * @param	clmNo カラムNo配列
	 * @return	String[] 行番号とカラムNo配列に対応した、値の配列
	 */
//	private String[] getTableModelData( final int[] clmNo, final int row ) {
	private String[] getTableModelData( final int row, final int[] clmNo ) {
		String[] values = new String[clmNo.length];
		for( int i = 0; i < values.length; i++ ) {
			values[i] = table.getValue( row, clmNo[i] );
		}
		return values;
	}

	/**
	 * 指定の行番号の、カラムNo配列(int[])に対応したカラム名の配列を返します。
	 *
	 * @og.rev 4.3.1.1 (2008/08/23) 廃止
	 *
	 * @param	clmNo カラムNo配列
	 * @return	String[] カラムNo配列に対応した、カラム名の配列
	 */
//	private String[] getTableModelClm( final int[] clmNo ) {
//		String[] clm = new String[clmNo.length];
//		for( int i = 0; i < clm.length; i++ ) {
//			clm[i] = table.getColumnName( clmNo[i] );
//		}
//		return clm;
//	}

	/**
	 * カラムNo配列(int[])に対応したカラム名のメッセージリソース文字列を返します。
	 *
	 * @og.rev 4.3.1.1 (2008/08/23) ErrMessageManager 内部クラスへ移動
	 *
	 * @param	clmNo カラムNo配列
	 * @return	String カラムNo配列に対応した、カラム名のメッセージリソース
	 */
//	private String getKeysLabel( final int[] clmNo ) {
//		ResourceManager resource = getResource();
//
//		StringBuilder buf = new StringBuilder();
//		if( clmNo.length > 0 ) {
//			String key = table.getColumnName( clmNo[0] );
//			buf.append( resource.getLabel( key ) );
//			for( int i=1; i<clmNo.length; i++ ) {
//				key = table.getColumnName( clmNo[i] );
//				buf.append( "," ).append( resource.getLabel( key ) );
//			}
//		}
//
//		return buf.toString();
//	}

	/**
	 * ErrMessage を管理している メソッド集約型内部クラス
	 *
	 * 繰返し処理部と、固定部が混在したエラーメッセージで、固定部を先に処理し、
	 * 繰返し部は、必要時に処理するようにしました。
	 * また、実際にエラーが発生して必要になるまで、実行遅延させます。
	 *
	 * @og.rev 4.2.1.0 (2008/04/11) 新規追加
	 * @og.rev 4.3.0.0 (2008/07/24) クラス宣言をstatic化
	 */
	private static final class ErrMessageManager {
		// 引数として初期設定される変数
		private String title     = null;
		private String from      = null;
		private String[] msgKeys = null;
		private ResourceManager resource = null;
		private DBTableModel    table    = null;
		private int[]  clmNo    = null;

		// 内部引数として処理されたキャッシュ値
		private ErrorMessage errMessage  = null;
		private String names     = null;
		private String fromLbl   = null;
		private String[] msgVals = null;

		private boolean isFirst  = true;		// 初期化されていない=true

		/**
		 * ErrMessage のタイトルを設定します。
		 *
		 * @param title String タイトル
		 */
		public void setTitle( final String title ) { this.title = title; }

		/**
		 * 処理対象のテーブル名を設定します。
		 *
		 * @param from String テーブル名
		 */
		public void setFrom( final String from ) { this.from = from; }

		/**
		 * 処理対象のテーブルオブジェクトを設定します。
		 *
		 * @param table DBTableModel オブジェクト
		 */
		public void setDBTableModel( final DBTableModel table ) { this.table = table; }

		/**
		 * ResourceManagerオブジェクトを設定します。
		 *
		 * @param resource ResourceManager オブジェクト
		 */
		public void setResourceManager( final ResourceManager resource ) { this.resource = resource; }

		/**
		 * msgParamKeys 属性の配列を設定します。
		 *
		 * @param msgKeys String[] msgParamKeys 属性の配列
		 */
		public void setParamKeys( final String[] msgKeys ) { this.msgKeys = msgKeys; }

		/**
		 * カラム名列を設定します。
		 *
		 * @param clmNo String カラム名
		 */
		public void setClmNos( final int[] clmNo ) { this.clmNo = clmNo ; }

		/**
		 * 初期処理を行います。
		 * エラー処理は、エラー時のみ実行する為、必要のない場合は、処理が不要です。
		 * 最初のエラー出力までは、内部オブジェクトの構築処理を行いません。
		 * ２回目以降は、内部変数にキャッシュされた変換値を利用して、高速化します。
		 *
		 * @og.rev 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
		 */
		private void firstExecute() {
			errMessage = new ErrorMessage( title );

			// テーブル(from) をキーにラベルリソースから値を取得します。
			// 4.2.3.2 (2008/06/20) from が、null なら、なにもしない。
			if( from != null ) {
				fromLbl  = resource.getLabel( from );
			}

			// カラム番号配列から、カラム名のラベルリソース情報のCSV文字列を作成します。
			names = getKeysLabel( clmNo );

			if( msgKeys != null && msgKeys.length > 0 ) {
				int size = msgKeys.length;
				msgVals = new String[size] ;

				for( int i=0; i<size; i++ ) {
					String key = msgKeys[i] ;
					if( key != null ) {
						if(		 "@KEY".equals( key ) ) { msgVals[i] = names;	}
						else if( "@TBL".equals( key ) ) { msgVals[i] = fromLbl;}
						else if( key.startsWith( "{#" ) && key.endsWith( "}" )  ) {
							msgVals[i] = resource.getLabel( key.substring( 2,key.length()-1 ));
						}
						else {
							msgVals[i] = key;
						}
					}
				}
			}
		}

		/**
		 * カラムNo配列(int[])に対応したカラム名のメッセージリソース文字列を返します。
		 *
		 * @param	clmNo カラムNo配列
		 * @return	String カラムNo配列に対応した、カラム名のメッセージリソース
		 */
		private String getKeysLabel( final int[] clmNo ) {
			StringBuilder buf = new StringBuilder();
			if( table != null && clmNo.length > 0 ) {
				String key = table.getColumnName( clmNo[0] );
				buf.append( resource.getLabel( key ) );
				for( int i=1; i<clmNo.length; i++ ) {
					key = table.getColumnName( clmNo[i] );
					buf.append( "," ).append( resource.getLabel( key ) );
				}
			}

			return buf.toString();
		}

		/**
		 * カラム名列を設定します。
		 * 
		 * @og.rev 4.3.5.7 (2008/03/22) エラーメッセージの行番号を実際の行番号と一致させる。
		 *
		 * @param row int カラム名
		 * @param id String カラム名
		 * @param values String[] 指定の行に対する値
		 */
		public void addMessage( final int row, final String id, final String[] values ) {
			if( isFirst ) { firstExecute(); isFirst = false; }

			String vals = StringUtil.array2csv( values );
			if( msgVals == null ) {
//				errMessage.addMessage( row, ErrorMessage.NG, id, names, vals, fromLbl );
				errMessage.addMessage( row + 1, ErrorMessage.NG, id, names, vals, fromLbl );
			}
			else {
				int size = msgKeys.length;
				String[] args = new String[size] ;

				for( int i=0; i<size; i++ ) {
					String key = msgVals[i] ;
					if( key != null ) {
						if(		 "@VAL".equals( key ) ) { args[i] = vals; }
						else if( key.startsWith( "[" ) && key.endsWith( "]" )  ) {
							if( table != null ) {
								args[i] = table.getValue( row,key.substring( 1,key.length()-1 ) );
							}
						}
						else {
							args[i] = key;
						}
					}
				}
//				errMessage.addMessage( row, ErrorMessage.NG, id, args );
				errMessage.addMessage( row + 1, ErrorMessage.NG, id, args );
			}
		}

		/**
		 * ErrorMessageオブジェクトを返します。
		 *
		 * @return ErrorMessage オブジェクト
		 */
		public ErrorMessage getErrMessage() { return errMessage; }
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectOutputStream
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 4.0.0 (2006/09/31) 新規追加
	 * @serialData
	 *
	 * @param strm ObjectInputStream
	 * @see #release2()
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException, ClassNotFoundException {
		strm.defaultReadObject();
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title(this.getClass().getName() )
		.println( "VERSION", VERSION )
		.println( "tableId", tableId )
		.println( "dbid", dbid )
		.println( "command", command )
		.println( "exist", exist )
		.println( "msg", msg )
		.println( "Other...", getAttributes().getAttribute() ).fixForm().toString();
	}
}
