/*
 * 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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;											// 6.4.3.4 (2016/03/11)

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

import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.db.Transaction;
import org.opengion.fukurou.model.Formatter;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)
import org.opengion.fukurou.util.ArraySet;						// 6.4.3.4 (2016/03/11)
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.resource.ResourceManager;

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

/**
 * 画面で入力されたデータのチェックを行うためのタグです。
 *
 * commandがNEWの場合は検索条件等のリクエストパラメータに対してチェックを行います。
 * commandがENTRYの場合は、登録時のDBテーブルモデルに対するチェックを行います。
 * (値の取得は、先に選択された行のみについて、実行されます。)
 *
 * チェックを行うための定義は、SQL文 又は JavaScriptの式が記述可能です。
 * これらの式はタグのボディー部分に記述します。
 *
 * SQL文によりチェックを行う場合は、必ず件数が返されるように記述して下さい(select count(*) ･･･ 等)
 * このSQL文で取得された件数とexistの属性値とを照合しチェックを行います。
 * いずれの場合も、成立時は、正常とみなします。
 * (｢true:存在する｣ には、データが存在した場合に、ＯＫで、なければエラーです。)
 *
 * JavaScript式を記述する場合は、必ずtrue or falseを返す式を指定して下さい。
 * この式を評価した結果falseが返される場合は、エラーとみなします。
 * 式に不等号等を使用する場合は、CDATAセクションで囲うようにして下さい。
 *
 * また、いずれのチェック方法の場合でも、引数部に[カラム名]を用いたHybs拡張SQL文を
 * 指定することが可能です。
 * メッセージIDの{0},{1}にはそれぞれ[カラム名]指定されたカラム名及びデータがCSV形式で
 * 自動的に設定されます。
 *
 * ※ このタグは、Transaction タグの対象です。
 *
 * @og.formSample
 * <pre>
 * ●形式：
 *       ・&lt;og:dataCheck
 *                    command       = "{&#064;command}"
 *                    exist         = "[auto|true|false|one|notuse]"
 *                    errRemove     = "[true|false]"
 *                    lbl           = "{&#064;lbl}"
 *                    lblParamKeys  = "ZY03"      : メッセージリソースのキーをCSV形式で指定。{2} 以降にセット
 *                    sqlType       = "{&#064;sqlType}"
 *                    execType      = "INSERT|COPY|UPDATE|MODIFY|DELETE"  : sqlType を含む場合、実行
 *                    conditionKey  = "FGJ"        : 条件判定するカラムＩＤを指定(初期値は columnId )
 *                    conditionList = "0|1|8|9"    : 条件判定する値のリストを、"|"で区切って登録(初期値は、無条件)
 *                    uniqCheckClms = "CLM,LANG"   : DBTableModel内でのユニークキーチェックを行うためのカラム
 *         &gt;
 *
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *         (SQL文 又は JavaScript式)
 *       ：なし( from属性、where属性を使用して、SQL文を内部で作成します)
 *
 * ●Tag定義：
 *   &lt;og:dataCheck
 *       command            【TAG】コマンド (NEW or ENTRY)をセットします
 *       exist              【TAG】データベースのチェック方法[auto/true/false/one/notuse]を指定します(初期値:auto[自動])
 *       tableId            【TAG】(通常は使いません)結果をDBTableModelに書き込んで、sessionに登録するときのキーを指定します
 *       dbid               【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します(初期値:null)
 *       lbl                【TAG】ラベルリソースIDを指定します
 *       lblParamKeys       【TAG】ラベルリソースの引数をCSV形式で指定します
 *       errRemove          【TAG】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値:false)
 *       sqlType            【TAG】このチェックを行う、SQLタイプ を指定します
 *       execType           【TAG】このチェックを行う、実行タイプ を指定します
 *       conditionKey       【TAG】条件判定するカラムＩＤを指定します
 *       conditionList      【TAG】条件判定する値のリストを、"|"で区切って登録します(初期値:無条件)
 *       uniqCheckClms      【TAG】指定されたキーに従って、メモリ上のテーブルに対してユニークキーチェックを行います
 *       beforeErrorJsp     【TAG】エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します
 *       afterErrorJsp      【TAG】エラーが発生した際に、エラーメッセージの表示後にincludeするJSPを指定します
 *       selectedAll        【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)
 *  X    msg                【廃止】メッセージIDを指定します(lbl 属性を使用してください)
 *  X    msgParamKeys       【廃止】メッセージリソースの引数をCSV形式で指定します(lblParamKeys 属性を使用してください)
 *       from               【TAG】tableExist タグ廃止に伴う、簡易機能追加。チェックするデータベース名(from 句)を指定します。
 *       where              【TAG】tableExist タグ廃止に伴う、簡易機能追加。チェックする検索条件(where句)を指定します。
 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:dataCheck&gt;
 *
 * ●使用例
 *       ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    exist     = "true"
 *                    lbl       = "MSG0001"
 *         &gt;
 *             select count(*) from GEA03 where clm = [CLM]
 *         &lt;/og:dataCheck&gt;
 *
 *          ・exist 属性の値に応じて、チェック方法が異なります。
 *            [ auto , true , false , one , notuse が指定できます。]
 *
 *       ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    lbl       = "MSG0001"
 *         &gt;
 *           &lt;![CDATA[
 *             [DYSTART] &lt; [DY] &amp;&amp; [DY] &lt; [DYEND]
 *           ]]&gt;
 *         &lt;/og:dataCheck&gt;
 *
 *         ・&lt;og:dataCheck
 *                    command   = "ENTRY"
 *                    lbl       = "MSG0001"
 *         &gt;
 *           &lt;![CDATA[
 *             [GOKEI] &lt; [TANKA] * [RITU]
 *           ]]&gt;
 *         &lt;/og:dataCheck&gt;
 *
 *    ※ og:tableExist タグが廃止されました。og:dataCheckタグで置き換えてください。
 *       ・&lt;og:tableExist
 *                    command = "{&#064;command}"
 *                    names   = "USERID,SYSTEM_ID"
 *                    from    = "GE10"
 *                    where   = "USERID=? AND SYSTEM_ID=?"
 *                    exist   = "true"
 *         /&gt;
 * 
 *        ⇒
 *       ・&lt;og:dataCheck
 *                    command = "{&#064;command}"
 *                    exist   = "true"
 *                    from    = "GE10"
 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"
 *         /&gt;
 * 
 *       ・&lt;og:tableExist
 *                    command = "{&#064;command}"
 *                    from    = "GE10"
 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"  /&gt;
 *        ⇒
 *       ・&lt;og:dataCheck
 *                    command = "{&#064;command}"
 *                    from    = "GE10"
 *                    where   = "USERID=[USERID] AND SYSTEM_ID=[SYSTEM_ID]"  /&gt;
 *         /&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 {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.3.4 (2016/03/11)" ;
	private static final long serialVersionUID = 643420160311L ;

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

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

	// 6.4.3.4 (2016/03/11) String配列 から、Setに置き換えます。
	private static final Set<String> COMMAND_SET = new ArraySet<>( CMD_ENTRY, CMD_NEW );

	/** 内部変数 */
	private transient DBTableModel	table		;
	private transient boolean		isSql		;
	private transient boolean		isUniqCheck	;		// 4.3.4.0 (2008/12/01) 追加
	private transient ScriptEngine	jsEngine	;
	private transient String		bodyStr		;		// 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			;
	private String		lbl				;
	private String[]	lblParamKeys	;			// 4.2.0.1 (2008/03/27)
	private boolean		errRemove		;
	private String		sqlType			;			// INSERT,COPY,UPDATE,MODIFY,DELETE
	private String		execType		;			// INSERT,COPY,UPDATE,MODIFY,DELETE

	private String		conditionKey	;			// 4.2.0.1 (2008/03/27)
	private String		conditionList	;			// 4.2.0.1 (2008/03/27)
	private String		from			;			// 4.2.0.1 (2008/03/27)
	private String		where			;			// 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加
	private String[]	uniqCheckClms	;			// 4.3.4.0 (2008/12/01)

	private String		beforeErrorJsp	;			// 5.1.9.0 (2010/08/01)
	private String		afterErrorJsp	;			// 5.1.9.0 (2010/08/01)
	private boolean		selectedAll		;			// 5.1.9.0 (2010/08/01)

	private boolean		isExec			;			// 6.3.4.0 (2015/08/01) パラメータではなく毎回設定します。

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public DataCheckTag() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 4.1.1.0 (2008/02/22) 新規作成
	 * @og.rev 4.1.2.0 (2008/03/12) sqlType,execType 判定
	 * @og.rev 6.3.4.0 (2015/08/01) caseKey,caseVal,caseNN,caseNull,caseIf 属性対応
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		isExec = useTag() && ( sqlType == null || execType == null || execType.indexOf( sqlType ) >= 0 ) ;

		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		return isExec
					? EVAL_BODY_BUFFERED		// Body を評価する
					: SKIP_BODY ;				// Body を評価しない

	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規追加
	 * @og.rev 6.3.1.1 (2015/07/10) BodyString,BodyRawStringは、CommonTagSupport で、trim() します。
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doAfterBody() {
		bodyStr = getBodyString();
		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()で行う。
	 * @og.rev 5.1.9.0 (2010/08/01) エラーメッセージの表示前後にincludeするJSPを指定できるようにする。
	 * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
	 * @og.rev 5.3.7.0 (2011/07/01) TransactionReal の引数変更 、Transaction対応で、close処理を入れる。
	 * @og.rev 6.3.6.1 (2015/08/28) Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();
		int rtnCode = EVAL_PAGE;

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

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

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

			// 6.3.6.1 (2015/08/28) Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
			try( final Transaction tran = getTransaction() ) {
				// command="NEW"の場合
				if( CMD_NEW.equals( command ) ) {
					if( isSql ) {
						checkSql( bodyStr, manager, null, 0, DBTableModel.UPDATE_TYPE, tran );		// 5.1.9.0 (2010/08/01)
					}
					else {
						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, tran );		// 5.1.9.0 (2010/08/01)
						}
					}
					else {
						System.out.println( "DBTableModel doesn't exist!! need this when command=\"ENTRY\"" );
					}
				}
				tran.commit();				// 6.3.6.1 (2015/08/28)
			}

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

				// 5.1.9.0 (2010/08/01) エラーメッセージの表示前にincludeするJSPを指定
				if( beforeErrorJsp != null && beforeErrorJsp.length() > 0 ) {
					includeJsp( beforeErrorJsp );
				}

				jspPrint( TaglibUtil.makeHTMLErrorTable( errMessage, getResource() ) );

				// 5.1.9.0 (2010/08/01) エラーメッセージの表示後にincludeするJSPを指定
				if( afterErrorJsp != null && afterErrorJsp.length() > 0 ) {
					includeJsp( afterErrorJsp );
				}
			}
		}

		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 追加
	 * @og.rev 5.1.9.0 (2010/08/01) beforeErrorJsp , afterErrorJsp, selectedAll 追加
	 * @og.rev 5.7.6.2 (2014/05/16) where 追加。tableExist タグに伴う便利機能追加
	 * @og.rev 6.3.4.0 (2015/08/01) isExec は、パラメータではなく、ローカル変数。
	 */
	@Override
	protected void release2() {
		super.release2();
		tableId 		= HybsSystem.TBL_MDL_KEY;
		dbid 			= null;
		command 		= CMD_ENTRY;
		table 			= null;
		exist 			= "auto";
		errRemove 		= false;
		lbl 			= null;
		lblParamKeys	= 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
		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)
		where			= null;		// 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加
		bodyStr			= null;		// 4.3.4.0 (2008/12/01))
		uniqCheckClms	= null;		// 4.3.4.0 (2008/12/01)
		beforeErrorJsp	= null;		// 5.1.9.0 (2010/08/01)
		afterErrorJsp	= null;		// 5.1.9.0 (2010/08/01)
		selectedAll		= false;	// 5.1.9.0 (2010/08/01)
	}

	/**
	 * 引数及びボディー部分のチェックを行い、処理のタイプを判別します。
	 *
	 * @og.rev 5.5.8.0 (2012/11/01) タイプ判別変更
	 * @og.rev 5.6.1.1 (2013/02/08) FROM 部の切り出し位置修正
	 * @og.rev 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加。from属性とwhere属性追加
	 */
	private void checkParam() {
		isUniqCheck = uniqCheckClms != null && uniqCheckClms.length > 0 ;
		if( isUniqCheck ) {
			if( !CMD_ENTRY.equals( command ) ) {
				final String errMsg = "ユニークキーチェックは、command=\"ENTRY\"の場合のみ使用可能です。"
							+ " command=" + command ;		// 5.1.8.0 (2010/07/01) errMsg 修正
				throw new HybsSystemException( errMsg );
			}
		}
		// 5.7.6.2 (2014/05/16) tableExist タグに伴う便利機能追加。from属性とwhere属性追加
		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		else if( from == null ) {
			if( bodyStr == null || bodyStr.isEmpty() ) {
				final String errMsg = "Body部分にチェック定義を記述して下さい。";
				throw new HybsSystemException( errMsg );
			}

			// SQLチェックかJavaScriptによるチェックかの判定
			final String query = bodyStr.toUpperCase( Locale.JAPAN );		// 4.2.0.1 (2008/03/27)
			if( query.indexOf( "SELECT" ) == 0 ) { // 5.5.8.0 (2012/11/01) 先頭に限定する。(trim済のため)
				isSql = true;
				final int st = query.indexOf( "FROM" ) ;
				final int ed = query.indexOf( "WHERE" ) ;
				if( st > 0 && st < ed ) {
					from = query.substring( st+"FROM".length(),ed ).trim();		// 5.6.1.1 (2013/02/08)
				}
			}
			else {
				jsEngine = new ScriptEngineManager().getEngineByName( "JavaScript" );
			}
		}
		else {
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
				.append( "SELECT count(*) FROM " ).append( from );
			if( where != null ) { buf.append( " WHERE " ).append( where ); }
			bodyStr = buf.toString();
			isSql = true;
		}

	}

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

		final int cnt = DBUtil.dbExist( str, values, tran, dbid );			// 5.1.9.0 (2010/08/01)

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

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

		}
		return okFlag;
	}

	/**
	 * JavaScriptの式を実行します。
	 * 実行した結果がboolean型でない場合はエラーとなります。
	 *
	 * @param str  実行するSQL文
	 * @param	manager	オブジェクト
	 * @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 ErrMessageManager manager, final String[] values
							, final int row, final ScriptEngine engine ) {
		// JavaScriptエンジンによる評価
		Object obj = null;
		try {
			obj = engine.eval( str );
		}
		catch( final ScriptException ex ) {
			final String errMsg = "JavaScript式のパースに失敗しました。[" + str + "]";
			throw new HybsSystemException( errMsg , ex );
		}

		// 返り値がBoolean型かチェック
		boolean okFlag = false;
		// 4.2.0.1 (2008/03/27) instanceof に変更
		if( obj instanceof Boolean ) {	// 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
			okFlag = ((Boolean)obj).booleanValue();
		}
		else {
			final String errMsg = "JavaScript式には true 若しくは false が返るように設定して下さい"
						+ " Object=" + obj ;			// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new HybsSystemException( errMsg );
		}

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

			manager.addMessage( row, id, values );
		}

		return okFlag;
	}

	/**
	 * DBテーブルモデルの各行に対してデータチェックを行います。
	 *
	 * @param str		チェック対象の文字列
	 * @param manager	ErrMessageManagerオブジェクト
	 * @param tran		トランザクションオブジェクト
	 *
	 * @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対応
	 * @og.rev 5.1.9.0 (2010/08/01) Transaction 対応します。
	 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
	 */
	private void checkRows( final String str, final ErrMessageManager manager, final Transaction tran ) {

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

		final Formatter format = new Formatter( table,str );		// 6.4.3.4 (2016/03/11)
		final int[] clmNo = format.getClmNos();
		// 4.2.0.1 (2008/03/27) カラム名のメッセージリソース文字列を作成します。
		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 );		// 不正指定はエラー
		}

		final List<Integer> list = new ArrayList<>();
		boolean okFlag = false;
		for( int i=0; i<rowNo.length; i++ ) {
			final int row = rowNo[i] ;
			final 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 ) {
				final String conVal = "|" + table.getValue( row,cndKeyNo ) + "|" ;
				if( conditionList.indexOf( conVal ) < 0 ) { continue; }
			}

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

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

		if( errRemove ) {
			final 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) 新規作成
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @param manager ErrMessageManagerオブジェクト
	 */
	private void checkUnique( final ErrMessageManager manager ) {
		final int[] rowNo = getParameterRows();
		if( rowNo.length == 0 ) { return; }

		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( uniqCheckClms == null ) { return; }

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

		manager.setClmNos( clmNo );

		final List<Integer> list = new ArrayList<>();
		final Map<String,Integer> map = new HashMap<>();
		for( int i=0; i<rowNo.length; i++ ) {
			final int row = rowNo[i] ;
			final String[] values = getTableModelData( row, clmNo );
			final String key = StringUtil.array2line( values, " + " );

			if( map.get( key ) == null ) {
				map.put( key, 1 );
				if( errRemove ) {
					list.add( row );
				}
			}
			else {
				// ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
				id = ( lbl == null ? "ERR0027" : lbl );
				manager.addMessage( row, id, values );
			}
		}

		if( errRemove ) {
			final 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 );
		}
	}

	/**
	 * 【TAG】(通常は使いません)結果のDBTableModelを、sessionに登録するときのキーを指定します
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @og.tag
	 * 検索結果より、DBTableModelオブジェクトを作成します。これを、下流のviewタグ等に
	 * 渡す場合に、通常は、session を利用します。その場合の登録キーです。
	 * query タグを同時に実行して、結果を求める場合、同一メモリに配置される為、
	 * この tableId 属性を利用して、メモリ空間を分けます。
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	id テーブルID (sessionに登録する時のID)
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ), tableId );
	}

	/**
	 * 【TAG】(通常は使いません)Queryオブジェクトを作成する時のDB接続IDを指定します(初期値:null)。
	 *
	 * @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="../../../../constant-values.html#org.opengion.hayabusa.taglib.DataCheckTag.CMD_NEW">コマンド定数</a>
	 */
	public void setCommand( final String cmd ) {
		final 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 ) ) {
			final String errMsg = "exist 属性は、(auto,true,false,one,notuse)を指定してください。 [" + exist + "]" + CR;
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * 【TAG】エラー時の選択行を取り除いて継続処理を行うかどうか[true/false]を指定します(初期値: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 から取得されます。
	 * 引数を変更する場合は、lblParamKeys を使用してください。
	 *
	 * @param id メッセージID
	 * @see    #setLblParamKeys( String )
	 */
	@Override
	public void setLbl( final String id ) {
		// 継承親のメソッドを使わない。
		lbl = nval( getRequestParameter( id ), lbl );
	}

	/**
	 * 【TAG】ラベルリソースの引数をCSV形式で指定します。
	 *
	 * @og.tag
	 * ラベルリソースのキーをCSV形式で指定することで、設定します。
	 * ラベルに引数( {0},{1} など ) がある場合、ここで指定した値を
	 * 順番に、{0},{1},{2}・・・ に当てはめていきます。
	 * キーワードは、CSV形式で指定し、それを分解後、ラベルリソースで
	 * リソース変換を行います。(つまり、記述された値そのものでは在りません)
	 * PL/SQL では、"{#PN}" などと指定していた分は、同様に "PN" と指定しです。
	 * 内部的に、where 条件に指定されたキーと値は、&#064;KEY と &#064;VAL に、
	 * from と where の間の文字列は、&#064;TBL に対応付けられます。
	 * {&#064;XXXX} 変数も使用できます。実データの値を取出したい場合は、[PN]と
	 * すれば、DBTableModel の PN の値を取出します。
	 * なにも指定しない場合は、キー={0} 、値={1}、from={2} です。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) 新規追加
	 *
	 * @param keys メッセージリソースのキー(CSV)
	 * @see    #setLbl( String )
	 */
	public void setLblParamKeys( final String keys ) {
		lblParamKeys = 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】条件判定するカラムＩＤを指定します(初期値:null)。
	 *
	 * @og.tag
	 * 指定のカラムＩＤの値と、conditionList の値を比較して、
	 * 存在する場合は、check処理を実行します。
	 * この処理が有効なのは、command="ENTRY" の場合のみです。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) 新規追加
	 *
	 * @param	key カラムＩＤ
	 * @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 条件判定する値("|"で区切)
	 * @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つめ以降の重複行は無視されます。
	 * なお、キーはCSV形式(CSV形式)で複数指定が可能です。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規追加
	 *
	 * @param	clm チェックキー(CSV形式)
	 */
	public void setUniqCheckClms( final String clm ) {
		final String tmp = nval( getRequestParameter( clm ),null );
		uniqCheckClms = StringUtil.csv2Array( tmp );
	}

	/**
	 * 【TAG】エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
	 *
	 * @og.tag
	 * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
	 * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
	 * 通常は、戻るリンクなどを指定します。
	 *
	 * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
	 * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
	 * 例) beforeErrorJsp = "/jsp/common/history_back.jsp"
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param jsp 表示前にincludeするJSPファイル名
	 */
	public void setBeforeErrorJsp( final String jsp ) {
		beforeErrorJsp = nval( getRequestParameter( jsp ),beforeErrorJsp );
	}

	/**
	 * 【TAG】エラーが発生した際に、エラーメッセージの表示後にincludeするJSPを指定します。
	 *
	 * @og.tag
	 * エラーが発生した際に、エラーメッセージの表示前にincludeするJSPを指定します。
	 * エラーが発生していない場合は、ここで指定されたJSPが処理されることはありません。
	 *
	 * 指定の方法は、相対パス、絶対パスの両方で指定することができます。
	 * 但し、絶対パスで指定した場合、その基点は、コンテキストのルートディレクトリになります。
	 * 例) afterErrorJsp = "/jsp/common/history_back.jsp"
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param jsp 表示後にincludeするJSPファイル名
	 */
	public void setAfterErrorJsp( final String jsp ) {
		afterErrorJsp = nval( getRequestParameter( jsp ),afterErrorJsp );
	}

	/**
	 * 【TAG】データを全件選択済みとして処理するかどうか[true/false]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 全てのデータを選択済みデータとして扱って処理します。
	 * 全件処理する場合に、(true/false)を指定します。
	 * 初期値は false です。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param  all 選択済み処理可否 [true:全件選択済み/false:通常]
	 */
	public void setSelectedAll( final String all ) {
		selectedAll = nval( getRequestParameter( all ),selectedAll );
	}

	/**
	 * 【TAG】チェックするデータベース名(from 句)を指定します。
	 *
	 * @og.tag
	 * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
	 * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
	 * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
	 * 作成します。
	 * from が指定された場合は、BODY は無視されますので、ご注意ください。
	 *
	 * @og.rev 5.7.6.2 (2014/05/16) 新規追加
	 *
	 * @param  frm チェックするテーブルID
	 */
	public void setFrom( final String frm ) {
		from = nval( getRequestParameter( frm ),from );
	}

	/**
	 * 【TAG】チェックする検索条件(where句)を指定します。
	 *
	 * @og.tag
	 * これは、tableExist タグ廃止に伴う便利機能で、通常、BODYに記述された
	 * SELECT count(*) from XXXX where XXXXX で、チェックしますが、
	 * from 属性 と、where 属性を指定する事で、内部で、チェック用のSQL文を
	 * 作成します。
	 * where は、from が指定された場合のみ、有効ですし、where を指定しなければ、
	 * 全件検索になります。
	 * tableExist タグと異なるのは、where の指定の仕方で、tableExist タグでは、
	 * names 属性と、対応する where には、? で記述していましたが、
	 * dataCheck タグでは、通常の [] でDBTableModelの値を指定します。
	 *
	 * @og.rev 5.7.6.2 (2014/05/16) 新規追加
	 *
	 * @param  whr チェックするWHERE条件
	 */
	public void setWhere( final String whr ) {
		where = nval( getRequestParameter( whr ),where );
	}

	/**
	 * 指定の行番号の、カラムNo配列(int[])に対応した値の配列を返します。
	 *
	 * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を
	 * 処理の対象とします。
	 *
	 * @og.rev 4.2.0.1 (2008/03/27) row と clm を入れ替えます。(他とあわせます)
	 *
	 * @param	row   行番号
	 * @param	clmNo カラムNo配列(可変長引数)
	 *
	 * @return	行番号とカラムNo配列に対応した、値の配列
	 */
	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;
	}

	/**
	 * エラーメッセージの前後に処理するJSPをインクルードします。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
	 *
	 * @param jsp JSP名
	 */
	private void includeJsp( final String jsp ) {
		try {
			pageContext.include( jsp, false );
		} catch( final IOException ex ) {
			final String errMsg = jsp + " の include に失敗しました。 ";
			throw new HybsSystemException( errMsg,ex );
		} catch( final ServletException ex ) {
			final String errMsg = jsp + " の include に失敗しました。 ";
			throw new HybsSystemException( errMsg,ex );
		}
	}

	/**
	 * 表示データの HybsSystem.ROW_SEL_KEY を元に、選ばれた 行を処理の対象とします。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @return	選択行の配列
	 * @og.rtnNotNull
	 */
	@Override
	protected int[] getParameterRows() {
		final int[] rowNo ;
		if( selectedAll ) {
			final int rowCnt = table.getRowCount();
			rowNo = new int[ rowCnt ];
			for( int i=0; i<rowCnt; i++ ) {
				rowNo[i] = i;
			}
		} else {
			rowNo = super.getParameterRows();
		}
		return rowNo ;
	}

	/**
	 * 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 ResourceManager	resource;
		private DBTableModel	table	;
		private String 		title		;
		private String 		from		;
		private String[]	lblKeys		;
		private int[]		clmNo		;

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

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

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

		/**
		 * 処理対象のテーブル名を設定します。
		 *
		 * @param	from	テーブル名
		 */
		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; }

		/**
		 * lblParamKeys 属性の配列を設定します。
		 *
		 * @param	lblKeys	属性の配列(可変長引数)
		 */
		public void setParamKeys( final String... lblKeys ) { this.lblKeys = lblKeys; }

		/**
		 * カラム名列を設定します。
		 *
		 * @param	clmNo	カラム名配列(可変長引数)
		 */
		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( lblKeys != null && lblKeys.length > 0 ) {
				final int size = lblKeys.length;
				lblVals = new String[size] ;

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

		/**
		 * カラムNo配列(int[])に対応したカラム名のメッセージリソース文字列を返します。
		 *
		 * @param	clmNo カラムNo配列(可変長引数)
		 * @return	カラムNo配列に対応した、カラム名のメッセージリソース
		 * @og.rtnNotNull
		 */
		private String getKeysLabel( final int... clmNo ) {
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
			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 ) );		// 6.0.2.5 (2014/10/31) char を append する。
				}
			}

			return buf.toString();
		}

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

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

				for( int i=0; i<size; i++ ) {
					final String key = lblVals[i] ;
					if( key != null ) {
						if(		 "@VAL".equals( key ) ) { args[i] = vals; }
						else if( StringUtil.startsChar( key,'[' ) && key.endsWith( "]" )  ) {		// 6.4.1.1 (2016/01/16) １文字 String.startsWith
							if( table != null ) {
								args[i] = table.getValue( row,key.substring( 1,key.length()-1 ) );
							}
						}
						else {
							args[i] = key;
						}
					}
				}
				errMessage.addMessage( row + 1, ErrorMessage.NG, id, args );
			}
		}

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

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