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