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

import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Arrays;

import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.db.DBFunctionName;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.db.Transaction;
import org.opengion.fukurou.model.Formatter;
import org.opengion.fukurou.util.Closer;
import org.opengion.fukurou.util.ErrMsg;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.HybsLoader;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.SystemParameter;
import org.opengion.fukurou.util.HybsDateUtil;
import static org.opengion.fukurou.util.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.util.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * 業務ロジックを処理するために必要な共通メソッドの実行を行っている抽象クラスです。
 *
 * メインロジックについては、各サブクラスで実装する必要があります。
 *
 * @og.rev 5.1.1.0 (2009/12/01) 新規作成
 * @og.group 業務ロジック
 *
 * @version 5.0
 * @author Hiroki Nakamura
 * @since JDK1.6,
 */
public abstract class AbstractBizLogic {

	/** エラーメッセージをセットする際に使用します {@value} */
	protected static final int OK        = ErrorMessage.OK;
	/** エラーメッセージをセットする際に使用します {@value} */
	protected static final int WARNING   = ErrorMessage.WARNING;
	/** エラーメッセージをセットする際に使用します {@value} */
	protected static final int NG        = ErrorMessage.NG;
	/** エラーメッセージをセットする際に使用します {@value} */
	protected static final int EXCEPTION = ErrorMessage.EXCEPTION;
	/** エラーメッセージをセットする際に使用します {@value} */
	protected static final int ORCL_ERR  = ErrorMessage.ORCL_ERR;

	private Connection	conn	;
	private Transaction tran	;			// 5.1.9.0 (2010/08/01) シーケンス対応
	private String		dbid	;			// 5.1.9.0 (2010/08/01) シーケンス対応
	DBFunctionName		dbName	;			// 5.1.9.0 (2010/08/01) シーケンス対応
	private HybsLoader	loader	;
	private String[]	keys	;
	private String[]	vals	;
	private final Map<String, String> variableMap  = new HashMap<String, String>();
	private final Map<String, Formatter> formatMap = new HashMap<String, Formatter>();
	private final Map<String, SystemParameter> sysParamMap = new HashMap<String, SystemParameter>();
	private final ErrorMessage errMsg = new ErrorMessage();
	private String bizRtn		;					// 5.1.8.0 (2010/07/01) メソッド名と変数名を分ける。
	private boolean debugFlag	;					// 5.1.8.0 (2010/07/01) メソッド名と変数名を分ける。

	private final StringBuilder debugMsg = new StringBuilder();
	private boolean	useParamMetaData	;			// 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)

	/**
	 * 配列側テーブルモデル
	 *
	 * 配列型テーブルモデル自体は、protected属性であるため、サブクラスから直接参照することができます。
	 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの
	 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。
	 * (この想定がなければ、本来は、package privateにすべきです)
	 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。
	 */
	protected ArrayTableModel table ;

	/**
	 * 配列型テーブルモデルの現在の処理行
	 *
	 * 行番号自体は、protected属性であるため、サブクラスから直接参照することができます。
	 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの
	 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。
	 * (この想定がなければ、本来は、package privateにすべきです)
	 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。
	 *
	 * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。
	 * よって、オリジナルのDBTableModelの行番号ではありません。
	 */
	protected int row = -1;

	/**
	 * DBのトランザクションオブジェクトを指定します。
	 * 各実装クラスでは、コネクションのcommit,rollbackは行われません。
	 * (全てのDB処理は、1つのトランザクションとして処理されます。)
	 * このため、commit,rollbackは呼び出し元で行う必要があります。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
	 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
	 *
	 * @param tr トランザクション
	 */
	public void setTransaction( final Transaction tr ) {
		tran = tr;
		conn = tran.getConnection( dbid );
		useParamMetaData = ConnectionFactory.useParameterMetaData( dbid );	// 5.3.8.0 (2011/08/01)
	}

	/**
	 * 接続先IDを指定します。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
	 *
	 * @param id 接続先ID
	 */
	void setDbid( final String id ) {
		dbid = id;
	}

	/**
	 * 業務ロジックのクラスをロードするためのクラスローダーをセットします。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param ldr クラスローダー
	 */
	void setLoader( final HybsLoader ldr ) {
		if( loader != null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "既にクラスローダーがセットされています。"
									+ " OLD:" + loader
									+ " IN :" + ldr ;
			throw new RuntimeException( errMsg );
		}
		loader = ldr;
	}

	/**
	 * 配列型テーブルモデルをセットします。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param tbl 配列型テーブルモデル
	 */
	void setTable( final ArrayTableModel tbl ) {
		if( table != null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "既に配列型テーブルモデルがセットされています。"
								+ " OLD:" + table
								+ " IN :" + tbl ;
			throw new RuntimeException( errMsg );
		}
		table = tbl;
	}

