/*
 * 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 org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBTableModelUtil;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)

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

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

/**
 * ２つの DBTableModel の 集合処理を行うタグです。
 *
 * マスタとスレーブのそれぞれの DBTableModel を取得し、マージ、差分、排他などの
 * データ処理を行います。結果を、指定の tableId と scope に書き出します。
 * マスタテーブルは、masterTableId と masterScope より取り出します。スレーブテーブルは、
 * slaveTableId と slaveScope より取り出します。
 * 取り出された DBTableModel は、マスタテーブルに対して、スレーブテーブル情報を参照する形で行われ、
 * 結果はマスタテーブルに上書きされる形で行われます。
 * 指定できるアクションは、全体集合(UNION_ALL)、和集合(UNION)、積集合(INTERSECT)、
 * 差集合(MINUS)、差分集合(DIFFERENCE)、列合成(UNION_CLM)、 列追加(ADD_CLM)、 グループ(GROUP)です。
 * 列合成と列追加、グループ以外の処理では、カラム順とカラム数は同数でなければなりません。
 * primaryKeys や unionClms などの指定のキー名は、マスタテーブルに存在する必要があります。
 * マスタテーブルと同じカラム番号でスレーブテーブルよりデータを読み出します。
 * (カラム名や属性は、異なってもかまいませんが、マスタテーブルに準拠します。)
 * また、単独(マスタテーブルのみ)で、和集合と同等の、グループ(GROUP)を使用すると、指定の
 * カラムでのユニーク化を行うことが可能になります。グループ処理では、先行優先とし、
 * ２回目に現れた情報を削除することになります。グループ が指定された場合は、
 * スレーブテーブルは無視されます。いずれの処理においても、集合処理を行う主キーで
 * 一旦グループ化されます。全体集合(UNION_ALL)で処理する場合でも、主キーがユニークで
 * ない場合は、マスター、スレーブの各テーブルで一旦グループ化された後で、マージされます。
 * (マージ後には、同一主キーを持つ行は存在します。)
 * 全体集合(UNION_ALL)の場合のみ、mergeKeys を指定する必要はありません。その場合は、
 * キーなしのため、マスターとスレーブのテーブルを単に合成するだけになります。
 *
 * 処理前後でのDBTableModelの件数は、以下の変数で取得することが可能です。
 *  処理前のマスターテーブル : {&#064;DB.MASTER_COUNT}
 *  処理前のスレーブテーブル : {&#064;DB.SLAVE_COUNT}
 *  処理後                   : {&#064;DB.COUNT}
 *
 * @og.formSample
 * ●形式：
 *       ・&lt;og:tableMerge
 *             action        = "UNION_ALL|UNION|INTERSECT|MINUS|DIFFERENCE|UNION_CLM|ADD_CLM|GROUP|UNION_SELROW"
 *             tableId       = "DEFAULT"        出力テーブルの tableId
 *             scope         = "session"        出力テーブルの scope
 *             masterTableId = "DEFAULT"        マスタテーブルの tableId
 *             masterScope   = "session"        マスタテーブルの scope
 *             slaveTableId  = "DEFAULT"        スレーブテーブルの tableId
 *             slaveScope    = "request"        スレーブテーブルの scope
 *             masterKeys    = "AA,BB,CC"       マスタテーブルの集合処理を行う主キー
 *             slaveKeys     = "AA,BB,CC"       スレーブテーブルの集合処理を行う主キー(null時=masterKeys)
 *             diffKeys      = "DD,EE"          マスタテーブルのDIFFERENCE時の差分カラム名
 *             unionClms     = "DD,EE"          UNION_CLM,ADD_CLM時にスレーブからマスタへ追加するカラム名
 *             modifyClms    = "FF,GG"          DIFFERENCE時にスレーブからマスタへ値を更新するカラム名
 *             noSideEffect  = "false"          テーブルモデルに対する副作用(true:ない/false:ある)
 *             useDiffData   = "true"           DIFFERENCE時に差分のスレーブデータを追加するかどうか
 *             useCheckOnly  = "false"          マスタテーブルの選択行のデータのみを対象に処理を行うかどうか
 *             groupAddClms  = "FF,GG"          masterKeysで集合処理するときに、相違データをCSV連結して残すカラム名
 *             display       = "true"           処理概要を表示するかどうか
 *         /&gt;
 * ●body：なし
 *
 * ●Tag定義：
 *   &lt;og:tableMerge
 *       command            【TAG】コマンド (NEW,RENEW)をセットします(初期値:NEW)
 *       action             【TAG】アクションを指定します(UNION_ALL|UNION|INTERSECT|MINUS|DIFFERENCE|UNION_CLM|ADD_CLM|GROUP|UNION_SELROW)
 *       tableId            【TAG】出力先のtableIdを指定します (初期値:HybsSystem#TBL_MDL_KEY[=h_tblmdl])
 *       scope              【TAG】出力先のscopeを指定します(初期値:session)
 *       masterTableId      【TAG】マスタテーブルのtableIdを指定します (初期値:HybsSystem#TBL_MDL_KEY[=h_tblmdl])
 *       masterScope        【TAG】マスタテーブルのscopeを指定します(初期値:session)
 *       slaveTableId       【TAG】スレーブテーブルのtableIdを指定します (初期値:HybsSystem#TBL_MDL_KEY[=h_tblmdl])
 *       slaveScope         【TAG】スレーブテーブルのscopeを指定します(初期値:session)
 *       masterKeys         【TAG】マスタテーブルの集合処理を行う主キーを指定します
 *       slaveKeys          【TAG】スレーブテーブルの集合処理を行う主キーを指定します
 *       diffKeys           【TAG】マスタテーブルのDIFFERENCE時の差分カラム名を(CSV形式)指定します
 *       unionClms          【TAG】スレーブからマスタへ追加するカラム名をCSV形式で指定します
 *       modifyClms         【TAG】スレーブからマスタへ値を更新するカラム名をCSV形式で指定します
 *       groupAddClms       【TAG】集合処理するときに、相違データをCSV連結して残すカラム名をCSV形式で指定します
 *       noSideEffect       【TAG】テーブルモデルに対する副作用の有無[true:ない/false:ある]を指定します(初期値:false:ある)
 *       useDiffData        【TAG】差分のスレーブデータを結果テーブルに追加するかどうかを指定します(初期値:true)
 *       useCheckOnly       【TAG】マスタテーブルの選択行のデータのみを対象に処理を行うかどうかを指定します(初期値:false)
 *       stopZero           【TAG】検索結果が０件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
 *       display            【TAG】マージの結果を表示するかどうかを指定します(初期値:true)
 *       mainTrans          【TAG】(通常使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します(初期値:false)
 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:true)
 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:true)
 *       separator          【TAG】groupAddClmsで文字列を連結する項目区切り文字をセットします(初期値:",")
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   /&gt;
 *
 * ●使用例
 *     例１)デフォルト以外に必要な属性のみ指定するサンプル
 *     &lt;og:tableMerge action="UNION"
 *           slaveScope = "request" masterKeys = "AA,BB,CC"
 *     /&gt;
 *
 *         ・出力先、マスターともに初期値は、tableId="DEFAULT" scope="session" です。
 *           スレーブは、tableId か scope をける必要がある為、scope="request" を指定しています。
 *           比較するカラム名は、マスタ、スレーブ同じであれば、マスタのみ指定でかまいません。
 *
 *     例２)マスタ、スレーブともメモリに残らないように request で作成します。
 *     &lt;og:tableMerge action="INTERSECT"
 *           masterScope  = "request"
 *           slaveScope   = "request"
 *           slaveTableId = "SLAVE"
 *           masterKeys   = "AA,BB,CC"
 *     /&gt;
 *
 *         ・マスタ、スレーブともメモリに残らないように request で作成した場合は、
 *           どちらかの TableId を変える必要があります。
 *           出力先は初期値の、tableId="DEFAULT" scope="session" です。
 *
 * @og.rev 3.8.0.9 (2005/10/17) 新規追加
 * @og.group ＤＢ登録
 *
 * @version  0.9.0	2000/10/17
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class TableMergeTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.7.6.2 (2014/05/16)" ;

	private static final long serialVersionUID = 576220140516L ;

	/** action 引数に渡す事の出来る アクションコマンド  全体集合 {@value} */
	public static final String ACT_UNION_ALL	= "UNION_ALL" ;
	/** action 引数に渡す事の出来る アクションコマンド  和集合 {@value} */
	public static final String ACT_UNION		= "UNION" ;
	/** action 引数に渡す事の出来る アクションコマンド  積集合 {@value} */
	public static final String ACT_INTERSECT	= "INTERSECT" ;
	/** action 引数に渡す事の出来る アクションコマンド  差集合{@value} */
	public static final String ACT_MINUS		= "MINUS" ;
	/** action 引数に渡す事の出来る アクションコマンド  差分集合{@value} */
	public static final String ACT_DIFFERENCE	= "DIFFERENCE" ;
	/** action 引数に渡す事の出来る アクションコマンド  列合成{@value} */
	public static final String ACT_UNION_CLM	= "UNION_CLM" ;
	/** action 引数に渡す事の出来る アクションコマンド  列合成{@value} */
	public static final String ACT_ADD_CLM		= "ADD_CLM" ;
	/** action 引数に渡す事の出来る アクションコマンド  グループ {@value} */
	public static final String ACT_GROUP		= "GROUP" ;
	/** action 引数に渡す事の出来る アクションコマンド  グループ {@value} */
	public static final String ACT_UNION_SELROW	= "UNION_SELROW" ;// 4.3.2.0 (2008/09/11)

	/** action 引数に渡す事の出来る アクションコマンド リスト  */
	private static final String[] ACTION_LIST = new String[] {
		ACT_UNION_ALL , ACT_UNION , ACT_INTERSECT , ACT_MINUS , ACT_DIFFERENCE , ACT_UNION_CLM , ACT_ADD_CLM , ACT_GROUP, ACT_UNION_SELROW };

	/** command 引数に渡す事の出来る コマンド  新規 {@value} */
	public static final String CMD_NEW	 = "NEW" ;
	/** command 引数に渡す事の出来る コマンド  再検索 {@value} */
	public static final String CMD_RENEW = "RENEW" ;
	/** command 引数に渡す事の出来る コマンド リスト  */
	private static final String[] COMMAND_LIST = new String[] { CMD_NEW , CMD_RENEW };

	private String	command		= CMD_NEW;

	private String	action		;
	private String	tableId		= HybsSystem.TBL_MDL_KEY;		// 出力先の tableId
	private String	scope		= "session";					// 出力先の scope

	private String	masterTableId	= HybsSystem.TBL_MDL_KEY;	// マスタテーブルの tableId
	private String	masterScope		= "session";				// マスタテーブルの scope
	private String	slaveTableId	= HybsSystem.TBL_MDL_KEY;	// スレーブテーブルの tableId
	private String	slaveScope		= "request";				// スレーブテーブルの scope
	private String	masterKeys		;							// マスタテーブルの集合処理を行う主キー
	private String	slaveKeys		;							// スレーブテーブルの集合処理を行う主キー(null時=masterKeys)
	private String	diffKeys		;							// マスタテーブルのDIFFERENCE時の差分カラム名
	private String	unionClms		;							// UNION_CLM時にスレーブからマスタへ追加するカラム名
	private String	modifyClms		;							// DIFFERENCE時にスレーブからマスタへ値を更新するカラム名
	private String	groupAddClms	;							// 5.1.4.0 (2010/03/01) masterKeysで集合処理するときに、相違データをCSV連結して残すカラム名
	private boolean	noSideEffect	;							// テーブルモデルに対する副作用(true:ない/false:ある)
	private boolean	useDiffData		= true;						// DIFFERENCE時に差分のスレーブデータを追加するかどうか
	private boolean	useCheckOnly	;							// マスタテーブルの選択行のデータのみを対象に処理を行うかどうか
	private boolean	display			= true;         
	private boolean	isMainTrans		= true;						// 5.1.6.0 (2010/05/01) DBLastSqlの処理の見直し
	private String	separator		= ","; 						// 5.3.1.0 (2011/01/01) groupAddClmnsで結合する際の区切り文字
	private boolean	stopZero		;							// 5.7.6.2 (2014/05/16) stopZero属性追加

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 4.3.2.0 (2008/09/11) UNION_SELROWアクション対応
	 * @og.rev 4.3.3.0 (2008/10/01) 処理前後のテーブル件数を取得できるようにする
	 * @og.rev 4.3.3.1 (2008/10/08) スレーブのnullチェック追加
	 * @og.rev 5.1.6.0 (2010/05/01) DBLastSqlの処理は、DBTableModelが新規作成された処理でのみ行う。
	 * @og.rev 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
	 * @og.rev 5.7.6.2 (2014/05/16) stopZero属性、DB.COUNT キーで検索件数をリクエストにセットする。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)
		// 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
		if( !useTag() || ! check( command, COMMAND_LIST ) ) { return EVAL_PAGE ; }

		// 整合性チェック：アクションリストとの存在チェック
		if( !check( action, ACTION_LIST ) ) {
			final String errMsg = "指定のアクションは実行できません。アクションエラー"
							+ CR
							+ "action=[" + action + "] "
							+ CR
							+ "指定可能なアクション:"
							+ StringUtil.array2csv( ACTION_LIST ) ;
			throw new HybsSystemException( errMsg );
		}

		// スレーブテーブルの集合処理を行う主キー(null時=masterKeys)
		if( slaveKeys == null ) { slaveKeys = masterKeys; }

		super.setScope( masterScope );
		DBTableModel masterTable = (DBTableModel)getObject( masterTableId );

		// 整合性チェック：マスタテーブルは必須
		if( masterTable == null ) {
			final String errMsg = "マスタテーブルは必須です。"
						+ CR
						+ "action=[" + action + "] "
						+ "masterTableId=[" + masterTableId + "] "
						+ "masterScope=[" + masterScope + "] " ;
			throw new HybsSystemException( errMsg );
		}
		if( noSideEffect ) { masterTable = cloneTable( masterTable ); }
		final Map<String,Integer> masterMap = makeKeyMap( masterTable,masterKeys,useCheckOnly );

		DBTableModel slaveTable = null;
		Map<String,Integer> slaveMap = null;

		// 整合性チェック：action="GROUP" と "ADD_CLM" 以外では、スレーブテーブルは必須
		if( ! ACT_GROUP.equalsIgnoreCase( action ) && !ACT_ADD_CLM.equalsIgnoreCase( action ) ) {
			super.setScope( slaveScope );
			slaveTable = (DBTableModel)getObject( slaveTableId );

			if( slaveTable == null ) {
				final String errMsg = "action=\"" + action + "\" 時には、スレーブテーブルが必要です。"
							+ CR
							+ "slaveTableId=[" + slaveTableId + "] "
							+ "slaveScope=[" + slaveScope + "] " ;
				throw new HybsSystemException( errMsg );
			}
			if( noSideEffect ) { slaveTable = cloneTable( slaveTable ); }
			slaveMap = makeKeyMap( slaveTable,slaveKeys,false );	// スレーブはuseCheckOnly無関係

			// その場合、マスタとスレーブが同一メモリ上のテーブル(物理的に同じ)でない事。
			if( slaveTable == masterTable ) {
				final String errMsg = "マスタとスレーブが同一メモリになっています。"
							+ "通常、検索結果は TableId と Scope を別々に指定します。"
							+ CR
							+ "action=[" + action + "] "
							+ "masterTableId=[" + masterTableId + "] "
							+ "slaveTableId=[" + slaveTableId + "] "
							+ "masterScope=[" + masterScope + "] "
							+ "slaveScope=[" + slaveScope + "] " ;
				throw new HybsSystemException( errMsg );
			}
		}

		super.setScope( scope );
		useMainTrans( isMainTrans );			// 5.1.6.0 (2010/05/01) DBLastSqlの処理の見直し
		startQueryTransaction( tableId );		// 3.6.0.8 (2004/11/19)

		// 設定値の整合性チェック
		// 整合性チェック：action="UNION_ALL" と "GROUP"と "ADD_CLM"と "UNION_CLM" 以外では、masterKeys が必須
		if( ! ACT_UNION_ALL.equalsIgnoreCase( action )
			&& !ACT_UNION_SELROW.equalsIgnoreCase( action ) // 4.3.2.0 (2008/09/11)
			&& !ACT_GROUP.equalsIgnoreCase( action )
			&& !ACT_ADD_CLM.equalsIgnoreCase( action )
			&& !ACT_UNION_CLM.equalsIgnoreCase( action )
			&& masterKeys == null ) {
				final String errMsg = "action=\"" + action + "\" 時には、masterKeys が必須です。"
							+ CR
							+ "masterKeys=[" + masterKeys + "] ";
				throw new HybsSystemException( errMsg );
		}

		// 設定値の整合性チェック
		// 整合性チェック：action="DIFFERENCE" では、diffKeys が必須
		if( ACT_DIFFERENCE.equalsIgnoreCase( action ) && diffKeys == null ) {
			final String errMsg = "action=\"" + action + "\" 時には、diffKeys が必須です。"
						+ CR
						+ "diffKeys=[" + diffKeys + "] ";
			throw new HybsSystemException( errMsg );
		}

		// 設定値の整合性チェック
		// 整合性チェック：列合成(UNION_CLM) と グループ(GROUP) と 列追加(ADD_CLM) 以外では、テーブルカラム数が同じである必要がある。
		if( ! ACT_UNION_CLM.equalsIgnoreCase( action )
			 && !ACT_GROUP.equalsIgnoreCase( action )
			 && !ACT_ADD_CLM.equalsIgnoreCase( action ) ) {
			final int mClmSize = masterTable.getColumnCount();
			final int sClmSize = slaveTable.getColumnCount();

			if( mClmSize != sClmSize ) {
				final String errMsg = "action=\"" + action + "\" 時には、テーブルカラム数が異なってはいけません。"
							+ CR
							+ "Master=" + mClmSize + " ,["
							+ StringUtil.array2csv( masterTable.getNames() ) + "]"
							+ CR
							+ "Slave =" + sClmSize + " ,["
							+ StringUtil.array2csv( slaveTable.getNames() ) + "]";
				throw new HybsSystemException( errMsg );
			}
		}

		StringBuilder buf = null;
		if( display ) {
			buf = new StringBuilder( BUFFER_MIDDLE )
				.append( action ).append( '(' )											// 6.0.2.5 (2014/10/31) char を append する。
				.append( " M[" ).append( masterTable.getRowCount() ).append( ']' );		// 6.0.2.5 (2014/10/31) char を append する。
			if( slaveTable != null ) {
				buf.append( ",S[" ).append( slaveTable.getRowCount() ).append( ']' );	// 6.0.2.5 (2014/10/31) char を append する。
			}
		}

		// 4.3.3.0 (2008/10/01)
		setRequestAttribute( "DB.MASTER_COUNT"   , String.valueOf( masterTable.getRowCount() ) );
		// 4.3.3.1 (2008/10/08) nullチェック
		if( slaveTable != null ) {
			setRequestAttribute( "DB.SLAVE_COUNT", String.valueOf( slaveTable.getRowCount() ) );
		}

		DBTableModel table = null;
		if( ACT_UNION_ALL.equalsIgnoreCase( action ) ) {			// 全体集合
			table = makeUnionAll( masterTable,slaveTable );
		}
		else if( ACT_UNION_SELROW.equalsIgnoreCase( action ) ) {	// 4.3.2.0 (2008/09/11) 全体集合(slave表をmaster表のチェック行から追加)
			table = makeUnionSelrow( masterTable,slaveTable );
		}
		else if( ACT_UNION.equalsIgnoreCase( action ) ) {			// 和集合
			table = makeUnion( masterTable,masterMap,slaveTable,slaveMap );
		}
		else if( ACT_INTERSECT.equalsIgnoreCase( action ) ) {		// 積集合
			table = makeIntersect( masterTable,masterMap,slaveMap );
		}
		else if( ACT_MINUS.equalsIgnoreCase( action ) ) {			// 差集合
			table = makeMinus( masterTable,masterMap,slaveMap );
		}
		else if( ACT_DIFFERENCE.equalsIgnoreCase( action ) ) {		// 差分集合
			table = makeDifference( masterTable,masterMap,slaveTable,slaveMap );
		}
		else if( ACT_UNION_CLM.equalsIgnoreCase( action ) ) {		// 列合成
			if( unionClms == null ) {
				final String errMsg = "action=\"UNION_CLM\" 時には、unionClms が必須です。" ;
				throw new HybsSystemException( errMsg );
			}

			table = makeUnionClm( masterTable,slaveKeys,slaveTable,slaveMap );
		}
		else if( ACT_ADD_CLM.equalsIgnoreCase( action ) ) {		// 列追加
			if( unionClms == null ) {
				final String errMsg = "action=\"ADD_CLM\" 時には、unionClms が必須です。" ;
				throw new HybsSystemException( errMsg );
			}

			table = makeAddClm( masterTable );
		}
		else if( ACT_GROUP.equalsIgnoreCase( action ) ) {			// グループ
			table = makeGroup( masterTable );
		}

		// 6.0.2.4 (2014/10/17) NP:null 値を利用している可能性があります。 
		if( table == null ) {
			final String errMsg = "テーブルデータを作成できませんでした。action=[" + action + "]" ;
			throw new HybsSystemException( errMsg );
		}

