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

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.opengion.fukurou.db.ResultSetValue;				// 6.0.4.0 (2014/11/28)
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.resource.ResourceManager;
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * DBTableModelを継承した TableModelの編集設定による変換を行うための実装クラスです。
 *
 * このクラスでは、オブジェクト初期化後は、通常のDBTableModelと同じ振る舞いをします。
 * オブジェクト初期化時(createメソッド呼び出し時)に、検索結果オブジェクトから直接、編集設定に
 * 応じて変換されたDBTableModelを生成します。
 *
 * このような実装を行う理由は、メモリ使用量を節約するためです。
 * この編集設定では、集計機能を備えていますが、一旦DBTableModel作成後に集計処理を行うと、
 * メモリを大量に使用する恐れがあるため、検索結果オブジェクトから直接集計処理を行い、DBTableModelを
 * 生成しています。
 *
 * DBTableModel インターフェースは，データベースの検索結果(Resultset)をラップする
 * インターフェースとして使用して下さい。
 *
 * @og.rev 5.3.6.0 (2011/06/01) 新規作成
 * @og.group テーブル管理
 *
 * @version  5.0
 * @author   Hiroki Nakamura
 * @since    JDK6.0,
 */
public class DBTableModelEditor extends DBTableModelImpl {
	private static final String			JS		= HybsSystem.JOINT_STRING;
	private static final DecimalFormat	FORMAT	= new DecimalFormat( "0.#########" );

	private int rowCountColumn = -1;
	private DBEditConfig config;

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

	/**
	 * DBTableModel を設定し、このオブジェクトを初期化します。
	 *
	 * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
	 * @og.rev 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する
	 * @og.rev 6.0.2.5 (2014/10/31) FireBardでの日付型取得対応
	 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
	 * @og.rev 6.0.4.0 (2014/11/28) queryタグが複数ある場合の事前チェックの条件訂正
	 *
	 * @param	result			検索結果オブジェクト
	 * @param	skipRowCount	読み飛ばし件数
	 * @param	maxRowCount		最大検索件数
	 * @param	resource		ResourceManagerオブジェクト
	 * @param	config			編集設定オブジェクト
	 * @throws	SQLException データベースアクセスエラー
	 */
	public void create( final ResultSet result, final int skipRowCount, final int maxRowCount, final ResourceManager resource, final DBEditConfig config ) throws SQLException {
		if( result == null || config == null || resource == null ) {
			final String errMsg = "DBTableModelまたは、DBEditConfigが設定されていません。";
			throw new HybsSystemException( errMsg );	// 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
		}

		this.config = config;

		/**********************************************************************
		 * 各パラメーターの初期化処理
		 **********************************************************************/
		// 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
		final ResultSetValue rsv = new ResultSetValue( result );		// 6.0.4.0 (2014/11/28)
		int colCnt = rsv.getColumnCount();						// 6.0.4.0 (2014/11/28)

		if( config.useGroup() || config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) {
			rowCountColumn = colCnt;
			colCnt++;
		}
		init( colCnt );

		DBColumn[] dbColumn = new DBColumn[numberOfColumns];
		boolean[] sumFilter			= new boolean[numberOfColumns];
		boolean[] groupFilter		= new boolean[numberOfColumns];
		boolean[] subTotalFilter	= new boolean[numberOfColumns];
		boolean[] totalFilter		= new boolean[numberOfColumns];
		boolean   sumFilterCheck	= false;						// 6.0.2.1 (2014/09/26)
		if( config.useGrandTotal() ) { sumFilterCheck = true; }		// 6.0.4.0 (2014/11/28)
		for( int column=0; column<numberOfColumns; column++ ) {
			String name = null;
			// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
			if( column == rowCountColumn ) {
				name = "rowCount";
				dbColumn[column] = resource.makeDBColumn( name );
			}
			else {
				name = rsv.getColumnName(column);					// 6.0.4.0 (2014/11/28)
				dbColumn[column] = resource.getDBColumn( name );
				if( dbColumn[column] == null ) {
					dbColumn[column] = DBTableModelUtil.makeDBColumn( name,column,rsv,resource );	// 6.0.4.0 (2014/11/28)
				}
			}

			setDBColumn( column,dbColumn[column] );
			sumFilter[column]		= config.isSumClm( name );
			groupFilter[column]		= config.isGroupClm( name );
			subTotalFilter[column]	= config.isSubTotalClm( name );
			totalFilter[column]		= config.isTotalClm( name );
			// 6.0.4.0 (2014/11/28) queryタグが複数ある場合の事前チェックの条件訂正
			if( sumFilter[column] || groupFilter[column] || subTotalFilter[column] || totalFilter[column] ) {
				sumFilterCheck = true;
			}
		}

		/**********************************************************************
		 * 集計、ソート、合計処理
		 **********************************************************************/
		// 集計キーに基づく集計処理を行いデータを追加します。
		if( config.useGroup() ) {
			// 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
			addGroupRows( rsv, skipRowCount, maxRowCount, sumFilter, groupFilter );
		}
		// 通常と同じように結果カーソルからデータを読込みデータを追加します。
		else {
	 		// 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
			// 6.0.2.5 (2014/10/31) int[] types 復活。isOther ﾌﾗｸﾞも使います。
			// 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
			addPlainRows( rsv, skipRowCount, maxRowCount );
		}

		// ソート処理
		if( getRowCount() > 0 && config.useOrderBy() ) {
			sort();
		}

		// 小計・合計行を追加します。
		if( getRowCount() > 0 && !isOverflow()
			&& ( config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) ) {

		 	// 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する
			if( !sumFilterCheck ) {
				final String errMsg = "小計、合計カラムが存在しません。"
								+ " これは、queryタグが複数あり、mainTrans=false で制御されていない可能性があります。" ;
				throw new HybsSystemException( errMsg );
			}

			addTotalRows( maxRowCount, resource, sumFilter, groupFilter, subTotalFilter, totalFilter );
		}
	}