	/**
	 * 固定値のキー配列を指定します。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param ks キー配列(可変長引数)
	 */
	void setKeys( final String... ks ) {
		if( keys != null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "既に固定値配列(キー)がセットされています。" 	+ CR
							+ "   KESY   =" + Arrays.toString( keys )			+ CR
							+ "   in keys=" + Arrays.toString( ks ) ;
			throw new RuntimeException( errMsg );
		}
		if( ks != null && ks.length > 0 ) { keys = ks; }		// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
	}

	/**
	 * 固定値の値配列を指定します。
	 * このメソッドは、1度しかセットすることができません。2回以上呼び出しするとエラーになります。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param vs 値配列(可変長引数)
	 */
	void setVals( final String... vs ) {
		if( vals != null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "既に固定値配列(値)がセットされています。"	+ CR
							+ "   VALS   =" + Arrays.toString( vals )		+ CR
							+ "   in vals=" + Arrays.toString( vs ) ;
			throw new RuntimeException( errMsg );
		}
		if( vs != null && vs.length > 0 ) { vals = vs; }		// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
	}

	/**
	 * この処理の実行ユーザーIDを指定します。
	 *
	 * @param id 実行ユーザーID
	 */
	void setUserId( final String id ) {
		variableMap.put( "CON.USERID", id);
	}

	/**
	 * 親(呼び出し)PGIDを指定します。
	 *
	 * @param id 親PGID
	 */
	void setParentPgId( final String id ) {
		variableMap.put( "CON.PGPID", id );
	}

	/**
	 * デバッグモードにします。
	 */
	void setDebug() {
		debugFlag = true;
	}

	/**
	 * デバッグメッセージを取得します。
	 *
	 * @return デバッグメッセージ
	 * @og.rtnNotNull
	 */
	String getDebugMsg() {
		return debugMsg.toString();
	}

	/**
	 * 処理を実行します。
	 * 処理の方法は、main()メソッドにより定義されます。
	 * 実装クラスで発生した全ての例外は、Throwableオブジェクトとしてスローされます。
	 * 呼び出し元では、例外を確実にcatchして、commit,rollbackを行ってください。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) シーケンス対応
	 *
	 * @return 処理が成功したかどうか
	 * @throws Throwable 実行時の全エラーを上位に転送します。
	 */
	boolean exec() throws Throwable {
		dbName = DBFunctionName.getDBName( ConnectionFactory.getDBName( dbid ) );
		makeParamMap();
		init();
		return main();
	}

	/**
	 * 処理のメインロジックの前処理を記述します。
	 *
	 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。
	 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの
	 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。
	 * (この想定がなければ、本来は、package privateにすべきです)
	 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。
	 */
	abstract protected void init();

	/**
	 * 処理のメインロジックを記述します。
	 *
	 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。
	 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの
	 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。
	 * (この想定がなければ、本来は、package privateにすべきです)
	 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。
	 *
	 * @return 処理が正常終了したか
	 */
	abstract protected boolean main();

	/**
	 * 結果ステータスを返します。
	 *
	 * @return 結果ステータス
	 */
	int getKekka() {
		return errMsg.getKekka();
	}

	/**
	 * エラーメッセージオブジェクトを返します。
	 *
	 * @return エラーメッセージ
	 */
	ErrorMessage getErrMsg() {
		return errMsg;
	}

	/**
	 * 業務ロジックの戻り値を返します。
	 *
	 * @return 戻り値
	 */
	String getReturn() {
		return bizRtn;
	}

	/**
	 * 業務ロジックを実行するために、テーブルモデルが外部からセットされる必要があるか
	 * を返します。
	 * 必須である場合、その業務ロジックは、子ロジックとして呼び出すことができません。
	 * これは、子ロジック呼び出し時は、テーブルモデルがセットされないためです。
	 * (このクラスは、テーブルモデルが外部から指定されている必要はありません。)
	 *
	 * このメソッド自体は、protected属性であるため、サブクラスから直接参照することができます。
	 * 但し、これは、各業務ロジックで直接参照することを想定したものではなく、BizLogicの
	 * メイン構造を拡張するサブクラスを定義する際に使用することを想定しています。
	 * (この想定がなければ、本来は、package privateにすべきです)
	 * このため、業務ロジックを各実装クラスでは直接参照しないで下さい。
	 *
	 * @return	テーブルモデルが外部からセットされる必要があるかどうか(常にfalse)
	 */
	protected boolean isRequireTable() {
		return false;
	}

	/**
	 * デバッグモードかどうかを返します。
	 *
	 * @return デバッグモードかどうか
	 */
	final protected boolean isDebug() {
		return debugFlag;
	}

	/**
	 * デバッグメッセージを追加します。
	 *
	 * @param msg 追加するデバッグメッセージ
	 */
	final protected void debug( final String msg ) {
		debugMsg.append( msg ).append( CR );
	}

	/**
	 * 指定されたキーの値を返します。
	 *
	 * @param key キー
	 *
	 * @return 変数値
	 */
	final protected String var( final String key ) {
		return variableMap.get( key );
	}

