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