/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.model;

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.StringUtil;	// 6.2.0.0 (2015/02/27)
import org.opengion.fukurou.util.FileInfo;		// 6.2.0.0 (2015/02/27)

import java.util.List;
import java.util.ArrayList;
import java.util.Map;							// 6.1.0.0 (2014/12/26) ConstData を Mapで管理
import java.util.HashMap;						// 6.1.0.0 (2014/12/26) ConstData を Mapで管理
import java.util.Arrays;						// 6.2.0.0 (2015/02/27)
import java.io.File;							// 6.2.0.0 (2015/02/27)

/**
 * EXCELやテキストファイルを、イベント方式に準拠して、読み込み処理を行います。
 * TableModelHelperイベントは、openGion形式のファイル読み取りに準拠した方法をサポートします。
 * ※ openGion形式のEXCEL/テキストファイルとは、#NAME 列に、カラム名があり、#で始まる
 *    レコードは、コメントとして判断し、読み飛ばす処理の事です。
 *
 * このイベントクラスは、サブクラスを作成し、EXCEL関連の EventReader_XLS、EventReader_XLSX
 * クラスや、EventReader_TEXT などのテキスト関連のクラスで、eventReader メソッドの引数に指定します。
 * EventReader_XLS と、EventReader_XLSX は、対象のEXCEL形式が異なりますが、実際は、
 * POIUtil#eventReader( String , TableModelHelper ) を使用すれば、拡張子に応じて使用するクラスを
 * 選択します。
 *
 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
 * @og.rev 6.2.0.0 (2015/02/27) パッケージ変更(util → model),クラス名変更(ExcelReaderEvent → TableModelHelper)
 * @og.group ファイル入力
 *
 * @version  6.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK7.0,
 */
public class TableModelHelper {
	private int		nowRowNo	= -1;		// 現在の行番号
	private boolean	isColSkip	;			// カラムスキップ中かどうか(true:スキップ中)
	private boolean	isNowName	;			// カラム名配列の収集中かどうか(true:収集中)
	private boolean	isReadBreak	;			// 読み取り処理を途中で中止するかどうか(true:中止)
	private String	nullBreakClm;			// データがnullまたはゼロ文字列の場合に、Sheet読み取りを停止
	private String	nullSkipClm	;			// 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加

	private int		skipRowCnt	;			// 読み飛ばす行数(読み飛ばし中でも ContDataは取得する)
	private int		nBrkClmNo = -1;			// nullBreakClmの列番号
	private int		nSkpClmNo = -1;			// 6.2.3.0 (2015/05/01) nullSkipClmの列番号

	private int		 clmSize	= -1 ;		// カラムサイズ(-1は未設定)
	private String[] names		;			// カラム名配列
	private String[] vals		;			// 値の文字列配列(１行分)
	private boolean	useVals		;			// 値配列に設定されたか？

	private List<Integer> colList	;		// カラム番号:name が null かゼロ文字列の場合は、飛ばします。
	private List<String>  nmsList	;		// カラム番号に対応したカラム名配列

	private ConstData	cnstData	;

	private boolean	 	useDebug	;		// 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加

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

	/**
	 * ファイルの読み取り開始時にイベントが発生します。
	 *
	 * 新しいファイルの読み取り開始毎に、１回呼ばれます。
	 * 戻り値が、true の場合は、そのファイルの読み取りを継続します。
	 * false の場合は、そのファイルの読み取りは行われません。
	 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
	 * super.startFile(String,int) してください。
	 * 初期実装では、true を返します。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) 新規作成
	 *
	 * @param   file  読み取りファイル
	 * @return  読取継続するか [true:継続/false:読取らない]
	 */
	public boolean startFile( final File file ) {
		if( useDebug ) { System.out.println( "startFile=[" + file + "]" ); }
		// ファイル属性を設定する。
		if( cnstData != null ) { cnstData.putConstFile( file ); }
		return true;
	}