	/**
	 * 指定されたキーの値をint型に変換して返します。
	 *
	 * @param key キー
	 *
	 * @return 変数値
	 */
	final protected int vari( final String key ) {
		return var( key ) == null ? 0 : Integer.parseInt( var( key ) );		// 6.0.2.4 (2014/10/17) メソッド間違い
	}

	/**
	 * 指定されたキーの値をdouble型に変換して返します。
	 *
	 * @param key キー
	 *
	 * @return 変数値
	 */
	final protected double vard( final String key ) {
		return var( key ) == null ? 0.0 : Double.valueOf( var( key ) );
	}

	/**
	 * パラメーターのキー一覧を配列形式で返します。
	 * このパラメーターは、業務ロジック内でセットされたパラメーターも含まれますのでご注意下さい。
	 *
	 * @return パラメーターのキー配列
	 */
	final protected String[] varKeys() {
		final Set<String> keys = variableMap.keySet();
		return keys.toArray( new String[keys.size()] );
	}

	/**
	 * 指定されたキーで値を登録します。
	 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、
	 * エラーとなります。
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) チェックのバグを修正
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param key キー
	 * @param val 値
	 */
	final protected void set( final String key, final String val ) {
		// 6.0.2.5 (2014/10/31) 素直に、variableMap で、キー有無を判定する。
		if( variableMap.containsKey( key ) ) {
			final String errMsg = "すでに登録済みのキーを定義することはできません。"	+ CR
							+ "   key =" + key				+ CR
							+ "   val =" + val				+ CR
							+ "   元  =" + variableMap.get( key ) ;
			throw new RuntimeException( errMsg );
		}
		variableMap.put( key, val );
	}

	/**
	 * 指定されたキーで値を登録します。
	 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、
	 * エラーとなります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
	 *
	 * @param key キー
	 * @param val 値
	 */
	final protected void set( final String key, final int val ) {
		set( key, String.valueOf( val ) );
	}

	/**
	 * 指定されたキーで値(double型)を登録します。
	 * パラメーターとしてこの業務ロジックが呼ばれる際の引数となっている場合は、
	 * エラーとなります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
	 *
	 * @param key キー
	 * @param val 値
	 */
	final protected void set( final String key, final double val ) {
		set( key, String.valueOf( val ) );
	}

	/**
	 * 処理中の行の指定されたキー(カラム名)の値を返します。
	 *
	 * @param key キー
	 *
	 * @return 値
	 */
	final protected String line( final String key ) {
		return line( key, row );
	}

	/**
	 * メインの配列型テーブルモデルに対して、行を指定して値を取得します。
	 * 指定された行が範囲を超えている場合は、nullを返します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) テーブルに存在しないカラム名を指定した場合に、NullPointerExceptionが発生するバグを修正
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param key キー
	 * @param rw 行番号(インデックス)
	 *
	 * @return 値
	 */
	final protected String line( final String key, final int rw ) {
		if( table == null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "配列型テーブルモデルがセットされていないため、#line( String,int )メソッドはできません。"	+ CR
							+ "   line( " + key + "," + rw + " );"	+ CR ;
			throw new RuntimeException( errMsg );
		}
		else if( rw < 0 || rw >= table.getRowCount() ) {
			return null;
		}
		else {
			final int col = table.getColumnNo( key );
			if( col < 0 ) {
				return null;
			}
			else {
				return table.getValue( rw, col );
			}
		}
	}

	/**
	 * 処理中の行の指定されたキー(カラム名)の値をint型に変換して返します。
	 *
	 * @param key キー
	 *
	 * @return 値
	 */
	final protected int linei( final String key ) {
		return line( key ) == null ? 0 : Integer.parseInt( line( key ) );		// 6.0.2.4 (2014/10/17) メソッド間違い
	}

	/**
	 * メインの配列型テーブルモデルに対して、行を指定して値をint型に変換して返します。
	 * 指定された行が範囲を超えている場合は、nullを返します。
	 *
	 * @param key キー
	 * @param rw 行番号(インデックス)
	 *
	 * @return 値
	 */
	final protected int linei( final String key, final int rw ) {
		return line( key, rw ) == null ? 0 : Integer.parseInt( line( key, rw ) );		// 6.0.2.4 (2014/10/17) メソッド間違い
	}

	/**
	 * 処理中の行の指定されたキー(カラム名)の値をdouble型に変換して返します。
	 *
	 * @param key キー
	 *
	 * @return 値
	 */
	final protected double lined( final String key ) {
		return line( key ) == null ? 0.0 : Double.valueOf( line( key ) );
	}

	/**
	 * メインの配列型テーブルモデルに対して、行を指定して値をdouble型に変換して返します。
	 * 指定された行が範囲を超えている場合は、nullを返します。
	 *
	 * @param key キー
	 * @param rw 行番号(インデックス)
	 *
	 * @return 値
	 */
	final protected double lined( final String key, final int rw ) {
		return line( key, rw ) == null ? 0.0 : Double.valueOf( line( key, rw ) );
	}

