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.ResultSetMetaData;
021import java.sql.SQLException;
022import java.text.DecimalFormat;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029
030import org.opengion.fukurou.db.DBUtil;
031import org.opengion.fukurou.util.StringUtil;
032import org.opengion.hayabusa.common.HybsSystem;
033import org.opengion.hayabusa.common.HybsSystemException;
034import org.opengion.hayabusa.resource.LabelData;
035import org.opengion.hayabusa.resource.ResourceManager;
036
037/**
038 * DBTableModelを継承した TableModelの編集設定による変換を行うための実装クラスです。
039 *
040 * このクラスでは、オブジェクト初期化後は、通常のDBTableModelと同じ振る舞いをします。
041 * オブジェクト初期化時(createメソッド呼び出し時)に、検索結果オブジェクトから直接、編集設定に
042 * 応じて変換されたDBTableModelを生成します。
043 *
044 * このような実装を行う理由は、メモリ使用量を節約するためです。
045 * この編集設定では、集計機能を備えていますが、一旦DBTableModel作成後に集計処理を行うと、
046 * メモリを大量に使用する恐れがあるため、検索結果オブジェクトから直接集計処理を行い、DBTableModelを
047 * 生成しています。
048 *
049 * DBTableModel インターフェースは,データベースの検索結果(Resultset)をラップする
050 * インターフェースとして使用して下さい。
051 *
052 * @og.rev 5.3.6.0 (2011/06/01) 新規作成
053 * @og.group テーブル管理
054 *
055 * @version  5.0
056 * @author   Hiroki Nakamura
057 * @since    JDK6.0,
058 */
059public class DBTableModelEditor extends DBTableModelImpl {
060        private static final String JS                          = HybsSystem.JOINT_STRING;
061        private static final DecimalFormat FORMAT       = new DecimalFormat( "0.#########" );
062
063        private int rowCountColumn = -1;
064        private DBEditConfig config;
065
066        /**
067         * DBTableModel を設定し、このオブジェクトを初期化します。
068         *
069         * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
070         *
071         * @param       result                  検索結果オブジェクト
072         * @param       skipRowCount    読み飛ばし件数
073         * @param       maxRowCount             最大検索件数
074         * @param       resource                ResourceManagerオブジェクト
075         * @param       config                  エディット設定オブジェクト
076         * @throws      SQLException データベースアクセスエラー
077         */
078        public void create( final ResultSet result, final int skipRowCount, final int maxRowCount, final ResourceManager resource, final DBEditConfig config ) throws SQLException {
079                if( result == null || config == null || resource == null ) {
080                        String errMsg = "DBTableModelまたは、DBEditConfigが設定されていません。";
081                        throw new HybsSystemException( errMsg );        // 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
082                }
083
084                this.config = config;
085
086                /**********************************************************************
087                 * 各パラメーターの初期化処理
088                 **********************************************************************/
089                ResultSetMetaData metaData      = result.getMetaData();
090                int colCnt = metaData.getColumnCount();
091                if( config.useGroup() || config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) {
092                        rowCountColumn = colCnt;
093                        colCnt++;
094                }
095                init( colCnt );
096
097                DBColumn[] dbColumn = new DBColumn[numberOfColumns];
098                int[] types  = new int[numberOfColumns];
099                boolean[] sumFilter = new boolean[numberOfColumns];
100                boolean[] groupFilter = new boolean[numberOfColumns];
101                boolean[] subTotalFilter = new boolean[numberOfColumns];
102                boolean[] totalFilter = new boolean[numberOfColumns];
103                for( int column=0; column<numberOfColumns; column++ ) {
104                        String name = null;
105                        if( column != rowCountColumn ) {
106                                name = metaData.getColumnLabel(column+1).toUpperCase(Locale.JAPAN);
107                                types[column] = metaData.getColumnType(column+1);
108                                dbColumn[column] = resource.getDBColumn( name );
109                                if( dbColumn[column] == null ) {
110                                        LabelData labelData  = resource.getLabelData( name );
111                                        dbColumn[column] = DBTableModelUtil.makeDBColumn( name,labelData,metaData,column,resource.getLang() );
112                                }
113                        }
114                        else {
115                                name = "rowCount";
116                                dbColumn[column] = resource.makeDBColumn( name );
117                        }
118
119                        setDBColumn( column,dbColumn[column] );
120
121                        sumFilter[column] = config.isSumClm( name );
122                        groupFilter[column] = config.isGroupClm( name );
123                        subTotalFilter[column] = config.isSubTotalClm( name );
124                        totalFilter[column] = config.isTotalClm( name );
125                }
126
127                /**********************************************************************
128                 * 集計、ソート、合計処理
129                 **********************************************************************/
130                // 集計キーに基づく集計処理を行いデータを追加します。
131                if( config.useGroup() ) {
132                        addGroupRows( result, types, skipRowCount, maxRowCount, sumFilter, groupFilter  );
133                }
134                // 通常と同じように結果カーソルからデータを読込みデータを追加します。
135                else {
136                        // 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
137                        addPlainRows( result, skipRowCount, maxRowCount );
138                }
139
140                // ソート処理
141                if( getRowCount() > 0 && config.useOrderBy() ) {
142                        sort();
143                }
144
145                // 小計・合計行を追加します。
146                if( getRowCount() > 0 && !isOverflow()
147                        && ( config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) ) {
148                        addTotalRows( maxRowCount, resource, sumFilter, groupFilter, subTotalFilter, totalFilter );
149                }
150        }
151
152        /**
153         * 集計キーの設定に基づき、DBTableModelの行を追加します。
154         * 内部的には、キーブレイクではなく、内部マップにより集計処理を行っているため、
155         * 集計キーが検索順により散在した場合でも1まとまりで集計されます。
156         *
157         * @og.rev 5.3.9.0 (2011/09/01) 値がNULLの場合にエラーになるバグを修正
158         * @og.rev 5.6.1.0 (2013/02/01) doubleをBigDecimalに
159         *
160         * @param result 検索結果オブジェクト
161         * @param types カラムタイプの配列
162         * @param skipRowCount 読み飛ばし件数
163         * @param maxRowCount 最大検索件数
164         * @param sumFilter 集計項目フィルター
165         * @param groupFilter グループキーフィルター
166         * @throws SQLException データベースアクセスエラー
167         */
168        private void addGroupRows( final ResultSet result, final int[] types, final int skipRowCount, final int maxRowCount
169                                                                , final boolean[] sumFilter, final boolean[] groupFilter ) throws SQLException {
170                int numberOfRows = 0;
171                while( numberOfRows < skipRowCount && result.next() ) {
172                        // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。
173                        numberOfRows ++ ;
174                }
175                numberOfRows = 0;
176
177                Map<String,String[]> groupLinkedMap = new LinkedHashMap<String,String[]>();
178                Map<String,Integer> groupCountMap = new HashMap<String,Integer>();
179                Map<String,BigDecimal[]> sumMap = new HashMap<String,BigDecimal[]>(); // 5.6.1.0 (2013/02/01)
180                while( numberOfRows < maxRowCount && result.next() ) {
181                        StringBuilder groupKey = new StringBuilder();
182                        BigDecimal[] sumVals = new BigDecimal[config.getSumClmCount()]; // 5.6.1.0 (2013/02/01) 
183                        String[] groupVals = new String[config.getGroupClmCount()];
184                        int sc = 0;
185                        int gc = 0;
186                        for( int column=0; column<numberOfColumns; column++ ) {
187                                if( column != rowCountColumn ) {
188                                        String val = DBUtil.getValue( result, column, types[column] );
189                                        if( sumFilter[column] ) {
190                                                // 5.3.9.0 (2011/09/01) 値がNULLの場合の対応漏れ
191                                                // sumVals[sc++] = ( val != null && val.length() > 0 ? Double.valueOf( val ) : 0 );
192                                                sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); // 5.6.1.0 (2013/02/01)
193                                        }
194                                        if( groupFilter[column] ) {
195                                                groupVals[gc++] = val;
196                                                groupKey.append( val ).append( JS );
197                                        }
198                                }
199                        }
200
201                        String key = groupKey.toString();
202                        int groupCount = 0;
203                        if( groupLinkedMap.containsKey( key ) ) {
204                                BigDecimal[] eSumVals = sumMap.get( key ); // 5.6.1.0 (2013/02/01)
205                                for( int i=0; i<config.getSumClmCount(); i++ ) {
206                                        sumVals[i] = sumVals[i] == null ? BigDecimal.ZERO : sumVals[i].add( eSumVals[i] ); // 5.6.1.0 (2013/02/01)
207                                }
208                                sumMap.put( key, sumVals );
209                                groupCount = groupCountMap.get( key ).intValue() + 1;
210                        }
211                        else {
212                                groupLinkedMap.put( key, groupVals );
213                                groupCount = 1;
214                                numberOfRows++;
215                        }
216                        sumMap.put( key, sumVals );
217                        groupCountMap.put( key, Integer.valueOf( groupCount ) );
218                }
219
220                for( Map.Entry<String, String[]> entry : groupLinkedMap.entrySet() ) {
221                        String key = entry.getKey();
222                        addRow( groupFilter, entry.getValue(), groupCountMap.get( key ), sumFilter, sumMap.get( key ) );
223                }
224
225                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
226                if( numberOfRows >= maxRowCount && result.next() ) {
227                        setOverflow( true );
228                }
229        }
230
231        /**
232         * 検索結果オブジェクトを順に読み取り、そのままDBTableModelの行を追加します。
233         *
234         * @og.rev 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。
235         *
236         * @param result 検索結果オブジェクト
237         * @param skipRowCount 読み飛ばし件数
238         * @param maxRowCount 最大検索件数
239         * @throws      SQLException データベースアクセスエラー
240         */
241        private void addPlainRows( final ResultSet result, final int skipRowCount, final int maxRowCount ) throws SQLException {
242                int numberOfRows = 0;
243                while( numberOfRows < skipRowCount && result.next() ) {
244                        // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。
245                        numberOfRows ++ ;
246                }
247                numberOfRows = 0;
248
249                while( numberOfRows < maxRowCount && result.next() ) {
250                        numberOfRows++ ;
251                        String[] columnValues = new String[numberOfColumns];
252                        for( int column=0; column<numberOfColumns; column++ ) {
253                                if( column != rowCountColumn ) {
254                                        Object obj = result.getObject(column+1);
255                                        columnValues[column] = ( obj == null ? "" : String.valueOf( obj ) );
256                                }
257                                else {
258                                        columnValues[column] = "";
259                                }
260                        }
261                        addColumnValues( columnValues );
262                }
263
264                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
265                if( numberOfRows >= maxRowCount && result.next() ) {
266                        setOverflow( true );
267                }
268        }
269
270        /**
271         * DBTableModelのソート処理を行います。
272         *
273         */
274        private void sort() {
275                // orderByClmsによる並び替え
276                DBTableModelSorter temp = new DBTableModelSorter();
277                temp.setModel( this );
278                String[] oClms = StringUtil.csv2Array( config.getOrderByClms() );
279                for( int i=oClms.length-1; i>=0; i-- ) {
280                        String oc = oClms[i];
281                        boolean ascending = true;
282                        if( oc.startsWith( "!" ) ) {
283                                oc = oc.substring( 1 );
284                                ascending = false;
285                        }
286                        int clmNo = getColumnNo( oc );
287                        temp.sortByColumn( clmNo, ascending );
288                }
289                this.data = temp.data;
290                this.rowHeader = temp.rowHeader;
291        }
292
293        /**
294         * DBTableModelからデータを読み取り、エディット設定情報を元に合計行の追加処理を行います。
295         * 合計行の追加は、キーブレイクにより行われますので、同じキーが複数回出現した場合は、
296         * それぞれの行に対して、合計行が付加されます。
297         *
298         * @og.rev 5.3.7.0 (2011/07/01) 小計、合計行追加処理でオーバーフローフラグがセットされないバグを修正
299         * @og.rev 5.6.1.0 (2013/02/01) 誤差回避のため、doubleではなくdecimalで計算する
300         * @og.rev 5.6.8.1 (2013/09/13) 1行目が合計されていなかったので修正
301         *
302         * @param       maxRowCount 最大検索件数
303         * @param       resource リソースマネージャー
304         * @param       sumFilter 集計項目フィルター
305         * @param       groupFilter グループキーフィルター
306         * @param       subTotalFilter 小計キーフィルター
307         * @param       totalFilter 合計キーフィルター
308         *
309         * @return      オーバーフローしたかどうか(最大件数が超えた場合でかつ次のデータがある場合は、true)
310         */
311        private boolean addTotalRows( final int maxRowCount, final ResourceManager resource, final boolean[] sumFilter
312                                                                        ,  final boolean[] groupFilter, final boolean[] subTotalFilter,  final boolean[] totalFilter ) {
313
314                String subTotalLabel = ( config.useSubTotal() ? resource.makeDBColumn( "EDIT_SUBTOTAL_VALUE" ).getLongLabel() : null );
315                String totalLabel = ( config.useTotal() ? resource.makeDBColumn( "EDIT_TOTAL_VALUE" ).getLongLabel() : null );
316                String grandTotalLabel = ( config.useGrandTotal() ? resource.makeDBColumn( "EDIT_GRANDTOTAL_VALUE" ).getLongLabel() : null );
317
318                int numberOfRows = getRowCount();
319                int sumClmCount = config.getSumClmCount();
320                BigDecimal subTotalSum[] = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
321                BigDecimal totalSum[] = new BigDecimal[sumClmCount];
322                BigDecimal grandTotalSum[] = new BigDecimal[sumClmCount];
323
324                String lastSubTotalKey = null;
325                String lastTotalKey = null;
326
327                int subTotalCount = 0;
328                int totalCount = 0;
329                int grandTotalCount = 0;
330                int rowCount =0;
331
332                int tblIdx = 0;
333                while( numberOfRows < maxRowCount && tblIdx < getRowCount() ) {
334                        BigDecimal[] sumVals = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
335                        StringBuilder groupKey = new StringBuilder();
336                        StringBuilder subTotalKey = new StringBuilder();
337                        StringBuilder totalKey = new StringBuilder();
338
339                        int sc = 0;
340                        for( int column=0; column<numberOfColumns; column++ ) {
341                                String val = getValue( tblIdx, column );
342                                if( groupFilter[column] )               { groupKey.append( val ).append( JS ); }
343                                if( sumFilter[column] )                 { sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); } // 5.6.1.0 (2013/02/01)
344                                if( subTotalFilter[column] )    { subTotalKey.append( val ).append( JS ); }
345                                if( totalFilter[column] )               { totalKey.append( val ).append( JS ); }
346                                if( column == rowCountColumn )  { rowCount = ( val != null && val.length() > 0 ? Integer.valueOf( val ) : 0 ); }
347                        }
348
349                        // 小計キーブレイク処理
350                        if( numberOfRows < maxRowCount && config.useSubTotal() && lastSubTotalKey != null && lastSubTotalKey.length() > 0
351                                && !lastSubTotalKey.equals( subTotalKey.toString() ) ) {
352                                addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
353                                subTotalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
354                                subTotalCount = 0;
355                                numberOfRows++;
356                                tblIdx++;
357                        }
358
359                        // 合計キーブレイク処理
360                        if( numberOfRows < maxRowCount && config.useTotal() && lastTotalKey != null && lastTotalKey.length() > 0
361                                && !lastTotalKey.equals( totalKey.toString() ) ) {
362                                addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
363                                totalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01)
364                                totalCount = 0;
365                                numberOfRows++;
366                                tblIdx++;
367                        }
368
369                        // 小計、合計、総合計単位に集計項目の合計値を加算します。
370                        for( int cnt=0; cnt<sumClmCount; cnt++ ) {
371                                subTotalSum[cnt] = subTotalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : subTotalSum[cnt].add(sumVals[cnt]); // 5.6.8.1 (2013/09/13)
372                                totalSum[cnt] = totalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : totalSum[cnt].add(sumVals[cnt]);
373                                grandTotalSum[cnt] = grandTotalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : grandTotalSum[cnt].add(sumVals[cnt]);
374                        }
375
376                        lastSubTotalKey = subTotalKey.toString();
377                        lastTotalKey = totalKey.toString();
378
379                        // グループ集計時はグルーピングした行数を加算する。
380                        int gcnt = 1;
381                        if( config.useGroup() && rowCountColumn >= 0 && rowCount > 0 ) {
382                                gcnt = rowCount;
383                        }
384                        subTotalCount += gcnt;
385                        totalCount    += gcnt;
386                        grandTotalCount += gcnt;
387
388                        tblIdx++;
389                }
390
391                // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー
392                boolean isOverFlow = tblIdx < getRowCount() ;
393
394                // 小計キー最終行処理
395                if( config.useSubTotal() && lastSubTotalKey != null ) {
396                        if( numberOfRows < maxRowCount ) {
397                                addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx );
398                                numberOfRows++;
399                                tblIdx++;
400                        }
401                        else {
402                                isOverFlow = true;
403                        }
404                }
405
406                // 合計キー最終行処理
407                if( config.useTotal() && lastTotalKey != null ) {
408                        if( numberOfRows < maxRowCount ) {
409                                addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx );
410                                numberOfRows++;
411                                tblIdx++;
412                        }
413                        else {
414                                isOverFlow = true;
415                        }
416                }
417
418                // 総合計処理
419                if( config.useGrandTotal() && numberOfRows > 0 ) {
420                        if( numberOfRows < maxRowCount ) {
421                                boolean[] grandTotalFilter = new boolean[numberOfColumns];
422                                // 総合計のラベル表示廃止
423                                // grandTotalFilter[0] = true;
424                                addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, tblIdx );
425                                numberOfRows++;
426                                tblIdx++;
427                        }
428                        else {
429                                isOverFlow = true;
430                        }
431                }
432
433                if( isOverFlow ) {
434                        setOverflow( true );
435                }
436
437                return isOverFlow;
438        }
439
440        /**
441         * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
442         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
443         * 
444         * @og.rev 5.6.1.0 (2013/02/01) doubleをdecimalに
445         *
446         * @param keyFilter キーフィルタ
447         * @param keyVals キーの値配列
448         * @param keyCount 集計した行のカウント
449         * @param sumFilter 集計フィルタ
450         * @param sumVals 集計値配列
451         * @param aRow 挿入する行番号
452         */
453        private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
454                        , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
455                String[] columnValues = new String[numberOfColumns];
456                int sc = 0;
457                int kc = 0;
458                for( int column=0; column<numberOfColumns; column++ ) {
459                        String val = "";
460                        if( keyFilter[column] ) {
461                                val = keyVals[kc++];
462                        }
463                        if( sumFilter[column] ) {
464                                val = FORMAT.format( sumVals[sc++] );
465                        }
466                        if( column == rowCountColumn ) {
467                                val = String.valueOf( keyCount );
468                        }
469                        columnValues[column] = val;
470                }
471
472                if( aRow < 0 ) {
473                        addColumnValues( columnValues );
474                }
475                else {
476                        addValues( columnValues, aRow, false );
477                }
478        }
479
480        /**
481         * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
482         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
483         * 
484         * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimal
485         *
486         * @param keyFilter キーフィルタ
487         * @param keyVals キーの値配列
488         * @param keyCount 集計した行のカウント
489         * @param sumFilter 集計フィルタ
490         * @param sumVals 集計値配列
491         */
492        private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount
493                        , final boolean[] sumFilter, final BigDecimal[] sumVals ) {
494                addRow( keyFilter, keyVals, keyCount, sumFilter, sumVals, -1 );
495        }
496
497        /**
498         * キーの値、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。
499         * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。
500         * 
501         * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimalに
502         *
503         * @param keyFilter キーフィルタ
504         * @param keyVal キーの値
505         * @param keyCount 集計した行のカウント
506         * @param sumFilter 集計フィルタ
507         * @param sumVals 集計値配列
508         * @param aRow 挿入する行番号
509         */
510        private void addRow( final boolean[] keyFilter, final String keyVal, final int keyCount
511                        , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) {
512                List<String> tmp = new ArrayList<String>();
513                for( int column=0; column<numberOfColumns; column++ ) {
514                        if( keyFilter[column] ) {
515                                tmp.add( keyVal );
516                        }
517                }
518                addRow( keyFilter, tmp.toArray( new String[tmp.size()] ), keyCount, sumFilter, sumVals, aRow );
519        }
520}