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.plugin.view;
017
018import java.io.Serializable;
019import java.util.Arrays;
020import java.util.Comparator;
021import java.util.LinkedHashSet;
022import java.util.Set;
023import java.util.TreeSet;
024
025import org.opengion.fukurou.util.StringUtil;
026import org.opengion.hayabusa.common.HybsSystem;
027import org.opengion.hayabusa.common.HybsSystemException;
028import org.opengion.hayabusa.db.DBColumn;
029import org.opengion.hayabusa.db.DBColumnConfig;
030import org.opengion.hayabusa.db.DBTableModel;
031import org.opengion.hayabusa.db.DBTableModelSorter;
032import org.opengion.hayabusa.db.DBTableModelUtil;
033import org.opengion.hayabusa.db.Selection;
034import org.opengion.hayabusa.html.CrossMap;
035import org.opengion.hayabusa.html.ViewCrossTableParam;
036import org.opengion.hayabusa.resource.ResourceManager;
037
038/**
039 * クロス集計テーブル作成クラスです。
040 *
041 *   select dept.dname,emp.deptno,substrb(job,1,2) as X,job,mgr,sum(sal),count(*)
042 *   from emp,dept
043 *   where emp.deptno = dept.deptno
044 *   group by dept.dname,emp.deptno,cube(job,mgr)
045 *   order by emp.deptno,job,mgr;
046 *
047 *   HEAD1   :ヘッダー。前段と同じデータは表示させない。
048 *   HEAD2   :キーブレイクさせるカラム。また、前段と同じデータは表示させない。
049 *   HEAD3   :キーブレイクさせないカラム。また、前段と同じデータでも表示させる。
050 *   ROW     :行データのヘッダーになるカラム
051 *   COL     :列データのヘッダーになるカラム。下記のSUM1,SUM2の両方のヘッダーになる。
052 *   SUM1    :列データの値になるカラム。
053 *   SUM2    :列データの値になるカラム。
054 *
055 *   SUMカラムの数、キーブレイクのカラム名、グループ化するカラム名を
056 *   指定することで、これらのクロス集計結果の表示方法を指定します。
057 *
058 *   breakColumn    = "DEPTNO"             キーブレイクのカラム名
059 *   noGroupColumns = "X"                  グループ化するカラム名
060 *   sumNumber      = "2"                  SUMカラムの数
061 *   cubeXColumn    = "JOB"                CUBE計算の1つ目(X)カラムを指定
062 *   cubeYColumn    = "MGR"                CUBE計算の2つ目(Y)カラムを指定
063 *   cubeSortType   = "NUMBER"             CUBE Y の列ヘッダーのソート方法を指定
064 *   gokeiSortDir   = "false"              合計カラムのソート方向を指定(初期値:ソートしない)
065 *   shokeiLabel    = "SHOKEI"             列小計のカラムに表示するラベルID
066 *   gokeiLabel     = "GOKEI"              列合計のカラムに表示するラベルID
067 *   useHeaderColumn= "false"              ヘッダーカラムにレンデラー、エディターを適用するかを指定
068 *   useClassAdd    = "false"              各列情報のclass属性に、カラム名などを付与するかどうかを指定
069 *   useHeaderResource = "false"           ヘッダー表示にラベルリソースを利用するか
070 *
071 *   各カラムの属性(HEAD,SUM等)を認識する方法
072 *
073 *     HEAD1 HEAD2 HEAD3 ROW COL SUM1 SUM2 という並びを認識する方法は、
074 *     多数の前提条件を利用して、出来るだけ少ないパラメータで自動認識
075 *     させています。
076 *     若干理解しにくいかもしれませんが、慣れてください。
077 *
078 *     前提条件:
079 *       ROW,COL は、必ず1個ずつ存在する。
080 *       HEAD群、ROW,COL,SUM群 という並びになっている。
081 *       SUM群の数は、パラメータで指定する。
082 *     計算方法:
083 *       HEAD数=カラム数(7)-SUM数(2)-1(ROW,COL分) = 4 個 (0 ~ 3)
084 *       ROWアドレス=cubeXColumn 設定                       (3)      ※ アドレスは0から始まる為
085 *       COLアドレス=cubeYColumn 設定                       (4)
086 *       SUMアドレス=HEAD数+1 ~ カラム数(7)-1            (5 ~ 6)
087 *
088 * @og.rev 3.5.4.0 (2003/11/25) 新規作成
089 * @og.group 画面表示
090 *
091 * @version  4.0
092 * @author       Kazuhiko Hasegawa
093 * @since    JDK5.0,
094 */
095public class ViewForm_HTMLCrossTable extends ViewForm_HTMLTable {
096        //* このプログラムのVERSION文字列を設定します。   {@value} */
097        private static final String VERSION = "5.7.4.3 (2014/03/28)" ;
098
099        private String[] groupByData = null;
100        private String[] groupByCls  = null;
101
102        // 3.5.4.8 (2004/02/23) 機能改善
103        private int             rowClmNo        = -1;                   // ROWカラムのカラム番号
104        private int             colClmNo        = -1;                   // CLMカラムのカラム番号
105        private int             headCount       = 0;                    // HEADカラムの数
106        private int             sumCount        = 1;                    // 合計カラムの数
107        private int             breakClmNo      = -1;                   // ブレークするカラムのカラム番号
108        private boolean[]       noGroupClm = null;              // グループ化する/しないのフラグ配列
109        private String          shokeiLabel  = "小計";    // 列小計のカラムに表示するラベルID
110        private String          gokeiLabel   = "合計";    // 列合計のカラムに表示するラベルID
111        private String          gokeiSortDir = null;    // 列合計のカラムをソートする方向
112
113        // 3.5.6.3 (2004/07/12) ソート方式[STRING,NUMBER,LOAD]
114        private String          cubeSortType = "LOAD";
115
116        private DBTableModel table2             = null;
117        private boolean          firstStep      = true;
118
119        private String[]        clmKeys         = null;                         // 集計部のカラムキー(集計カラムが複数でも一つ)の配列
120        private String[]        clsAdd          = null;                         // 5.2.2.0 (2010/11/01) class属性に付与されるカラムキー
121
122        private String          noDisplayKeys           = null;         // 3.7.0.4 (2005/03/18)
123        private String          columnDisplayKeys       = null;         // 5.2.2.0 (2010/11/01)
124
125        private boolean         firstClmGokei           = false;        // 5.0.0.3 (2009/09/22)
126        private boolean         useHeaderColumn         = false;        // 5.2.2.0 (2010/11/01)
127        private boolean         useClassAdd                     = false;        // 5.2.2.0 (2010/11/01) class属性にカラムキーを追加するかどうか
128        private boolean         useHeaderResource       = false;        // 5.5.5.0 (2012/07/28)
129        private String          headerCode                      = null;         // 5.5.5.0 (2012/07/28)
130
131        private static final Comparator<String> numberSort = new NumberComparator();
132
133        /**
134         * 初期化します。
135         * ここでは、内部で使用されているキャッシュをクリアし、
136         * 新しいモデル(DBTableModel)と言語(lang) を元に内部データを再構築します。
137         * ただし、設定情報は、以前の状態がそのままキープされています。
138         *
139         * @og.rev 3.5.4.8 (2004/02/23) paramInit メソッドで、初期化を行います。
140         * @og.rev 3.5.6.1 (2004/06/25) lang 言語コード 属性を削除します。
141         *
142         * @param       table   DBTableModelオブジェクト
143         */
144        @Override
145        public void init( final DBTableModel table ) {
146                table2          = table;
147                firstStep       = true;
148        }
149
150        /**
151         * 内容をクリア(初期化)します。
152         *
153         * @og.rev 3.5.6.3 (2004/07/12) cubeSortType , gokeiSortDir 属性を追加します。
154         * @og.rev 3.7.0.4 (2005/03/18) noDisplayKeys 属性を追加します。
155         * @og.rev 3.7.1.1 (2005/05/31) shokeiLabel,gokeiLabel の初期値変更
156         * @og.rev 5.2.2.0 (2010/11/01) columnDisplayKeys、clsAdd、useClassAdd 属性を追加します
157         * @og.rev 5.5.5.0 (2012/07/20) useHeaderResource追加
158         */
159        @Override
160        public void clear() {
161                super.clear();
162                groupByData = null;
163                groupByCls  = null;
164                rowClmNo        = -1;                   // ROWカラムのカラム番号
165                colClmNo        = -1;                   // CLMカラムのカラム番号
166                headCount       = 0;                    // HEADカラムの数
167                sumCount        = 1;                    // 合計カラムの数
168                breakClmNo      = -1;                   // ブレークするカラムのカラム番号
169                noGroupClm      = null;                 // グループ化する/しないのフラグ配列
170                table2          = null;
171                firstStep       = true;
172                clmKeys         = null;
173                clsAdd          = null;                 // 5.2.2.0 (2010/11/01)
174                shokeiLabel     = "小計";         // 列小計のカラムに表示するラベルID
175                gokeiLabel      = "合計";         // 列合計のカラムに表示するラベルID
176                cubeSortType = "LOAD";          // 3.5.6.3 (2004/07/12)
177                gokeiSortDir = null;            // 3.5.6.3 (2004/07/12) 列合計のカラムをソートする方向
178                noDisplayKeys           = null; // 3.7.0.4 (2005/03/18)
179                columnDisplayKeys       = null; // 5.2.2.0 (2010/11/01)
180                firstClmGokei           = false;        // 5.2.2.0 (2010/11/01)
181                useHeaderColumn         = false;        // 5.2.2.0 (2010/11/01)
182                useClassAdd                     = false;        // 5.2.2.0 (2010/11/01)
183                useHeaderResource       = false;        // 5.5.5.0 (2012/07/20)
184                headerCode                      = null;         // 5.5.5.0 (2012/07/28)
185        }
186
187        /**
188         * DBTableModel から HTML文字列を作成して返します。
189         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
190         * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
191         *
192         * @og.rev 3.5.5.0 (2004/03/12) No 欄そのものの作成判断ロジックを追加
193         * @og.rev 3.5.6.1 (2004/06/25) lang 言語コード 属性を削除します。
194         * @og.rev 3.5.6.4 (2004/07/16) ヘッダーとボディー部をJavaScriptで分離
195         * @og.rev 3.7.0.4 (2005/03/18) setNoDisplay メソッドを追加
196         * @og.rev 4.3.1.0 (2008/09/08) 編集行のみを表示する属性(isSkipNoEdit)追加
197         * @og.rev 5.0.0.3 (2009/09/22) 合計列をcubeの先頭に出せるようにする
198         * @og.rev 5.1.0.0 (2009/11/04) ↑で合計列が複数カラム存在する場合に正しく表示されないバグを修正
199         * @og.rev 5.2.2.0 (2010/11/01) setColumnDisplay メソッドを追加
200         *
201         * @param  startNo        表示開始位置
202         * @param  pageSize   表示件数
203         *
204         * @return      DBTableModelから作成された HTML文字列
205         */
206        @Override
207        public String create( final int startNo, final int pageSize )  {
208                if( firstStep ) {
209                        paramInit( table2 );
210                        super.init( makeCrossTable(table2) );
211                        super.setNoDisplay( noDisplayKeys ) ;                           // 3.7.0.4 (2005/03/18)
212                        super.setColumnDisplay( columnDisplayKeys ) ;           // 5.2.2.0 (2010/11/01)
213                        markerSet( this );              // 3.5.6.4 (2004/07/16)
214                        firstStep = false;
215                }
216
217                if( getRowCount() == 0 ) { return ""; } // 暫定処置
218
219                int clmCnt = getColumnCount();  // 3.5.5.7 (2004/05/10)
220
221                headerLine       = null;
222
223                int lastNo = getLastNo( startNo, pageSize );
224                int blc = getBackLinkCount();
225                String backData = null;
226
227                StringBuilder out = new StringBuilder( HybsSystem.BUFFER_LARGE );
228
229                out.append( getCountForm( startNo,pageSize ) );
230                out.append( getHeader() );
231
232                String ckboxTD = "  <td class=\"" + ViewCrossTableParam.HEADER1 + "\">";
233
234                out.append("<tbody>").append( HybsSystem.CR );
235                int bgClrCnt = 0;
236                boolean shokei;
237                for( int row=startNo; row<lastNo; row++ ) {
238                        if( isSkip( row ) || isSkipNoEdit( row ) ) { continue; } // 4.3.1.0 (2008/09/08)
239                        // キーブレイク時のヘッダー設定
240                        if( breakClmNo >= 0 ) {
241                                String val = getValue( row,breakClmNo );
242                                if( backData == null ) {        // キーブレイクの初期データ設定。
243                                        backData = val;
244                                }
245                                else {
246                                        if( ! backData.equals( val ) ) {
247                                                backData = val;
248                                                out.append( getHeadLine() );
249                                        }
250                                }
251                        }
252                        // 小計ヘッダー時のクラス設定
253                        String val2 = getValue( row,rowClmNo );
254                        if( val2.length() == 0 ) {
255                                shokei = true;
256                                out.append("<tr class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">");
257                        }
258                        else {
259                                shokei = false;
260                                out.append("<tr").append( getBgColorCycleClass( bgClrCnt++ ) ).append(">");
261                        }
262                        out.append( HybsSystem.CR );
263                        // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加
264                        if( isNumberDisplay() ) {
265                                out.append( makeCheckbox( ckboxTD, row, blc ) ).append( HybsSystem.CR );
266                        }
267                        for(int column = 0; column < clmCnt; column++) {
268                                if( isColumnDisplay( column ) ) {
269                                        if( column < headCount-1 ) {            // CUBEではない行ヘッダー部
270                                                String val = getGroupData( column,getRendererValue(row,column) );
271                                                out.append("  <td class=\"").append( groupByCls[column] ).append("\">");
272                                                out.append( val );
273                                        }
274                                        else if( column == headCount-1 ) {      // ヘッダーの最後尾
275                                                if( shokei ) {
276                                                        out.append("  <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">");
277                                                        out.append( shokeiLabel );
278                                                }
279                                                else {
280                                                        if( breakClmNo > 0 ) {  // ヘッダーがある場合
281                                                                out.append("  <td class=\"").append( groupByCls[column-1] ).append("\">");
282                                                        }
283                                                        else {
284                                                                out.append("  <td class=\"").append( ViewCrossTableParam.HEADER1 ).append("\">");
285                                                        }
286                                                        out.append( getRendererValue(row,column) );
287                                                }
288                                        }
289                                        // else if( column >= clmCnt-sumCount ) {       // CUBEの最終カラム(列合計)
290                                        else if( column >= clmCnt-sumCount && ! firstClmGokei ) {       // 5.0.0.3 (2009/09/22) CUBEの最終カラム(列合計)
291                                                out.append("  <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">");
292                                                out.append( getRendererValue(row,column) );
293                                        }
294                                        else if( column >= headCount && column < headCount + sumCount && firstClmGokei ) {              // 5.1.0.0 (2009/11/04)
295                                                        out.append("  <td class=\"").append( ViewCrossTableParam.SHOKEI ).append("\">");
296                                                        out.append( getRendererValue(row,clmCnt-sumCount+(column-headCount)) ); // 5.1.0.0 (2009/11/04)
297                                        }
298                                        else {          // カラム SUM列
299                                                if( useClassAdd && clsAdd[column] != null ) {
300                                                        out.append("  <td class=\"").append( clsAdd[column] ).append("\">");
301                                                }
302                                                else {
303                                                        out.append("  <td>");
304                                                }
305                                                if( firstClmGokei ){
306                                                        out.append( getRendererValue(row,column-sumCount) ); // 5.1.0.0 (2009/11/04)
307                                                }
308                                                else{
309                                                        out.append( getRendererValue(row,column) );
310                                                }
311                                        }
312                                        out.append("</td>").append( HybsSystem.CR );
313                                }
314                        }
315                        out.append("</tr>").append( HybsSystem.CR );
316                }
317                out.append("</tbody>").append( HybsSystem.CR );
318                out.append("</table>").append( HybsSystem.CR );
319
320                out.append( getScrollBarEndDiv() );     // 3.8.0.3 (2005/07/15)
321                return out.toString();
322        }
323
324        /**
325         * パラメータ内容を初期化します。
326         *
327         * @og.rev 3.5.4.8 (2004/02/23) 新規作成
328         * @og.rev 3.5.6.3 (2004/07/12) 列ヘッダーのソート方法を指定
329         * @og.rev 5.0.0.3 (2009/09/22) 合計行をCUBEの先頭に持ってくるためのフラグ追加
330         * @og.rev 5.2.2.0 (2010/11/01) useHeaderColumn,useClassAdd 属性の追加
331         *
332         * @param       table   入力もとの DBTableModelオブジェクト
333         */
334        private void paramInit( final DBTableModel table ) {
335                String breakColumn              = getParam( ViewCrossTableParam.BREAK_COLUMN_KEY     , null );
336                String noGroupColumns   = getParam( ViewCrossTableParam.NO_GROUP_COLUMNS_KEY , null );
337                String sumNumber                = getParam( ViewCrossTableParam.SUM_NUMBER_KEY       , null );
338                shokeiLabel                             = getParam( ViewCrossTableParam.SHOKEI_LABEL_KEY     , shokeiLabel );
339                gokeiLabel                              = getParam( ViewCrossTableParam.GOKEI_LABEL_KEY      , gokeiLabel );
340                String cubeXColumn              = getParam( ViewCrossTableParam.CUBE_X_COLUMN_KEY    , null );  // CUBE計算の1つ目(X)カラムを指定
341                String cubeYColumn              = getParam( ViewCrossTableParam.CUBE_Y_COLUMN_KEY    , null );  // CUBE計算の2つ目(Y)カラムを指定
342                cubeSortType                    = getParam( ViewCrossTableParam.CUBE_SORT_TYPE_KEY   , "LOAD" );        // 3.5.6.3 (2004/07/12)
343                gokeiSortDir                    = getParam( ViewCrossTableParam.GOKEI_SORT_DIR_KEY   , null );  // 3.5.6.3 (2004/07/12)
344                firstClmGokei                   = StringUtil.nval( getParam( ViewCrossTableParam.FIRST_CLM_GOKEI_KEY , null ), false);  // 5.0.0.3 (2009/09/22)
345                useHeaderColumn                 = StringUtil.nval( getParam( ViewCrossTableParam.USE_HEADER_COLUMN   , null ), false);  // 5.2.2.0 (2010/11/01)
346                useClassAdd                             = StringUtil.nval( getParam( ViewCrossTableParam.USE_CLASS_ADD       , null ), false);  // 5.2.2.0 (2010/11/01)
347                useHeaderResource               = StringUtil.nval( getParam( ViewCrossTableParam.USE_HEADER_RSC      , null ), false);  // 5.5.5.0 (2012/07/20)
348                headerCode                              = getParam( ViewCrossTableParam.HEADER_CODE_KEY     , null );   // 5.5.5.0 (2012/07/28)
349                
350                if( sumNumber != null ) {
351                        sumCount = Integer.parseInt( sumNumber );
352                }
353
354                // HEAD数=カラム数-SUM数-1(COL分) ROW は、HEADに含みます。
355                headCount = table.getColumnCount() - sumCount - 1;
356
357                // 3.5.5.9 (2004/06/07)
358                if( cubeXColumn != null ) {
359                        rowClmNo = table.getColumnNo( cubeXColumn );
360                }
361                else {
362                        rowClmNo = headCount-1;                 // ROWカラムのカラム番号
363                }
364
365                // 3.5.5.9 (2004/06/07)
366                if( cubeYColumn != null ) {
367                        colClmNo = table.getColumnNo( cubeYColumn );
368                }
369                else {
370                        colClmNo = headCount;           // CLMカラムのカラム番号
371                }
372
373                if( breakColumn != null ) {
374                        breakClmNo = table.getColumnNo( breakColumn );
375                }
376
377                groupByData = new String[headCount];
378                groupByCls  = new String[headCount];
379                Arrays.fill( groupByCls,ViewCrossTableParam.HEADER2 );          // 変であるが、最初に入れ替えが発生する為。
380
381                noGroupClm    = new boolean[headCount];         // グループ化する/しないのフラグ配列
382                Arrays.fill( noGroupClm,false );
383
384                if( noGroupColumns != null ) {
385                        String[] gClms = StringUtil.csv2Array( noGroupColumns );
386                        for( int i=0; i<gClms.length; i++ ) {
387                                noGroupClm[table.getColumnNo( gClms[i] )] = true;
388                        }
389                }
390
391                if( ! "true".equalsIgnoreCase( gokeiSortDir ) &&
392                        ! "false".equalsIgnoreCase( gokeiSortDir ) ) {
393                                gokeiSortDir = null;
394                }
395        }
396
397        /**
398         * CUBEではない行ヘッダー部の値が前と同じならば、ゼロ文字列を返します。
399         *
400         * @param       clm     カラム番号
401         * @param       val     比較する値
402         *
403         * @return      前と同じなら,""を、異なる場合は、引数の val を返します。
404         */
405        private String getGroupData( final int clm,final String val ) {
406                if( noGroupClm[clm] ) { return val; }
407
408                if( val.equals( groupByData[clm] )) {
409                        return "";
410                }
411                else {
412                        groupByData[clm] = val;
413                        groupByCls[clm] = groupByCls[clm].equals( ViewCrossTableParam.HEADER1 )
414                                                                        ? ViewCrossTableParam.HEADER2
415                                                                        : ViewCrossTableParam.HEADER1 ;
416                        return val;
417                }
418        }
419
420        /**
421         * 選択用のチェックボックスと行番号と変更タイプ(A,C,D)を表示します。
422         *
423         * @param  ckboxTD チェックボックスのタグ(マルチカラム時のrowspan対応)
424         * @param  row   行番号
425         * @param  blc   バックラインカウント(先頭へ戻るリンク間隔)
426         *
427         * @return      tdタグで囲まれたチェックボックスのHTML文字列
428         */
429        @Override
430        protected String makeCheckbox( final String ckboxTD,final int row,final int blc ) {
431                StringBuilder out = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
432
433                out.append( ckboxTD ).append("</td>");
434                out.append( ckboxTD ).append("</td>");
435                out.append( ckboxTD );
436                // 3.5.1.0 (2003/10/03) Noカラムに、numberType 属性を追加
437                if( blc != 0 && (row+1) % blc == 0 ) {
438                        out.append( "<a href=\"#top\">" ).append( row+1 ).append(  "</a>" );
439                } else {
440                        out.append( row+1 );
441                }
442                out.append("</td>");
443
444                return out.toString();
445        }
446
447        /**
448         * ヘッダー繰り返し部を、getTableHead()メソッドから分離。
449         *
450         * @og.rev 3.5.4.5 (2004/01/23) 実装をgetHeadLine( String thTag )に移動
451         * @og.rev 3.5.5.0 (2004/03/12) No 欄そのものの作成判断ロジックを追加
452         * @og.rev 5.0.0.3 (2009/09/17) 合計行を出力する位置をfirstClmGokeiで変える
453         * @og.rev 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応
454         * @og.rev 5.5.5.0 (2012/07/28) useHeaderResource利用時のヘッダのラベル/コードリソース対応
455         * @og.rev 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し
456         * @og.rev 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。
457         *
458         * @return      テーブルのタグ文字列
459         */
460        @Override
461        protected String getHeadLine() {
462                if( headerLine != null ) { return headerLine; }         // キャッシュを返す。
463
464                String rowspan = "";
465                if( sumCount > 1 ) { rowspan = " rowspan=\"2\""; }
466
467                String thTag = "<th" + rowspan;
468
469                StringBuilder buf = new StringBuilder( HybsSystem.BUFFER_MIDDLE );
470
471                buf.append("<tr").append( rowspan ).append(" class=\"row_h\"").append(" >").append( HybsSystem.CR );
472
473                // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加
474                if( isNumberDisplay() ) {
475                        buf.append( thTag ).append(" colspan='3'>").append( getNumberHeader() ).append("</th>");
476                }
477
478                buf.append( HybsSystem.CR );
479                // ヘッダー部分は、そのまま表示します。
480                for(int column = 0; column < headCount; column++) {
481                        if( isColumnDisplay( column ) ) {
482                                buf.append( thTag ).append(">");
483                                buf.append( getColumnLabel(column) );
484                                buf.append("</th>").append( HybsSystem.CR );
485                        }
486                }
487
488                // ヘッダー部分(上段)は、カラム配列を利用します。
489                String colspan = "";
490                if( sumCount > 1 ) { colspan = " colspan='" + sumCount + "'"; }
491
492                // 5.2.2.0 (2010/11/01) 集計部の ColumnDisplay/NoDisplay 対応
493                String gokeiClm = null;
494                if( isColumnDisplay( headCount+(clmKeys.length-1)*sumCount ) ) {
495                        String temp = clmKeys[clmKeys.length-1];
496                        if( temp == null || temp.length() == 0 ) {
497                                temp = gokeiLabel;
498                        }
499
500                        gokeiClm = "<th" + colspan + ">" + temp + "</th>" + HybsSystem.CR ;
501                }
502
503                // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。
504                // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。
505                if( firstClmGokei && gokeiClm != null ) {
506                        buf.append( gokeiClm );
507                }
508
509                // 3.7.0.4 (2005/03/18) カラム配列は、カラム番号と別物
510                ResourceManager resource = getResourceManager();
511                Selection selection = null;
512                if( headerCode != null && headerCode.length() > 0 && resource != null ){
513                        DBColumn clmTmp = resource.getDBColumn( headerCode );
514                        //selection = new Selection_CODE(resource.getCodeData( headerCode )); code直の場合
515                        if ( clmTmp != null ){
516                                selection = clmTmp.getSelection();
517                        }
518                }
519
520                // 5.7.4.2 (2014/03/20) ヘッダーのリソース適用見直し
521                // 5.7.4.3 (2014/03/28) useHeaderResource 単独でリソース適用します。
522                DBColumn colClm = null;
523                if( useHeaderResource ) {
524                        colClm = table2.getDBColumn( colClmNo );
525                }
526
527                for( int keyNo = 0; keyNo < clmKeys.length-1; keyNo++ ) {
528                        // 5.2.2.0 (2010/11/01) ColumnDisplay/NoDisplay 対応
529                        if( isColumnDisplay( headCount+keyNo ) ) {
530                                buf.append( "<th").append( colspan ).append( ">" );
531                                if( selection != null ){
532                                        buf.append( selection.getValueLabel( clmKeys[keyNo] ) );
533                                }
534                                // 5.7.4.3 (2014/03/28) ヘッダーのリソース適用は、CLMカラムのカラム番号のみとします。
535                                else if( colClm != null ) {
536                                        buf.append( colClm.getRendererValue( clmKeys[keyNo] ) );
537                                }
538                                else{
539                                        buf.append( clmKeys[keyNo] );
540                                }
541                                buf.append("</th>").append( HybsSystem.CR );
542                        }
543                }
544
545                // 5.2.2.0 (2010/11/01) 最後のカラムが、合計行。
546                // 5.0.0.3 (2009/09/22) firstClmGokei が trueの場合はcubeの先頭に出すようにします。
547                if( ! firstClmGokei && gokeiClm != null ) {
548                        buf.append( gokeiClm );
549                }
550
551                buf.append("</tr>").append( HybsSystem.CR );
552
553                if( sumCount > 1 ) {
554                        buf.append("<tr").append(" class=\"row_h\"").append(" >").append( HybsSystem.CR );
555                        int clmCnt = getColumnCount();  // 3.5.5.7 (2004/05/10)
556                        for(int column = headCount; column < clmCnt; column++) {
557                                if( isColumnDisplay( column ) ) {
558                                        buf.append( "<th>");
559                                        buf.append( getColumnLabel(column) );
560                                        buf.append("</th>").append( HybsSystem.CR );
561                                }
562                        }
563                        buf.append("</tr>").append( HybsSystem.CR );
564                }
565
566                headerLine = buf.toString();
567                return headerLine;
568        }
569
570        /**
571         * クロス集計結果の DBTableModelオブジェクトを作成します。
572         *
573         * @og.rev 3.5.4.8 (2004/02/23) paramInit メソッドで、初期化を行います。
574         * @og.rev 3.5.6.3 (2004/07/12) 列ヘッダーのソート可否の指定を追加
575         * @og.rev 4.0.0.0 (2007/11/27) ヘッダーカラムのエディター、レンデラー適用対応
576         * @og.rev 4.3.5.7 (2008/03/22) ↑リソースが存在しない場合は、ラベルのみ入れ替え
577         * @og.rev 5.2.2.0 (2010/11/01) useHeaderColumn,useClassAdd 属性の追加
578         * @og.rev 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。
579         *
580         * @param       table   入力もとの DBTableModelオブジェクト
581         *
582         * @return      DBTableModelオブジェクト
583         */
584        private DBTableModel makeCrossTable( final DBTableModel table ) {
585                Set<String> clmData = gatSortAlgorithmSet();
586
587                // 列のキーとなるカラムの値を取得します。
588                int rowCnt = table.getRowCount();               // 3.5.5.7 (2004/05/10)
589                for( int row=0; row<rowCnt; row++ ) {
590                        String clm = table.getValue( row,colClmNo );
591                        if( clm.length() > 0 ) { clmData.add( clm ); }
592                }
593                // ゼロストリングは、合計行になりますので、最後に追加します。
594
595                // 3.5.6.3 (2004/07/12) ゼロストリングは、合計行になりますので、最後に追加します。
596                clmKeys = clmData.toArray( new String[clmData.size() + 1] ) ;
597
598                clmKeys[clmKeys.length-1] = "" ;
599
600                int numberOfColumns =  headCount + clmKeys.length * sumCount ;
601
602                DBTableModel tableImpl = DBTableModelUtil.newDBTable();
603                tableImpl.init( numberOfColumns );
604
605                // ヘッダーカラム(ROWデータ含む)は、そのまま、設定します。
606                for(int column=0; column<headCount; column++) {
607                        tableImpl.setDBColumn( column,table.getDBColumn(column) );
608                }
609
610                // 列情報は、合計値のカラム定義を使用します。
611                DBColumn[] dbColumn = new DBColumn[sumCount];
612                for( int i=0; i<sumCount; i++ ) {
613                        dbColumn[i] = table.getDBColumn(headCount + 1 + i);
614                }
615
616                // 列情報は、列の名前をカラムの値に変えて、合計カラム列のコピー情報を設定します。
617
618                int sumId = 0;
619                ResourceManager resource = getResourceManager();
620                useHeaderColumn = useHeaderColumn && resource != null ; // 5.2.2.0 (2010/11/01)
621
622                // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加
623
624                clsAdd = new String[numberOfColumns];
625
626                // 列情報カラムは、ヘッダー分に割り当てられる為、開始が、headCount からになります。
627                for(int column=headCount; column<numberOfColumns; column++) {
628                        DBColumn dbClm = dbColumn[sumId];
629                        String clmKey  = clmKeys[ (column-headCount)/sumCount ];
630
631                        // 5.2.2.0 (2010/11/01) useClassAdd 属性の追加
632                        if( useClassAdd ) {
633                                 // ※ 特殊対応:cssなどで指定できるIDやCLASS属性は、先頭文字が数字の場合は、
634                                 // 無効になります。(つまり、効きません。)
635                                 // 表示ヘッダーは、年月や、社員番号(数字)などのケースもあります。そこで、先頭が数字の
636                                 // 場合は、"x"(小文字のx)を自動的に頭に追加します。
637                                StringBuilder buf = new StringBuilder();
638                                if( clmKey != null && clmKey.length() > 0 ) {
639                                        char ch = clmKey.charAt(0);
640                                        if( ch >= '0' && ch <= '9' ) {
641                                                buf.append( "x" );
642                                        }
643                                        buf.append( clmKey );
644                                }
645
646                                String nm = dbClm.getName();
647                                if( nm != null && nm.length() > 0 ) {
648                                        buf.append( " " );
649                                        char ch = nm.charAt(0);
650                                        if( ch >= '0' && ch <= '9' ) {
651                                                buf.append( "x" );
652                                        }
653                                        buf.append( nm );
654                                }
655                                clsAdd[column] = buf.toString();
656                        }
657
658                        // 5.7.4.3 (2014/03/28) useHeaderColumn の適用条件を、最初の集計カラムのみに変更。
659                        if( useHeaderColumn && sumId == 0 ) {
660                                DBColumn clmTmp = resource.getDBColumn( clmKey );
661                                if( clmTmp == null ) {
662                                        DBColumnConfig dbCfg2 = dbClm.getConfig();
663                                        if( clmKey != null && clmKey.length() > 0 ) {   // 5.2.2.0 (2010/11/01)
664                                                dbCfg2.setName( clmKey );
665                                                dbCfg2.setLabelData( resource.getLabelData( clmKey ) );
666                                        }
667                                        else {
668                                                dbCfg2.setLabelData( resource.getLabelData( gokeiLabel ) );
669                                        }
670                                        dbClm = new DBColumn( dbCfg2 );
671                                }
672                                else {
673                                        dbClm = clmTmp;
674                                }
675                        }
676
677                        tableImpl.setDBColumn( column,dbClm );
678
679                        sumId++;
680                        if( sumId % sumCount == 0 ) {
681                                sumId = 0;
682                        }
683                }
684
685                // クロス集計データの作成
686                CrossMap cross = new CrossMap( clmKeys,headCount,sumCount );
687                for( int row=0; row<rowCnt; row++ ) {
688                        String[] data = table.getValues( row );
689                        cross.add( data );
690                }
691
692                // データ部の設定
693                int size = cross.getSize();
694                for( int row=0; row<size; row++ ) {
695                        tableImpl.addValues( cross.get( row ), row );
696                }
697
698                tableImpl.resetModify();
699
700                final DBTableModel model ;
701                if( gokeiSortDir != null ) {
702                        DBTableModelSorter temp = new DBTableModelSorter();
703                        temp.setModel( tableImpl );
704
705                        boolean direction = Boolean.valueOf( gokeiSortDir ).booleanValue();
706                        temp.sortByColumn( numberOfColumns-1,direction );
707                        model = temp ;
708                }
709                else {
710                        model = tableImpl;
711                }
712                return model ;
713        }
714
715        /**
716         * 列ヘッダーのソート方法に応じた、Setオブジェクトを返します。
717         * ここでは、NUMBER , STRING , LOAD の3種類用意しています。
718         *
719         * @og.rev 3.5.6.3 (2004/07/12) 新規作成
720         *
721         * @return      ソート方法に応じたSetオブジェクト
722         */
723        private Set<String> gatSortAlgorithmSet() {
724                final Set<String> rtnSet ;
725
726                if( "LOAD".equalsIgnoreCase( cubeSortType ) ) {
727                        rtnSet = new LinkedHashSet<String>();
728                }
729                else if( "NUMBER".equalsIgnoreCase( cubeSortType ) ) {
730                        rtnSet = new TreeSet<String>( numberSort );
731                }
732                else if( "STRING".equalsIgnoreCase( cubeSortType ) ) {
733                        rtnSet = new TreeSet<String>();
734                }
735                else {
736                        String errMsg = "cubeSortType は、NUMBER,STRING,LOAD 以外指定できません。" +
737                                                        "  cubeSortType=[" + cubeSortType + "]";
738                        throw new HybsSystemException( errMsg );
739                }
740
741                return rtnSet ;
742        }
743
744        /**
745         * 表示不可カラム名を、カンマ区切りで与えます。
746         * 例:"OYA,KO,HJO,SU,DYSET,DYUPD"
747         * null を与えた場合は,なにもしません。
748         *
749         * 注意:このクラスでは、DBTableModel を作り直すタイミングが、
750         * create メソッド実行時です。(パラメータの初期化が必要な為)
751         * よって、このメソッドは、初期が終了後に、再セットします。
752         *
753         * @og.rev 3.7.0.4 (2005/03/18) 新規作成
754         *
755         * @param       columnName      カラム名
756         */
757        @Override
758        public void setNoDisplay( final String columnName ) {
759                noDisplayKeys = columnName;
760        }
761
762        /**
763         * 表示可能カラム名を、カンマ区切りで与えます。
764         * 例:"OYA,KO,HJO,SU,DYSET,DYUPD"
765         * setColumnDisplay( int column,boolean rw ) の簡易版です。
766         * null を与えた場合は,なにもしません。
767         * また、全カラムについて、有効にする場合は、columnName="*" を設定します。
768         *
769         * @og.rev 5.2.2.0 (2010/11/01) 新規追加
770         *
771         * @param       columnName      カラム名
772         */
773        @Override
774        public void setColumnDisplay( final String columnName ) {
775                columnDisplayKeys = columnName;
776        }
777
778        /**
779         * NUMBER ソート機能(整数限定) 内部クラス
780         * これは通常のソートではなく、ヘッダーに使うラベルのソートなので、
781         * 整数のみと限定します。実数の場合は、桁合わせ(小数点以下の桁数)
782         * されているという前提です。
783         *
784         * @og.rev 3.5.6.3 (2004/07/12) 新規作成
785         *
786         */
787        private static class NumberComparator implements Comparator<String>,Serializable {
788                private static final long serialVersionUID = 400020050131L ;    // 4.0.0.0 (2005/01/31)
789
790                public int compare( final String s1, final String s2 ) {
791                        if( s1.length() > s2.length() )      { return 1;  }
792                        else if( s1.length() < s2.length() ) { return -1; }
793                        else {
794                                return s1.compareTo( s2 );
795                        }
796                }
797        }
798
799        /**
800         * 表示項目の編集(並び替え)が可能かどうかを返します
801         *
802         * @og.rev 5.1.6.0 (2010/05/01) 新規追加
803         *
804         * @return      表示項目の編集(並び替え)が可能かどうか(false:不可能)
805         */
806        @Override
807        public boolean isEditable() {
808                return false;
809        }
810}