	/**
	 * テーブルのカラム名の一覧を配列形式で返します。
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @return テーブルのカラム名配列
	 */
	final protected String[] lineKeys() {
		if( table == null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "配列型テーブルモデルがセットされていないため、#lineKeys()メソッドはできません。" ;
			throw new RuntimeException( errMsg );
		}
		else {
			return table.getNames();
		}
	}

	/**
	 * テーブルにカラムが存在しているかを返します。
	 *
	 * @og.rev 5.2.0.0 (2010/09/01)
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param clm カラム名
	 *
	 * @return 存在している場合true、存在していない場合false
	 */
	final protected boolean isLine( final String clm ) {
		if( table == null ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "配列型テーブルモデルがセットされていないため、#isLine( String )メソッドはできません。"	+ CR
							+ "   isLine( " + clm + " );"	+ CR ;
			throw new RuntimeException( errMsg );
		}
		return table.getColumnNo( clm ) >= 0 ;
	}

	/**
	 * 業務ロジックの戻り値をセットします。
	 *
	 * @param rtn 戻り値
	 */
	final protected void rtn( final String rtn ) {
		bizRtn = rtn;
	}

	/**
	 * 子ロジックを実行します。
	 * 実行する子ロジックの呼び出しは、親クラスと同じソースパス、クラスパスで呼び出しされます。
	 * 子ロジックに渡す引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * また、子ロジックの戻り値は、val("SUB_RETURN")で取得することができます。
	 *
	 * @param subLogicName 子ロジック名
	 * @param key キー(CSV形式)
	 * @param val 値(CSV形式)
	 *
	 * @return 処理が正常終了したか
	 */
	final protected boolean call( final String subLogicName, final String key, final String val ) {
		return call( subLogicName, key, val, row, table );
	}

	/**
	 * 子ロジックを実行します。
	 * 実行する子ロジックの呼び出しは、親クラスと同じソースパス、クラスパスで呼び出しされます。
	 * 子ロジックに渡す引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * この場合の値は、引数で指定された、配列型テーブルモデルの行に対応する値になります。
	 * また、子ロジックの戻り値は、val("RETURN")で取得することができます。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) シーケンス対応
	 * @og.rev 5.4.1.0 (2011/11/01) 値にカンマが含まれている場合に正しく動作しないバグを修正
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param subLogicName 子ロジック名
	 * @param key キー(CSV形式)
	 * @param val 値(CSV形式)
	 * @param rw 行番号(インデックス)
	 * @param tbl 配列型テーブルモデル
	 *
	 * @return 処理が正常終了したか
	 */
	final protected boolean call( final String subLogicName, final String key, final String val, final int rw, final ArrayTableModel tbl ) {
		final AbstractBizLogic subLogic = (AbstractBizLogic)loader.newInstance( subLogicName );

		if( subLogic.isRequireTable() ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "このクラスは、外部からテーブルモデルをセットする必要があるため、子ロジックとして呼び出すことはできません。" + CR
							+ "  [クラス名=" + subLogic.getClass().getName() + "]"	+ CR
							+ "   subLogicName =" + subLogicName 
							+ "   key =[" + key + "]"
							+ "   val =[" + val + "]" + CR ;
			throw new RuntimeException( errMsg );
		}

		subLogic.setTransaction( tran );
		subLogic.setLoader( loader );
		subLogic.setKeys( StringUtil.csv2Array( key ) );
		// 5.4.1.0 (2011/11/01) 値にカンマが含まれている場合に正しく動作しないバグを修正
		String[] vals = StringUtil.csv2Array( val );
		for( int i=0; i<vals.length; i++ ) {
			vals[i] = replaceParam( vals[i], rw, tbl );
		}
		subLogic.setVals( vals );
		subLogic.setUserId( variableMap.get( "CON.USERID" ) );
		subLogic.setParentPgId( variableMap.get( "CON.PGID" ) );
		if( debugFlag ) {
			subLogic.setDebug();
		}

		boolean rtn;
		try {
			rtn = subLogic.exec();
		}
		catch( Throwable th ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "子ロジックの呼び出しでエラーが発生しました。" + CR
							+ "   subLogicName =" + subLogicName  + CR
							+ "   key =[" + key + "]"
							+ "   val =[" + val + "]" + CR ;
			throw new RuntimeException( errMsg ,th );
		}
		variableMap.put( "RETURN", subLogic.getReturn() );

		if( debugFlag ) { debug( subLogic.getDebugMsg() ); }

		final ErrMsg[] errs = subLogic.getErrMsg().toArray();
		if( errs.length > 0 ) {
			final ErrorMessage errMsgTmp = new ErrorMessage();
			for( int i = 0; i < errs.length; i++ ) {
				errMsgTmp.addMessage( errs[i].copy( rw ) );
			}
			errMsg.append( errMsgTmp );
		}

		return rtn;
	}