//		if( table != null ) {		// 6.0.2.4 (2014/10/17) 先にチェックを入れました。
			// 3.6.0.8 (2004/11/19) トランザクションチェックを行います。
			super.setScope( scope );
			if( ! commitTableObject( tableId, table ) ) {
				return SKIP_PAGE ;
			}
//		}

		// 5.7.6.2 (2014/05/16) マージした結果の DBTableModel のデータ件数
		final int rowCnt = table.getRowCount();

		if( display ) {
			buf.append( " ) = [" ).append( rowCnt ).append( ']' );		// 6.0.2.5 (2014/10/31) char を append する。
			jspPrint( buf.toString() );
		}

		// 5.7.6.2 (2014/05/16) 検索結果の件数を、"DB.COUNT" キーでリクエストにセットする。
		setRequestAttribute( "DB.COUNT" , String.valueOf( rowCnt ) );

		// 5.7.6.2 (2014/05/16) 件数０件かつ stopZero = true
		if( rowCnt == 0 && stopZero ) { return SKIP_PAGE; }

		// 4.3.4.4 (2009/01/01)
//		setRequestAttribute( "DB.COUNT"   , ( table == null ? 0 :String.valueOf( table.getRowCount() ) ) );

		return EVAL_PAGE ;		// ページの残りを評価する。
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) groupAddClms 追加
	 * @og.rev 5.1.6.0 (2010/05/01) DBLastSqlの処理は、DBTableModelが新規作成された処理でのみ行う。
	 * @og.rev 5.3.1.0 (2011/01/01)	separator追加
	 * @og.rev 5.7.6.2 (2014/05/16) stopZero属性追加
	 */
	@Override
	protected void release2() {
		super.release2();
		command			= CMD_NEW;
		action			= null;
		tableId			= HybsSystem.TBL_MDL_KEY;	// 出力先の tableId
		scope			= "session";				// 出力先の scope
		masterTableId	= HybsSystem.TBL_MDL_KEY;	// マスタテーブルの tableId
		masterScope		= "session";				// マスタテーブルの scope
		slaveTableId	= HybsSystem.TBL_MDL_KEY;	// スレーブテーブルの tableId
		slaveScope		= "request";				// スレーブテーブルの scope
		masterKeys		= null;						// マスタテーブルの集合処理を行う主キー
		slaveKeys		= null;						// スレーブテーブルの集合処理を行う主キー(null時=masterKeys)
		diffKeys		= null;						// マスタテーブルのDIFFERENCE時の差分カラム名
		unionClms		= null;						// スレーブからマスタへ追加するカラム名
		modifyClms		= null;						// スレーブからマスタへ値を更新するカラム名
		groupAddClms	= null;						// 5.1.4.0 (2010/03/01) masterKeysで集合処理するときに、相違データをCSV連結して残すカラム名
		noSideEffect	= false;					// テーブルモデルに対する副作用(true:ない/false:ある)
		useDiffData		= true;						// DIFFERENCE時に差分のスレーブデータを追加するかどうか
		useCheckOnly	= false;					// マスタテーブルの選択行のデータのみを対象に処理を行うかどうか
		display			= true;
		stopZero		= false;					// 5.7.6.2 (2014/05/16) stopZero属性追加
		isMainTrans		= true;						// 5.1.6.0 (2010/05/01) DBLastSqlの処理の見直し
		separator		= ",";
	}

	/**
	 * 指定のテーブルの指定のカラム(CSV)より、行番号をマップ化します。
	 * なお、引数のテーブルについては、主キーによるグループ処理が行われます。(副作用あり)
	 *
	 * @og.rev 4.3.2.0 (2008/09/11) マスタキー未指定時は何もしない
	 * @og.rev 5.1.4.0 (2010/03/01) groupAddClms 追加
	 * @og.rev 5.3.1.0 (2011/01/01) groupAddClms の separator指定対応
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	table	マップ作成元のデータテーブル(副作用あり)
	 * @param	keys  	マップ作成の主キー(CSV形式)
	 * @param	useCheckOnly 	チェック行のみ対象 [true:する/false:しない]
	 *
	 * @return マップ化された主キーと行番号
	 */
	private Map<String,Integer> makeKeyMap( final DBTableModel table, final String keys, final boolean useCheckOnly ) {

		// カラム名をカラム番号に変換します。
		final int[] clmNo = makeColumnNo( table,keys );
		final int   clmSize = clmNo.length;
		if( clmSize == 0 ) {	// マスタキー未指定時には全カラムを使用します。
			return null; // 4.3.2.0 (2008/09/11)
		}

		// groupAddClms をカラム番号に変換します。
		final int[] grpClmNo = makeColumnNo( table,groupAddClms );
		final int   grpClmSize = grpClmNo.length;

		final Map<String,Integer> tempMap = new LinkedHashMap<String,Integer>();

		// 注意：GROUP化の過程で テーブル件数が動的に変化します。
//		StringBuilder buf ;
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );	// 6.1.0.0 (2014/12/26) refactoring
		for( int row=0; row<table.getRowCount(); row++ ) {
			if( useCheckOnly && table.getModifyType( row ).isEmpty() ) {
				continue;
			}

//			buf = new StringBuilder();
			buf.setLength(0);													// 6.1.0.0 (2014/12/26) refactoring
			for( int i=0; i<clmSize; i++ ) {
				buf.append( table.getValue( row,clmNo[i] ) ).append( '|' );		// 6.0.2.5 (2014/10/31) char を append する。
			}
			// 主キー連結文字列
			final String key = buf.toString().toUpperCase(Locale.JAPAN);		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( tempMap.containsKey( key ) ) {
	 			// 5.1.4.0 (2010/03/01) groupAddClms 追加
				final int orgRow = tempMap.get( key ).intValue() ;				// 追加済みの行番号を取得
				for( int i=0; i<grpClmSize; i++ ) {
					final int clm    = grpClmNo[i];
					final String val1 = table.getValue( orgRow,clm ) ;			// 既存の行・カラムのデータ
					final String val2 = table.getValue( row,clm ) ;				// 削除する行・カラムのデータ
					// 5.3.1.0 (2011/01/01)
					table.setValueAt( val1 + separator + val2,orgRow,clm );		// CSV 連結、データを戻す。
				}
				table.removeValue( row );
				row-- ;
			}
			else {							// まだ、未登録の場合
				tempMap.put( key,Integer.valueOf( row ) );
			}
		}

		return tempMap;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 全体集合 処理を実行します。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	slaveTbl	スレーブテーブルモデル
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeUnionAll( final DBTableModel masterTbl,final DBTableModel slaveTbl ) {
		final DBTableModel table = masterTbl;

		for( int row=0; row<slaveTbl.getRowCount(); row++ ) {
			final String[] vals = slaveTbl.getValues( row );
			table.addColumnValues( vals );
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 全体集合 処理を実行します。
	 * スレーブ表は、マスタ表のチェックされた行を起点として登録されます。
	 * チェックされていない場合は、スレーブ表は先頭から追加されます。
	 *
	 * @og.rev 4.3.2.0 (2008/09/11) 新規作成
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	slaveTbl	スレーブテーブルモデル
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeUnionSelrow( final DBTableModel masterTbl,final DBTableModel slaveTbl ) {
		final DBTableModel table = masterTbl;

		final int insRowNo = ( getParameterRows().length > 0 ) ? getParameterRows()[0] + 1 : 0 ;
		for( int row=0; row<slaveTbl.getRowCount(); row++ ) {
			final String[] vals = slaveTbl.getValues( row );
			table.addValues( vals, insRowNo + row, false );
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 和集合 処理を実行します。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	masterMap	マスターテーブルの主キーマップ
	 * @param	slaveTbl	スレーブテーブルモデル
	 * @param	slaveMap	スレーブテーブルの主キーマップ
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeUnion( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
									final DBTableModel slaveTbl,final Map<String,Integer> slaveMap ) {
		final DBTableModel table = masterTbl;

		@SuppressWarnings("rawtypes")
		final Map.Entry[] entry = slaveMap.entrySet().toArray( new Map.Entry[slaveMap.size()] );

		final int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( key != null ) { key = key.toUpperCase(Locale.JAPAN); }		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( ! masterMap.containsKey( key ) ) {		// マスタにスレーブデータが存在しない
				final int row =  ((Integer)entry[i].getValue()).intValue();
				final String[] vals = slaveTbl.getValues( row );
				table.addColumnValues( vals );
			}
		}
		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 積集合 処理を実行します。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	masterMap	マスターテーブルの主キーマップ
	 * @param	slaveMap	スレーブテーブルの主キーマップ
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeIntersect( final DBTableModel masterTbl,final Map<String,Integer> masterMap, final Map<String,Integer> slaveMap ) {
		final DBTableModel table = masterTbl;

		@SuppressWarnings("rawtypes")
		final Map.Entry[] entry = masterMap.entrySet().toArray( new Map.Entry[masterMap.size()] );

		final int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( key != null ) { key = key.toUpperCase(Locale.JAPAN); }		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( ! slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在しない
				final int row =  ((Integer)entry[i].getValue()).intValue();
				table.rowDelete( row );				// 論理削除
			}
		}

		// 行番号が変わらないように逆順削除します。
		for( int row=table.getRowCount()-1; row>=0; row-- ) {
			if( DBTableModel.DELETE_TYPE.equalsIgnoreCase( table.getModifyType( row ) ) ) {		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
				table.removeValue( row );
			}
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 差集合 処理を実行します。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	masterMap	マスターテーブルの主キーマップ
	 * @param	slaveMap	スレーブテーブルの主キーマップ
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeMinus( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
									final Map<String,Integer> slaveMap ) {
		final DBTableModel table = masterTbl;

		@SuppressWarnings("rawtypes")
		final Map.Entry[] entry = masterMap.entrySet().toArray( new Map.Entry[masterMap.size()] );

		final int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( key != null ) { key = key.toUpperCase(Locale.JAPAN); }		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在する
				final int row =  ((Integer)entry[i].getValue()).intValue();
				table.rowDelete( row );				// 論理削除
			}
		}

		// 行番号が変わらないように逆順削除します。
		for( int row=table.getRowCount()-1; row>=0; row-- ) {
			if( DBTableModel.DELETE_TYPE.equalsIgnoreCase( table.getModifyType( row ) ) ) {		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
				table.removeValue( row );
			}
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 差分集合 処理を実行します。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	masterMap	マスターテーブルの主キーマップ
	 * @param	slaveTbl	スレーブテーブルモデル
	 * @param	slaveMap	スレーブテーブルの主キーマップ
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeDifference( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
										 final DBTableModel slaveTbl ,final Map<String,Integer> slaveMap ) {
		final DBTableModel table = masterTbl;

		// テーブルの差分属性のカラム番号を求めます。(マスタ、スレーブ共通)
		final int[] diffClmNo = makeColumnNo( table,diffKeys );
		final int diffClmSize = diffClmNo.length;

		// スレーブからマスタへ値の再セットを行うカラム番号を求めます。(マスタ、スレーブ共通)
		final int[] modClmNo = makeColumnNo( table,modifyClms );
		final int modClmSize = modClmNo.length;

		final int clmSize = table.getColumnCount();
		final DBTableModel newTable = DBTableModelUtil.newDBTable();
		newTable.init( clmSize );

		// テーブルの全カラムを新たに作成するテーブルにコピーします。
		for( int i=0; i<clmSize; i++ ) {
			newTable.setDBColumn( i,table.getDBColumn( i ) );
		}

		// スレーブの先頭カラムが、WRITABLE かどうか
		final boolean writeFlag = "WRITABLE".equalsIgnoreCase( slaveTbl.getColumnName(0) );

		@SuppressWarnings("rawtypes")
		final Map.Entry[] entry = masterMap.entrySet().toArray( new Map.Entry[masterMap.size()] );

		final int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( key != null ) { key = key.toUpperCase(Locale.JAPAN); }		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在する
				final int mrow =  ((Integer)entry[i].getValue()).intValue();
				final int srow =  slaveMap.get( key ).intValue();
				boolean unmatched = false;
				for( int j=0; j<diffClmSize; j++ ) {
					final String mval = table.getValue( mrow,diffClmNo[j] );
					final String sval = slaveTbl.getValue( srow,diffClmNo[j] );
					if( mval != null && !mval.equalsIgnoreCase( sval ) ) { unmatched = true; break; }		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
				}

				if( unmatched ) {		// 属性情報が異なる場合のみ処理
					// データのコピー
					String[] mvals = new String[clmSize];
					System.arraycopy( table.getValues( mrow ),0,mvals,0 ,clmSize );

					// スレーブの値をマスタの値にセットする。
					for( int j=0; j<modClmSize; j++ ) {
						final String val = slaveTbl.getValue( srow,modClmNo[j] );
						mvals[modClmNo[j]] = val;
					}
					newTable.addColumnValues( mvals );

					if( useDiffData ) {
						// データのコピー
						String[] svals = new String[clmSize];
						System.arraycopy( slaveTbl.getValues( srow ),0,svals,0 ,clmSize );
						if( writeFlag ) { svals[0] = "0"; }	// 書き込み不許可
						for( int j=0; j<diffClmSize; j++ ) {
							final int dclmNo = diffClmNo[j];
							final String mval = mvals[dclmNo];
							final String sval = svals[dclmNo];
							if( mval != null && !mval.equalsIgnoreCase( sval ) ) {		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
								svals[dclmNo] = "<span class=\"unmatched\">" + sval + "</span>" ;
							}
						}
						newTable.addColumnValues( svals );
	//					newTable.setRowWritable( newTable.getRowCount()-1,false );	// 書き込み不許可
					}
				}
			}
		}
		return newTable;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 列合成 処理を実行します。
	 *
	 * すでに、マスタ にカラムが存在している場合は、値の書き換えを、
	 * カラムが存在しなければ、カラムを追加します。
	 * マスタとスレーブのデータ突合せ時のキーは、slaveKeys になります。
	 * これは、masterMap を使用しないことを意味します。(masterKeys 指定によるGROUP化は
	 * 行います。masterKeys を指定しない場合は、サマライズなしで処理します。)
	 * 具体的には、マスタテーブルの複数の行に対して、スレーブデータの値が設定
	 * される可能性があることを意味します。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 * @param	slaveKeys 	スレーブキー
	 * @param	slaveTbl	スレーブテーブルモデル
	 * @param	slaveMap	スレーブテーブルの主キーマップ
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeUnionClm( final DBTableModel masterTbl ,final String slaveKeys ,
										final DBTableModel slaveTbl ,final Map<String,Integer> slaveMap ) {

		final DBTableModel table = makeAddClm( masterTbl );

		// カラム名をカラム番号に変換します。
		final int[] keyClmNo = makeColumnNo( table,slaveKeys );

		final int[] mClmNo = makeColumnNo( table   ,unionClms );
		final int[] sClmNo = makeColumnNo( slaveTbl,unionClms );
		final int   addSize = mClmNo.length;		// unionClms の個数なので、マスタ、スレーブは同一

		// 突合せを行い、マッチするカラムについて、値をセットし直します。
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );	// 6.1.0.0 (2014/12/26) refactoring
		for( int row=0; row<table.getRowCount(); row++ ) {
			String[] mvals = table.getValues( row );
//			final StringBuilder buf = new StringBuilder();
			buf.setLength(0);										// 6.1.0.0 (2014/12/26) refactoring
			for( int i=0; i<keyClmNo.length; i++ ) {
				buf.append( mvals[keyClmNo[i]] ).append( '|' );		// 6.0.2.5 (2014/10/31) char を append する。
			}
			// 主キー連結文字列
			final String key = buf.toString().toUpperCase(Locale.JAPAN);		// 5.5.8.5 (2012/11/27) 大文字・小文字を区別しない。
			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタに対応するデータが存在する
				final int slvRow = slaveMap.get( key ).intValue();
				final String[] svals = slaveTbl.getValues( slvRow );		// スレーブの行番号
				for( int j=0; j<addSize; j++ ) {
					mvals[mClmNo[j]] = svals[sClmNo[j]];
				}
				table.setValues( mvals,row );
			}
		}
		table.resetModify();

		return table;
	}

	/**
	 * 指定のマスタテーブルに 列追加 処理を実行します。
	 *
	 * すでに、マスタ にカラムが存在している場合は、値の書き換えを、
	 * カラムが存在しなければ、カラムを追加します。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeAddClm( final DBTableModel masterTbl ) {
		final String[]  addClms	 = StringUtil.csv2Array( unionClms );
		final int       addClmSize = addClms.length;
		boolean[] useAdd	 = new boolean[addClmSize];
		int addSize = 0;
		for( int i=0; i<addClmSize; i++ ) {
			if( masterTbl.getColumnNo( addClms[i],false ) < 0 ) {	// 存在しなければ -1
				useAdd[i] = true;
				addSize++ ;
			}
			else {
				useAdd[i] = false;		// マスタに存在すれば、追加不要
			}
		}

		final int mstrClmSize = masterTbl.getColumnCount();
		final int clmSize = mstrClmSize + addSize;

		final DBTableModel table = DBTableModelUtil.newDBTable();
		table.init( clmSize );
		int clmNo = 0;
		// マスタテーブルの全カラムを新たに作成するテーブルにコピーします。
		for( int i=0; i<mstrClmSize; i++ ) {
			table.setDBColumn( clmNo++,masterTbl.getDBColumn( i ) );
		}
		// 追加するカラムを新たに作成するテーブルに追加します。
		String[] addClmVal = new String[addSize];	// 追加分のみ
		int addCnt = 0;
		for( int i=0; i<addClmSize; i++ ) {
			if( useAdd[i] ) {
				final DBColumn column = getDBColumn( addClms[i] );
				addClmVal[addCnt++] = nval( column.getDefault(),"" );
				table.setDBColumn( clmNo++,column );
			}
		}
		// 一旦、すべてのマスタデータを新テーブルにコピーします。
		for( int row=0; row<masterTbl.getRowCount(); row++ ) {
			final String[] vals = new String[clmSize];
			System.arraycopy( masterTbl.getValues( row ),0,vals,0 ,mstrClmSize );	// マスタデータのコピー
			System.arraycopy( addClmVal,0,vals,mstrClmSize ,addSize );				// 追加列情報の初期値
			table.addColumnValues( vals );
		}

		return table;
	}

	/**
	 * 指定のマスタテーブルを使用して グループ 処理を実行します。
	 *
	 * @param	masterTbl	マスターテーブルモデル
	 *
	 * @return	処理結果のテーブルモデル
	 */
	private DBTableModel makeGroup( final DBTableModel masterTbl ) {
		final DBTableModel table = masterTbl;

		return table;
	}

	/**
	 * 指定のテーブルと同じテーブルを別オブジェクトとして作成します。
	 *
	 * @param	tbl	コピー元テーブルモデル
	 *
	 * @return	コピーされた新テーブルモデル
	 */
	private DBTableModel cloneTable( final DBTableModel tbl ) {
		final int clmSize = tbl.getColumnCount();

		final DBTableModel table = DBTableModelUtil.newDBTable();
		table.init( clmSize );

		// テーブルの全カラムを新たに作成するテーブルにコピーします。
		for( int i=0; i<clmSize; i++ ) {
			table.setDBColumn( i,tbl.getDBColumn( i ) );
		}
		// すべてのデータを新テーブルにコピーします。
		for( int row=0; row<tbl.getRowCount(); row++ ) {
			final String[] vals = new String[clmSize];
			System.arraycopy( tbl.getValues( row ),0,vals,0 ,clmSize );	// データのコピー
			table.addColumnValues( vals );
		}
		return table;
	}

	/**
	 * 指定のテーブルより、カラム列名に対応するカラム番号配列を作成します。
	 *
	 * カラム名のCSVデータが 指定されない場合(clmcsv == null)は、長さ０の配列を返します。
	 *
	 * @param	table	テーブルモデル
	 * @param	clmcsv	カラム名のCSVデータ
	 *
	 * @return	カラム名に対応する、列番号配列(なければ、長さ０配列)
	 */
	private int[] makeColumnNo( final DBTableModel table,final String clmcsv ) {

		// マスタテーブルの差分属性のカラム番号を求めます。
		final String[] clms = StringUtil.csv2Array( clmcsv );
		int[] clmNo = new int[clms.length];

		// カラム名をカラム番号に変換します。
		for( int i=0; i<clms.length; i++ ) {
			clmNo[i] = table.getColumnNo( clms[i] );
		}

		return clmNo;
	}

	/**
	 * 【TAG】コマンド (NEW,RENEW)をセットします(初期値:NEW)。
	 *
	 * @og.tag
	 * コマンドは,HTMLから(get/post)指定されますので,CMD_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 * 初期値は NEW です。
	 *
	 * @param	cmd	コマンド (public static final 宣言されている文字列)
	 * @see		<a href="../../../../constant-values.html#org.opengion.hayabusa.taglib.TableMergeTag.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】アクションを指定します[UNION_ALL/UNION/INTERSECT/MINUS/DIFFERENCE/UNION_CLM/ADD_CLM/GROUP/UNION_SELROW]。
	 *
	 * @og.tag
	 * 指定できるアクションは、全体集合(UNION_ALL)、全体集合(挿入位置指定)(UNION_SELROW)、和集合(UNION)
	 * 、積集合(INTERSECT)、差集合(MINUS)、差分集合(DIFFERENCE)、列合成(UNION_CLM)、列追加(ADD_CLM)、 グループ(GROUP)です。
	 * 列合成とグループ以外の処理では、カラム順とカラム数は同数でなければなりません。
	 * primaryKeys や unionClms などの指定のキー名は、マスタテーブルに存在する必要があります。
	 * マスタテーブルと同じカラム番号でスレーブテーブルよりデータを読み出します。
	 * (カラム名や属性は、異なってもかまいませんが、マスタテーブルに準拠します。)
	 * また、単独(マスタテーブルのみ)で、和集合と同等の、グループ(GROUP)を使用すると、指定の
	 * カラムでのユニーク化を行うことが可能になります。グループ処理では、先行優先とし、
	 * ２回目に現れた情報を削除することになります。グループ が指定された場合は、
	 * スレーブテーブルは無視されます。いずれの処理においても、集合処理を行う主キーで
	 * 一旦グループ化されます。全体集合(UNION_ALL)で処理する場合でも、主キーがユニークで
	 * ない場合は、マスター、スレーブの各テーブルで一旦グループ化された後で、マージされます。
	 * (マージ後には、同一主キーを持つ行は存在します。)
	 * 全体集合(UNION_ALL)の場合のみ、mergeKeys を指定する必要はありません。その場合は、
	 * キーなしのため、マスターとスレーブのテーブルを単に合成するだけになります。
	 *
	 * <table border="1" frame="box" rules="all" >
	 *   <caption>アクションの説明</caption>
	 *   <tr><th>action      </th><th>名称    </th><th>処理概要												</th><th>１</th><th>２</th><th>３</th><th>４</th><tr>
	 *   <tr><td>UNION_ALL   </td><td>全体集合</td><td>マスタとスレーブを合成
	 *   <tr><td>UNION_SELROW</td><td>全体集合</td><td>マスタとスレーブを合成(マスタ表のチェック行を起点に追加</td><td>○</td><td>　</td><td>　</td><td>　</td><tr>
	 *   <tr><td>UNION       </td><td>和集合  </td><td>マスタとスレーブのユニーク部のみ合成					</td><td>○</td><td>○</td><td>　</td><td>　</td><tr>
	 *   <tr><td>INTERSECT   </td><td>積集合  </td><td>マスタとスレーブのユニーク部が一致するマスタのみ選択	</td><td>○</td><td>○</td><td>　</td><td>　</td><tr>
	 *   <tr><td>MINUS       </td><td>差集合  </td><td>マスタからスレーブに存在するユニーク部を削除した残り	</td><td>○</td><td>○</td><td>　</td><td>　</td><tr>
	 *   <tr><td>DIFFERENCE  </td><td>差分集合</td><td>ユニーク部が一致し、差分カラムが異なるマスタのみ選択	</td><td>○</td><td>○</td><td>　</td><td>○</td><tr>
	 *   <tr><td>UNION_CLM   </td><td>列合成  </td><td>マスタとキー一致するスレーブのカラム情報を追加		</td><td>○</td><td>○</td><td>○</td><td>　</td><tr>
	 *   <tr><td>ADD_CLM     </td><td>列追加  </td><td>UNION_CLMとの違いは、カラムのみ追加することです。	</td><td>　</td><td>　</td><td>○</td><td>　</td><tr>
	 *   <tr><td>GROUP       </td><td>グループ</td><td>マスタのユニーク部化									</td><td>　</td><td>　</td><td>　</td><td>　</td><tr>
	 * </table>
	 *
	 * ※：マスタテーブルオブジェクトは、常に必須
	 * １：スレーブテーブルオブジェクト必須
	 * ２：masterKeys 属性必須
	 * ３：unionClms 属性必須(スレーブテーブルのカラム名または追加するカラム名)
	 * ４：diffKeys 属性必須(DIFFERENCE時の差分カラム名)、modifyClms 属性使用可能
	 *
	 * @param	action アクション [UNION_ALL/UNION/INTERSECT/MINUS/DIFFERENCE/UNION_CLM/ADD_CLM/GROUP/UNION_SELROW]
	 */
	public void setAction( final String action ) {
		this.action = nval( getRequestParameter( action ),this.action );
	}

	/**
	 * 【TAG】出力先のtableIdを指定します
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @og.tag
	 * 集合処理結果の DBTableModel をメモリにセットする場合のキー(tableId)を指定します。
	 * (初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	id テーブルID (sessionに登録する時のID)
	 * @see		org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ),tableId );
	}

	/**
	 * 【TAG】出力先のscopeを指定します(初期値:session)。
	 *
	 * @og.tag
	 * 集合処理結果の DBTableModel をメモリにセットする場合のスコープを指定します。
	 * ここでは、マスタやスレーブのスコープ設定が必要な為、superクラスのメソッドを
	 * オーバーライドしてこのオブジェクト内でキープしています。
	 * 初期値は、session です。
	 *
	 * @param	scope	出力先のscope
	 */
	@Override
	public void setScope( final String scope ) {
		this.scope = nval( getRequestParameter( scope ),this.scope );
	}

	/**
	 * 【TAG】マスタテーブルのtableIdを指定します
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @og.tag
	 * 集合処理のマスタとなる DBTableModel をメモリから取り出す場合のキー(tableId)を指定します。
	 * (初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	masterTableId	マスタテーブルID
	 * @see		org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY
	 */
	public void setMasterTableId( final String masterTableId ) {
		this.masterTableId = nval( getRequestParameter( masterTableId ),this.masterTableId );
	}

	/**
	 * 【TAG】マスタテーブルのscopeを指定します(初期値:session)。
	 *
	 * @og.tag
	 * 集合処理のマスタとなる DBTableModel をメモリから取り出す場合のスコープを指定します。
	 * 初期値は、session です。
	 *
	 * @param	masterScope	マスタスコープ
	 */
	public void setMasterScope( final String masterScope ) {
		this.masterScope = nval( getRequestParameter( masterScope ),this.masterScope );
	}

	/**
	 * 【TAG】マスタテーブルの集合処理を行う主キーを指定します。
	 *
	 * @og.tag
	 * 集合処理を行う場合の、カラム名を、CSV形式(CSV形式)で指定します。
	 * このキーの組み合わせを元に、集合処理の突合せを行います。
	 * なお、アクションがグループ(GROUP)以外の処理では、マスタとスレーブのカラム数と
	 * 並び順は、同じでなければなりません。カラム名は、各々別々でもかまいません。
	 * アクションが全体集合(UNION_ALL)以外の場合は、必須属性になります。
	 *
	 * @param	masterKeys	マスタテーブル主キー (CSV形式)
	 */
	public void setMasterKeys( final String masterKeys ) {
		this.masterKeys = nval( getRequestParameter( masterKeys ),this.masterKeys );
	}

	/**
	 * 【TAG】スレーブテーブルの集合処理を行う主キーを指定します。
	 *
	 * @og.tag
	 * 集合処理を行う場合の、カラム名を、CSV形式(CSV形式)で指定します。
	 * このキーの組み合わせを元に、集合処理の突合せを行います。
	 * なお、アクションがグループ(GROUP)以外の処理では、マスタとスレーブのカラム数と
	 * 並び順は、同じでなければなりません。カラム名は、各々別々でもかまいません。
	 * null の場合は、masterKeys と同じとします。
	 *
	 * @param	slaveKeys	スレーブ主キー (CSV形式)
	 */
	public void setSlaveKeys( final String slaveKeys ) {
		this.slaveKeys = nval( getRequestParameter( slaveKeys ),this.slaveKeys );
	}

	/**
	 * 【TAG】マスタテーブルのDIFFERENCE時の差分カラム名を(CSV形式)指定します。
	 *
	 * @og.tag
	 * アクションが差分処理(DIFFERENCE)の場合に、差分チェックを行うカラム名を、
	 * CSV形式(CSV形式)で指定します。
	 * 差分処理とは、masterKeys で指定されたキーでは、マスタ、スレーブともに存在し
	 * かつ、このキー(diffKeys)で指定されたキーの値が異なるマスタレコードを
	 * 抜き出します。
	 * つまり、主キーは存在し、属性が異なる情報のピックアップになりますので、
	 * データ更新(UPDATE)対象を見つける場合に使用できます。
	 * アクションが差分処理(DIFFERENCE)の場合は、必須属性になります。
	 *
	 * @param	diffKeys	差分カラム名 (CSV形式)
	 * @see		#setMasterKeys( String )
	 */
	public void setDiffKeys( final String diffKeys ) {
		this.diffKeys = nval( getRequestParameter( diffKeys ),this.diffKeys );
	}

	/**
	 * 【TAG】スレーブテーブルのtableIdを指定します
	 *		(初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @og.tag
	 * 集合処理のスレーブとなる DBTableModel をメモリから取り出す場合のキー(tableId)を指定します。
	 * なお、アクションがグループ(GROUP)の場合は、スレーブテーブルは使用されません。
	 * (初期値:HybsSystem#TBL_MDL_KEY[={@og.value org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY}])。
	 *
	 * @param	slaveTableId	スレーブテーブルID
	 * @see		org.opengion.hayabusa.common.HybsSystem#TBL_MDL_KEY
	 */
	public void setSlaveTableId( final String slaveTableId ) {
		this.slaveTableId = nval( getRequestParameter( slaveTableId ),this.slaveTableId );
	}

	/**
	 * 【TAG】スレーブテーブルのscopeを指定します(初期値:session)。
	 *
	 * @og.tag
	 * 集合処理のスレーブとなる DBTableModel をメモリから取り出す場合のスコープを指定します。
	 * なお、アクションがグループ(GROUP)の場合は、スレーブテーブルは使用されません。
	 * 初期値は、session です。
	 *
	 * @param	slaveScope	スレーブスコープ
	 */
	public void setSlaveScope( final String slaveScope ) {
		this.slaveScope = nval( getRequestParameter( slaveScope ),this.slaveScope );
	}

	/**
	 * 【TAG】スレーブからマスタへ追加するカラム名をCSV形式で指定します。
	 *
	 * @og.tag
	 * アクションが列合成(UNION_CLM)または列追加(ADD_CLM)の場合に使用されます。
	 * 列合成(UNION_CLM)は、マスタとスレーブの主キーに対して、ここで指定のスレーブの
	 * カラム列名を、マスタの列に追加します。主キーがマッチしない行に関しては、
	 * カラムの初期値が適用されたデータを作成します。
	 * 列追加(ADD_CLM)は、マスタテーブルに指定のカラムを追加するだけです、スレーブテーブルは
	 * 参照しません。よって、主キーも指定不要です。
	 *
	 * @param	unionClms	列合成カラム名 (CSV形式)
	 */
	public void setUnionClms( final String unionClms ) {
		this.unionClms = nval( getRequestParameter( unionClms ),this.unionClms );
	}

	/**
	 * 【TAG】スレーブからマスタへ値を更新するカラム名をCSV形式で指定します。
	 *
	 * @og.tag
	 * アクションが差分処理(DIFFERENCE)の場合に、結果にマスタテーブルが抜き出されますが、
	 * 更新する場合に、スレーブ特有のユニークキー(例：UNIQ)を用いて更新する場合、
	 * 指定のカラム値は、スレーブの値にセットしておきたい場合があります。
	 * ここでは、指定のカラムについて、値だけスレーブからマスタへセットします。
	 * なお、値の更新については、マスタとスレーブが同一キーという制約があります。
	 *
	 * @param	modifyClms	値更新カラム名 (CSV形式)
	 */
	public void setModifyClms( final String modifyClms ) {
		this.modifyClms = nval( getRequestParameter( modifyClms ),this.modifyClms );
	}

	/**
	 * 【TAG】集合処理するときに、相違データをCSV連結して残すカラム名をCSV形式で指定します。
	 *
	 * @og.tag
	 * masterKeysで集合処理するときに、通常、最初に見つかった行データのみ残りますが、
	 * ここに指定したカラムについては、発生都度、自分自身の情報に、CSV形式で連結して
	 * いきます。
	 * この操作により、本来削除された情報が、１行のCSV形式で取得できる効果が得られます。
	 * これは、value タグの action="APPEND" を、DBTableModel に対して実施するような感じです。
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) 新規追加
	 *
	 * @param	groupAddClms	相違データCSV連結 (CSV形式)
	 */
	public void setGroupAddClms( final String groupAddClms ) {
		this.groupAddClms = nval( getRequestParameter( groupAddClms ),this.groupAddClms );
	}

	/**
	 * 【TAG】テーブルモデルに対する副作用の有無[true:ない/false:ある]を指定します(初期値:false:ある)。
	 *
	 * @og.tag
	 * すべての処理で、DBTableModel に対して、ユニーク化やグループ化などの集合処理を
	 * 行う過程で、マスタテーブルに対して直接処理を行うと、副作用が発生します。
	 * 同様に、スレーブテーブルにおいても、一旦キー列でグループ化されるため、副作用が
	 * 発生します。これは、無駄なメモリ領域の確保と、テーブル(マスタ、スレーブとも)の
	 * コピー処理時間の節約になります。初期値の設定も副作用がある状態になっています。
	 * ところが、都合によっては、色々な action を連続して行いたい場合など、毎回、
	 * データベースを検索するよりもメモリ上でコピーしたほうが都合がよいケースでは、
	 * 副作用が出ないように、noSideEffect="true" に設定します。
	 * ただし、マスタ、スレーブともテーブルをコピーを行い、結果のテーブルも派生する為、
	 * 通常、２つの領域(マスタと結果は同じテーブルに書かれる)で良い所を、５つの領域が
	 * 作成されます。
	 * 初期値は、副作用がある(noSideEffect="false")です。
	 *
	 * @param	noSideEffect	処理結果副作用 [true:ない/false:ある]
	 */
	public void setNoSideEffect( final String noSideEffect ) {
		this.noSideEffect = nval( getRequestParameter( noSideEffect ),this.noSideEffect );
	}

	/**
	 * 【TAG】差分のスレーブデータを結果テーブルに追加するかどうかを指定します(初期値:true)。
	 *
	 * @og.tag
	 * アクションが差分処理(DIFFERENCE)の場合に、結果にマスタテーブルが抜き出されますが、
	 * 差分対象のスレーブデータと比較したい場合があります。
	 * このフラグを true にセットすると、書込み禁止属性が付いた状態で、スレーブデータが
	 * 結果テーブルに追加されます。
	 * なお、この処理では、通常と異なり、マスタテーブルにはグループ化の副作用は発生しますが、
	 * 結果テーブルは新規に作成され、先頭行に必ず WRITABLE カラムが付加されます。
	 * 初期値は、true：追加する です。
	 *
	 * @param	flag	差分追加 [true:追加する/false:追加しない]
	 */
	public void setUseDiffData( final String flag ) {
		useDiffData = nval( getRequestParameter( flag ),useDiffData );
	}

	/**
	 * 【TAG】マスタテーブルの選択行のデータのみを対象に処理を行うかどうか[true:する/false:しない]を指定します(初期値:false)。
	 *
	 * @og.tag
	 * 処理対象のマスタテーブルについて、選択行が指定された場合に、選択行のみを処理対象に
	 * するか、全件を対象にするかを指定します。
	 * 積集合や差分集合など通常は、全件を対象にすることになりますが、列合成や列追加など、
	 * マスタテーブルに対してのみ作用を及ぼす処理では、選択行のみを対象に処理を行う事が
	 * 考えられます。その場合、初期グループ化と同じで、対象とする行が選択行のみになります。
	 * 初期値は、false：全件対象 です。
	 *
	 * @param	flag	選択行のみ対象 [true:選択行のみ/false:全件]
	 */
	public void setUseCheckOnly( final String flag ) {
		useCheckOnly = nval( getRequestParameter( flag ),useCheckOnly );
	}

	/**
	 * 【TAG】マージの結果を表示するかどうか[true:する/false:しない]を指定します(初期値:true)。
	 *
	 * @og.tag
	 * true で、マージ結果を表示します。 false では、何も表示しません(初期値:true)
	 * マスタテーブルの件数は、通常、キーでグループ化されるため、入力件数と異なります。
	 * 同様に、スレーブ件数も異なります。結果の件数は、処理結果が現実的かどうかの
	 * 判断に使用されます。
	 * 初期値は、true：表示する です。
	 *
	 * @param	flag	結果表示 [true:する/false:しない]
	 */
	public void setDisplay( final String flag ) {
		display = nval( getRequestParameter( flag ),display );
	}

	/**
	 * 【TAG】検索結果が０件のとき処理を停止するかどうか[true/false]を指定します(初期値:false[続行する])。
	 *
	 * @og.tag
	 * 初期値は、false(続行する)です。
	 *
	 * @og.rev 5.7.6.2 (2014/05/16) 新規追加
	 *
	 * @param  cmd ０件時停止 [true:処理を中止する/false:続行する]
	 */
	public void setStopZero( final String cmd ) {
		stopZero = nval( getRequestParameter( cmd ),stopZero );
	}

	/**
	 * 【TAG】(通常使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します(初期値:true)。
	 *
	 * @og.tag
	 * この値は、ファイルダウンロード処理に影響します。この値がtrueに指定された時にcommitされたDBTableModelが
	 * ファイルダウンロードの対象の表になります。
	 *
	 * このパラメーターは、通常、各タグにより実装され、ユーザーが指定する必要はありません。
	 * 但し、1つのJSP内でDBTableModelが複数生成される場合に、前に処理したDBTableModelについてファイルダウンロードをさせたい
	 * 場合は、後ろでDBTableModelを生成するタグで、明示的にこの値をfalseに指定することで、ファイルダウンロード処理の対象から
	 * 除外することができます。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規作成
	 *
	 * @param  flag	メイントランザクションかどうか [true:メイン/false:その他]
	 */
	public void setMainTrans( final String flag ) {
		isMainTrans = nval( getRequestParameter( flag ),isMainTrans );
	}

	/**
	 * 【TAG】groupAddClmsで文字列を連結する項目区切り文字をセットします(初期値:",")。
	 *
	 * @og.tag
	 * groupAddClmsで文字列を連結する項目区切り文字をセットします(初期値:",")。
	 * 初期値は、"," に設定されています。
	 *
	 * @og.rev 5.3.1.0 (2011/01/01)
	 *
	 * @param   sepa	項目区切り文字 (初期値:",")
	 */
	public void setSeparator( final String sepa ) {
		separator = nval( getRequestParameter( sepa ),separator );
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "action"			,action			)
				.println( "tableId"			,tableId		)
				.println( "scope"			,scope			)
				.println( "masterTableId"	,masterTableId	)
				.println( "masterScope"		,masterScope	)
				.println( "slaveTableId"	,slaveTableId	)
				.println( "slaveScope"		,slaveScope		)
				.println( "masterKeys"		,masterKeys		)
				.println( "diffKeys"		,diffKeys		)
				.println( "unionClms"		,unionClms		)
				.println( "modifyClms"		,modifyClms		)
				.println( "noSideEffect"	,noSideEffect	)
				.println( "useDiffData"		,useDiffData	)
				.println( "useCheckOnly"	,useCheckOnly	)
				.println( "display"			,display		)
				.println( "ACTION_LIST"		,ACTION_LIST	)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
