/*
 * 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.DBTableModel;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.io.TableReader;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.Closer ;
import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.util.StringUtil.nval ;

import java.io.File;
import java.io.BufferedReader;
import java.util.Locale ;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
 * 指定のファイルを DBTableModel オブジェクトに読み取るファイル入力タグです。
 *
 * データ（DBTableModel)と、コントローラ（ReadTableタグ)を与えて、外部からコントロールすることで、
 * 各種形式で データ（DBTableModel)を表示させることが できます。
 * ReadTableタグ に対して、コマンドを与えることにより、内部のコントローラの実装に対応した
 * 形式でデータを作成します。
 * すべての読取の初期クラス名を リソースファイルの TABLE_READER_DEFAULT_CLASS で指定可能です。
 * その場合、AutoReader を指定すると、Excel と Default(テキスト) を順番に試します。
 * 
 * 入力件数を"DB.COUNT" キーでリクエストにセットしています。
 *
 * @og.formSample
 * ●形式：
 *     &lt;og:readTable
 *         command      = "NEW"
 *         fileURL      = "{&#064;USER.ID}"     読み取り元ディレクトリ名
 *         filename     = "{&#064;filename}"    読み取り元ファイル名
 *         encode       = "UnicodeLittle"       読み取り元ファイルエンコード名
 *         maxRowCount  = "10000"               読取最大件数
 *     /&gt;
 * ●body：なし
 *
 * ●使用例
 *
 *     &lt;og:readTable
 *         command        = "NEW"
 *         readerClass    = "Fixed"               固定長データの読み取り
 *         modifyType     = "{&#064;modifyType}"  読取時のモディファイタイプ（A,C等）
 *         fileURL        = "{&#064;USER.ID}"     読み取り元ディレクトリ名
 *         filename       = "{&#064;filename}"    読み取り元ファイル名
 *         encode         = "Shift_JIS"           読み取り元ファイルエンコード名
 *         maxRowCount    = "10000"               読取最大件数
 *         columns        = "OYA,KO,HJO,SU,DYSTR,DYEND"   #NAME に対応するカラム列
 *         useNumber      = "false"               行番号の存在しないデータを読み取ります。
 *         adjustColumns  = "OYA,KO,HJO,SU"       データ変換するカラム列("*" で全カラム)
 *         checkColumns   = "OYA,KO,HJO,SU"       整合性チェックするカラム列("*" で全カラム)
 *         nullCheck      = "OYA,KO,SU"           NULLチェックを実行します("*" で全カラム)
 *         stopZero       = "true"                取得0件の場合に以降の処理を停止します
 *     /&gt;
 *
 * @og.group ファイル入力
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class ReadTableTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "4.0.0 (2005/08/31)" ;

	private static final long serialVersionUID = 4000 ;	// 4.0.0 (2005/01/31)

	private static final int ERROR_ROW_COUNT = 200 ;	// 4.0.0 (2007/05/25)

	/** command 引数に渡す事の出来る コマンド  新規作成 {@value} */
	public static final String CMD_NEW   = "NEW" ;
	/** command 引数に渡す事の出来る コマンド  再検索 {@value} */
	public static final String CMD_RENEW = "RENEW" ;

	private static final String[] COMMAND_LIST = new String[] { CMD_NEW , CMD_RENEW };

	private String	separator		= TableReader.TAB_SEPARATOR;	 // 項目区切り文字
	private String	fileURL 		= HybsSystem.sys( "FILE_URL" );
	private String	filename		= HybsSystem.sys( "FILE_FILENAME"		);	 // ファイル名
	private String	encode			= HybsSystem.sys( "FILE_ENCODE"			);	 // ファイルエンコーディング  "JISAutoDetect" ,"JIS", "EUC_JP", "MS932", "SJIS" , "Windows-31J" , "Shift_JIS"
	private String  readerClass     = HybsSystem.sys( "TABLE_READER_DEFAULT_CLASS" );		// 3.8.5.3 (2006/08/07)
	private int		maxRowCount		= -1;
//	private String	displayMsg		= "MSG0033";	// 　件検索しました。
	private String	displayMsg		= HybsSystem.sys( "VIEW_DISPLAY_MSG" );
	private String	notfoundMsg		= "MSG0077";	// 対象データはありませんでした。
	private int 	executeCount	= -1;			// 検索/実行件数
	private String	modifyType		= null;
//	private String[] checkColumns	= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
//	private String[] adjustColumns	= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
//	private String[] nullCheck		= null;		// 3.8.0.2 (2005/06/30) nullチェック確認
	private String adjustColumns	= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
	private String checkColumns		= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
	private String nullCheck		= null;		// 3.8.0.2 (2005/06/30) nullチェック確認