	/**
	 * SQLを実行します。
	 * SQL文には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * select文を発行した場合、その結果セットは、var(カラム名)で取得することができます。
	 * 2行以上が返された場合でも、1行目のみが登録されます。
	 * また、検索件数、更新件数については、var("SQL_ROWCOUNT")で取得することができます。
	 *
	 * @param sq SQL文字列
	 */
	final protected void sql( final String sq ) {
		sql( sq, row, table );
	}

	/**
	 * SQLを実行します。
	 * SQL文には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * [XXXX]形式の変数の置き換えには、引数で指定された配列型テーブルモデルの行が使用されます。
	 * select文を発行した場合、その結果セットは、var(カラム名)で取得することができます。
	 * 2行以上が返された場合でも、1行目のみが登録されます。
	 * また、検索件数、更新件数については、var("SQL_ROWCOUNT")で取得することができます。
	 *
	 * @param sq SQL文字列
	 * @param rw 行番号(インデックス)
	 * @param tbl 配列型テーブルモデル
	 */
	final protected void sql( final String sq, final int rw, final ArrayTableModel tbl ) {
		final ArrayTableModel tbl2 = execSQL( sq, rw, tbl );

		if( tbl2 != null && tbl2.getRowCount() > 0 ) {
			final String[] names = tbl2.getNames();
			final String[] vals = tbl2.getValues( 0 );
			for( int i = 0; i < names.length; i++ ) {
				variableMap.put( names[i], vals[i] );
			}
		}
	}

	/**
	 * シーケンス名よりシーケンスオブジェクトを検索し、次の値を取り出します。
	 * DBに対するシーケンスオブジェクトは予め作成されている必要があります。
	 *
	 * また、MySQLの場合は、シーケンスオブジェクトが実装されていないため、
	 * 内部的には、引数のシーケンス名と同じ名前のテーブルから、Integer型の
	 * "SEQID"という項目名を検索することにより、シーケンスをエミュレートしています。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param seqName	シーケンス名
	 *
	 * @return シーケンス番号
	 * @see org.opengion.fukurou.db.DBFunctionName#getSequence(String,Transaction)
	 */
	final protected int seq( final String seqName ) {
		return dbName.getSequence( seqName, tran );
	}

	/**
	 * SQLを実行します。
	 *
	 * @param sq SQL文字列
	 * @param rw 行番号(インデックス)
	 * @param tbl 配列型テーブルモデル
	 *
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @return 結果セット(配列型テーブルモデル)
	 *
	 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
	 * @og.rev 5.1.8.0 (2010/07/01) column名は大文字化し、項目名の取得は#getColumnLabel()で行う。(PotgreSQL対応&バグ修正)
	 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)、setNull 対応
	 */
	private ArrayTableModel execSQL( final String sq, final int rw, final ArrayTableModel tbl ) {

		String sql = replaceParam( sq, false ); // [XXXX]の変換はここでは行わない。
		Formatter format = null ;
		if( tbl != null && sql.indexOf( '[' ) >= 0 ) {
			format = getFormatter( sql, tbl );
			sql = format.getQueryFormatString();
		}

		PreparedStatement	pstmt	= null;
		ResultSet			result	= null;
		ArrayTableModel		tbl2	= null;
		try {
			pstmt = conn.prepareStatement( sql );
			if( tbl != null && format != null ) {
				final int[] clmNo = format.getClmNos();

				// 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
				if( useParamMetaData ) {
					final ParameterMetaData pMeta = pstmt.getParameterMetaData();
					for( int i = 0; i < clmNo.length; i++ ) {
						final int type = pMeta.getParameterType( i+1 );
						// 5.3.8.0 (2011/08/01) setNull 対応
						final String val = tbl.getValue( rw, clmNo[i] );
						if( val == null || val.isEmpty() ) {
							pstmt.setNull( i+1, type );
						}
						else {
							pstmt.setObject( i+1, val, type );
						}
					}
				}
				else {
					for( int i = 0; i < clmNo.length; i++ ) {
						pstmt.setObject( i+1, tbl.getValue( rw, clmNo[i] ) );
					}
				}
			}
			final boolean status = pstmt.execute();
			result = pstmt.getResultSet();

			if( status ) {
				final ResultSetMetaData metaData = result.getMetaData();
				final int cols = metaData.getColumnCount();

				String[] names = new String[cols];
				for( int i = 0; i < cols; i++ ) {
					// 5.1.8.0 (2010/07/01) column名は大文字化し、項目名の取得は#getColumnLabel()で行う。(PotgreSQL対応&バグ修正)
					names[i] = metaData.getColumnLabel( i+1 ).toUpperCase( Locale.JAPAN );
				}

				final String[][] tblVals = DBUtil.resultToArray( result, false );
				tbl2 = new ArrayTableModel( names, tblVals );

				variableMap.put( "SQL_ROWCOUNT", String.valueOf( pstmt.getFetchSize() ) );
			}
			else {
				variableMap.put( "SQL_ROWCOUNT", String.valueOf( pstmt.getUpdateCount() ) );
			}
		}
		catch( SQLException ex ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "配列型テーブルモデルの生成に失敗しました。"	+ CR
							+ "   sql =" + sql		+ CR
							+ "   ArrayTableModel=" + tbl ;
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.resultClose( result );
			Closer.stmtClose( pstmt );
		}
		return tbl2;
	}