	/**
	 * 集計キーの設定に基づき、DBTableModelの行を追加します。
	 * 内部的には、キーブレイクではなく、内部マップにより集計処理を行っているため、
	 * 集計キーが検索順により散在した場合でも1まとまりで集計されます。
	 *
	 * @og.rev 5.3.9.0 (2011/09/01) 値がNULLの場合にエラーになるバグを修正
	 * @og.rev 5.6.1.0 (2013/02/01) doubleをBigDecimalに変更
	 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
	 * @og.rev 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
	 *
	 * @param rsv ResultSetValueオブジェクト
	 * @param skipRowCount 読み飛ばし件数
	 * @param maxRowCount 最大検索件数
	 * @param sumFilter 集計項目フィルター
	 * @param groupFilter グループキーフィルター
	 * @throws SQLException データベースアクセスエラー
	 */
	private void addGroupRows( final ResultSetValue rsv, final int skipRowCount, final int maxRowCount
								, final boolean[] sumFilter, final boolean[] groupFilter ) throws SQLException {
		int numberOfRows = 0;
		while( numberOfRows < skipRowCount && rsv.next() ) {
			// 注意 resultSet.next() を先に判定すると必ず１件読み飛ばしてしまう。
			numberOfRows ++ ;
		}
		numberOfRows = 0;

		final Map<String,String[]>	groupLinkedMap	= new LinkedHashMap<>();
		final Map<String,Integer>	groupCountMap	= new HashMap<>();
		final Map<String,BigDecimal[]>	sumMap		= new HashMap<>();			// 5.6.1.0 (2013/02/01)
		final StringBuilder groupKey = new StringBuilder( BUFFER_MIDDLE );		// 6.1.0.0 (2014/12/26) refactoring
		while( numberOfRows < maxRowCount && rsv.next() ) {
			groupKey.setLength(0);												// 6.1.0.0 (2014/12/26) refactoring
			BigDecimal[] sumVals = new BigDecimal[config.getSumClmCount()];		// 5.6.1.0 (2013/02/01)
			String[]   groupVals = new String[config.getGroupClmCount()];
			int sc = 0;
			int gc = 0;
			for( int column=0; column<numberOfColumns; column++ ) {
				if( column != rowCountColumn ) {
					final String val = rsv.getValue( column );
					if( sumFilter[column] ) {
						// 5.3.9.0 (2011/09/01) 値がNULLの場合の対応漏れ
						// sumVals[sc++] = ( val != null && val.length() > 0 ? Double.valueOf( val ) : 0 );
	//					sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); // 5.6.1.0 (2013/02/01)
						// 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
						sumVals[sc++] = new BigDecimal( unscoDel( val,"0" ) );

					}
					if( groupFilter[column] ) {
						groupVals[gc++] = val;
						groupKey.append( val ).append( JS );
					}
				}
			}

			final String key = groupKey.toString();
			int groupCount = 0;
			if( groupLinkedMap.containsKey( key ) ) {
				final BigDecimal[] eSumVals = sumMap.get( key ); // 5.6.1.0 (2013/02/01)
				for( int i=0; i<config.getSumClmCount(); i++ ) {
					sumVals[i] = sumVals[i] == null ? BigDecimal.ZERO : sumVals[i].add( eSumVals[i] ); // 5.6.1.0 (2013/02/01)
				}
				sumMap.put( key, sumVals );
				groupCount = groupCountMap.get( key ).intValue() + 1;
			}
			else {
				groupLinkedMap.put( key, groupVals );
				groupCount = 1;
				numberOfRows++;
			}
			sumMap.put( key, sumVals );
			groupCountMap.put( key, Integer.valueOf( groupCount ) );
		}

		// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
		groupLinkedMap.forEach( (k,v) -> addRow( groupFilter, v, groupCountMap.get( k ), sumFilter, sumMap.get( k ) ) );

		// 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
		if( numberOfRows >= maxRowCount && rsv.next() ) {
			setOverflow( true );
		}
	}