//	private boolean allColumnCheck	= false;	// 3.6.0.2 (2004/10/04) 取り込み時チェック

	private transient DBTableModel table	  = null;
	private String		command		= CMD_NEW;
	private String		tableId		= HybsSystem.TBL_MDL_KEY ;
	private String		sheetName	= null ;		// 3.5.4.2 (2003/12/15)

	// 3.5.4.5 (2004/01/23) 外部よりカラム列（カンマ区切り）を指定できるようにする。
	private String		columns		= null;
	private boolean		useNumber	= true;			// 3.7.0.5 (2005/04/11)
	
	private boolean		stopZero	= false;		// 4.3.7.0 (2009/06/01) stopZero属性追加

	private static final String[] AUTO_READER_CLASS  = new String[] { "Excel","Default"      ,"Default"     };
	private static final String[] AUTO_READER_ENCODE = new String[] { null   ,"UnicodeLittle","Windows-31J" };

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 3.0.1.4 (2003/03/17) displayMsg が ０Byteの場合は、件数も表示しないように変更。
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 3.5.4.1 (2003/12/01) 引数の BufferedReader を、InputStream に変更。
	 * @og.rev 3.5.4.3 (2004/01/05) 引数の InputStream を、 BufferedReader に戻す。
	 * @og.rev 3.5.6.5 (2004/08/09) 暫定的に、DBTableModelを先行削除します。
	 * @og.rev 3.6.0.0 (2004/09/24) DBTableModel の先行削除は、scope="session" の場合のみ。
	 * @og.rev 3.6.0.2 (2004/10/04) 取り込み時チェック用に、checkColumns,adjustColumns 属性追加
	 * @og.rev 3.6.0.8 (2004/11/19) DBTableModel をセーブする時に、トランザクションチェックを行います。
	 * @og.rev 3.8.5.3 (2006/08/07) readerClassが "Excel"でエラーが発生したとき、もう一度Defaultで再読取を行います。
	 * @og.rev 4.0.0.0 (2007/10/12) checkTableColumn 前に、modifyType 設定を行います。
	 * @og.rev 4.0.0.0 (2007/10/18) メッセージリソース統合( getResource().getMessage > getResource().getLabel )
	 * @og.rev 4.3.1.1 (2008/10/08) columnsが指定されている場合は、AutoReader禁止
	 * @og.rev 4.3.7.0 (2009/06/01) stopZero機能,DB.COUNTリクエストキーへ読込件数セットを追加
	 *
	 * @return  int
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)
		if( check( command, COMMAND_LIST ) ) {
			startQueryTransaction( tableId );		// 3.6.0.8 (2004/11/19)

			// 3.5.6.5 (2004/08/09) 削除するのは、セッションのオブジェクトでよい。
			// 3.6.0.0 (2004/09/24) 削除するのは、scope="session" の場合のみ。
			if( "session".equals( getScope() ) ) {
				removeSessionAttribute( tableId );
				removeSessionAttribute( HybsSystem.VIEWFORM_KEY );
			}

			if( maxRowCount < 0 ) {
				maxRowCount	= sysInt( "DB_MAX_ROW_COUNT" );
			}

			// ファイル の読み込み：AutoReader 処理
			BufferedReader pw = null;
			final String[] READER_CLASS  ;
			final String[] READER_ENCODE ;
			if( "AutoReader".equalsIgnoreCase( readerClass ) ) {
				// 4.3.1.1 (2008/10/08)
				if( columns != null && columns.length() > 0 ) {
					String errMsg = "columnsが指定されている場合は、readerClass=\"AutoReader\"は使えません";
					throw new HybsSystemException( errMsg ); // 4.3.4.4 (2009/01/01)
				}
				READER_CLASS  = AUTO_READER_CLASS ;
				READER_ENCODE = AUTO_READER_ENCODE;
			}
			else {
				READER_CLASS  = new String[] { readerClass };
				READER_ENCODE = new String[] { encode };
			}

			StringBuilder tempMsg = new StringBuilder();
			for( int i=0; i<READER_CLASS.length; i++ ) {
				readerClass = READER_CLASS[i];
				encode      = READER_ENCODE[i];

				try {
					if( "Excel".equalsIgnoreCase( readerClass ) ) {
						create( null );
					}
					else {
						pw = getBufferedReader();
						create( pw );
					}
					// 成功すれば、エラーメッセージをクリアして、その場で抜ける。
					tempMsg = null;
					break;
				}
				// 3.8.5.3 (2006/08/07) readerClassが "Excel"でエラーが発生したとき、もう一度Defaultで再読取を行います。
				catch( HybsSystemException ex ) {
					tempMsg.append( "readerClass=["  ).append( readerClass )
							.append( "],encode=["    ).append( encode )
							.append( "] Error!"      ).append( HybsSystem.CR )
							.append( ex.getMessage() ).append( HybsSystem.CR ) ;

//					String tmp = "readerClass=[" + readerClass + "],encode=[" + encode + "] Error!"
//								+ HybsSystem.CR
//								+ ex.getMessage()
//								+ HybsSystem.CR ;
//					System.err.print( tmp );
//					tempMsg += tmp ;
				}
				finally {
					Closer.ioClose( pw );		// 4.0.0 (2006/01/31) close 処理時の IOException を無視
				}
			}

			if( tempMsg != null ) {	// 最後までエラーがあれば、例外処理を発行します。
					System.err.print( tempMsg.toString() );
				throw new HybsSystemException( tempMsg.toString() );
			}

			if( table != null ) {
				// 3.6.0.2 (2004/10/04)
//				table = setAdjustTable( table );

				// 4.0.0.0 (2007/10/12) checkTableColumn 前に、modifyType 設定を行います。
				executeCount = table.getRowCount();
				if( modifyType != null ) {
					for( int row=0; row<executeCount; row++ ) {
						table.setModifyType( row,modifyType );
					}
				}

				ErrorMessage errMsg = checkTableColumn( table );
				if( errMsg != null && ! errMsg.isOK()) {
					jspPrint( TaglibUtil.makeHTMLErrorTable( errMsg,getResource() ) );
					return SKIP_PAGE ;
				}

			}
			// 3.6.0.8 (2004/11/19) トランザクションチェックを行います。
			if( ! commitTableObject( tableId, table ) ) {
				jspPrint( "ReadTableTag Query処理が割り込まれました。DBTableModel は登録しません。" );
				return (SKIP_PAGE);
			}

			StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_SMALL );

			// 実行件数の表示 command="NEW" のときのみ、displayMsg を表示させます。
			// 4.0.0 (2005/11/30) 出力順の変更。一番最初に出力します。