	/**
	 * エラーメッセージを追加します。
	 * エラーメッセージの引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 *
	 * @param kekka エラーレベル
	 * @param id エラーメッセージID
	 * @param args エラーメッセージパラメーター
	 */
	final protected void error( final int kekka, final String id, final String... args ) {
		error( row, kekka, id, args );
	}

	/**
	 * 行指定でエラーメッセージを追加します。
	 * エラーメッセージの引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 *
	 * @param rw 行番号(インデックス)
	 * @param kekka エラーレベル
	 * @param id エラーメッセージID
	 * @param args エラーメッセージパラメーター
	 */
	final protected void error( final int rw, final int kekka, final String id, final String... args ) {
		errMsg.addMessage( rw, kekka, id, replaceParam( args ) );
	}

	/**
	 * パラメーターの必須チェックを行います。
	 * キーは、CSV形式で複数指定することができます。
	 *
	 * @param cs カラム(CSV形式)
	 *
	 * @return エラーが発生した場合はfalse、それ以外はtrue
	 */
	final protected boolean must( final String cs ) {
		if( cs == null || cs.isEmpty() ) {
			return true;
		}

		final String[] clms = StringUtil.csv2Array( cs );
		for( int i=0; i<clms.length; i++ ) {
			final String val = variableMap.get( clms[i] );
			if( val == null || val.isEmpty() ) {
				error( 2, "ERR0012", "{#" + clms[i] + "}" );
				return false ;
			}
		}
		return true;
	}

	/**
	 * マスタチェックを行います。
	 *
	 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加
	 *
	 * @see #exist(String, String, String, String, String, String)
	 * @param type エラーチェックのタイプ
	 * @param tblId テーブル名
	 * @param ns カラム(CSV形式)
	 * @param vs 値(CSV形式)
	 *
	 * @return エラーが発生した場合はfalse、それ以外はtrue
	 */
	final protected boolean exist( final String type, final String tblId, final String ns, final String vs ) {
		return exist( type, tblId, ns, vs, null, null,true );
	}

	/**
	 * マスタチェックを行います。
	 *
	 * 引数に指定されたテーブル名、及び条件句を生成するためのカラム、値から
	 * 件数を取得し、typeに応じて件数チェックを行います。
	 * (カラム、値には、CSV形式で複数指定することができます)
	 *  type=true  存在する場合true  存在しない場合false
	 *  type=false 存在する場合false 存在しない場合true
	 *  type=one   1件以内    true  2件以上     false
	 *
	 * 必須チェックの引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 *
	 * また、固定値カラム、値にも条件となるカラム及び値を指定することができますが、
	 * ここで指定されたカラムは、エラーメッセージ表示時にカラム、値が画面に表示されません。
	 *
	 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加
	 *
	 * @param type エラーチェックのタイプ
	 * @param tblId テーブル名
	 * @param ns カラム(CSV形式)
	 * @param vs 値(CSV形式)
	 * @param conNs 固定値カラム(CSV形式)
	 * @param conVs 固定値(CSV形式)
	 *
	 * @return エラーが発生した場合はfalse、それ以外はtrue
	 */
	final protected boolean exist( final String type, final String tblId
			, final String ns, final String vs, final String conNs, final String conVs ) {
		return exist( type, tblId, ns, vs, conNs, conVs,true );
	}

