    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.util.List;
    019import java.util.ArrayList;
    020
    021import org.opengion.hayabusa.common.HybsSystemException;
    022import org.opengion.hayabusa.html.ViewTimeTableParam;
    023
    024/**
    025 * 時間軸を持つタイムテーブルの表示を行うクラスです。
    026 *
    027 * パラメータが必要な場合は、ViewTimeTableParamTag を使用してください。
    028 *
    029 * パラメータが設定されていない場合は、ViewForm_HTMLTimeTable の初期値が使用されます。
    030 * (パラメータを使用するには、viewタグのuseParam 属性をtrueに設定する必要があります。)
    031 *
    032 * SELECT文は、日付、キー、備考、開始時刻、終了時刻、リンクが、必須項目で、この並び順は、
    033 * 完全に固定です。よって、カラム位置を指定する必要はありませんが、SELECT文を自由に
    034 * 設定することも出来ませんので、ご注意ください。
    035 * この固定化に伴い、WRITABLE 指定も使用できません。
    036 * なお、日付、キー、備考 に関しては、columnDisplay 属性で、表示の ON/OFF 制御は可能です。
    037 * また、日付ブレイク、キーブレイクの設定で、カラム自体をテーブルの外に出すことが可能です。
    038 * (キーと備考はセットになっています。)
    039 *
    040 * タイムテーブルが空きの場合のリンクを指定できます。(ViewTimeTableParam.NULL_LINK_CLM_ID)
    041 * (ViewTimeTableParam の nullLinkColumn 属性)
    042 * 指定しない場合は、空きのリンクは作成されません。
    043 * このリンクは、特殊で、引数に、パラメータを追加できますが、"($1)"、"($2)" で指定します。
    044 * この($1)、($2)は、開始時刻、終了時刻がセットされますが、SELECT文の固定カラムと同じ
    045 * 並び順ですが、DBTableModelの値を設定しているわけではありません。
    046 * 空きの場合は、データ自体が存在しない場合と、日付、キー のみが 外部結合で生成された
    047 * レコードが実際に存在する場合がありますが、外部結合で生成されたレコードには、
    048 * 開始時刻、終了時刻はありません。($1) と($2)には、それぞれ、最小開始時刻と最大終了時刻を
    049 * セットします。
    050 *
    051 * 例として、&amp;TMSTART=($1)&amp;TMEND=($2) という文字列の ($*) 部分を解析して割当ます。
    052 *
    053 * ブレーク処理を行うカラムＩＤをCSV形式でセットできます。(ViewTimeTableParam.BREAK_CLMS)
    054 * (ViewTimeTableParam の breakClms 属性)
    055 * これは、ブレイク毎にテーブルが分かれて、テーブルの先頭に、ブレイクした
    056 * 値が表示されます。
    057 * 例えば、日付カラムをブレイクカラムとして設定すると、日付がブレイクするたび、
    058 * 日付をヘッダーに出して、テーブルを作成します。
    059 * ブレークカラムは、CSV形式で複数指定できます。その場合は、複数指定のカラムの
    060 * 合成された値で、キーブレイクの判定を行います。(簡単に言うとＯＲ判定になります。)
    061 * なお、ブレイクカラムを指定した場合は、自動的に、noDisplay 属性にその値をセット
    062 * します。
    063 *
    064 * @og.rev 5.4.0.0 (2011/10/01) 新規追加
    065 * @og.group 画面表示
    066 *
    067 * @version  4.0
    068 * @author       Kazuhiko Hasegawa
    069 * @since    JDK5.0,
    070 */
    071public class ViewForm_HTMLTimeTable extends ViewForm_HTMLTable {
    072        /** このプログラムのVERSION文字列を設定します。   {@value} */
    073        private static final String VERSION = "7.0.1.0 (2018/10/15)" ;
    074
    075        private int             intval          = 30;                   // 30分 単位に colspan を設定する。
    076        private int             minStTime       = 480;                  // 08:00 のこと。8H=480M
    077        private int             maxEdTime       = 1260;                 // 21:00 のこと。21H=1260M
    078
    079        // 6.4.1.1 (2016/01/16) dyClmNo → DY_CLMNO , keyClmNo → KEY_CLMNO , tmstClmNo → TMST_CLMNO , tmedClmNo → TMED_CLMNO , linkClmNo → LINK_CLMNO , clmCnt → CLM_CNT refactoring
    080        private static final int        DY_CLMNO                = 0;    // ヘッダー1：ただし、グループ化する場合は、外出し
    081        private static final int        KEY_CLMNO       = 1;    // ヘッダー2：ただし、グループ化する場合は、外出し
    082        // private static final int     bikoClmNo       = 2;    // ヘッダー3：(内部では使用していません。)
    083
    084        private static final int        TMST_CLMNO      = 3;    // 時間枠の開始時刻(含む)
    085        private static final int        TMED_CLMNO      = 4;    // 時間枠の終了時刻(含まない)
    086        private static final int        LINK_CLMNO      = 5;    // 時間枠に表示する予約情報(変更画面へのリンク)
    087
    088        private static final int        CLM_CNT         = 3;    // 決め打ち。今は、dyClm,keyClm の２つだけ表示
    089
    090        // 引数パース機能付き データが存在しない場合のリンクのベースを設定。
    091        private int             nullLinkClmNo   = -1 ;          // 固定ではなく可変の場合に利用するリンクカラムNo
    092
    093        private int             tdClassColumnNo = -1 ;          // 5.4.3.7 (2012/01/20) データを入れるTDタグにclass属性を付与する場合のカラムNo
    094
    095        private boolean isDyBreak               = true;         // 日付でブレイクするかどうかを指定
    096        private boolean isBookingMerge  ;                       // 5.4.4.2 (2012/02/03) ブッキングデータをマージするかどうかを指定
    097
    098        /**
    099         * デフォルトコンストラクター
    100         *
    101         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    102         */
    103        public ViewForm_HTMLTimeTable() { super(); }            // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    104
    105        /**
    106         * 内容をクリア(初期化)します。
    107         *
    108         * @og.rev 5.4.3.7 (2012/01/20) tdClassColumnNo 追加
    109         * @og.rev 5.4.4.2 (2012/02/03) isBookingMerge 追加
    110         *
    111         */
    112        @Override
    113        public void clear() {
    114                super.clear();
    115                intval          = 30;                   // 30分 単位に colspan を設定する。
    116                minStTime       = 480;                  // 08:00 のこと。8H=480M
    117                maxEdTime       = 1260;                 // 21:00 のこと。21H=1260M
    118
    119                nullLinkClmNo   = -1;                   // 固定ではなく可変の場合に利用するリンクカラム
    120                tdClassColumnNo = -1 ;                  // 5.4.3.7 (2012/01/20) データを入れるTDタグにclass属性を付与する場合のカラムNo
    121                isDyBreak               = true;                 // 日付でブレイクするかどうかを指定
    122                isBookingMerge  = false;                // 5.4.4.2 (2012/02/03) ブッキングデータをマージするかどうかを指定
    123        }
    124
    125        /**
    126         * DBTableModel から HTML文字列を作成して返します。
    127         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
    128         * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
    129         *
    130         * @og.rev 5.4.3.7 (2012/01/20) tdClassColumnNo 追加
    131         * @og.rev 5.4.4.2 (2012/02/03) isBookingMerge 追加
    132         * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
    133         *
    134         * @param  startNo        表示開始位置
    135         * @param  pageSize   表示件数
    136         *
    137         * @return      DBTableModelから作成された HTML文字列
    138         * @og.rtnNotNull
    139         */
    140        @Override
    141        public String create( final int startNo, final int pageSize )  {
    142                if( getRowCount() == 0 ) { return ""; } // 暫定処置
    143
    144                paramInit();
    145                headerLine       = null;
    146                final int lastNo = getLastNo( startNo, pageSize );
    147                final int hsc = getHeaderSkipCount();
    148                int hscCnt = 1;
    149
    150                final StringBuilder out = new StringBuilder( BUFFER_LARGE );
    151
    152                if( isDyBreak ) {
    153                        out.append( getRendererValue( 0,DY_CLMNO ) ).append( CR );
    154                        setColumnDisplay( DY_CLMNO,false );     // 日付ブレイクなら、setColumnDisplay をfalse にセット
    155                }
    156
    157                out.append( getHeader() )
    158                        .append("<tbody>").append( CR );
    159
    160                int bgClrCnt = 0;
    161
    162                final int maxColspan = (maxEdTime-minStTime)/intval ;           // この数が、TDの数になる。
    163                int rowColspan = 0;                                                                     // 5.5.0.3(2012/03/12) １行の累積TD数。最大は、maxColspan で、
    164                int stTime     = minStTime;
    165
    166                String backData    = "";        // 初期値。１回目にキーブレイクさせる。
    167
    168                final StringBuilder buf2 = new StringBuilder( BUFFER_MIDDLE );  // 6.1.0.0 (2014/12/26) refactoring
    169
    170                final List<String> dblBooking = new ArrayList<>();              // 重複データがあったときのデータ格納
    171                String nlVal     = null;                // 空リンクのベース値
    172                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
    173                String bkNlVal   = null;                // キーブレイク時の元の空リンクのベース値
    174                String bkDyVal   = "";                  // キーブレイク時の元の日付
    175                for( int row=startNo; row<lastNo; row++ ) {
    176                        // キーブレイクの判定
    177                        bkNlVal   = nlVal;
    178
    179                        nlVal  = nullLinkClmNo   < 0 ? null : getRendererValue( row,nullLinkClmNo );
    180                        final String tdCls  = tdClassColumnNo < 0 ? null : getValue( row,tdClassColumnNo );             // 5.4.3.7 (2012/01/20) tdClassColumnNo 追加
    181                        final String dyVal  = getValue( row,DY_CLMNO );                                                                                 // 日付項目の値
    182                        final String keyVal = getValue( row,KEY_CLMNO );                                                                                // キー項目の値
    183                        if( row==startNo ) { bkDyVal = dyVal; }         // 初期データをセット。
    184
    185                        String linkVal = getRendererValue( row,LINK_CLMNO );
    186
    187                        // キーブレイク判定。キーブレイクは、一番初めから来る。
    188                        if( !backData.equals( dyVal + keyVal ) ) {
    189                                backData = dyVal + keyVal;                      // null は来ないはず
    190
    191                                // minStTime < stTime の時だけ、処理を行う。(最初のキーブレイクは処理しないため)
    192                                if( minStTime < stTime ) {
    193                                        // まずは、前の td の残りを出力する。ここでは、キーブレイク前の値を使用する。
    194                                        if( stTime < maxEdTime ) {
    195                                                out.append("  ");               // td タグの出力前の段落
    196                                                // 残データの書き出しは、最大TD数-それまでにセットした数。
    197                                                final int td = maxColspan - rowColspan;         // 5.5.0.3(2012/03/12)
    198                                                appendTDTag( out , null , td ,  // 5.5.0.3(2012/03/12)
    199                                                                                makeLinkValue( bkNlVal , stTime , maxEdTime ) );
    200                                        }
    201                                        out.append("</tr>").append( CR );
    202                                }
    203                                stTime = minStTime;             // 初期化
    204
    205                                // データかぶりが発生したときの処理
    206                                if( !dblBooking.isEmpty() ) {
    207                                        for( final String bkdt : dblBooking ) {
    208                                                // 6.0.2.5 (2014/10/31) char を append する。
    209                                                out.append(" <tr").append( getBgColorCycleClass( bgClrCnt-1 ) ).append('>').append( CR )
    210                                                        .append( bkdt )
    211                                                        .append("</tr>").append( CR );
    212                                        }
    213                                        dblBooking.clear();
    214                                }
    215
    216                                // 日付ブレイク処理
    217                                if( isDyBreak && row > startNo && !bkDyVal.equals( dyVal ) ) {
    218                                        bkDyVal = dyVal;
    219                                        out.append(" <tr class=\"dummy\">");
    220                                        for( int column=0; column<CLM_CNT; column++ ) {
    221                                                if( isColumnDisplay( column ) ) {
    222//                                                      out.append("<td/>");
    223                                                        out.append("<td></td>");                        // 7.0.1.0 (2018/10/15)
    224                                                }
    225                                        }
    226                                        for( int i=0; i<maxColspan ; i++ ) {            // 5.5.0.3(2012/03/12) 空td の出力。TD の colspan の基準になる。
    227//                                              out.append("<td/>");
    228                                                out.append("<td></td>");                                // 7.0.1.0 (2018/10/15)
    229                                        }
    230                                        out.append("</tr>").append( CR )
    231                                                .append("</tbody>").append( CR )
    232                                                .append("</table>").append( CR )
    233                                                .append( getRendererValue( row,DY_CLMNO ) ).append( CR )
    234                                                .append( getHeader() )
    235                                                .append("<tbody>").append( CR );
    236                                        hscCnt = 1;
    237                                }
    238
    239                                // ヘッダー繰り返し属性( headerSkipCount )を採用
    240                                if( hsc > 0 && hscCnt % hsc == 0 ) {
    241                                        out.append( getHeadLine() );
    242                                        hscCnt = 1;
    243                                }
    244                                else {
    245                                        // 特殊処理：ここの処理では、一番最初も、実行されるので、＋＋しないように加工しておく。
    246                                        if( row > startNo ) { hscCnt ++ ; }
    247                                }
    248
    249                                // ここから、新しい行が始まる。
    250                                // 6.0.2.5 (2014/10/31) char を append する。
    251                                out.append(" <tr").append( getBgColorCycleClass( bgClrCnt++ ) ).append('>').append( CR );
    252                                rowColspan = 0 ;                // 5.5.0.3(2012/03/12) 初期化
    253
    254                                for( int column=0; column<CLM_CNT; column++ ) {
    255                                        if( isColumnDisplay( column ) ) {
    256                                                // ヘッダー部分に加工しやすいように、class 属性を与えておく。
    257                                                out.append("  <td class=\"" ).append( getColumnName( column ) ).append( "\">" )
    258                                                        .append( getValueLabel(row,column) )
    259                                                        .append("</td>").append( CR );
    260                                        }
    261                                }
    262                        }
    263
    264                        // 文字列型の時分情報を数字に変換する。
    265                        int clStTime = getStr2Time( getValue( row,TMST_CLMNO ) , -1 );
    266                        final boolean nullData = clStTime < 0 ;                                         // 開始時刻が        null の場合、-1 が返されるのでフラグをセットする。
    267                        if( clStTime < minStTime ) { clStTime = minStTime; }    // 最小値以下の場合は、最小値に合せる。
    268
    269                        int clEdTime = getStr2Time( getValue( row,TMED_CLMNO ) , maxEdTime );
    270                        if( clEdTime > maxEdTime ) { clEdTime = maxEdTime; }    // 最大値以上の場合は、最大値に合せる。
    271
    272                        if( clStTime == clEdTime ) { clEdTime = clEdTime + intval ; }   // 最初と最後が同じ場合は、intval分 進めておく。
    273
    274                        // 最初と最後が異なる場合は、間に空欄が入る。同じ場合は、連続なので、空欄は入らない。
    275                        if( stTime < clStTime ) {
    276                                out.append("  ");               // td タグの出力前の段落
    277
    278                                // 5.5.0.3(2012/03/12) 間に空欄が入る場合、その大きさが最小TD単位より大きければ、分割する。
    279                                // ただし、直前の TD 個数が、最小でない場合のみ。
    280                                final int td = (clStTime-stTime)/intval;                // 5.5.0.3(2012/03/12)
    281                                rowColspan += td;
    282                                appendTDTag( out , null , td ,          // 5.5.0.3(2012/03/12)
    283                                                                                makeLinkValue( nlVal , stTime , clStTime ) ).append( CR );
    284                        }
    285                        // 前のデータとかぶっている。つまり、ブッキングデータがある。
    286                        else if( stTime > clStTime ) {
    287                                // 5.4.4.2 (2012/02/03) ブッキングデータをマージする機能を追加
    288                                if( isBookingMerge ) {
    289                                        if( stTime < clEdTime ) {
    290                                                final int td = (clEdTime-stTime)/intval;                // 5.5.0.3(2012/03/12)
    291                                                rowColspan += td ;
    292                                                appendTDTag( out , tdCls , td , linkVal ).append( CR );
    293                                                stTime = clEdTime;
    294                                        }
    295                                        continue;
    296                                }
    297
    298                                buf2.setLength(0);              // 6.1.0.0 (2014/12/26) refactoring
    299                                buf2.append("  ");              // td タグの出力前の段落
    300                                for( int column=0; column<CLM_CNT; column++ ) {
    301                                        if( isColumnDisplay( column ) ) {
    302                                                // ヘッダー部分に加工しやすいように、class 属性を与えておく。
    303//                                              buf2.append("<td class=\"" ).append( getColumnName( column ) ).append( "\"/>" );
    304                                                buf2.append("<td class=\"" ).append( getColumnName( column ) ).append( "\"></td>" );    // 7.0.1.0 (2018/10/15)
    305                                        }
    306                                }
    307
    308                                // 5.4.3.7 (2012/01/20)
    309                                appendTDTag( buf2 , null  , (clStTime-minStTime)/intval );                              // 最初からデータまで
    310                                appendTDTag( buf2 , tdCls , (clEdTime-clStTime)/intval , linkVal );             // データ部: 5.4.3.7 (2012/01/20) td に class属性追加
    311                                appendTDTag( buf2 , null  , (maxEdTime-clEdTime)/intval );                              // データから最後まで
    312
    313                                dblBooking.add( buf2.toString() );
    314                                continue;
    315                        }
    316                        // 前も後ろも最小と最大になっているのは、予約レコードが無いため。
    317                        // stTime == clStTime のケース。nullData = true で、予約レコードが無いため。
    318                        else if( nullData ) {
    319                                linkVal = makeLinkValue( nlVal , minStTime , maxEdTime );
    320                        }
    321                        // 5.4.3.7 (2012/01/20) linkVal を共通に使用している箇所を修正
    322
    323                        out.append("  ");               // td タグの出力前の段落
    324                        final int td = (clEdTime-clStTime)/intval;              // 5.5.0.3(2012/03/12)
    325                        rowColspan += td ;
    326                        appendTDTag( out , tdCls , td , linkVal ).append( CR ); // 5.5.0.3(2012/03/12)
    327
    328                        stTime = clEdTime ;
    329                }
    330
    331                // 残処理：データが残っている場合は、書き出す必要がある。
    332                if( minStTime < stTime && stTime < maxEdTime ) {
    333                        out.append("  ");               // td タグの出力前の段落
    334                        // 残データの書き出しは、最大TD数-それまでにセットした数。
    335                        final int td = maxColspan - rowColspan;         // 5.5.0.3(2012/03/12)
    336                        appendTDTag( out , null , td ,          // 5.5.0.3(2012/03/12)
    337                                                                                makeLinkValue( nlVal , stTime , maxEdTime ) );
    338                }
    339                out.append("</tr>").append( CR );
    340
    341                // データかぶりが発生したときの処理
    342                if( !dblBooking.isEmpty() ) {
    343                        for( final String bkdt : dblBooking ) {
    344                                // 6.0.2.5 (2014/10/31) char を append する。
    345                                out.append(" <tr").append( getBgColorCycleClass( bgClrCnt-1 ) ).append('>').append( CR )
    346                                        .append( bkdt )
    347                                        .append("</tr>").append( CR );
    348                        }
    349                }
    350
    351                // カラムの結合があるため、td タグを個別に出力しておかないと、レイアウトがずれる。
    352                out.append(" <tr class=\"dummy\">");
    353                for( int column=0; column<CLM_CNT; column++ ) {
    354                        if( isColumnDisplay( column ) ) {
    355//                              out.append("<td/>");
    356                                out.append("<td></td>");                        // 7.0.1.0 (2018/10/15)
    357                        }
    358                }
    359                for( int i=0; i<maxColspan ; i++ ) {            // 5.5.0.3(2012/03/12) 空td の出力。TD の colspan の基準になる。
    360//                      out.append("<td/>");
    361                        out.append("<td></td>");                                // 7.0.1.0 (2018/10/15)
    362                }
    363                out.append("</tr>").append( CR )
    364                        .append("</tbody>").append( CR )
    365                        .append("</table>").append( CR )
    366                        .append( getScrollBarEndDiv() );                // 3.8.0.3 (2005/07/15)
    367                return out.toString();
    368        }
    369
    370        /**
    371         * パラメータ内容を初期化します。
    372         *
    373         * @og.rev 5.4.3.7 (2012/01/20) tdClassColumnNo 追加。intval の実値化
    374         * @og.rev 5.4.4.2 (2012/02/03) isBookingMerge 追加
    375         *
    376         */
    377        private void paramInit() {
    378                final String s_intval           = getParam( ViewTimeTableParam.TIME_INTERVAL    , null );
    379                final String s_minStTime        = getParam( ViewTimeTableParam.MIN_START_TIME   , null );
    380                final String s_maxEdTime        = getParam( ViewTimeTableParam.MAX_END_TIME             , null );
    381
    382                isDyBreak               = Boolean.valueOf( getParam( ViewTimeTableParam.USE_DY_BREAK, "true" ) );
    383
    384                // 5.4.4.2 (2012/02/03) ブッキングデータをマージするかどうかを指定
    385                isBookingMerge  = Boolean.valueOf( getParam( ViewTimeTableParam.USE_BOOKING_MERGE, "false" ) );
    386
    387                // nullリンクのカラム名指定。nullClm が優先順位が高い。
    388                final String s_nullClm  = getParam( ViewTimeTableParam.NULL_LINK_CLM_ID , null );
    389                if( s_nullClm != null ) {
    390                        nullLinkClmNo = getColumnNo( s_nullClm );
    391                }
    392
    393                // 5.4.3.7 (2012/01/20) データを入れるTDタグにclass属性を付与する場合のカラムNo
    394                final String s_tdClsClm = getParam( ViewTimeTableParam.TD_CLASS_COLUMN_ID       , null );
    395                if( s_tdClsClm != null ) {
    396                        tdClassColumnNo = getColumnNo( s_tdClsClm );
    397                }
    398
    399                if( s_intval != null ) {
    400                        intval = Integer.parseInt( s_intval ) ; // 5.4.3.7 (2012/01/20)
    401                }
    402                minStTime       = getStr2Time( s_minStTime      , minStTime );                  // 最小開始時刻。0800 なら、80。 30分=5 換算
    403                maxEdTime       = getStr2Time( s_maxEdTime      , maxEdTime );                  // 最大終了時刻。2100 なら、210。30分=5 換算
    404        }
    405
    406        /**
    407         * DBTableModel から テーブルのタグ文字列を作成して返します。
    408         *
    409         * @og.rev 5.4.3.7 (2012/01/20) colgroup は不要
    410         *
    411         * @return      テーブルのタグ文字列
    412         * @og.rtnNotNull
    413         */
    414        @Override
    415        protected String getTableHead() {
    416                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    417                        .append("<thead id=\"header\">").append( CR )
    418                        .append( getHeadLine() )
    419                        .append("</thead>").append( CR );
    420
    421                return buf.toString();
    422        }
    423
    424        /**
    425         * ヘッダー繰り返し部を、getTableHead()メソッドから分離。
    426         *
    427         * @og.rev 6.1.2.0 (2015/01/24) キャッシュを返すのを、#getHeadLine() に移動。
    428         *
    429         * @param       thTag タグの文字列
    430         *
    431         * @return      テーブルのタグ文字列
    432         * @og.rtnNotNull
    433         */
    434        @Override
    435        protected String getHeadLine( final String thTag ) {
    436
    437                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    438                        .append( "<tr class=\"row_h\" >" ).append( CR );
    439
    440                for( int column=0; column<CLM_CNT; column++ ) {
    441                        if( isColumnDisplay( column ) ) {
    442                                buf.append( thTag )
    443                                        .append(" class=\"" ).append( getColumnName( column ) ).append( "\">" )
    444                                        .append( getColumnLabel(column) )
    445                                        .append("</th>").append( CR );
    446                        }
    447                }
    448
    449                final String thTagTmp = thTag + " class=\"timeVar\" colspan=\"" + 60/intval + "\">" ;
    450
    451                for( int tm=minStTime; tm<maxEdTime; tm+=60 ) { // ヘッダーは、１時間単位とする。
    452                        buf.append( thTagTmp ).append( tm/60 ).append("</th>").append( CR );
    453                }
    454
    455                buf.append("</tr>").append( CR );
    456
    457                return buf.toString();                          // 6.1.2.0 (2015/01/24)
    458        }
    459
    460        /**
    461         * TDタグ文字列を簡易的に合成します。
    462         *
    463         * ここでは、主に、class 属性、colspan 属性を設定することを目的にしています。
    464         * colspan の値によって、動作を変化させています。
    465         *   0: タグそのものを生成しません。これは、第一引数をそのまま返します。
    466         *   1: colspan 属性を出力しません。(Default値なので)
    467         *   それ以外は、colspan に引数を設定します。
    468         * BODY 部も、無指定の場合は、/&gt; 止めのタグを出力します。
    469         *
    470         * 返り値の StringBuilder は、第一引数そのものを返します。よって、このメソッドに、
    471         * append を連結していくことも可能です。
    472         * (return値を使わなくても、第一引数の StringBuilder は変化しています。副作用というやつ。)
    473         *
    474         * @og.rev 5.4.3.7 (2012/01/20) tdタグ専用に変更。
    475         * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
    476         *
    477         * @param       buf             このStringBuilderに append します。
    478         * @param       cls             追加する class 属性
    479         * @param       colspan td , th タグ属性に追加する colspan値。
    480         *                     0 の場合は、タグ自体を出力しません。
    481         *                     1 の場合は、colspan を出力しません。
    482         * @param       body    タグの BODY 部に出力する内容(String可変長引数)０件の場合は、BODYなし
    483         *
    484         * @return      appendされたStringBuilder(第一引数と同じオブジェクト)
    485         * @og.rtnNotNull
    486         */
    487        private StringBuilder appendTDTag( final StringBuilder buf , final String cls , final int colspan , final String... body ) {
    488                // 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)
    489                if( colspan != 0 ) {
    490                        buf.append( "<td" );
    491                        // class 属性の追加
    492                        if( cls != null && !cls.isEmpty() ) {
    493                                buf.append( " class=\"" ).append( cls ).append( '"' );                  // 6.0.2.5 (2014/10/31) char を append する。
    494                        }
    495
    496                        // colspan 属性の追加
    497                        if( colspan > 1 ) {
    498                                buf.append( " colspan=\"" ).append( colspan ).append( '"' );    // 6.0.2.5 (2014/10/31) char を append する。
    499                        }
    500
    501                        // 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"／＞" 止めを、"＞" に変更します)。
    502                        if( body == null || body.length == 0 ) {                // 6.3.9.1 (2015/11/27) 可変長引数でもnullは来る。
    503//                              buf.append( " />" );
    504                                buf.append( " ></td>" );                                        // 7.0.1.0 (2018/10/15)
    505                        }
    506                        else {
    507                                buf.append( '>' );                                                      // 6.0.2.5 (2014/10/31) char を append する。
    508                                for( final String bd : body ) {
    509                                        buf.append( bd ).append( ' ' );                 // 6.3.9.1 (2015/11/27) スペースで連結追加
    510                                }
    511                                buf.append( "</td>" );
    512                        }
    513                }
    514
    515                return buf;
    516        }
    517
    518        /**
    519         * 時間文字列を数字に変換します。
    520         *
    521         * "0800" は、480に、"2100" は、1260 に変換します。
    522         *
    523         * @og.rev 5.4.3.7 (2012/01/20) 計算方法の変更
    524         *
    525         * @param       val     時間文字列の値(0800 など)
    526         * @param       defTm   引数の時間文字列が null の場合の初期値
    527         *
    528         * @return      時間文字列を数字に変換した結果( 80 など)
    529         */
    530        private int getStr2Time( final String val , final int defTm ) {
    531                // 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)
    532                final int rtn ;
    533                if( val == null || val.isEmpty() ) { rtn = defTm; }
    534                else if( val.length() == 4 ) {
    535                        rtn = Integer.parseInt( val.substring( 0,2 ) )*60 +
    536                                        Integer.parseInt( val.substring( 2 ) ) ;        // 5.4.3.7 (2012/01/20)
    537                }
    538                else {
    539                        final String errMsg = "時間引数は、４桁必要です。" +
    540                                                        "  val=[" + val + "]";
    541                        throw new HybsSystemException( errMsg );
    542                }
    543
    544                return rtn ;
    545        }
    546
    547        /**
    548         * 数字を時間文字列に変換します。
    549         *
    550         * 480は、"0800" に、"1260"は、2100 に変換します。
    551         *
    552         * @og.rev 5.4.3.7 (2012/01/20) 計算方法の変更
    553         *
    554         * @param       timeVal 引数の時間文字列が null の場合の初期値
    555         *
    556         * @return      数字を時間文字列に変換した結果( "0800" など)
    557         * @og.rtnNotNull
    558         */
    559        private String getInt2StrTime( final int timeVal ) {
    560                int tmpVal = timeVal;
    561                if(      tmpVal < minStTime ) { tmpVal = minStTime; }   // 最小値以下の場合は、最小値に合せる
    562                else if( tmpVal > maxEdTime ) { tmpVal = maxEdTime; }   // 最大値以上の場合は、最大値に合せる。
    563
    564                return String.valueOf(100+ tmpVal/60).substring(1) + String.valueOf(100+tmpVal%60).substring(1);
    565        }
    566
    567        // 引数に、 を追加する。
    568
    569        /**
    570         * リンク文字列をパースします。
    571         *
    572         * データの空欄にリンクを作成するときに、元となるリンク文字列の引数を設定します。
    573         * 引数は、&TMSTART=(stTime)&TMEND=(edTime) を追加するイメージです。
    574         * stTime、edTime は、それぞれ、($1)、($2) の変数が割り当てられます。
    575         * stTime、edTime は、#getInt2StrTime( int ) メソッドで変換した文字列を利用します。
    576         *
    577         * @param       lnkVal  リンクのベースとなる値
    578         * @param       stTime  開始時刻の数字表記
    579         * @param       edTime  終了時刻の数字表記
    580         *
    581         * @return      リンク文字列をパースした結果
    582         */
    583        private String makeLinkValue( final String lnkVal,final int stTime,final int edTime ) {
    584                // 6.3.9.0 (2015/11/06) Found 'DD'-anomaly for variable(PMD)
    585                return lnkVal == null ? ""
    586                                                          : lnkVal.replace( "%28%241%29",getInt2StrTime( stTime ) )
    587                                                                                .replace( "%28%242%29",getInt2StrTime( edTime ) ) ;
    588        }
    589
    590        /**
    591         * 表示項目の編集(並び替え)が可能かどうかを返します。
    592         *
    593         * @return      表示項目の編集(並び替え)が可能かどうか(false:不可能)
    594         */
    595        @Override
    596        public boolean isEditable() {
    597                return false;
    598        }
    599}