//			boolean	useStatusBar = HybsSystem.sysBool( "VIEW_USE_DISPLAY_MSG" );
			if( CMD_NEW.equals( command ) ) {
//				if( useStatusBar && executeCount > 0 && displayMsg != null && displayMsg.length() > 0 ) {
				if( executeCount > 0 && displayMsg != null && displayMsg.length() > 0 ) {
					buf.append( executeCount );
//					buf.append( getResource().getMessage( displayMsg ) );
					buf.append( getResource().getLabel( displayMsg ) );
					buf.append( HybsSystem.BR );
				}
				else if( executeCount == 0 && notfoundMsg != null && notfoundMsg.length() > 0 ) {
//					buf.append( getResource().getMessage( notfoundMsg ) );
					buf.append( getResource().getLabel( notfoundMsg ) );
					buf.append( HybsSystem.BR );
				}
			}
			
			// 4.3.7.0 (2009/06/01) 読込件数を、"DB.COUNT" キーでリクエストにセットする。
			setRequestAttribute( "DB.COUNT"   , String.valueOf( executeCount ) );

			jspPrint( buf.toString() );
		}
		
		// 4.3.7.0 (2009/06/01) stopZero機能を追加 
		final int rtnCode ;
		if( executeCount == 0 && stopZero )	{
			rtnCode = SKIP_PAGE;
		}
		else {
			rtnCode = EVAL_PAGE;
		}

		// return(EVAL_PAGE);
		return ( rtnCode );
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 2.0.0.4 (2002/09/27) カスタムタグの release() メソッドを、追加
	 * @og.rev 3.1.1.2 (2003/04/04) Tomcat4.1 対応。release2() を doEndTag()で呼ぶ。
	 * @og.rev 3.1.3.0 (2003/04/10) FILE_ENCODE から、エンコード情報を取得する。
	 * @og.rev 3.1.4.0 (2003/04/18) command 属性に、初期値（NEW)を設定する。
	 * @og.rev 3.5.4.2 (2003/12/15) EXCELのシート名を指定できるように変更。
	 * @og.rev 3.5.4.5 (2004/01/23) 外部よりカラム列（カンマ区切り）を指定できるようにする。
	 * @og.rev 3.6.0.2 (2004/10/04) checkColumns,adjustColumns,allColumnCheck 属性追加
	 * @og.rev 3.7.0.5 (2005/04/11) useNumber 属性を追加します。
	 * @og.rev 3.8.0.2 (2005/06/30) nullCheck 属性追加
	 * @og.rev 3.8.5.3 (2006/08/07) readerClass 属性の初期値をシステムリソースより取得します。
	 * @og.rev 4.3.7.0 (2009/06/01) stopZero属性追加
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		separator		= TableReader.TAB_SEPARATOR;   // 項目区切り文字
		fileURL			= HybsSystem.sys( "FILE_URL" );
		filename		= HybsSystem.sys( "FILE_FILENAME" );   // ファイル名
		encode			= HybsSystem.sys( "FILE_ENCODE"   );   // ファイルエンコーディング  "JISAutoDetect" ,"JIS", "EUC_JP", "MS932", "SJIS" , "Windows-31J" , "Shift_JIS"
		readerClass		= HybsSystem.sys( "TABLE_READER_DEFAULT_CLASS" );		// 3.8.5.3 (2006/08/07)
		maxRowCount		= -1;
//		displayMsg		= "MSG0033";	// 　件検索しました。
		displayMsg		= HybsSystem.sys( "VIEW_DISPLAY_MSG" );
		notfoundMsg		= "MSG0077";	// 対象データはありませんでした。
		executeCount	= -1;			// 検索/実行件数
		modifyType		= null;
		command			= CMD_NEW;
		table			= null;
		tableId			= HybsSystem.TBL_MDL_KEY ;
		sheetName		= null;		// 3.5.4.2 (2003/12/15)
		columns			= null;		// 3.5.4.5 (2004/01/23)
		useNumber		= true;		// 3.7.0.5 (2005/04/11)
		adjustColumns	= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
		checkColumns	= null;		// 3.6.0.2 (2004/10/04) 取り込み時チェック
		nullCheck		= null;		// 3.8.0.2 (2005/06/30)