	/**
	 * マスタチェックを行います。
	 * 引数に指定されたテーブル名、及び条件句を生成するためのカラム、値から
	 * 件数を取得し、typeに応じて件数チェックを行います。
	 * (カラム、値には、CSV形式で複数指定することができます)
	 *  type=true  存在する場合true  存在しない場合false
	 *  type=false 存在する場合false 存在しない場合true
	 *  type=one   1件以内    true  2件以上     false
	 *
	 * 必須チェックの引数には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 *
	 * また、固定値カラム、値にも条件となるカラム及び値を指定することができますが、
	 * ここで指定されたカラムは、エラーメッセージ表示時にカラム、値が画面に表示されません。
	 *
	 * isErrThrow は、エラーが発生した場合に、エラーメッセージ（ErrorMessage）に書き込むかどうかを指定します。
	 * 基本は、互換性を考慮し、true(書き込む)です。
	 * false にするケースは、存在ﾁｪｯｸを行い、あれば更新、なければ追加 など後続処理を行いたい場合に使います。
	 *
	 * @og.rev 5.6.3.1 (2013/04/05) isErrThrow 引数を追加
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 *
	 * @param type エラーチェックのタイプ
	 * @param tblId テーブル名
	 * @param ns カラム(CSV形式)
	 * @param vs 値(CSV形式)
	 * @param conNs 固定値カラム(CSV形式)
	 * @param conVs 固定値(CSV形式)
	 * @param isErrThrow 判定結果がfalseの場合に、error関数を呼ぶ場合は、true。呼ばない場合は、falseをセットします。
	 *
	 * @return エラーが発生した場合はfalse、それ以外はtrue
	 */
	final protected boolean exist( final String type, final String tblId
			, final String ns, final String vs, final String conNs, final String conVs, final boolean isErrThrow ) {
		if( ns == null || ns.isEmpty() || vs == null || vs.isEmpty() ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "カラム又は、値にnullは指定できません。"	+ CR
							+ "   ns =[" + ns + "]"
							+ "   vs =[" + vs + "]" ;
			throw new RuntimeException( errMsg );
		}

		final String namesStr   = ns + ( conNs == null || conNs.isEmpty() ? "" : "," + conNs );
		final String[] namesArr = StringUtil.csv2Array( namesStr );
		final String valsStr    = vs + ( conVs == null || conVs.isEmpty() ? "" : "," + conVs );
		final String[] valsArr  = StringUtil.csv2Array( valsStr );
		if( namesArr.length != valsArr.length ) {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "カラムと値の個数が異なります。"			+ CR
							+ "   names = [" + namesStr	+ "]"	+ CR
							+ "   vals  = [" + valsStr	+ "]";
			throw new RuntimeException( errMsg );
		}

		final StringBuilder sb = new StringBuilder( BUFFER_MIDDLE );
		sb.append( "select count(*) CNT from " ).append( tblId );
		for( int i=0 ;i<namesArr.length; i++ ) {
			if( i==0 )	{ sb.append( " where " ); }
			else 		{ sb.append( " and " ); }
			sb.append( namesArr[i] ).append( " = " ).append( valsArr[i] );
		}

		int count = 0;
		final ArrayTableModel tbl2 = execSQL( sb.toString(), row, table );
		if( tbl2 != null && tbl2.getRowCount() >= 0 ) {
			count = Integer.parseInt( tbl2.getValues( 0 )[0] );			// 6.0.2.4 (2014/10/17) メソッド間違い
		}

		final String repVals = replaceParam( vs );
		if( "true".equalsIgnoreCase( type ) ) {
			// ERR0025=データ未登録エラー。キー={0}、値={1} のデータは、存在していません。
			if( count <= 0 ) {
				if( isErrThrow ) { error( NG, "ERR0025", "{#" + ns + "}", repVals ); }	// 5.6.3.1 (2013/04/05) 
				return false;
			}
		}
		else if( "false".equalsIgnoreCase( type ) ) {
			// ERR0026=データ登録済みエラー。キー={0}、値={1} のデータは、すでに存在しています。
			if( count > 0 ) {
				if( isErrThrow ) { error( NG, "ERR0026", "{#" + ns + "}", repVals ); }	// 5.6.3.1 (2013/04/05) 
				return false;
			}
		}
		else if( "one".equalsIgnoreCase( type ) ) {
			// ERR0027=データ２重登録エラー。キー={0}、値={1} のデータは、重複して存在しています。
			if( count > 1 ) {
				if( isErrThrow ) { error( NG, "ERR0027", "{#" + ns + "}", repVals ); }	// 5.6.3.1 (2013/04/05) 
				return false;
			}
		}
		else {
			// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "typeは、true、false、oneのいずれかで指定する必要があります。"	+ CR
							+ "   type = [" + type	+ "]";
			throw new RuntimeException( errMsg );
		}
		return true;
	}

	/**
	 * 引数に指定されたキー、値をマップ形式に変換します。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 * @og.rev 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
	 */
	private void makeParamMap() {
		if( keys != null && vals != null ) {
			if( keys.length == vals.length ) {
				for( int i = 0; i < keys.length; i++ ) {
					variableMap.put( keys[i], vals[i] );
				}
			}
			else {
				// 5.6.7.0 (2013/07/27) Exception を throw するとき、一旦、errMsg 変数にセットします。
			final String errMsg = "keysとvalsの個数が異なります。"			+ CR
							+ "   keys   =" + Arrays.toString( keys )		+ CR
							+ "   vals   =" + Arrays.toString( vals ) ;
				throw new RuntimeException( errMsg );
			}
		}

		final String ymdh = HybsDateUtil.getDate( "yyyyMMddHHmmss" );		// 5.5.7.2 (2012/10/09) HybsDateUtil を利用
		variableMap.put( "CON.YMDH", ymdh );
		variableMap.put( "CON.YMD", ymdh.substring( 0,8 ) );
		variableMap.put( "CON.HMS", ymdh.substring( 8 ) );

		variableMap.put( "CON.PGID", this.getClass().getSimpleName() );
	}

	/**
	 * {&#064;XXXX}形式及び[XXXX]形式の文字列配列の置き換えを行います。
	 *
	 * @og.rev 6.2.2.0 (2015/03/27) #replaceParam( String[] , int , ArrayTableModel )  廃止に伴う処置
	 *
	 * @param str 置き換え対象の配列
	 *
	 * @return 置き換え結果の文字列
	 */
	private String[] replaceParam( final String[] str ) {
		for( int i = 0; i < str.length; i++ ) {
			str[i] = replaceParam( str[i], row, table );
		}
		return str;
	}

