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