//		allColumnCheck	= false;	// 3.6.0.2 (2004/10/04) 取り込み時チェック
		stopZero		= false;	// 4.3.7.0 (2009/06/01) soptZero追加
	}

	/**
	 * TableReader の実オブジェクトを生成して，BufferedReader に書き込みます。
	 *
	 * @og.rev 3.5.4.1 (2003/12/01) 引数の BufferedReader を、InputStream に変更。
	 * @og.rev 3.5.4.2 (2003/12/15) TableReader のサブクラス名変更。
	 * @og.rev 3.5.4.2 (2003/12/15) EXCELのシート名を指定できるように変更。
	 * @og.rev 3.5.4.3 (2004/01/05) 引数の InputStream を、 BufferedReader に戻す。
	 * @og.rev 3.5.4.5 (2004/01/23) TableReader に、encode を渡すように変更。
	 * @og.rev 3.5.6.0 (2004/06/18) 各種プラグイン関連付け設定を、システムパラメータ に記述します。
	 * @og.rev 3.7.0.5 (2005/04/11) useNumber 属性を追加します。
	 * @og.rev 4.0.0 (2005/01/31) キーの指定を、TableReader. から、TableReader_ に変更します。
	 * @og.rev 4.0.0 (2005/01/31) lang ⇒ ResourceManager へ変更
	 *
	 */
	protected void create( final BufferedReader out )  {

		String className = HybsSystem.sys( "TableReader_" + readerClass ) ;		// 4.0.0 (2005/01/31)
		TableReader reader = (TableReader)HybsSystem.newInstance( className );	// 3.5.5.3 (2004/04/09)

		reader.setResourceManager( getResource() );	// 4.0.0 (2005/01/31)
		reader.setSeparator( separator );
		reader.setEncode( encode );						// 3.5.4.5 (2004/01/23)
		reader.setColumns( columns );					// 3.5.4.5 (2004/01/23)
		reader.setUseNumber( useNumber );				// 3.7.0.5 (2005/04/11)
		reader.setMaxRowCount( maxRowCount );
		if( reader.isExcel() ) {						// 3.5.4.3 (2004/01/05)
			reader.setFilename( HybsSystem.url2dir( StringUtil.urlAppend( fileURL,filename )));
			reader.setSheetName( sheetName );			// 3.5.4.2 (2003/12/15)
			reader.readDBTable();
		}
		else {
			reader.readDBTable( out );
		}
		table = reader.getDBTableModel();
	}

	/**
	 * BufferedReader を取得します。
	 *
	 * ここでは、一般的なファイル出力を考慮した BufferedReader を作成します。
	 *
	 * @og.rev 2.2.0.0 (2002/12/17) 中国語（国際化）対応 エンコードの取得方法変更
	 * @og.rev 3.1.3.0 (2003/04/10) FILE_ENCODE から、エンコード情報を取得する。
	 * @og.rev 3.5.4.1 (2003/12/01) 引数の BufferedReader を、InputStream に変更。
	 * @og.rev 3.5.4.3 (2004/01/05) 引数の InputStream を、 BufferedReader に戻す。
	 * @og.rev 3.5.5.9 (2004/06/07) FileUtil.getBufferedReader を使用
	 *
	 * @return   InputStream
	 */
	private BufferedReader getBufferedReader() {
		if( filename == null ) {
			String errMsg = "ファイル名がセットされていません。";
			throw new HybsSystemException( errMsg );
		}
		String directory = HybsSystem.url2dir( fileURL );
		File file = new File( StringUtil.urlAppend( directory,filename ) );

		BufferedReader out = FileUtil.getBufferedReader( file,encode );

		return out ;
	}

	/**
	 * adjustColumns に指定されたカラムを再登録します。
	 * カラムオブジェクトのDBType属性に対応した変換を行い、再設定します。
	 *
	 * @og.rev 3.6.0.2 (2004/10/04) 新規作成
	 *
	 * @param   table DBTableModel 登録前のDBTableModel
	 * @return  DBTableModel 登録後のDBTableModel
	 */
