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