	/**
	 * 検索結果オブジェクトを順に読み取り、そのままDBTableModelの行を追加します。
	 *
	 * @og.rev 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
	 * @og.rev 6.0.2.5 (2014/10/31) int[] types 復活。isOther ﾌﾗｸﾞも使います。
	 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
	 *
	 * @param rsv ResultSetValueオブジェクト
	 * @param skipRowCount 読み飛ばし件数
	 * @param maxRowCount 最大検索件数
	 * @throws	SQLException データベースアクセスエラー
	 */
	private void addPlainRows( final ResultSetValue rsv, final int skipRowCount, final int maxRowCount ) throws SQLException {
		int numberOfRows = 0;
		while( numberOfRows < skipRowCount && rsv.next() ) {
			// 注意 resultSet.next() を先に判定すると必ず１件読み飛ばしてしまう。
			numberOfRows ++ ;
		}
		numberOfRows = 0;

		// 6.0.2.5 (2014/10/31) 行列のループなので、 CLOB 使用可否でループを分ける。
			// 6.0.2.5 (2014/10/31) typesを考慮した処理
			while( numberOfRows < maxRowCount && rsv.next() ) {
				numberOfRows++ ;
				String[] columnValues = new String[numberOfColumns];
				for( int column=0; column<numberOfColumns; column++ ) {
					// 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
					if( column == rowCountColumn ) {
						columnValues[column] = "";
					}
					else {
						columnValues[column] = rsv.getValue( column );
					}
				}
				addColumnValues( columnValues );
			}

		// 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
		if( numberOfRows >= maxRowCount && rsv.next() ) {
			setOverflow( true );
		}
	}