//	private DBTableModel setAdjustTable( final DBTableModel table ) {
//		if( adjustColumns != null && adjustColumns.length > 0 ) {
//			int rowCnt = table.getRowCount();
//			int[] clmNo = new int[adjustColumns.length];
//			for( int i=0; i<clmNo.length; i++ ) {
//				clmNo[i] = table.getColumnNo( adjustColumns[i] );
//			}
//
//			for( int row=0; row<rowCnt; row++ ) {
//				String[] vals = table.getValues( row );
//				for( int i=0; i<clmNo.length; i++ ) {
//					int clm = clmNo[i];
//					DBColumn dbClm = table.getDBColumn( clm );
//					vals[clm] = dbClm.valueSet( vals[clm] );
//				}
//				table.setValues( vals,row );
//			}
//		}
//		return table ;
//	}

	/**
	 * カラム文字列(CSV形式)から、カラム番号配列を作成します。
	 * 簡易メソッドです。
	 * 引数が、"*" の場合は、全カラムを指定したことになります。
	 * null の場合は、サイズが ０ の配列を返します。
	 *
	 * @og.rev 4.0.0 (2007/05/25) 新規作成
	 *
	 * @param   clms  String カラム文字列(CSV形式)
	 * @param   table DBTableModel
	 * @return  int[] カラム番号配列(無い場合は、長さ０の配列)
	 */
	private int[] makeClmNos( final String clms,final DBTableModel table ) {
		final int[] clmNo;

		if( clms == null ) {
			clmNo = new int[0];
		}
		else if( "*".equals( clms ) ) {
			int size = table.getColumnCount();
			clmNo = new int[size];
			for( int i=0; i<size; i++ ) {
				clmNo[i] = i;
			}
		}
		else {
			String[] clmStr = StringUtil.csv2Array( clms );
			int size = clmStr.length;
			clmNo = new int[size];
			for( int i=0; i<size; i++ ) {
				clmNo[i] = table.getColumnNo( clmStr[i] );
			}
		}

		return clmNo;
	}

	/**
	 * checkColumns に指定されたカラムをチェックします。
	 * カラムオブジェクトのDBType属性に対応したチェックを行います。
	 * チェック結果で、エラーが発生した場合は、ErrorMessage オブジェクトを
	 * 返します。
	 * DBColumn#valueCheck( String ) の結果のErrorMessageをすべて append
	 * していきます。
	 * useAdjust==true で、かつ、エラーがない場合は、adjustColumns 処理結果を
	 * DBTableModel に反映させます。
	 *
	 * @og.rev 3.6.0.2 (2004/10/04) 新規作成
	 * @og.rev 3.8.0.2 (2005/06/30) nullチェック確認
	 * @og.rev 4.0.0 (2007/05/25) 処理順序書き換え
	 *
	 * @param   table DBTableModel
	 * @return  ErrorMessage カラムキー ＋ 値 のエラーメッセージオブジェクト
	 */
	private ErrorMessage checkTableColumn( final DBTableModel table ) {
		ErrorMessage errMsg = new ErrorMessage( "Check Columns Error!" );

		int rowCnt = table.getRowCount();
		int[] adjClmNo = makeClmNos( adjustColumns,table );
		int[] chkClmNo = makeClmNos( checkColumns,table );
		int[] nllclmNo = makeClmNos( nullCheck,table );

		boolean useAdjust = (adjClmNo.length > 0) ;

		for( int row=0; row<rowCnt; row++ ) {
			String[]   vals   = table.getValues( row );
			DBColumn[] dbClms = table.getDBColumns();

			// adjustColumns 処理
			for( int i=0; i<adjClmNo.length; i++ ) {
				int no = adjClmNo[i];
				vals[no] = dbClms[no].valueSet( vals[no] );
			}

			// checkColumns 処理
			for( int i=0; i<chkClmNo.length; i++ ) {
				int no = chkClmNo[i];
				errMsg.append( row+1,dbClms[no].valueCheck( vals[no] ) );
			}

			// nullCheck 処理
			for( int i=0; i<nllclmNo.length; i++ ) {
				int no = nllclmNo[i];
				if( vals[no] == null || vals[no].length() == 0 ) {
					String label = dbClms[no].getLabel();
					// ERR0012 : 指定のデータがセットされていません。（NULLエラー）。key={0}
					errMsg.addMessage( row+1,ErrorMessage.NG,"ERR0012",label );
				}
			}

			// adjustColumns 処理結果を反映させます。
			if( useAdjust && errMsg.isOK() ) { table.setValues( vals,row ); }
			if( errMsg.size() > ERROR_ROW_COUNT ) { break; }
		}

		return errMsg;
	}

