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