001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.db;
017
018import java.math.BigDecimal;
019import java.sql.ResultSet;
020import java.sql.SQLException;
021import java.text.DecimalFormat;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.opengion.fukurou.db.ResultSetValue;                          // 6.0.4.0 (2014/11/28)
029import org.opengion.fukurou.util.StringUtil;
030import org.opengion.hayabusa.common.HybsSystem;
031import org.opengion.hayabusa.common.HybsSystemException;
032import org.opengion.hayabusa.resource.ResourceManager;
033import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
034
035/**
036 * DBTableModelを継承した TableModelの編集設定による変換を行うための実装クラスです。
037 *
038 * このクラスでは、オブジェクト初期化後は、通常のDBTableModelと同じ振る舞いをします。
039 * オブジェクト初期化時(createメソッド呼び出し時)に、検索結果オブジェクトから直接、編集設定に
040 * 応じて変換されたDBTableModelを生成します。
041 *
042 * このような実装を行う理由は、メモリ使用量を節約するためです。
043 * この編集設定では、集計機能を備えていますが、一旦DBTableModel作成後に集計処理を行うと、
044 * メモリを大量に使用する恐れがあるため、検索結果オブジェクトから直接集計処理を行い、DBTableModelを
045 * 生成しています。
046 *
047 * DBTableModel インターフェースは、データベースの検索結果(Resultset)をラップする
048 * インターフェースとして使用して下さい。
049 *
050 * @og.rev 5.3.6.0 (2011/06/01) 新規作成
051 * @og.group テーブル管理
052 *
053 * @version  5.0
054 * @author   Hiroki Nakamura
055 * @since    JDK6.0,
056 */
057public class DBTableModelEditor extends DBTableModelImpl {
058        private static final String                     JS              = HybsSystem.JOINT_STRING;
059        private static final DecimalFormat      FORMAT  = new DecimalFormat( "0.#########" );
060
061        private int rowCountColumn = -1;
062        private DBEditConfig config;
063
064        /**
065         * デフォルトコンストラクター
066         *
067         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
068         */
069        public DBTableModelEditor() { super(); }                // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
070
071        /**
072         * DBTableModel を設定し、このオブジェクトを初期化します。
073         *
074         * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
075         * @og.rev 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する
076         * @og.rev 6.0.2.5 (2014/10/31) FireBardでの日付型取得対応
077         * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
078         * @og.rev 6.0.4.0 (2014/11/28) queryタグが複数ある場合の事前チェックの条件訂正
079         *
080         * @param       result                  検索結果オブジェクト
081         * @param       skipRowCount    読み飛ばし件数
082         * @param       maxRowCount             最大検索件数
083         * @param       resource                ResourceManagerオブジェクト
084         * @param       config                  編集設定オブジェクト
085         * @throws      SQLException データベースアクセスエラー
086         */
087        public void create( final ResultSet result, final int skipRowCount, final int maxRowCount, final ResourceManager resource, final DBEditConfig config ) throws SQLException {
088                if( result == null || config == null || resource == null ) {
089                        final String errMsg = "DBTableModelまたは、DBEditConfigが設定されていません。";
090                        throw new HybsSystemException( errMsg );        // 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
091                }
092
093                this.config = config;
094
095                /**********************************************************************
096                 * 各パラメーターの初期化処理
097                 **********************************************************************/
098                // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
099                final ResultSetValue rsv = new ResultSetValue( result );                // 6.0.4.0 (2014/11/28)
100                int colCnt = rsv.getColumnCount();                                              // 6.0.4.0 (2014/11/28)
101
102                if( config.useGroup() || config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) {
103                        rowCountColumn = colCnt;
104                        colCnt++;
105                }
106                init( colCnt );
107
108                DBColumn[] dbColumn = new DBColumn[numberOfColumns];
109                boolean[] sumFilter                     = new boolean[numberOfColumns];
110                boolean[] groupFilter           = new boolean[numberOfColumns];
111                boolean[] subTotalFilter        = new boolean[numberOfColumns];
112                boolean[] totalFilter           = new boolean[numberOfColumns];
113                boolean   sumFilterCheck        = false;                                                // 6.0.2.1 (2014/09/26)
114                if( config.useGrandTotal() ) { sumFilterCheck = true; }         // 6.0.4.0 (2014/11/28)
115                for( int column=0; column<numberOfColumns; column++ ) {
116                        String name = null;
117                        // 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
118                        if( column == rowCountColumn ) {
119                                name = "rowCount";
120                                dbColumn[column] = resource.makeDBColumn( name );
121                        }
122                        else {
123                                name = rsv.getColumnName(column);                                       // 6.0.4.0 (2014/11/28)
124                                dbColumn[column] = resource.getDBColumn( name );
125                                if( dbColumn[column] == null ) {
126                                        dbColumn[column] = DBTableModelUtil.makeDBColumn( name,column,rsv,resource );   // 6.0.4.0 (2014/11/28)
127                                }
128                        }
129
130                        setDBColumn( column,dbColumn[column] );
131                        sumFilter[column]               = config.isSumClm( name );
132                        groupFilter[column]             = config.isGroupClm( name );
133                        subTotalFilter[column]  = config.isSubTotalClm( name );
134                        totalFilter[column]             = config.isTotalClm( name );
135                        // 6.0.4.0 (2014/11/28) queryタグが複数ある場合の事前チェックの条件訂正
136                        if( sumFilter[column] || groupFilter[column] || subTotalFilter[column] || totalFilter[column] ) {
137                                sumFilterCheck = true;
138                        }
139                }
140
141                /**********************************************************************
142                 * 集計、ソート、合計処理
143                 **********************************************************************/
144                // 集計キーに基づく集計処理を行いデータを追加します。
145                if( config.useGroup() ) {
146                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
147                        addGroupRows( rsv, skipRowCount, maxRowCount, sumFilter, groupFilter );
148                }
149                // 通常と同じように結果カーソルからデータを読込みデータを追加します。
150                else {
151                        // 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
152                        // 6.0.2.5 (2014/10/31) int[] types 復活。isOther フラグも使います。
153                        // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
154                        addPlainRows( rsv, skipRowCount, maxRowCount );
155                }
156
157                // ソート処理
158                if( getRowCount() > 0 && config.useOrderBy() ) {
159                        sort();
160                }
161
162                // 小計・合計行を追加します。
163                if( getRowCount() > 0 && !isOverflow()
164                        && ( config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) ) {
165
166                        // 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する
167                        if( !sumFilterCheck ) {
168                                final String errMsg = "小計、合計カラムが存在しません。"
169                                                                + " これは、queryタグが複数あり、mainTrans=false で制御されていない可能性があります。" ;
170                                throw new HybsSystemException( errMsg );
171                        }
172
173                        addTotalRows( maxRowCount, resource, sumFilter, groupFilter, subTotalFilter, totalFilter );
174                }
175        }
176
177        /**
178         * 集計キーの設定に基づき、DBTableModelの行を追加します。
179         * 内部的には、キーブレイクではなく、内部マップにより集計処理を行っているため、
180         * 集計キーが検索順により散在した場合でも1まとまりで集計されます。
181         *
182         * @og.rev 5.3.9.0 (2011/09/01) 値がNULLの場合にエラーになるバグを修正
183         * @og.rev 5.6.1.0 (2013/02/01) doubleをBigDecimalに変更
184         * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
185         * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
186         * @og.rev 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
187         *
188         * @param rsv ResultSetValueオブジェクト
189         * @param skipRowCount 読み飛ばし件数
190         * @param maxRowCount 最大検索件数
191         * @param sumFilter 集計項目フィルター
192         * @param groupFilter グループキーフィルター
193         * @throws SQLException データベースアクセスエラー
194         */
195        private void addGroupRows( final ResultSetValue rsv, final int skipRowCount, final int maxRowCount
196                                                                , final boolean[] sumFilter, final boolean[] groupFilter ) throws SQLException {
197                int numberOfRows = 0;
198                while( numberOfRows < skipRowCount && rsv.next() ) {
199                        // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。
200                        numberOfRows ++ ;
201                }
202                numberOfRows = 0;
203
204                final Map<String,String[]>      groupLinkedMap  = new LinkedHashMap<>();
205                final Map<String,Integer>       groupCountMap   = new HashMap<>();
206                final Map<String,BigDecimal[]>  sumMap          = new HashMap<>();                      // 5.6.1.0 (2013/02/01)
207                final StringBuilder groupKey = new StringBuilder( BUFFER_MIDDLE );              // 6.1.0.0 (2014/12/26) refactoring
208                while( numberOfRows < maxRowCount && rsv.next() ) {
209                        groupKey.setLength(0);                                                                                          // 6.1.0.0 (2014/12/26) refactoring
210                        BigDecimal[] sumVals = new BigDecimal[config.getSumClmCount()];         // 5.6.1.0 (2013/02/01)
211                        String[]   groupVals = new String[config.getGroupClmCount()];
212                        int sc = 0;
213                        int gc = 0;
214                        for( int column=0; column<numberOfColumns; column++ ) {
215                                if( column != rowCountColumn ) {
216                                        final String val = rsv.getValue( column );
217                                        if( sumFilter[column] ) {
218                                                // 5.3.9.0 (2011/09/01) 値がNULLの場合の対応漏れ
219                                                // sumVals[sc++] = ( val != null && val.length() > 0 ? Double.valueOf( val ) : 0 );
220        //                                      sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); // 5.6.1.0 (2013/02/01)
221                                                // 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
222                                                sumVals[sc++] = new BigDecimal( unscoDel( val,"0" ) );
223
224                                        }
225                                        if( groupFilter[column] ) {
226                                                groupVals[gc++] = val;
227                                                groupKey.append( val ).append( JS );
228                                        }
229                                }
230                        }
231
232                        final String key = groupKey.toString();
233                        int groupCount = 0;
234                        if( groupLinkedMap.containsKey( key ) ) {
235                                final BigDecimal[] eSumVals = sumMap.get( key ); // 5.6.1.0 (2013/02/01)
236                                for( int i=0; i<config.getSumClmCount(); i++ ) {
237                                        sumVals[i] = sumVals[i] == null ? BigDecimal.ZERO : sumVals[i].add( eSumVals[i] ); // 5.6.1.0 (2013/02/01)
238                                }
239                                sumMap.put( key, sumVals );
240                                groupCount = groupCountMap.get( key ).intValue() + 1;
241                        }
242                        else {
243                                groupLinkedMap.put( key, groupVals );
244                                groupCount = 1;
245                                numberOfRows++;
246                        }
247                        sumMap.put( key, sumVals );
248                        groupCountMap.put( key, Integer.valueOf( groupCount ) );
249                }
250
251                // 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
252                groupLinkedMap.forEach( (k,v) -> addRow( groupFilter, v, groupCountMap.get( k ), sumFilter, sumMap.get( k ) ) );
253
254                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
255                if( numberOfRows >= maxRowCount && rsv.next() ) {
256                        setOverflow( true );
257                }
258        }
259
260        /**
261         * 検索結果オブジェクトを順に読み取り、そのままDBTableModelの行を追加します。
262         *
263         * @og.rev 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
264         * @og.rev 6.0.2.5 (2014/10/31) int[] types 復活。isOther フラグも使います。
265         * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。
266         *
267         * @param rsv ResultSetValueオブジェクト
268         * @param skipRowCount 読み飛ばし件数
269         * @param maxRowCount 最大検索件数
270         * @throws      SQLException データベースアクセスエラー
271         */
272        private void addPlainRows( final ResultSetValue rsv, final int skipRowCount, final int maxRowCount ) throws SQLException {
273                int numberOfRows = 0;
274                while( numberOfRows < skipRowCount && rsv.next() ) {
275                        // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。
276                        numberOfRows ++ ;
277                }
278                numberOfRows = 0;
279
280                // 6.0.2.5 (2014/10/31) 行列のループなので、 CLOB 使用可否でループを分ける。
281                        // 6.0.2.5 (2014/10/31) typesを考慮した処理
282                        while( numberOfRows < maxRowCount && rsv.next() ) {
283                                numberOfRows++ ;
284                                String[] columnValues = new String[numberOfColumns];
285                                for( int column=0; column<numberOfColumns; column++ ) {
286                                        // 6.1.0.0 (2014/12/26) refactoring : Avoid if(x != y) ..; else ..;
287                                        if( column == rowCountColumn ) {
288                                                columnValues[column] = "";
289                                        }
290                                        else {
291                                                columnValues[column] = rsv.getValue( column );
292                                        }
293                                }
294                                addColumnValues( columnValues );
295                        }
296
297                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
298                if( numberOfRows >= maxRowCount && rsv.next() ) {
299                        setOverflow( true );
300                }
301        }
302
303        /**
304         * DBTableModelのソート処理を行います。
305         *
306         */
307        private void sort() {
308                // orderByClmsによる並び替え
309                final DBTableModelSorter temp = new DBTableModelSorter();
310                temp.setModel( this );
311                final String[] oClms = StringUtil.csv2Array( config.getOrderByClms() );
312                for( int i=oClms.length-1; i>=0; i-- ) {
313                        String oc = oClms[i];
314                        boolean ascending = true;
315                        if( StringUtil.startsChar( oc , '!' ) ) {                               // 6.2.0.0 (2015/02/27) 1文字 String.startsWith
316                                oc = oc.substring( 1 );
317                                ascending = false;
318                        }
319                        final int clmNo = getColumnNo( oc );
320                        temp.sortByColumn( clmNo, ascending );
321                }
322                this.data = temp.data;
323                this.rowHeader = temp.rowHeader;
324        }
325
326        /**
327         * DBTableModelからデータを読み取り、編集設定情報を元に合計行の追加処理を行います。
328         * 合計行の追加は、キーブレイクにより行われますので、同じキーが複数回出現した場合は、
329         * それぞれの行に対して、合計行が付加されます。
330         *
331         * @og.rev 5.3.7.0 (2011/07/01) 小計、合計行追加処理でオーバーフローフラグがセットされないバグを修正
332         * @og.rev 5.6.1.0 (2013/02/01) 誤差回避のため、doubleではなくdecimalで計算する
333         * @og.rev 5.6.8.1 (2013/09/13) 1行目が合計されていなかったので修正
334         * @og.rev 6.0.2.1 (2014/09/26) queryタグが複数あり、mainTrans=false で制御されていない場合、エラーが発生する対応
335         *
336         * @param       maxRowCount 最大検索件数
337         * @param       resource リソースマネージャー
338         * @param       sumFilter 集計項目フィルター
339         * @param       groupFilter グループキーフィルター
340         * @param       subTotalFilter 小計キーフィルター
341         * @param       totalFilter 合計キーフィルター
342         *
343         * @return      オーバーフローしたかどうか(最大件数が超えた場合でかつ次のデータがある場合は、true)
344         */
345        private boolean addTotalRows( final int maxRowCount, final ResourceManager resource
346                                                                        , final boolean[] sumFilter
347                                                                        , final boolean[] groupFilter
348                                                                        , final boolean[] subTotalFilter
349                                                                        , final boolean[] totalFilter ) {
350
351                final String subTotalLabel      = ( config.useSubTotal()   ? resource.makeDBColumn( "EDIT_SUBTOTAL_VALUE"   ).getLongLabel() : null );
352                final String totalLabel         = ( config.useTotal()      ? resource.makeDBColumn( "EDIT_TOTAL_VALUE"      ).getLongLabel() : null );
353                final String grandTotalLabel= ( config.useGrandTotal() ? resource.makeDBColumn( "EDIT_GRANDTOTAL_VALUE" ).getLongLabel() : null );
354
355                int numberOfRows = getRowCount();
356                final int sumClmCount  = config.getSumClmCount();
357                BigDecimal subTotalSum[]   = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
358                BigDecimal totalSum[]      = new BigDecimal[sumClmCount];
359                BigDecimal grandTotalSum[] = new BigDecimal[sumClmCount];
360
361                String lastSubTotalKey = null;
362                String lastTotalKey    = null;
363
364                int subTotalCount   = 0;
365                int totalCount      = 0;
366                int grandTotalCount = 0;
367                int rowCount =0;
368
369                int tblIdx = 0;
370                final StringBuilder groupKey    = new StringBuilder( BUFFER_MIDDLE );   // 6.1.0.0 (2014/12/26) refactoring
371                final StringBuilder subTotalKey = new StringBuilder( BUFFER_MIDDLE );   // 6.1.0.0 (2014/12/26) refactoring
372                final StringBuilder totalKey    = new StringBuilder( BUFFER_MIDDLE );   // 6.1.0.0 (2014/12/26) refactoring
373                while( numberOfRows < maxRowCount && tblIdx < getRowCount() ) {
374                        BigDecimal[] sumVals      = new BigDecimal[sumClmCount];        // 5.6.1.0 (2013/02/01)
375                        groupKey.setLength(0);                                                                                                                  // 6.1.0.0 (2014/12/26) refactoring
376                        subTotalKey.setLength(0);                                                                                                               // 6.1.0.0 (2014/12/26) refactoring
377                        totalKey.setLength(0);                                                                                                                  // 6.1.0.0 (2014/12/26) refactoring
378
379                        int sc = 0;
380                        for( int column=0; column<numberOfColumns; column++ ) {
381                                final String val = getValue( tblIdx, column );
382                                if( groupFilter[column] )               { groupKey.append( val ).append( JS ); }
383                                // 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
384                //              if( sumFilter[column] )                 { sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); } // 5.6.1.0 (2013/02/01)
385                                if( sumFilter[column] )                 { sumVals[sc++] = new BigDecimal( unscoDel( val,"0" ) ) ; }                                                     // 7.0.5.0 (2019/09/09)
386                                if( subTotalFilter[column] )    { subTotalKey.append( val ).append( JS ); }
387                                if( totalFilter[column] )               { totalKey.append( val ).append( JS ); }
388                                // 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
389                //              if( column == rowCountColumn )  { rowCount = ( val != null && val.length() > 0 ? Integer.parseInt( val ) : 0 ); }       // 6.0.2.4 (2014/10/17) メソッド間違い
390                                if( column == rowCountColumn )  { rowCount = Integer.parseInt( unscoDel( val,"0" ) ); }                                                         // 7.0.5.0 (2019/09/09)
391                        }
392
393                        // 小計キーブレイク処理
394                        if( numberOfRows < maxRowCount && config.useSubTotal() && lastSubTotalKey != null && lastSubTotalKey.length() > 0
395                                && !lastSubTotalKey.equals( subTotalKey.toString() ) ) {
396                                addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
397                                subTotalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
398                                subTotalCount = 0;
399                                numberOfRows++;
400                                tblIdx++;
401                        }
402
403                        // 合計キーブレイク処理
404                        if( numberOfRows < maxRowCount && config.useTotal() && lastTotalKey != null && lastTotalKey.length() > 0
405                                && !lastTotalKey.equals( totalKey.toString() ) ) {
406                                addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
407                                totalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
408                                totalCount = 0;
409                                numberOfRows++;
410                                tblIdx++;
411                        }
412
413                        // 小計、合計、総合計単位に集計項目の合計値を加算します。
414                        // 6.0.2.0 (2014/09/19) BigDecimal.ZERO.add で、null エラーが発生するのは、query が複数あり、mainTrans=false で制御されていない場合
415                        for( int cnt=0; cnt<sc; cnt++ ) {
416                                subTotalSum[cnt]        = subTotalSum[cnt]      == null ? BigDecimal.ZERO.add(sumVals[cnt]) : subTotalSum[cnt].add(  sumVals[cnt]); // 5.6.8.1 (2013/09/13)
417                                totalSum[cnt]           = totalSum[cnt]         == null ? BigDecimal.ZERO.add(sumVals[cnt]) : totalSum[cnt].add(     sumVals[cnt]);
418                                grandTotalSum[cnt]      = grandTotalSum[cnt]== null ? BigDecimal.ZERO.add(sumVals[cnt]) : grandTotalSum[cnt].add(sumVals[cnt]);
419                        }
420
421                        lastSubTotalKey = subTotalKey.toString();
422                        lastTotalKey    = totalKey.toString();
423
424                        // グループ集計時はグルーピングした行数を加算する。
425                        int gcnt = 1;
426                        if( config.useGroup() && rowCountColumn >= 0 && rowCount > 0 ) {
427                                gcnt = rowCount;
428                        }
429                        subTotalCount += gcnt;
430                        totalCount    += gcnt;
431                        grandTotalCount += gcnt;
432
433                        tblIdx++;
434                }
435
436                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
437                boolean isOverFlow = tblIdx < getRowCount() ;
438
439                // 小計キー最終行処理
440                if( config.useSubTotal() && lastSubTotalKey != null ) {
441                        if( numberOfRows < maxRowCount ) {
442                                addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
443                                numberOfRows++;
444                                tblIdx++;
445                        }
446                        else {
447                                isOverFlow = true;
448                        }
449                }
450
451                // 合計キー最終行処理
452                if( config.useTotal() && lastTotalKey != null ) {
453                        if( numberOfRows < maxRowCount ) {
454                                addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
455                                numberOfRows++;
456                                tblIdx++;
457                        }
458                        else {
459                                isOverFlow = true;
460                        }
461                }
462
463                // 総合計処理
464                if( config.useGrandTotal() && numberOfRows > 0 ) {
465                        if( numberOfRows < maxRowCount ) {
466                                final boolean[] grandTotalFilter = new boolean[numberOfColumns];
467                                // 総合計のラベル表示廃止
468                                // grandTotalFilter[0] = true;
469
470                                if( config.useFirstTotal() ) {          // 6.1.1.0 (2015/01/17)
471                                        addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, 0 );
472                                }
473                                else {
474                                        addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, tblIdx );
475                                        tblIdx++;
476                                }
477                                numberOfRows++;
478                        }
479                        else {
480                                isOverFlow = true;
481                        }
482                }
483
484                if( isOverFlow ) {
485                        setOverflow( true );
486                }
487
488                return isOverFlow;
489        }
490
491        /**
492         * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
493         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
494         *
495         * @og.rev 5.6.1.0 (2013/02/01) doubleをdecimalに
496         *
497         * @param keyFilter キーフィルタ
498         * @param keyVals キーの値配列
499         * @param keyCount 集計した行のカウント
500         * @param sumFilter 集計フィルタ
501         * @param sumVals 集計値配列
502         * @param aRow 挿入する行番号
503         */
504        private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
505                        , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
506                String[] columnValues = new String[numberOfColumns];
507                int sc = 0;
508                int kc = 0;
509                for( int column=0; column<numberOfColumns; column++ ) {
510                        String val = "";
511                        if( keyFilter[column] ) {
512                                val = keyVals[kc++];
513                        }
514                        if( sumFilter[column] ) {
515                                synchronized( FORMAT ) {                                                        // 7.2.9.5 (2020/11/28)
516                                        val = FORMAT.format( sumVals[sc++] );
517                                }
518                        }
519                        if( column == rowCountColumn ) {
520                                val = String.valueOf( keyCount );
521                        }
522                        columnValues[column] = val;
523                }
524
525                if( aRow < 0 ) {
526                        addColumnValues( columnValues );
527                }
528                else {
529                        addValues( columnValues, aRow, false );
530                }
531        }
532
533        /**
534         * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
535         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
536         *
537         * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimal
538         *
539         * @param keyFilter キーフィルタ
540         * @param keyVals キーの値配列
541         * @param keyCount 集計した行のカウント
542         * @param sumFilter 集計フィルタ
543         * @param sumVals 集計値配列
544         */
545        private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
546                        , final boolean[] sumFilter, final BigDecimal[] sumVals ) {
547                addRow( keyFilter, keyVals, keyCount, sumFilter, sumVals, -1 );
548        }
549
550        /**
551         * キーの値、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
552         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
553         *
554         * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimalに
555         *
556         * @param keyFilter キーフィルタ
557         * @param keyVal キーの値
558         * @param keyCount 集計した行のカウント
559         * @param sumFilter 集計フィルタ
560         * @param sumVals 集計値配列
561         * @param aRow 挿入する行番号
562         */
563        private void addRow( final boolean[] keyFilter, final String keyVal, final int keyCount
564                        , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
565                final List<String> tmp = new ArrayList<>();
566                for( int column=0; column<numberOfColumns; column++ ) {
567                        if( keyFilter[column] ) {
568                                tmp.add( keyVal );
569                        }
570                }
571                addRow( keyFilter, tmp.toArray( new String[tmp.size()] ), keyCount, sumFilter, sumVals, aRow );
572        }
573
574        /**
575         * アンダーバー付きの文字列から、アンダーバーを削除します。
576         * null,ゼロ文字列の場合は、第二引数の値を返します。
577         *
578         * @og.rev 7.0.5.0 (2019/09/09) アンダーバー付きの文字列から、アンダーバーを削除する。
579         *
580         * @param    inStr 基準となる文字列
581         * @param    def デフォルト文字列
582         *
583         * @return  null、ゼロ文字列、"_"の場合は、デフォルト文字列を、そうでなければ、入力文字からアンダーバーを削除した文字列を返す。
584         */
585        public static String unscoDel( final String inStr,final String def ) {
586                final String rtn = StringUtil.nval2( inStr,def );
587
588                return rtn.charAt(0) == '_' ? rtn.substring(1) : rtn ;
589        }
590}