//	private ErrorMessage checkTableColumn( final DBTableModel table ) {
//		ErrorMessage errMsg = new ErrorMessage( "Check Columns Error!" );
//
//		if( allColumnCheck ) {
//			checkColumns = table.getNames();
//		}
//
//		int rowCnt = table.getRowCount();
//		if( checkColumns != null && checkColumns.length > 0 ) {
//			int[] clmNo = new int[checkColumns.length];
//			for( int i=0; i<clmNo.length; i++ ) {
//				clmNo[i] = table.getColumnNo( checkColumns[i] );
//			}
//
//			for( int row=0; row<rowCnt; row++ ) {
//				String[] vals = table.getValues( row );
//				for( int i=0; i<clmNo.length; i++ ) {
//					int clm = clmNo[i];
//					DBColumn dbClm = table.getDBColumn( clm );
//					errMsg.append( row+1,dbClm.valueCheck( vals[clm] ) );
//				}
//			}
//		}
//
//		// 3.8.0.2 (2005/06/30) nullチェック確認
//		if( nullCheck != null && nullCheck.length > 0 ) {
//			int[] clmNo = new int[nullCheck.length];
//			for( int i=0; i<clmNo.length; i++ ) {
//				clmNo[i] = table.getColumnNo( nullCheck[i] );
//			}
//
//			for( int row=0; row<rowCnt; row++ ) {
//				String[] vals = table.getValues( row );
//				for( int i=0; i<clmNo.length; i++ ) {
//					int clm = clmNo[i];
//					if( vals[clm] == null || vals[clm].length() == 0 ) {
//						String label = getResource().getLabel( nullCheck[i] );
//						// ERR0012 : 指定のデータがセットされていません。（NULLエラー）。key={0}
//						errMsg.addMessage( row+1,ErrorMessage.NG,"ERR0012",label );
//					}
//				}
//			}
//		}
//
//		return errMsg;
//	}

	/**
	 * 【TAG】(通常使いません)sessionから所得する DBTableModel オブジェクトの ID。
	 *
	 * @og.tag
	 * 表示処理後に，（内部ポインタを書き換えた）DBTableModel オブジェクトを
	 * 同じキーで、sessionに登録します。
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 *
	 * @param   tableId sessionに登録する時の ID
	 */
	public void setTableId( final String tableId ) {
		this.tableId = nval( getRequestParameter( tableId ), this.tableId );
	}

	/**
	 * 【TAG】可変長ファイルを作成するときの項目区切り文字をセットします。
	 *
	 * @og.tag 可変長ファイルを作成するときの項目区切り文字をセットします。
	 *
	 * @param   separator 項目区切り文字
	 */
	public void setSeparator( final String separator ) {
		this.separator = nval( getRequestParameter( separator ),this.separator );
	}

	/**
	 * 【TAG】読み取り元ディレクトリ名を指定します(初期値:FILE_URL)。
	 *
	 * @og.tag
	 * この属性で指定されるディレクトリより、ファイルを読み取ります。
	 * 指定方法は、通常の fileURL 属性と同様に、先頭が、'/' （UNIX) または、２文字目が、
	 * ":" （Windows）の場合は、指定のURLそのままのディレクトリに、そうでない場合は、
	 * システムパラメータ の FILE_URL 属性で指定のフォルダの下を指定します。
	 * fileURL = "{&#064;USER.ID}" と指定すると、FILE_URL 属性で指定のフォルダの下に、
	 * さらに、各個人ID別のフォルダを作成して、そこを操作します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) StringUtil.urlAppend メソッドの利用
	 * @og.rev 4.0.0.0 (2007/11/20) 指定されたディレクトリ名の最後が"\"or"/"で終わっていない場合に、"/"を付加する。
	 *
	 * @param	url 読み取り元ディレクトリ名
	 */
	public void setFileURL( final String url ) {
		String furl = nval( getRequestParameter( url ),null );
		if( furl != null ) {
			char ch = furl.charAt( furl.length()-1 );
			if( ch != '/' && ch != '\\' ) { furl = furl + "/"; }
			fileURL = StringUtil.urlAppend( fileURL,furl );
		}
	}

	/**
	 * 【TAG】ファイルを作成するときのファイル名をセットします(初期値:FILE_FILENAME)。
	 *
	 * @og.tag ファイルを作成するときのファイル名をセットします。
	 *
	 * @param   filename ファイル名
	 */
	public void setFilename( final String filename ) {
		this.filename = nval( getRequestParameter( filename ),this.filename );
	}

	/**
	 * 【TAG】ファイルを作成するときのファイルエンコーディング名をセットします(初期値:FILE_ENCODE)。
	 *
	 * @og.tag
	 * 初期値は、システムパラメータ の FILE_ENCODE 属性で、設定しています。
	 * Shift_JIS,MS932,Windows-31J,UTF-8,ISO-8859-1,UnicodeLittle
	 *
	 * @og.rev 2.2.0.0 (2002/12/17) 中国語（国際化）対応 エンコードの取得方法変更
	 * @og.rev 3.1.3.0 (2003/04/10) FILE_ENCODE から、エンコード情報を取得する。
	 *
	 * @param   enc ファイルエンコーディング名
	 * @see     <a href="http://www.iana.org/assignments/character-sets">IANA Charset Registry</a>
	 */
	public void setEncode( final String enc ) {
		encode = nval( getRequestParameter( enc ),encode );
	}

	/**
	 * 【TAG】実際に読み出すクラス名の略称(TableReader_**** の ****)をセットします(初期値:Default)。
	 *
	 * @og.tag
	 * 実際に読み出すクラス名（の略称）をセットします。
	 * これは、org.opengion.hayabusa.io 以下の TableReader_**** クラスの **** を
	 * 与えます。これらは、TableReader インターフェースを継承したサブクラスです。
	 * 属性クラス定義の
	 * <a href="/ge/jsp/DOC03/index.jsp?command=NEW&SORT=VERSION+DESC%2CSEQ%2CATTKEY&VALUENAME=readerClass" target="CONTENTS">readerClass</a>
	 * を参照願います。
	 *
	 * @param   readerClass クラス名（の略称）
	 * @see		org.opengion.hayabusa.io.TableReader  TableReaderのサブクラス
	 */
	public void setReaderClass( final String readerClass ) {
		this.readerClass = nval( getRequestParameter( readerClass ),this.readerClass );
	}

	/**
	 * 【TAG】読取時の最大取り込み件数をセットします(初期値:DB_MAX_ROW_COUNT)。
	 *
	 * @og.tag
	 * DBTableModelのデータとして登録する最大件数をこの値に設定します。
	 * サーバーのメモリ資源と応答時間の確保の為です。
	 * 0 をセットすると、無制限(Integer.MAX_VALUE)になります。
	 *
	 * @param   count 読取時の最大取り込み件数
	 */
	public void setMaxRowCount( final String count ) {
		maxRowCount = nval( getRequestParameter( count ),maxRowCount );
	}

	/**
	 * 【TAG】コマンド(NEW,RENEW)をセットします(初期値:NEW)。
	 *
	 * @og.tag
	 * コマンドは,HTMLから（get/post)指定されますので,CMD_xxx で設定される
	 * フィールド定数値のいづれかを、指定できます。
	 * 何も設定されない、または、null の場合は、"NEW" が初期値にセットされます。
	 *
	 * @param	cmd コマンド（public static final 宣言されている文字列)
	 * @see		<a href="{@docRoot}/constant-values.html#org.opengion.hayabusa.taglib.ReadTableTag.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】query の結果を画面上に表示するメッセージIDを指定します(初期値:MSG0033[　件検索しました])。
	 *
	 * @og.tag
	 * ここでは、検索結果の件数や登録された件数をまず出力し、
	 * その次に、ここで指定したメッセージをリソースから取得して
	 * 表示します。
	 * 表示させたくない場合は, displayMsg = "" をセットしてください。
	 * 初期値は、検索件数を表示します。
	 *
	 * @param   id ディスプレイに表示させるメッセージ ID
	 */
	public void setDisplayMsg( final String id ) {
		if( id != null ) { displayMsg = id; }
	}

	/**
	 * 【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])。
	 *
	 * @og.tag
	 * ここでは、検索結果がゼロ件の場合のみ、特別なメッセージを表示させます。
	 * 従来は、displayMsg と兼用で、『0　件検索しました』という表示でしたが、
	 * displayMsg の初期表示は、OFF になりましたので、ゼロ件の場合のみ別に表示させます。
	 * 表示させたくない場合は, notfoundMsg = "" をセットしてください。
	 * 初期値は、MSG0077[対象データはありませんでした]です。
	 *
	 * @param	id ディスプレイに表示させるメッセージ ID
	 */
	public void setNotfoundMsg( final String id ) {
		String ids = getRequestParameter( id );
		if( ids != null ) { notfoundMsg = ids; }
	}

	/**
	 * 【TAG】ファイル取り込み時の モディファイタイプ（A(追加),C(更新),D(削除)）を指定します。
	 *
	 * @og.tag
	 * ファイル読み込み時に、そのデータをA(追加）、C(更新）、D(削除)の
	 * モディファイタイプをつけた状態にします。
	 * その状態で、そのまま、update する事が可能になります。
	 *
	 * @param   type ファイル取り込み時の モディファイタイプ（A,C,D属性）
	 */
	public void setModifyType( final String type ) {
		modifyType = getRequestParameter( type );
	}

	/**
	 * 【TAG】EXCELファイルを読み込むときのシート名を設定します(初期値:第１シート)。
	 *
	 * @og.tag
	 * EXCELファイルを読み込む時に、シート名を指定します。これにより、複数の形式の
	 * 異なるデータを順次読み込むことや、シートを指定して読み取ることが可能になります。
	 * 初期値は、第一シートです。
	 *
	 * @og.rev 3.5.4.2 (2003/12/15) 新規追加
	 *
	 * @param   sheet EXCELファイルのシート名
	 */
	public void setSheetName( final String sheet ) {
		sheetName = nval( getRequestParameter( sheet ),sheetName );
	}

	/**
	 * 【TAG】読み取り元ファイルのカラム列を、外部（タグ）より指定します。
	 *
	 * @og.tag
	 * 読み取り元ファイルのカラム列を、外部（タグ）より指定します。
	 * ファイルに記述された #NAME より優先して使用されます。
	 * これは、元ファイルのカラムを順番に指定のカラム名に割り当てる機能で
	 * ファイルの特定のカラム列を抜き出して取り込む機能ではありません。
	 *
	 * @og.rev 3.5.4.5 (2004/01/23) 新規作成
	 *
	 * @param   clms 読み取り元ファイルのカラム列（カンマ区切り文字）
	 */
	public void setColumns( final String clms ) {
		columns = nval( getRequestParameter( clms ),columns );
	}

	/**
	 * 【TAG】読み取り元ファイルの整合性チェックを行うカラム列をカンマ指定します。
	 *
	 * @og.tag
	 * カラムオブジェクトのDBType属性に対応したチェックを行います。
	 * 指定のカラム名をカンマ区切り(CSV)で複数指定できます。
	 * 全てのカラムのチェックを行う場合は、allColumnCheck = "true" を
	 * 指定して下さい。
	 * 分解方法は、通常のパラメータ取得後に、CSV分解します。
	 *
	 * @og.rev 3.6.0.2 (2004/10/04) 新規追加 取り込み時チェック用
	 * @og.rev 3.8.8.5 (2007/03/09) 通常のパラメータ取得後に、CSV分解に戻します。
	 *
	 * @param   clms 整合性チェックを行うカラム列（カンマ区切り文字）
	 */
	public void setCheckColumns( final String clms ) {
		checkColumns = nval( getRequestParameter( clms ),checkColumns );

//		checkColumns = StringUtil.csv2Array( getRequestParameter( clms ) );
//		if( checkColumns.length == 0 ) { checkColumns = null; }
	}

	/**
	 * 【TAG】読み取り元ファイルのデータ変換を行うカラム列をカンマ指定します。
	 *
	 * @og.tag
	 * カラムオブジェクトのDBType属性に対応したデータ変換を行います。
	 * 指定のカラム名をカンマ区切り(CSV)で複数指定できます。
	 * 分解方法は、通常のパラメータ取得後に、CSV分解します。
	 *
	 * @og.rev 3.6.0.2 (2004/10/04) 新規追加 取り込み時データ変換
	 * @og.rev 3.8.8.5 (2007/03/09) 通常のパラメータ取得後に、CSV分解に戻します。
	 *
	 * @param   clms データ変換を行うカラム列（カンマ区切り文字）
	 */
	public void setAdjustColumns( final String clms ) {
		adjustColumns = nval( getRequestParameter( clms ),adjustColumns );

//		adjustColumns = StringUtil.csv2Array( getRequestParameter( clms ) );
//		if( adjustColumns.length == 0 ) { adjustColumns = null; }
	}

	/**
	 * 【TAG】全カラムチェックを行うかどうかを設定します(初期値：false)。
	 *
	 * @og.tag
	 * カラムの整合性チェックを行う場合、カラム名を指定せず、全カラムの
	 * チェックを実施することが出来ます。(allColumnCheck="true")
	 * 初期値は、行わない(false)です。
	 * 全カラムチェックを行う場合は、checkColumns の指定は、無視されます。
	 *
	 * @og.rev 3.6.0.2 (2004/10/04) 新規追加 取り込み時全チェック
	 *
	 * @param   flag 全チェックを行うかどうか（true:行う/false:行わない）
	 * @see     #setCheckColumns( String )
	 */