	/**
	 * {&#064;XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。
	 *
	 * @param str 置き換え対象の文字列
	 *
	 * @return 置き換え結果の文字列
	 */
	private String replaceParam( final String str ) {
		return replaceParam( str, row, table );
	}

	/**
	 * {&#064;XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。
	 * isRepTableにfalseを指定した場合、Formatterによる[XXXX]変換は行われません。
	 * (SQLの変換の場合は、PreparedStatementで処理させるため、[XXXX]の変換は行わない。)
	 *
	 * @param str 置き換え対象の文字列
	 * @param isRepTable Formatterによる[XXXX]変換を行うか
	 *
	 * @return 置き換え結果の文字列
	 */
	private String replaceParam( final String str, final boolean isRepTable ) {
		return isRepTable ? replaceParam( str, row, table) : replaceParam( str, 0, null ) ;
	}

	/**
	 * {&#064;XXXX}形式及び[XXXX]形式の文字列の置き換えを行います。
	 * [XXXX]形式の置き換えには、引数で指定された配列型テーブルモデル、行番号(インデックス)を使用します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) 引数チェック漏れ対応
	 * @og.rev 5.3.9.0 (2011/09/01) nullが連続する場合にゼロストリングに置き換えられないバグを修正
	 *
	 * @param str 置き換え対象の文字列
	 * @param rw 行番号(インデックス)
	 * @param tbl 配列型テーブルモデル
	 *
	 * @return 置き換え結果の文字列
	 */
	private String replaceParam( final String str, final int rw, final ArrayTableModel tbl ) {
		// 5.1.8.0 (2010/07/01) 引数チェック漏れ対応
		if( str == null || str.isEmpty() ) { return ""; }

		String rtn = str;

		// {@XXXX}の変換
		if( !variableMap.isEmpty() && rtn.indexOf( "{@" ) >= 0 ) {		// 6.1.1.0 (2015/01/17) refactoring
			final SystemParameter sysParam = getSysParam( rtn );
			rtn = sysParam.replace( variableMap );
		}

		// [XXXX]の変換
		if( tbl != null && rtn.indexOf( '[' ) >= 0 ) {
			final Formatter format = getFormatter( rtn, tbl );
			rtn = format.getFormatString( rw );
			// 以下3行はFormatterのバグを吸収(値がnullの場合に"null"という文字列で出力されてしまう)
			// 5.3.9.0 (2011/09/01) nullが連続する場合にゼロストリングに置き換えられないバグを修正
			rtn = ',' + rtn;
			rtn = rtn.replace( ",null", "," );
			rtn = rtn.substring( 1 );
		}

		return rtn;
	}

	/**
	 * [XXXX]変換を行うためのFormatterを取得します。
	 *
	 * @param str 変換文字列
	 * @param tbl 配列型テーブルモデル
	 *
	 * @return Formatterオブジェクト
	 */
	private Formatter getFormatter( final String str, final ArrayTableModel tbl ) {
		Formatter format = formatMap.get( str + tbl.toString() );
		if( format == null ) {
			format = new Formatter( tbl );
			format.setFormat( str );
			formatMap.put( str + tbl.toString(), format );
		}
		return format;
	}

	/**
	 * {&#064;XXXX}変換を行うためのSystemParameterオブジェクトを取得します。
	 *
	 * @param str 変換文字列
	 *
	 * @return SystemParameterオブジェクト
	 */
	private SystemParameter getSysParam( final String str ) {
		SystemParameter sysParam = sysParamMap.get( str );
		if( sysParam == null ) {
			sysParam = new SystemParameter( str );
			sysParamMap.put( str, sysParam );
		}
		return sysParam;
	}

	/**
	 * 検索SQLを実行し、結果を配列型テーブルモデルとして返します。
	 * SQL文には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * また、検索件数については、var("SQL_ROWCOUNT")で取得することができます。
	 *
	 * @param sq SQL文
	 *
	 * @return 配列型テーブルモデル
	 */
	final protected ArrayTableModel createTableBySql( final String sq ) {
		return createTableBySql( sq, row, table );
	}

	/**
	 * 検索SQLを実行し、結果を配列型テーブルモデルとして返します。
	 * SQL文には、{&#064;XXXX}形式及び[XXXX]形式の変数を使用することができます。
	 * [XXXX]形式の変数の置き換えには、引数で指定された配列型テーブルモデルの行が使用されます。
	 * また、検索件数については、var("SQL_ROWCOUNT")で取得することができます。
	 *
	 * @param sq SQL文
	 * @param rw 行番号(インデックス)
	 * @param tbl 配列型テーブルモデル
	 *
	 * @return 配列型テーブルモデル
	 */
	final protected ArrayTableModel createTableBySql( final String sq, final int rw, final ArrayTableModel tbl ) {
		return execSQL( sq, rw, tbl );
	}
}