	/**
	 * DBTableModelのソート処理を行います。
	 *
	 */
	private void sort() {
		// orderByClmsによる並び替え
		final DBTableModelSorter temp = new DBTableModelSorter();
		temp.setModel( this );
		final String[] oClms = StringUtil.csv2Array( config.getOrderByClms() );
		for( int i=oClms.length-1; i>=0; i-- ) {
			String oc = oClms[i];
			boolean ascending = true;
			if( StringUtil.startsChar( oc , '!' ) ) {				// 6.2.0.0 (2015/02/27) １文字 String.startsWith
				oc = oc.substring( 1 );
				ascending = false;
			}
			final int clmNo = getColumnNo( oc );
			temp.sortByColumn( clmNo, ascending );
		}
		this.data = temp.data;
		this.rowHeader = temp.rowHeader;
	}

	/**
	 * DBTableModelからデータを読み取り、編集設定情報を元に合計行の追加処理を行います。
	 * 合計行の追加は、キーブレイクにより行われますので、同じキーが複数回出現した場合は、
	 * それぞれの行に対して、合計行が付加されます。
	 *
	 * @og.rev 5.3.7.0 (2011/07/01) 小計、合計行追加処理でオーバーフローフラグがセットされないバグを修正
	 * @og.rev 5.6.1.0 (2013/02/01) 誤差回避のため、doubleではなくdecimalで計算する
	 * @og.rev 5.6.8.1 (2013/09/13) 1行目が合計されていなかったので修正
	 * @og.rev 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する対応
	 *
	 * @param	maxRowCount 最大検索件数
	 * @param	resource リソースマネージャー
	 * @param	sumFilter 集計項目フィルター
	 * @param	groupFilter グループキーフィルター
	 * @param	subTotalFilter 小計キーフィルター
	 * @param	totalFilter 合計キーフィルター
	 *
	 * @return	オーバーフローしたかどうか(最大件数が超えた場合でかつ次のデータがある場合は、true)
	 */
	private boolean addTotalRows( final int maxRowCount, final ResourceManager resource
									, final boolean[] sumFilter
									, final boolean[] groupFilter
									, final boolean[] subTotalFilter
									, final boolean[] totalFilter ) {

		final String subTotalLabel	= ( config.useSubTotal()   ? resource.makeDBColumn( "EDIT_SUBTOTAL_VALUE"   ).getLongLabel() : null );
		final String totalLabel		= ( config.useTotal()	   ? resource.makeDBColumn( "EDIT_TOTAL_VALUE"      ).getLongLabel() : null );
		final String grandTotalLabel= ( config.useGrandTotal() ? resource.makeDBColumn( "EDIT_GRANDTOTAL_VALUE" ).getLongLabel() : null );

		int numberOfRows = getRowCount();
		final int sumClmCount  = config.getSumClmCount();
		BigDecimal subTotalSum[]   = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
		BigDecimal totalSum[]      = new BigDecimal[sumClmCount];
		BigDecimal grandTotalSum[] = new BigDecimal[sumClmCount];

		String lastSubTotalKey = null;
		String lastTotalKey    = null;

		int subTotalCount   = 0;
		int totalCount      = 0;
		int grandTotalCount = 0;
		int rowCount =0;

		int tblIdx = 0;
		final StringBuilder groupKey    = new StringBuilder( BUFFER_MIDDLE );	// 6.1.0.0 (2014/12/26) refactoring
		final StringBuilder subTotalKey = new StringBuilder( BUFFER_MIDDLE );	// 6.1.0.0 (2014/12/26) refactoring
		final StringBuilder totalKey    = new StringBuilder( BUFFER_MIDDLE );	// 6.1.0.0 (2014/12/26) refactoring
		while( numberOfRows < maxRowCount && tblIdx < getRowCount() ) {
			BigDecimal[] sumVals      = new BigDecimal[sumClmCount];	// 5.6.1.0 (2013/02/01)
			groupKey.setLength(0);															// 6.1.0.0 (2014/12/26) refactoring
			subTotalKey.setLength(0);														// 6.1.0.0 (2014/12/26) refactoring
			totalKey.setLength(0);															// 6.1.0.0 (2014/12/26) refactoring

			int sc = 0;
			for( int column=0; column<numberOfColumns; column++ ) {
				final String val = getValue( tblIdx, column );
				if( groupFilter[column] )		{ groupKey.append( val ).append( JS ); }
				// 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
		//		if( sumFilter[column] )			{ sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); } // 5.6.1.0 (2013/02/01)
				if( sumFilter[column] )			{ sumVals[sc++] = new BigDecimal( unscoDel( val,"0" ) ) ; }							// 7.0.5.0 (2019/09/09)
				if( subTotalFilter[column] )	{ subTotalKey.append( val ).append( JS ); }
				if( totalFilter[column] )		{ totalKey.append( val ).append( JS ); }
				// 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
		//		if( column == rowCountColumn )	{ rowCount = ( val != null && val.length() > 0 ? Integer.parseInt( val ) : 0 ); }	// 6.0.2.4 (2014/10/17) メソッド間違い
				if( column == rowCountColumn )	{ rowCount = Integer.parseInt( unscoDel( val,"0" ) ); }								// 7.0.5.0 (2019/09/09)
			}

			// 小計キーブレイク処理
			if( numberOfRows < maxRowCount && config.useSubTotal() && lastSubTotalKey != null && lastSubTotalKey.length() > 0
				&& !lastSubTotalKey.equals( subTotalKey.toString() ) ) {
				addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
				subTotalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
				subTotalCount = 0;
				numberOfRows++;
				tblIdx++;
			}

			// 合計キーブレイク処理
			if( numberOfRows < maxRowCount && config.useTotal() && lastTotalKey != null && lastTotalKey.length() > 0
				&& !lastTotalKey.equals( totalKey.toString() ) ) {
				addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
				totalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
				totalCount = 0;
				numberOfRows++;
				tblIdx++;
			}

			// 小計、合計、総合計単位に集計項目の合計値を加算します。
			// 6.0.2.0 (2014/09/19) BigDecimal.ZERO.add で、null エラーが発生するのは、query が複数あり、mainTrans=false で制御されていない場合
			for( int cnt=0; cnt<sc; cnt++ ) {
				subTotalSum[cnt]	= subTotalSum[cnt]	== null ? BigDecimal.ZERO.add(sumVals[cnt]) : subTotalSum[cnt].add(  sumVals[cnt]); // 5.6.8.1 (2013/09/13)
				totalSum[cnt]		= totalSum[cnt]		== null ? BigDecimal.ZERO.add(sumVals[cnt]) : totalSum[cnt].add(     sumVals[cnt]);
				grandTotalSum[cnt]	= grandTotalSum[cnt]== null ? BigDecimal.ZERO.add(sumVals[cnt]) : grandTotalSum[cnt].add(sumVals[cnt]);
			}

			lastSubTotalKey	= subTotalKey.toString();
			lastTotalKey	= totalKey.toString();

			// グループ集計時はグルーピングした行数を加算する。
			int gcnt = 1;
			if( config.useGroup() && rowCountColumn >= 0 && rowCount > 0 ) {
				gcnt = rowCount;
			}
			subTotalCount += gcnt;
			totalCount    += gcnt;
			grandTotalCount += gcnt;

			tblIdx++;
		}

		// 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
		boolean isOverFlow = tblIdx < getRowCount() ;

		// 小計キー最終行処理
		if( config.useSubTotal() && lastSubTotalKey != null ) {
			if( numberOfRows < maxRowCount ) {
				addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
				numberOfRows++;
				tblIdx++;
			}
			else {
				isOverFlow = true;
			}
		}

		// 合計キー最終行処理
		if( config.useTotal() && lastTotalKey != null ) {
			if( numberOfRows < maxRowCount ) {
				addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
				numberOfRows++;
				tblIdx++;
			}
			else {
				isOverFlow = true;
			}
		}

		// 総合計処理
		if( config.useGrandTotal() && numberOfRows > 0 ) {
			if( numberOfRows < maxRowCount ) {
				final boolean[] grandTotalFilter = new boolean[numberOfColumns];
				// 総合計のラベル表示廃止
				// grandTotalFilter[0] = true;

				if( config.useFirstTotal() ) {		// 6.1.1.0 (2015/01/17)
					addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, 0 );
				}
				else {
					addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, tblIdx );
					tblIdx++;
				}
				numberOfRows++;
			}
			else {
				isOverFlow = true;
			}
		}

		if( isOverFlow ) {
			setOverflow( true );
		}

		return isOverFlow;
	}

	/**
	 * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
	 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
	 *
	 * @og.rev 5.6.1.0 (2013/02/01) doubleをdecimalに
	 *
	 * @param keyFilter キーフィルタ
	 * @param keyVals キーの値配列
	 * @param keyCount 集計した行のカウント
	 * @param sumFilter 集計フィルタ
	 * @param sumVals 集計値配列
	 * @param aRow 挿入する行番号
	 */
	private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
			, final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
		String[] columnValues = new String[numberOfColumns];
		int sc = 0;
		int kc = 0;
		for( int column=0; column<numberOfColumns; column++ ) {
			String val = "";
			if( keyFilter[column] ) {
				val = keyVals[kc++];
			}
			if( sumFilter[column] ) {
				synchronized( FORMAT ) {							// 7.2.9.5 (2020/11/28)
					val = FORMAT.format( sumVals[sc++] );
				}
			}
			if( column == rowCountColumn ) {
				val = String.valueOf( keyCount );
			}
			columnValues[column] = val;
		}

		if( aRow < 0 ) {
			addColumnValues( columnValues );
		}
		else {
			addValues( columnValues, aRow, false );
		}
	}

	/**
	 * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
	 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
	 *
	 * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimal
	 *
	 * @param keyFilter キーフィルタ
	 * @param keyVals キーの値配列
	 * @param keyCount 集計した行のカウント
	 * @param sumFilter 集計フィルタ
	 * @param sumVals 集計値配列
	 */
	private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
			, final boolean[] sumFilter, final BigDecimal[] sumVals ) {
		addRow( keyFilter, keyVals, keyCount, sumFilter, sumVals, -1 );
	}

	/**
	 * キーの値、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
	 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
	 *
	 * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimalに
	 *
	 * @param keyFilter キーフィルタ
	 * @param keyVal キーの値
	 * @param keyCount 集計した行のカウント
	 * @param sumFilter 集計フィルタ
	 * @param sumVals 集計値配列
	 * @param aRow 挿入する行番号
	 */
	private void addRow( final boolean[] keyFilter, final String keyVal, final int keyCount
			, final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
		final List<String> tmp = new ArrayList<>();
		for( int column=0; column<numberOfColumns; column++ ) {
			if( keyFilter[column] ) {
				tmp.add( keyVal );
			}
		}
		addRow( keyFilter, tmp.toArray( new String[tmp.size()] ), keyCount, sumFilter, sumVals, aRow );
	}

	/**
	 * アンダーバー付きの文字列から、アンダーバーを削除します。
	 * null,ゼロ文字列の場合は、第二引数の値を返します。
	 *
	 * @og.rev 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
	 *
	 * @param    inStr 基準となる文字列
	 * @param    def デフォルト文字列
	 *
	 * @return  null、ゼロ文字列、"_"の場合は、デフォルト文字列を、そうでなければ、入力文字からアンダーバーを削除した文字列を返す。
	 */
	public static String unscoDel( final String inStr,final String def ) {
		final String rtn = StringUtil.nval2( inStr,def );

		return rtn.charAt(0) == '_' ? rtn.substring(1) : rtn ;
	}
}