//	public void setAllColumnCheck( final String flag ) {
//		allColumnCheck = nval( getRequestParameter( flag ),allColumnCheck );
//	}

	/**
	 * 【TAG】NULL チェックすべきカラム列をカンマ区切りで指定します。
	 *
	 * @og.tag nullCheck="AAA,BBB,CCC,DDD"
	 * <del>先に配列に分解してからリクエスト変数の値を取得するようにします。
	 * こうする事で、リクエストにカンマ区切りの値を設定できるようになります。</del>
	 * 分解方法は、通常のパラメータ取得後に、CSV分解します。
	 *
	 * @og.rev 3.8.0.2 (2005/06/30) 新規追加
	 * @og.rev 3.8.8.5 (2007/03/09) 通常のパラメータ取得後に、CSV分解に戻します。
	 *
	 * @param   clms String
	 */
	public void setNullCheck( final String clms ) {
		nullCheck = nval( getRequestParameter( clms ),nullCheck );

//		nullCheck = StringUtil.csv2Array( getRequestParameter( clms ) );
//		if( nullCheck.length == 0 ) { nullCheck = null; }
	}

	/**
	 * 【TAG】行番号情報を、使用している(true)/していない(false)を指定します。
	 *
	 * @og.tag
	 * 通常のフォーマットでは、各行の先頭に行番号が出力されています。
	 * 読み取り時に、#NAME 属性を使用する場合は、この行番号を無視しています。
	 * #NAME 属性を使用せず、columns 属性でカラム名を指定する場合(他システムの
	 * 出力ファイルを読み取るケース等）では、行番号も存在しないケースがあり、
	 * その様な場合に、useNumber="false" を指定すれば、データの最初から読み取り始めます。
	 * この場合、出力データのカラムの並び順が変更された場合、columns 属性も
	 * 指定しなおす必要がありますので、できるだけ、#NAME 属性を使用するように
	 * してください。
	 * なお、EXCEL 入力には、この設定は適用されません。(暫定対応)
	 * デフォルトは、true（使用する） です。
	 *
	 * @og.rev 3.7.0.5 (2005/04/11) 新規追加
	 *
	 * @param   useNo 行番号情報を、使用している(true)/していない(false)を指定
	 */
	public void setUseNumber( final String useNo ) {
		useNumber = nval( getRequestParameter( useNo ),useNumber );
	}

	/**
	 * 【TAG】読込件数が０件のとき処理を続行するかどうか(true/false)を指定します(初期値:false[続行する])。
	 *
	 * @og.tag
	 * 初期値は、false(続行する)です。
	 * 
	 * @og.rev 4.3.7.0 (2009/06/01) 新規追加
	 *
	 * @param  cmd 読込件数が０件のとき、処理を中止する（true) / 続行する（false)
	 */
	public void setStopZero( final String cmd ) {
		stopZero = nval( getRequestParameter( cmd ), stopZero );
	}

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

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

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION	)
				.println( "separator"		,separator		)
				.println( "fileURL" 		,fileURL 		)
				.println( "filename"		,filename		)
				.println( "encode"			,encode			)
				.println( "readerClass" 	,readerClass 	)
				.println( "maxRowCount"		,maxRowCount	)
				.println( "displayMsg"		,displayMsg		)
				.println( "executeCount"	,executeCount	)
				.println( "modifyType"		,modifyType		)
				.println( "checkColumns"	,checkColumns	)
				.println( "adjustColumns"	,adjustColumns	)
				.println( "nullCheck"		,nullCheck		)
//				.println( "allColumnCheck"	,allColumnCheck	)
				.println( "command"			,command		)
				.println( "tableId"			,tableId		)
				.println( "sheetName"		,sheetName		)
				.println( "columns"			,columns		)
				.println( "useNumber"		,useNumber		)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