	/**
	 * ファイルの読み取り終了時にイベントが発生します。
	 *
	 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
	 * super.endFile(File) してください。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
	 *
	 * @param   file  読み取りファイル
	 */
	public void endFile( final File file ) {
		if( useDebug ) { System.out.println( "endFile=[" + file + "]" ); }
		isReadBreak = false;			// 読み取り再開
		endRow();						// 行終了処理
		if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_FILE ); }
	}

	/**
	 * シートの読み取り開始時にイベントが発生します。
	 *
	 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
	 *
	 * 新しいシートの読み取り開始毎に、１回呼ばれます。
	 * 戻り値が、true の場合は、そのシートの読み取りを継続します。
	 * false の場合は、そのシートの読み取りは行わず、次のシートまで
	 * イベントは発行されません。
	 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
	 * super.startSheet(String,int) してください。
	 * 初期実装では、true を返します。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 *
	 * @param   shtNm  シート名
	 * @param   shtNo  シート番号(0～)
	 * @return  読取継続するか [true:継続/false:読取らない]
	 */
	public boolean startSheet( final String shtNm,final int shtNo ) {
		if( useDebug ) { System.out.println( "startSheet=[" + shtNm + "], No=[" + shtNo + "]" ); }
		// シート名を設定する。
		if( cnstData != null ) { cnstData.putConstSheet( shtNm ); }
		return true;
	}

	/**
	 * シートの読み取り終了時にイベントが発生します。
	 *
	 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
	 *
	 * #columnNames( String[] ) や、#values( String[] ,int ) などは、行の処理が完了した時点で
	 * イベントが呼ばれるため、一番最後のレコードの終了条件が判りません。
	 * そこで、このイベントを呼ぶことで、シートの終了(=最終行の終了)処理を行うことができます。
	 * 
	 * 引数のシート番号は、参考情報で、#startSheet( String,int ) で呼ばれたシート番号と
	 * 比較できるようにしています。
	 * 初期実装では、固定値設定処理を行っていますので、オーバーライドする場合は、
	 * super.endSheet(int) してください。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
	 *
	 * @param   shtNo  シート番号(0～)
	 */
	public void endSheet( final int shtNo ) {
		if( useDebug ) { System.out.println( "endSheet No=[" + shtNo + "]" ); }
		isReadBreak = false;			// 読み取り再開
		endRow();						// 行終了処理
		if( cnstData != null ) { cnstData.clearValsMap( ConstData.END_SHEET ); }
	}

	/**
	 * 読み取り状態の時に、rowNo にある行データを引数にイベントが発生します。
	 *
	 * ※ 主に、行データの読み込み処理で使用されるメソッドです。
	 *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
	 *    処理の過程で、設定されます。
	 *    このメソッドから、各種イベントが発行されます。
	 *
	 * 行データをセパレータで分解したのち、value( String ,int ,int ) メソッドを呼びます。
	 * ただし、行データが、null、空文字列、#で始まる場合は、呼ばれません。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) 新規作成
	 * @og.rev 6.2.1.0 (2015/03/13) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除
	 * @og.rev 6.2.5.0 (2015/06/05) デバック時に１行単位に出力するのを止めます。
	 * @og.rev 7.1.0.0 (2020/01/27) セパレータ分割時にデータ分割されない場合は、エンコードエラーの可能性が高い。
	 *
	 * @param   line	行データ
	 * @param   rowNo	行番号(0～)
	 * @param   sepa	セパレータ
	 */
	protected void value( final String line,final int rowNo,final char sepa ) {
		nowRowNo = rowNo;	// 現在行の設定

		// 値が存在する場合のみ、処理する。
		if( line!=null && !line.isEmpty()  ) {
			// 6.2.5.0 (2015/06/05) デバック時に１行単位に出力するのを止めます。
	//		if( useDebug ) { System.out.println( "rowNo[" + rowNo + "], line=[" + line + "]" ); }

			// 先頭カラムのコメント判断と、#NAME列判断
			if( line.charAt(0)=='#' ) {
				// 互換性の問題。#NAME は、大文字小文字両方対応できないといけない。( 5 == "#NAME".length() の事)
				if( clmSize <= 0 && line.length() >= 5 && "#NAME".equalsIgnoreCase( line.substring( 0,5 ) ) ) {

					colList = new ArrayList<>() ;		// 初期化
					nmsList = new ArrayList<>() ;		// 初期化
					final String[] tmpNm = StringUtil.csv2Array( line ,sepa );
					for( int colNo=1; colNo<tmpNm.length; colNo++ ) {	// #NAME を無視(colNo=1～)
						final String nm = tmpNm[colNo];
						// #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処)
						if( nm != null && !nm.isEmpty() ) {
							colList.add( Integer.valueOf( colNo ) );
							nmsList.add( nm );
						}
					}
					isNowName = true;
				}
			}
			// clmSize > 0 は、#NAME が設定済みという意味
			else if( clmSize > 0 && skipRowCnt <= rowNo ) {						// skipRowCnt は、値セットだけ影響する。
				final String[] tmpVals = StringUtil.csv2Array( line ,sepa );
	//			if( cnstData != null ) { tmpVals = cnstData.getConstVals( tmpVals ); }
	//			int min = Math.min( clmSize,tmpVals.length );

				// 7.1.0.0 (2020/01/27) エンコードによってはエラーにならず1カラムのデータとして取れてしまう。
				// カラム数が1以上で、データ数が1の場合は、取り込みミスなので処理を終了する。
				if( clmSize > 1 && tmpVals.length == 1 ) {
					isReadBreak = true;			// 処理の停止
					clmSize     = 0;			// #NAME列未設定と同じ(-1にしないのは、単に初期値と違うという事)
					return ;					// 即抜ける
				}

				for( int i=0; i<clmSize; i++ ) {
					final int indx = colList.get( i );							// カラム番号の位置が 値配列の配列番号
					if( indx >= 0 && indx < tmpVals.length ) {
						vals[i] = StringUtil.csvOutQuote( tmpVals[indx] );		// 6.2.1.0 (2015/03/13)
					}
				}
				useVals = true;
	//			values( vals , rowNo );							// イベント発行(現在行)
			}

			endRow();				// 行末処理実行。名前設定処理が走ります。
		}
	}

	/**
	 * 読み取り状態の時に、rowNo,colNo にあるセルの値を引数にイベントが発生します。
	 *
	 * ※ 主に、EXCEL関係の読み取り処理で使用されるメソッドです。
	 *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
	 *    処理の過程で、設定されます。
	 *    このメソッドから、各種イベントが発行されます。
	 *
	 * 戻り値が、true の場合は、その行の読み取りを継続します。
	 * false の場合は、その行の読み取りは行わず、次の行まで
	 * イベントは発行されません。
	 *
	 * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、# で
	 * 始まる場合は、その行はスキップします。
	 * ここでの return は、#isSkip( int ) と逆になりますので、ご注意ください。
	 * 初期実装では、#NAME処理、行スキップ、行終了処理等を実行します。
	 * オーバーライドする場合は、super.value(String,int,int) してください。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.2.2.0 (2015/03/27) 先頭に、'0 が含まれる場合のセミコロンや、前後のダブルクオートは削除
	 * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
	 *
	 * @param   val     文字列値
	 * @param   rowNo   行番号(0～)
	 * @param   colNo   列番号(0～)
	 * @return  読み取りするかどうか(true:読み取りする/false:読み取りしない)
	 * @see		#isSkip( int )
	 */
	protected boolean value( final String val,final int rowNo,final int colNo ) {
	//	if( useDebug ) { System.out.println( "R[" + rowNo + "," + colNo + "]=" + val ); }

		// 値が存在する場合のみ、処理する。
		if( val!=null && val.length()>0 ) {
			// 行番号が異なった場合は、行の終了処理を実行する。
			if( nowRowNo != rowNo ) {
				endRow();									// 行終了処理
				nowRowNo = rowNo;							// 現在行の設定
			}

			// 固定文字の設定なので、コメントもスキップも無関係
			if( cnstData != null ) { cnstData.putConstValue( val,rowNo,colNo ); }

			// 先頭カラムのコメント判断と、#NAME列判断
			if( colNo==0 && val.charAt(0)=='#' ) {
				if( "#NAME".equalsIgnoreCase( val ) && clmSize <= 0 ) {	// clmSize <= 0 は、#NAME未設定という意味
					isNowName = true ;
					colList = new ArrayList<>() ;		// 初期化
					nmsList = new ArrayList<>() ;		// 初期化
				}
				isColSkip = !isNowName;						// #NAME の場合は、false(SKIPしない)
			}
			else if( isNowName ) {							// #NAME処理中
				// #NAME 設定も、null や、ゼロ文字列の場合は、登録しない。(一番上の if で対処)
				colList.add( Integer.valueOf( colNo ) );
				nmsList.add( val );
			}
			// 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
			else if( nSkpClmNo >= 0 && StringUtil.isNull( vals[nSkpClmNo] ) ) {		// nullSkipClm 処理
				isColSkip = true;	// Skip する
			}
			// clmSize > 0 は、#NAME が設定済みという意味
			else if( clmSize > 0 && skipRowCnt <= rowNo ) {						// skipRowCnt は、値セットだけ影響する。
				final int indx = colList.indexOf( Integer.valueOf( colNo ) );	// カラム番号の位置が 値配列の配列番号
				if( indx >= 0 ) {
					vals[indx] = StringUtil.csvOutQuote( val );					// 6.2.2.0 (2015/03/27)
					useVals = true;
				}
			}
		}

		return !isColSkip;
	}

	/**
	 * rowNo を元に、この行をスキップするかどうか判定のイベントが発生します。
	 *
	 * ※ EXCEL関係の列毎にイベントが発生する場合の列処理をスキップするのが目的です。
	 *    行単位に読み込む場合や、行のループに入れても、意味はありません。
	 *    引数から、行のループに入れてしまいそうですが、行列のループに入れてください。
	 *
	 * ※ 主に、EXCEL関係の読み取り処理で使用されるメソッドです。
	 *    このメソッドは、EventReader#eventReader( String , TableModelHelper )メソッドの
	 *    処理の過程で、設定されます。
	 *    このメソッドから、各種イベントが発行されます。
	 *
	 * 戻り値が、true の場合は、その行の読み取りをスキップします。
	 * false の場合は、読み取り処理を継続します。
	 * スキップ中は、行に関するイベントは発行されません。
	 *
	 * 値(val)を求める前に、行情報を入手し、スキップ中の現在行と同じ場合に、スキップ(=true)と判定します。
	 * スキップ中かどうかは、#value( String,int,int ) で、判定します。
	 * 初期実装では、スキップ中 かつ 現在行と同じ行の場合、または、シートブレーク中の場合に、true を返します。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
	 *
	 * @param   rowNo   行番号(0～)
	 * @return  スキップするかどうか(true:スキップする/false:スキップしない)
	 * @see		#value( String,int,int )
	 */
	protected boolean isSkip( final int rowNo ) {
		return isColSkip && nowRowNo == rowNo || isReadBreak ;
	}

	/**
	 * 行の終了時に実行されます。
	 *
	 * ここでは、rowNo がブレークするか、シート終了時に呼ぶことで、
	 * 一番最後の行の処理を行います。
	 *
	 * #NAME 処理中の場合(isNowName == true) は、カラム名配列を作成して、
	 * columnNames イベントを呼び出します。
	 * 値配列が設定されている場合(useVals == true) は、values イベントを
	 * 呼び出します。
	 * #NAME 処理が完了している場合は、値配列の初期化を行います。
	 * そのうえで、スキップの解除(isColSkip = false)と行データ
	 * 未設定(useVals = false)に、初期化します。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 * @og.rev 6.2.2.0 (2015/03/27) Overflow処理は、Tag側に戻す。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 * @see		#columnNames( String[] )
	 * @see		#values( String[],int )
	 */
	private void endRow() {
		if( isNowName ) {								// #NAME 処理中の場合
			// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
			if( colList == null ) {
				final String errMsg = "#value(String,int,char) または#value(String,int,int) を先に実行しておいてください。" ;
				throw new OgRuntimeException( errMsg );
			}

			clmSize = colList.size();
			names   = nmsList.toArray( new String[clmSize] );
			if( nullBreakClm != null ) {
				for( int i=0; i<clmSize; i++ ) {
					if( nullBreakClm.equalsIgnoreCase( names[i] ) ) {
						nBrkClmNo = i;
						break;
					}
				}
			}
			// 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
			if( nullSkipClm != null ) {
				for( int i=0; i<clmSize; i++ ) {
					if( nullSkipClm.equalsIgnoreCase( names[i] ) ) {
						nSkpClmNo = i;
						break;
					}
				}
			}

			if( cnstData != null ) { cnstData.setColumns( names ); }
			columnNames( names.clone() );				// イベント
			isNowName = false;
			nmsList   = null ;		// 不要
		}
		else if( useVals ) {							// 値配列が設定されている場合
			if( nBrkClmNo >= 0 && StringUtil.isNull( vals[nBrkClmNo] ) ) {		// nullBreakClm 処理
				isReadBreak = true;												// ReadBreak する。
			}
			else {
				if( cnstData != null ) { vals = cnstData.getConstVals( vals ); }
				values( vals , nowRowNo );					// イベント発行(現在行)
			}
		}

		if( clmSize > 0 ) {								// clmSize > 0 は、#NAME が設定済みという意味
			vals = new String[clmSize];					// 値配列の初期化
		}

		isColSkip = false;						// スキップの解除
		useVals	  = false;						// 行データ未設定にする。
	}

	/**
	 * シート数のイベントが発生します。
	 *
	 * ※ EXCEL関係以外の読み取りでは、このイベントは発生しません。
	 *
	 * 処理の開始前に、シート数のイベントが発生します。
	 * これを元に、処理するシート番号の選別が可能です。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) シートの数のイベント
	 *
	 * @param   size  シート数
	 */
	public void sheetSize( final int size ) {
		if( useDebug ) { System.out.println( "sheetSize=[" + size + "]" ); }
	}

	/**
	 * カラム名配列がそろった段階で、イベントが発生します。
	 *
	 * openGion での標準的な処理は、colNo==0 の時に、val の先頭が、#NAME
	 * で始まるレコードを、名前配列として認識します。
	 * #value( String,int,int ) で、この #NAME だけは、継続処理されます。
	 * その上で、#NAME レコードが終了した時点で、カラム名配列が完成するので
	 * そこで初めて、このメソッドが呼ばれます。(EXCEL関係の場合)
	 *
	 * 外部から設定する、#setNames( String , boolean ) 時も同様に呼ばれます。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 *
	 * @param   names  カラム名配列
	 * @see		#value( String,int,int )
	 * @see		#setNames( String , boolean )
	 */
	public void columnNames( final String[] names ) {
		if( useDebug ) { System.out.println( "columnNames=[" + Arrays.toString(names) + "]" ); }
	}

	/**
	 * row にあるセルのオブジェクト値がそろった段階で、イベントが発生します。
	 *
	 * 初期実装は、何もありません。
	 *
	 * @og.rev 6.0.3.0 (2014/11/13) 新規作成
	 *
	 * @param   vals    文字列値の１行分の配列
	 * @param   rowNo   行番号(0～)
	 */
	public void values( final String[] vals,final int rowNo ) {
		if( useDebug ) { System.out.println( "values[" + rowNo + "]=[" + Arrays.toString(vals) + "]" ); }
	}

	/**
	 * 外部からCSV形式のカラム名文字列を設定します。
	 *
	 * ※ イベント処理実行前の初期化処理です。
	 *
	 * カラム名配列を、#NAME で始まるレコードではなく、外部から指定します。
	 * ここで設定した場合、#columnNames( String[] )イベントも発生します。
	 * null か、長さゼロのカラム名は、設定されません。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
	 *
	 * @param   clms  CSV形式のカラム名文字列
	 * @param	useNumber	行番号情報 [true:使用している/false:していない]
	 * @see		#columnNames( String[] )
	 */
	public void setNames( final String clms , final boolean useNumber ) {
		if( clms != null && clms.length() > 0 ) {
			if( useDebug ) { System.out.println( "setNames=[" + clms + "]" ); }

			colList = new ArrayList<>() ;		// 初期化
			nmsList = new ArrayList<>() ;		// 初期化

			final String[] nms = StringUtil.csv2Array( clms );
			final int adrs = useNumber ? 1:0 ;	// useNumber =true の場合は、１件目(No)は読み飛ばす。
			for( int i=0; i<nms.length; i++ ) {
				// null か、長さゼロのカラム名は、設定されません。
				final String nm = nms[i];
				if( nm != null && nm.length() > 0 ) {
					colList.add( Integer.valueOf( i+adrs ) );
					nmsList.add( nm );
				}
			}

			isNowName = true;		// 名前設定中
			useVals   = false;		// データ未設定
			endRow();				// 行末処理実行。名前設定処理が走ります。
		}
	}

	/**
	 * カラム名配列が、設定されたかどうか、返します。
	 *
	 * カラム名配列は、#NAME で始まるレコードか、#setNames( String,boolean )メソッドを
	 * 使用して、外部から指定します。
	 * カラム名配列が未設定の場合は、データもセットできないので、処理の最後の判定に使います。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
	 *
	 * @return	カラム名配列が、設定されたかどうか(true:設定済み/false:未設定)
	 * @see		#setNames( String,boolean )
	 */
	public boolean isNameSet() {
		return clmSize > 0;			// clmSize > 0 は、#NAME が設定済みという意味
	}

	/**
	 * 以降のデータを読み飛ばすかどうかを指定します(初期値:false)。
	 *
	 * データを読み込む途中で、それ以降のデータを読み込む必要がなくなった場合に、
	 * true に設定すると、以降のデータを今のシート/ファイルが終了するまで、スキップします。
	 * 例えば、読み込むべき必須カラムで判定する、nullBreakClm 機能を実現できます。
	 * 初期値は、false:読み飛ばさない です。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) シートブレイク処理追加
	 *
	 * @param   flag  以降のデータを読み飛ばすかどうか [true:読み飛ばす/false:読み飛ばさない]
	 * @see		#isSkip( int )
	 */
	public void setReadBreak( final boolean flag ) {
		isReadBreak = flag ;
	}

	/**
	 * 先頭データの読み飛ばし件数を設定します。
	 *
	 * ※ イベント処理実行前の初期化処理です。
	 *
	 * データの読み始めの行を指定します。
	 * シート/ファイルの先頭行が、０行としてカウントしますので、設定値は、読み飛ばす
	 * 件数になります。(１と指定すると、１件読み飛ばし、２件目から読み込みます。)
	 * 読み飛ばしは、コメント行などは、無視しますので、実際の行数分読み飛ばします。
	 * ＃NAME属性や、columns 属性は、読み飛ばし中でも処理対象行になります。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
	 *
	 * @param	count 読み始めの初期値(0なら、読み飛ばしなし)
	 */
	public void setSkipRowCount( final int count ) {
		skipRowCnt = count;
	}

	/**
	 * ここに指定されたカラム列に NULL/ゼロ文字列 が現れた時点でSheetの読み取りを中止します。
	 *
	 * これは、指定のカラムは必須という事を条件に、そのレコードだけを読み取る処理を行います。
	 * 複数Sheetの場合は、次のSheetを読みます。
	 * Sheetが存在しない場合(通常のテキスト等)では、読み取り処理の終了になります。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) 新規追加
	 *
	 * @param   clm カラム列
	 */
	public void setNullBreakClm( final String clm ) {
		nullBreakClm = clm;
	}

	/**
	 * ここに指定されたカラム列に NULL が現れたレコードは読み飛ばします。
	 *
	 * 例えば、更新対象カラムで、null の場合は、何もしない、などのケースで使用できます。
	 * 複数カラムの場合は、AND条件やOR条件などが、考えられるため、
	 * カラムを一つにまとめて、指定してください。
	 *
	 * @og.rev 6.2.3.0 (2015/05/01) 行読み飛ばし nullSkipClm追加
	 *
	 * @param   clm カラム列
	 */
	public void setNullSkipClm( final String clm ) {
		nullSkipClm = clm;
	}

	/**
	 * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。
	 *
	 * ※ イベント処理実行前の初期化処理です。
	 *
	 * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。
	 * 行列は、EXCELオブジェクトに準拠するため、０から始まる整数です。
	 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
	 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として
	 * 設定することができます。
	 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。
	 *
	 * 5.7.6.3 (2014/05/23) より、
	 *   ①EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
	 *     なお、A1,A2,B1 の記述は、必ず、英字1文字＋数字 にしてください。(A～Zまで)
	 *     EXCEL2007形式で列数が拡張されていますが、列数は制限しています。
	 *   ②処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。
	 * 6.2.0.0 (2015/02/27) より、
	 *   ③EXCEL以外では、"FILE","NAME","SUFIX" が使えます。(EXCEL時にも使えます。)
	 *     NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC)
	 * 例えば、sheetConstKeys="CLM,LANG,NAME" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、
	 * NAMEカラムには、シート名を読み込むことができます。
	 * これは、内部処理の簡素化のためです。
	 *
	 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。
	 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1)))
	 *
	 * @og.rev 5.5.8.2 (2012/11/09) 新規追加
	 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
	 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
	 *
	 * @param	constKeys	固定値となるカラム名(CSV形式)
	 * @param	constAdrs	固定値となるアドレス(行-列,行-列...) or(A1,B3...)
	 */
	public void setConstData( final String constKeys,final String constAdrs ) {
		if( constKeys != null && !constKeys.isEmpty() && constAdrs != null && !constAdrs.isEmpty() ) {
			cnstData = new ConstData( constKeys,constAdrs );
			// setNames( String , boolean ) と、このメソッドの呼び出し順に影響がないようにするため。
			if( names != null ) { cnstData.setColumns( names ); }
		}
	}

	/**
	 * デバッグ情報を出力するかどうか[true:する/false:しない]を指定します。
	 *
	 * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
	 * どのシートなのか、判らなくなります。
	 * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
	 * 通常は使用しませんので、設定を無視します。
	 * 初期値は、false:デバッグ情報を出力しない です。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
	 *
	 * @param	useDebug	デバッグ出力するか [true:する/false:しない]
	 */
	public void setDebug( final boolean useDebug ) {
		this.useDebug = useDebug;
	}

	/**
	 * デバッグ情報を出力するかどうか[true:する/false:しない]を取得します。
	 *
	 * EXCELなどを読み取る場合、シートマージで読み取ると、エラー時の行番号が、連番になるため、
	 * どのシートなのか、判らなくなります。
	 * そこで、どうしてもわからなくなった場合に備えて、デバッグ情報を出力できるようにします。
	 *
	 * @og.rev 6.2.0.0 (2015/02/27) デバッグ情報の出力するかどうか。新規追加
	 *
	 * @return	デバッグ出力 [true:する/false:しない]
	 */
	protected boolean isDebug() {
		return useDebug ;
	}

	/**
	 * EXCELファイルの所定の位置から、固定値を取り出す処理を管理します。
	 * 
	 * この固定値の取出しは、内部処理に、非常に依存しているため、今は、
	 * TableModelHelper クラスに含めていますが、将来的には、分ける予定です。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
	 * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)追加
	 *
	 * @version  6.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK7.0,
	 */
	private static final class ConstData {
		/** 内部情報のクリア方法の指定 {@value} */
		public static final int END_FILE  = 1 ;
		/** 内部情報のクリア方法の指定 {@value} */
		public static final int END_SHEET = 2 ;

		// 6.2.0.0 (2015/02/27) 特殊記号処理 追加 (SHEET,FILE,NAME,SUFIX)
		private static final String KEYS = ",SHEET,FILE,NAME,SUFIX,";

		// ①cnstMap は、cnstKey をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション)
		private final Map<String,String> cnstMap   = new HashMap<>();			// 6.1.0.0 (2014/12/26) Mapで管理
		// ②rowcolMap は、rowcol をキーに、アドレスを持っています。
		private final Map<String,Integer> rowcolMap = new HashMap<>();		// 6.1.0.0 (2014/12/26) Mapで管理
		// ③valsMap は、アドレス をキーに、拾ってきた値を持っています。(Sheet毎のトランザクション)
		private final Map<Integer,String> valsMap   = new HashMap<>();		// 6.1.0.0 (2014/12/26) Mapで管理

		private final int maxRow  ;		// 6.4.9.1 (2016/08/05) final化。最大値持っておき、判定処理を早める。

		// ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
		private boolean isNameSet ;
		private File    tmpFile   ;
		private String  tmpShtNm  ;

		/**
		 * 固定値となるカラム名(CSV形式)と、固定値となるアドレス(行-列,行-列...) or(A1,B3...)を設定します。
		 *
		 * アドレスは、EXCEL上の行-列か、EXCEL列のA1,B3 などの形式を、CSV形式で指定します。
		 * 行列は、EXCELオブジェクトに準拠するため、０から始まる整数です。
		 * 0-0 ⇒ A1 , 1-0 ⇒ A2 , 0-1 ⇒ B1 になります。
		 * これにより、シートの一か所に書かれている情報を、DBTableModel のカラムに固定値として
		 * 設定することができます。
		 * 例として、DB定義書で、テーブル名をシートの全レコードに設定したい場合などに使います。
		 *
		 * 5.7.6.3 (2014/05/23) より、
		 *   ①EXCEL表記に準拠した、A1,A2,B1 の記述も処理できるように対応します。
		 *     なお、A1,A2,B1 の記述は、必ず、英字1文字＋数字 にしてください。(A～Zまで)
		 *     EXCEL2007形式で列数が拡張されていますが、列数は制限しています。
		 *   ②処理中のEXCELシート名をカラムに割り当てるために、"SHEET" という記号に対応します。
		 * 6.2.0.0 (2015/02/27) より、
		 *   ③EXCEL以外では、"FILE","NAME","SUFIX" が使えます。
		 *     NAME は、ファイル名から拡張子を取り除いた文字列になります。(AAA/BBB/CCC.xls → CCC)
		 * 例えば、sheetConstKeys="CLM,LANG,SHT" とし、sheetConstAdrs="0-0,A2,SHEET" とすると、
		 * SHTカラムには、シート名を読み込むことができます。
		 * これは、内部処理の簡素化のためです。
		 *
		 * ちなみに、EXCELのセルに、シート名を表示させる場合の関数は、下記の様になります。
		 * =RIGHT(CELL("filename",$A$1),LEN(CELL("filename",$A$1))-FIND("]",CELL("filename",$A$1)))
		 *
		 * @og.rev 5.5.8.2 (2012/11/09) 新規追加
		 * @og.rev 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
		 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
		 * @og.rev 6.2.0.0 (2015/02/27) 特殊記号(FILE,NAME,SUFIX)の追加対応
		 * @og.rev 6.4.9.1 (2016/08/05) maxRowをfinal化します。
		 *
		 * @param	constKeys	固定値となるカラム名(CSV形式)
		 * @param	constAdrs	固定値となるアドレス(行-列,行-列...) or(A1,B3...)
		 */
		ConstData( final String constKeys,final String constAdrs ) {
			final String[] cnstKeys = constKeys.split( "," );
			final String[] row_col  = constAdrs.split( "," );

			if( cnstKeys.length != row_col.length ) {
				final String errMsg = "キーに対するアドレスの個数が不一致です。Keys=[" + constKeys + "]"
							+ " , Adrs=[" + constAdrs + "]" ;
				throw new OgRuntimeException( errMsg );
			}

			int tmpRow = -1;								// 6.4.9.1 (2016/08/05)
			// 初期の カラムとアドレス(キーワード)の関連付け
			for( int j=0; j<cnstKeys.length; j++ ) {
				final String cnstKey = cnstKeys[j].trim();	// 前後の不要なスペースを削除
				if( !cnstKey.isEmpty() ) {
					String rowcol = row_col[j].trim();		// 前後の不要なスペースを削除
					if( !rowcol.isEmpty() ) {
						// 5.7.6.3 (2014/05/23) EXCEL表記(A2,B1等)の対応と、特殊記号(SHEET)の対応
						final int sep = rowcol.indexOf( '-' );
						if( sep > 0 ) {
							final int row = Integer.parseInt( rowcol.substring( 0,sep ) );
			//				int col = Integer.parseInt( rowcol.substring( sep+1 ) );
							if( tmpRow < row ) { tmpRow = row; }	// 6.4.9.1 (2016/08/05)
			//				rowcol = String.valueOf( (char)('A' + col) ) + String.valueOf( row + 1 ) ;	// row-col 形式なので、不要
						}
						// 6.2.0.0 (2015/02/27) 特殊記号(SHEET,FILE,NAME,SUFIX)の追加対応
						else if( !KEYS.contains( ',' + rowcol + ',' ) ) {
							final int row = Integer.parseInt( rowcol.substring( 1 ) ) -1;		// C6 の場合、rowは、6-1=5
							final int col = rowcol.charAt(0) - 'A' ;							// C6 の場合、colは、'C'-'A'=2
							if( tmpRow < row ) { tmpRow = row; }	// 6.4.9.1 (2016/08/05)
							rowcol = row + "-" + col ;
						}
						cnstMap.put( cnstKey , rowcol );		// 6.1.0.0 (2014/12/26) cnstMap に行列情報を設定する
					}
				}
			}
			maxRow = tmpRow;
		}

		/**
		 * カラム名配列を元に、固定値カラムのアドレスを求めます。
		 * カラム名配列は、順番に、指定する必要があります。
		 *
		 * @og.rev 6.1.0.0 (2014/12/26) カラム名配列設定の対応
		 *
		 * @param	names	カラム列配列(可変長引数)
		 */
		/* default */ void setColumns( final String... names ) {
			// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			if( names != null && names.length > 0 ) {
				// 5.5.8.2 (2012/11/09) 固定値取得用の cnstIndx の設定を行う。
				for( int i=0; i<names.length; i++ ) {
					final String rowcol = cnstMap.get( names[i] );		// cnstKey があれば、rowcol が取得できるはず
					if( rowcol != null ) {
						rowcolMap.put( rowcol , Integer.valueOf( i ) );
					}
				}
				isNameSet = true;			// 名前設定がされないと、FILEやSHEET キーワードが使えない。

				if( tmpFile  != null ) { putConstFile(  tmpFile  ); }
				if( tmpShtNm != null ) { putConstSheet( tmpShtNm ); }
			}
		}

		/**
		 * 読み取り時に、rowNo,colNo にあるセルの値を、固定値となるカラム名に関連付けます。
		 *
		 * イベントモデルでは、固定値の指定アドレス(rowNo,colNo)をピンポイントで取得することが
		 * できないため、イベント発生毎に、チェックする必要があります。
		 * そのため、固定値を使用すると、処理速度が低下します。
		 *
		 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
		 *
		 * @param   val     文字列値(null またはゼロ文字列は、不可)
		 * @param   rowNo   行番号(0～)
		 * @param   colNo   列番号(0～)
		 */
		/* default */ void putConstValue( final String val,final int rowNo,final int colNo ) {
			if( rowNo <= maxRow ) {
				final String rowcol = rowNo + "-" + colNo ;
				final Integer adrs = rowcolMap.get( rowcol );
				if( adrs != null ) {
					valsMap.put( adrs,val );
				}
			}
		}

		/**
		 * ファイル系特殊記号(FILE,NAME,SUFIX)を指定します。
		 *
		 * ../AAA/BBB/CCC.XLS というファイルオブジェクトに対して、
		 *   FILE    : CCC.XLS ファイル名
		 *   NAME    : CCC     拡張子なしのファイル名
		 *   SUFIX   : xls     ピリオド無しの拡張子(小文字に統一)
		 *
		 * これは、新しいファイルの読み取り開始時に、設定します。
		 *
		 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
		 *
		 * @og.rev 6.2.0.0 (2015/02/27) 新規作成
		 *
		 * @param   filNm  指定ファイル
		 */
		/* default */ void putConstFile( final File filNm ) {
			if( filNm != null ) {
				tmpFile = filNm;		// 名前設定がされるまで、一時保管する。(順番があるので呼ばれればセットしておく)
				if( isNameSet ) {		// 名前設定がされないと、FILEやSHEET キーワードが使えない。
					final FileInfo info = new FileInfo( filNm );

					for( final String key : FileInfo.KEY_LIST ) {
						final Integer adrs = rowcolMap.get( key );
						if( adrs != null ) {
							final String val = info.getValue( key );
							if( val != null ) {
								valsMap.put( adrs,val );
							}
						}
					}
				}
			}
		}

		/**
		 * シート名を外部から指定します。
		 * アドレス指定の特殊系として、"SHEET" 文字列を指定できます。
		 * これは、新しいシートの読み取り開始時に、設定します。
		 *
		 * ※ rowcolMap を使用する為、必ず #setColumns( String... ) の実行後に行います。
		 *
		 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
		 *
		 * @param	shtNm	シート名
		 */
		/* default */ void putConstSheet( final String shtNm ) {
			if( shtNm != null ) {
				tmpShtNm = shtNm;		// 名前設定がされるまで、一時保管する。
				if( isNameSet ) {		// 名前設定がされないと、FILEやSHEET キーワードが使えない。
					final Integer adrs = rowcolMap.get( "SHEET" );
					if( adrs != null ) {
						valsMap.put( adrs,shtNm );
					}
				}
			}
		}

		/**
		 * 内部の valsMap を初期化します。
		 * 固定値の読み取りは、シートごとに行います。
		 * 新しいシートにデータが設定されていない場合、前のシートの値が残ります。
		 * ここでは、シート呼出しごとに、毎回クリアします。
		 * 引数に応じて、クリアする範囲を限定します。
		 * END_FILE 時は、すべてクリア。END_SHEET 時は、ファイル情報は残します。
		 *
		 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
		 *
		 * @param   type 内部情報のクリア方法の指定(END_FILE or END_SHEET)
		 */
		/* default */ void clearValsMap( final int type ) {
			valsMap.clear();
			if( type == END_SHEET ) { putConstFile( tmpFile ); }	// 全削除後にファイル情報を再設定
		}

		/**
		 * 値配列のデータに、固定値を設定します。
		 * 引数の文字列配列に、固定値を設定しますので、配列オブジェクト自体を更新します。
		 *
		 * @og.rev 6.1.0.0 (2014/12/26) Excel関係改善
		 * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
		 *
		 * @param   vals    文字列値の１行分の配列
		 * @return  固定値を設定された１行分の文字列配列
		 */
		/* default */ String[] getConstVals( final String[] vals ) {
			if( vals != null && vals.length > 0 ) {
				// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
				valsMap.forEach( (k,v) -> {
							if( k < vals.length ) { vals[k] = v; }
				} );
			}
			return vals ;
		}
	}
}
