/*
 * 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 static org.opengion.fukurou.util.StringUtil.nval ;

import java.util.Map;
import java.util.HashMap;
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：なし
 *
 * ●使用例
 *     例１）デフォルト以外に必要な属性のみ指定するサンプル
 *     &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 = "4.0.0 (2005/11/30)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/11/30)

	/** 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		= null;
	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		= null;							// マスタテーブルの集合処理を行う主キー
	private String	slaveKeys		= null;							// スレーブテーブルの集合処理を行う主キー(null時=masterKeys)
	private String	diffKeys		= null;							// マスタテーブルのDIFFERENCE時の差分カラム名
	private String	unionClms		= null;							// UNION_CLM時にスレーブからマスタへ追加するカラム名
	private String	modifyClms		= null;							// DIFFERENCE時にスレーブからマスタへ値を更新するカラム名
	private String	groupAddClms	= null;							// 5.1.4.0 (2010/03/01) masterKeysで集合処理するときに、相違データをCSV連結して残すカラム名
	private boolean	noSideEffect	= false;						// テーブルモデルに対する副作用(true:ない/false:ある)
	private boolean	useDiffData		= true;							// DIFFERENCE時に差分のスレーブデータを追加するかどうか
	private boolean	useCheckOnly	= false;						// マスタテーブルの選択行のデータのみを対象に処理を行うかどうか
	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で結合する際の区切り文字

	/**
	 * 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 属性対応
	 *
	 * @return  int 後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)
		// 5.2.2.0 (2010/11/01) caseKey 、caseVal 属性対応
//		if( ! check( command, COMMAND_LIST ) ) { return(EVAL_PAGE); }
		if( !useTag() || ! check( command, COMMAND_LIST ) ) { return(EVAL_PAGE); }

		// 整合性チェック：アクションリストとの存在チェック
		if( !check( action, ACTION_LIST ) ) {
			String errMsg = "指定のアクションは実行できません。アクションエラー"
							+ HybsSystem.CR
							+ "action=[" + action + "] "
							+ HybsSystem.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 ) {
			String errMsg = "マスタテーブルは必須です。"
						+ HybsSystem.CR
						+ "action=[" + action + "] "
						+ "masterTableId=[" + masterTableId + "] "
						+ "masterScope=[" + masterScope + "] " ;
			throw new HybsSystemException( errMsg );
		}
		if( noSideEffect ) { masterTable = cloneTable( masterTable ); }
		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 ) {
				String errMsg = "action=\"" + action + "\" 時には、スレーブテーブルが必要です。"
							+ HybsSystem.CR
							+ "slaveTableId=[" + slaveTableId + "] "
							+ "slaveScope=[" + slaveScope + "] " ;
				throw new HybsSystemException( errMsg );
			}
			if( noSideEffect ) { slaveTable = cloneTable( slaveTable ); }
			slaveMap = makeKeyMap( slaveTable,slaveKeys,false );	// スレーブはuseCheckOnly無関係

			// その場合、マスタとスレーブが同一メモリ上のテーブル(物理的に同じ)でない事。
			if( slaveTable == masterTable ) {
				String errMsg = "マスタとスレーブが同一メモリになっています。"
							+ "通常、検索結果は TableId と Scope を別々に指定します。"
							+ HybsSystem.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 ) {
				String errMsg = "action=\"" + action + "\" 時には、masterKeys が必須です。"
							+ HybsSystem.CR
							+ "masterKeys=[" + masterKeys + "] ";
				throw new HybsSystemException( errMsg );
		}

		// 設定値の整合性チェック
		// 整合性チェック：action="DIFFERENCE" では、diffKeys が必須
		if( ACT_DIFFERENCE.equalsIgnoreCase( action ) && diffKeys == null ) {
			String errMsg = "action=\"" + action + "\" 時には、diffKeys が必須です。"
						+ HybsSystem.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 ) ) {
			int mClmSize = masterTable.getColumnCount();
			int sClmSize = slaveTable.getColumnCount();

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

		StringBuilder buf = null;
		if( display ) {
			buf = new StringBuilder();
			buf.append( action ).append( "(" );
			buf.append( " M[" ).append( masterTable.getRowCount() ).append( "]" );
			if( slaveTable != null ) {
				buf.append( ",S[" ).append( slaveTable.getRowCount() ).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 ) {
				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 ) {
				String errMsg = "action=\"ADD_CLM\" 時には、unionClms が必須です。" ;
				throw new HybsSystemException( errMsg );
			}

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

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

		if( display ) {
			buf.append( " ) = [" ).append( table.getRowCount() ).append( "]" );
			jspPrint( buf.toString() );
		}
	
		// 4.3.3.0 (2008/10/01)
		// 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追加
	 */
	@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;
		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指定対応
	 *
	 * @param	table  DBTableModel マップ作成元のデータテーブル(副作用あり)
	 * @param	keys   String  マップ作成の主キー(CSV形式)
	 * @param	useCheckOnly  boolean チェック行のみを対象にするかどうか
	 * @return マップ化された主キーと行番号
	 */
	private Map<String,Integer> makeKeyMap( final DBTableModel table, final String keys, final boolean useCheckOnly ) {

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

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

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

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

			buf = new StringBuilder();
			for( int i=0; i<clmSize; i++ ) {
				buf.append( table.getValue( row,clmNo[i] ) ).append( "|" );
			}
			String key = buf.toString();	// 主キー連結文字列
			if( tempMap.containsKey( key ) ) {
	 			// 5.1.4.0 (2010/03/01) groupAddClms 追加
				int orgRow = tempMap.get( key ).intValue() ;				// 追加済みの行番号を取得
				for( int i=0; i<grpClmSize; i++ ) {
					int clm    = grpClmNo[i];
					String val1 = table.getValue( orgRow,clm ) ;			// 既存の行・カラムのデータ
					String val2 = table.getValue( row,clm ) ;				// 削除する行・カラムのデータ
					// 5.3.1.0 (2011/01/01)
//					table.setValueAt( val1 + "," + val2,orgRow,clm );		// CSV 連結、データを戻す。
					table.setValueAt( val1 + separator + val2,orgRow,clm );		// CSV 連結、データを戻す。
				}
				table.removeValue( row );
				row-- ;
			}
			else {							// まだ、未登録の場合
				tempMap.put( key,Integer.valueOf( row ) );
			}
		}

		return tempMap;
	}

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

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

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

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

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 和集合 処理を実行します。
	 *
	 * @param	masterTbl  DBTableModel マスターテーブルモデル
	 * @param	masterMap  Map<String,Integer> マスターテーブルの主キーマップ
	 * @param	slaveTbl   DBTableModel スレーブテーブルモデル
	 * @param	slaveMap   Map<String,Integer> スレーブテーブルの主キーマップ
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeUnion( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
									final DBTableModel slaveTbl,final Map<String,Integer> slaveMap ) {
		DBTableModel table = masterTbl;

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

		int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( ! masterMap.containsKey( key ) ) {		// マスタにスレーブデータが存在しない
				int row =  ((Integer)entry[i].getValue()).intValue();
				String[] vals = slaveTbl.getValues( row );
				table.addColumnValues( vals );
			}
		}
		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 積集合 処理を実行します。
	 *
	 * @param	masterTbl  DBTableModel マスターテーブルモデル
	 * @param	masterMap  Map<String,Integer> マスターテーブルの主キーマップ
	 * @param	slaveMap   Map<String,Integer> スレーブテーブルの主キーマップ
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeIntersect( final DBTableModel masterTbl,final Map<String,Integer> masterMap, final Map<String,Integer> slaveMap ) {
		DBTableModel table = masterTbl;

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

		int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( ! slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在しない
				int row =  ((Integer)entry[i].getValue()).intValue();
				table.rowDelete( row );				// 論理削除
			}
		}

		// 行番号が変わらないように逆順削除します。
		for( int row=table.getRowCount()-1; row>=0; row-- ) {
			if( DBTableModel.DELETE_TYPE.equals( table.getModifyType( row ) ) ) {
				table.removeValue( row );
			}
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 差集合 処理を実行します。
	 *
	 * @param	masterTbl  DBTableModel マスターテーブルモデル
	 * @param	masterMap  Map<String,Integer> マスターテーブルの主キーマップ
	 * @param	slaveMap   Map<String,Integer> スレーブテーブルの主キーマップ
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeMinus( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
									final Map<String,Integer> slaveMap ) {
		DBTableModel table = masterTbl;

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

		int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在する
				int row =  ((Integer)entry[i].getValue()).intValue();
				table.rowDelete( row );				// 論理削除
			}
		}

		// 行番号が変わらないように逆順削除します。
		for( int row=table.getRowCount()-1; row>=0; row-- ) {
			if( DBTableModel.DELETE_TYPE.equals( table.getModifyType( row ) ) ) {
				table.removeValue( row );
			}
		}

		return table;
	}

	/**
	 * 指定のマスタ,スレーブテーブルを使用して 差分集合 処理を実行します。
	 *
	 * @param	masterTbl  DBTableModel マスターテーブルモデル
	 * @param	masterMap  Map<String,Integer> マスターテーブルの主キーマップ
	 * @param	slaveTbl   DBTableModel スレーブテーブルモデル
	 * @param	slaveMap   Map<String,Integer> スレーブテーブルの主キーマップ
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeDifference( final DBTableModel masterTbl,final Map<String,Integer> masterMap,
										 final DBTableModel slaveTbl ,final Map<String,Integer> slaveMap ) {
		DBTableModel table = masterTbl;

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

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

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

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

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

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

		int size = entry.length;
		for( int i=0; i<size; i++ ) {
			String key  = (String)entry[i].getKey();
			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタが存在する
				int mrow =  ((Integer)entry[i].getValue()).intValue();
				int srow =  (slaveMap.get( key )).intValue();
				boolean unmatched = false;
				for( int j=0; j<diffClmSize; j++ ) {
					String mval = table.getValue( mrow,diffClmNo[j] );
					String sval = slaveTbl.getValue( srow,diffClmNo[j] );
					if( mval != null && !mval.equals( sval ) ) { unmatched = true; break; }
				}

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

					// スレーブの値をマスタの値にセットする。
					for( int j=0; j<modClmSize; j++ ) {
						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++ ) {
							int dclmNo = diffClmNo[j];
							String mval = mvals[dclmNo];
							String sval = svals[dclmNo];
							if( mval != null && !mval.equals( sval ) ) {
								svals[dclmNo] = "<span class=\"unmatched\">" + sval + "</span>" ;
							}
						}
						newTable.addColumnValues( svals );
	//					newTable.setRowWritable( newTable.getRowCount()-1,false );	// 書き込み不許可
					}
				}
			}
		}
		return newTable;
	}

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

		DBTableModel table = makeAddClm( masterTbl );

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

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

		// 突合せを行い、マッチするカラムについて、値をセットし直します。
//		Map.Entry[] entry = slaveMap.entrySet().toArray( new Map.Entry[slaveMap.size()] );

		for( int row=0; row<table.getRowCount(); row++ ) {
			String[] mvals = table.getValues( row );
			StringBuilder buf = new StringBuilder();
			for( int i=0; i<keyClmNo.length; i++ ) {
				buf.append( mvals[keyClmNo[i]] ).append( "|" );
			}
			String key = buf.toString();	// 主キー連結文字列

			if( slaveMap.containsKey( key ) ) {		// スレーブにマスタに対応するデータが存在する
				int slvRow =  (slaveMap.get( key )).intValue();
				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  DBTableModel マスターテーブルモデル
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeAddClm( final DBTableModel masterTbl ) {
		String[]  addClms	 = StringUtil.csv2Array( unionClms );
		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;		// マスタに存在すれば、追加不要
			}
		}

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

		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] ) {
				DBColumn column = getDBColumn( addClms[i] );
				addClmVal[addCnt++] = nval( column.getDefault(),"" );
				table.setDBColumn( clmNo++,column );
			}
		}
		// 一旦、すべてのマスタデータを新テーブルにコピーします。
		for( int row=0; row<masterTbl.getRowCount(); row++ ) {
			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  DBTableModel マスターテーブルモデル
	 * @return	DBTableModel 処理結果のテーブルモデル
	 */
	private DBTableModel makeGroup( final DBTableModel masterTbl ) {
		DBTableModel table = masterTbl;

		return table;
	}

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

		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++ ) {
			String[] vals = new String[clmSize];
			System.arraycopy( tbl.getValues( row ),0,vals,0 ,clmSize );	// データのコピー
			table.addColumnValues( vals );
		}
		return table;
	}

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

		// マスタテーブルの差分属性のカラム番号を求めます。
		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="{@docRoot}/constant-values.html#org.opengion.hayabusa.taglib.TableMergeTag.CMD_NEW">コマンド定数</a>
	 */
	public void setCommand( final String cmd ) {
		String cmd2 = getRequestParameter( cmd );
		if( cmd2 != null && cmd2.length() > 0 ) { command = cmd2.toUpperCase(Locale.JAPAN); }
	}

	/**
	 * 【TAG】アクションを指定します(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>
	 * <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.tag
	 * 集合処理結果の DBTableModel をメモリにセットする場合のキー(tableId)を指定します。
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @param	tableId 出力先のtableId
	 */
	public void setTableId( final String tableId ) {
		this.tableId = nval( getRequestParameter( tableId ),this.tableId );
	}

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

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

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

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

	/**
	 * 【TAG】スレーブテーブルの集合処理を行う主キーを指定します。
	 *
	 * @og.tag
	 * 集合処理を行う場合の、カラム名を、カンマ区切り文字(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形式)で指定します。
	 * 差分処理とは、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.tag
	 * 集合処理のスレーブとなる DBTableModel をメモリから取り出す場合のキー(tableId)を指定します。
	 * なお、アクションがグループ(GROUP)の場合は、スレーブテーブルは使用されません。
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @param	slaveTableId スレーブテーブルのtableId
	 */
	public void setSlaveTableId( final String slaveTableId ) {
		this.slaveTableId = nval( getRequestParameter( slaveTableId ),this.slaveTableId );
	}

	/**
	 * 【TAG】スレーブテーブルのscopeを指定します(初期値:session)。
	 *
	 * @og.tag
	 * 集合処理のスレーブとなる DBTableModel をメモリから取り出す場合のスコープを指定します。
	 * なお、アクションがグループ(GROUP)の場合は、スレーブテーブルは使用されません。
	 * 初期値は、session です。
	 *
	 * @param	slaveScope スレーブテーブルのscope
	 */
	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 String スレーブデータを結果テーブルに追加するかどうか(初期値:true)
	 */
	public void setUseDiffData( final String flag ) {
		useDiffData = nval( getRequestParameter( flag ),useDiffData );
	}

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

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

	/**
	 * 【TAG】(通常使いません)タグで処理される処理がメインとなるトランザクション処理かどうかを指定します。(初期値:false)
	 *
	 * @og.tag
	 * (通常使いません)タグで処理される処理が、メインとなるトランザクション処理かどうかを指定します。(初期値:false)
	 * この値は、ファイルダウンロード処理に影響します。この値がtrueに指定された時にcommitされたDBTableModelが
	 * ファイルダウンロードの対象の表になります。
	 * 
	 * このパラメーターは、通常、各タグにより実装され、ユーザーが指定する必要はありません。
	 * 但し、1つのJSP内でDBTableModelが複数生成される場合に、前に処理したDBTableModelについてファイルダウンロードをさせたい
	 * 場合は、後ろでDBTableModelを生成するタグで、明示的にこの値をfalseに指定することで、ファイルダウンロード処理の対象から
	 * 除外することができます。
	 * 
	 * @og.rev 5.1.6.0 (2010/05/01) 新規作成
	 *
	 * @param  flag メイントランザクションかどうか
	 */
	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 このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.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() ;
	}
}
