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 org.opengion.fukurou.system.OgBuilder ;                                  // 6.4.4.2 (2016/04/01)
019import org.opengion.fukurou.util.StringUtil;
020import org.opengion.hayabusa.common.HybsSystem;
021import org.opengion.hayabusa.common.HybsSystemException;
022import org.opengion.hayabusa.db.DBTableModel;
023import org.opengion.hayabusa.html.TableFormatter;
024import org.opengion.hayabusa.html.ViewGanttTableParam;
025
026import java.util.regex.Pattern;
027import java.util.regex.Matcher;
028
029import java.util.Locale;
030import java.util.TreeSet;
031import java.util.List;
032import java.util.Date;
033import java.util.Calendar;
034import java.text.SimpleDateFormat;
035import java.text.ParseException;
036
037/**
038 * ガントチャート(テーブル形式)を作成する、ガントチャート表示クラスです。
039 *
040 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
041 * 各HTMLのタグに必要な setter/getterメソッドのみ,追加定義しています。
042 *
043 * AbstractViewForm を継承している為,ロケールに応じたラベルを出力させる事が出来ます。
044 *
045 * @og.group 画面表示
046 *
047 * @version  4.0
048 * @author       Kazuhiko Hasegawa
049 * @since    JDK5.0,
050 */
051public class ViewForm_HTMLGanttTable extends ViewForm_HTMLTable {
052        /** このプログラムのVERSION文字列を設定します。   {@value} */
053        private static final String VERSION = "6.8.2.0 (2017/10/13)" ;
054
055        // 3.5.4.0 (2003/11/25) TableFormatter クラス追加
056        private TableFormatter          headerFormat    ;
057        private TableFormatter[]        bodyFormats             ;
058        private TableFormatter          footerFormat    ;
059        private int                                     bodyFormatsCount;
060
061        // 繰り返すTD用 3.5.6.0 (2004/06/18) 廃止
062        private TableFormatter[]        itdFormats              ;               // 追加
063
064        private String ganttHeadLine            ;
065        private int[]  groupCols                        ;
066        private int    posDuration                      = -1;
067        private int    posDystart                       = -1;
068
069        private String formatDystart            = ViewGanttTableParam.DYSTART_FORMAT_VALUE;
070        private double  minDuration                     = StringUtil.parseDouble( ViewGanttTableParam.MIN_DURATION_VALUE ) ;    // 3.5.5.8 (2004/05/20)
071        private double  headerDuration          = minDuration ; // 3.5.5.8 (2004/05/20)
072
073        // 3.5.4.6 (2004/01/30) 初期値変更
074        private static final int BODYFORMAT_MAX_COUNT = 10;
075
076        // <(td|th)(.*)>(.*)</(td|th)>を確認する
077        private static final Pattern TDTH_PTN =                                                                 // 6.4.1.1 (2016/01/16) cpTdTh  → TDTH_PTN  refactoring
078                Pattern.compile("\\<(td|th)([^\\>]*)\\>(.*)\\</(td|th)\\>"
079                                , Pattern.DOTALL + Pattern.CASE_INSENSITIVE);
080        // 3.5.5.9 (2004/06/07)
081        private int maxDayCnt                           ;
082        private String headerLocale                     ;
083        private String[] headDays                       ;
084        private int headDaysCnt                         ;
085
086        // 3.5.6.3 (2004/07/12) 行チェックによる編集用の hidden
087        private static final String CHECK_ROW =
088                "<input type=\"hidden\" name=\"" + HybsSystem.ROW_SEL_KEY + "\" value=\"";
089
090        // 3.6.1.0 (2005/01/05) 開始日付けと終了日付けの指定
091        private boolean useSeqDay               ;
092        private String startDay                 ;
093        private String endDay                   ;
094
095        private boolean useItd                  ;               // 5.0.0.3 (2009/09/22)
096
097        /**
098         * デフォルトコンストラクター
099         *
100         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
101         */
102        public ViewForm_HTMLGanttTable() { super(); }           // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
103
104        /**
105         * 内容をクリア(初期化)します。
106         *
107         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
108         * @og.rev 3.5.0.0 (2003/09/17) Noカラムに、表示を全て消せるように、class 属性を追加。
109         * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
110         * @og.rev 3.5.5.8 (2004/05/20) minDuration ,  headerDuration 追加。 不要な変数削除
111         * @og.rev 3.5.6.0 (2004/06/18) ithFormat ,  itdFormat 属性削除、itdFormats属性を追加
112         * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加
113         * @og.rev 5.0.0.3 (2009/09/22) itdタグの有無でcolspan対策のtdの出力個数を調整
114         */
115        @Override
116        public void clear() {
117                super.clear();
118
119                headerFormat            = null;
120                bodyFormats                     = null;
121                footerFormat            = null;
122                bodyFormatsCount        = 0;
123
124                ganttHeadLine           = null;
125                itdFormats                      = null;         // 3.5.6.0 (2004/06/18)
126                groupCols                       = null;
127                posDuration                     = -1  ;
128                posDystart                      = -1;
129                formatDystart           = ViewGanttTableParam.DYSTART_FORMAT_VALUE;
130                minDuration                     = StringUtil.parseDouble( ViewGanttTableParam.MIN_DURATION_VALUE ) ;    // 3.5.5.8 (2004/05/20)
131                headerDuration          = minDuration ;         // 3.5.5.8 (2004/05/20)
132
133                maxDayCnt                       = 0;    // 3.5.5.9 (2004/06/07)
134                headerLocale            = null; // 3.5.5.9 (2004/06/07)
135                headDays                        = null; // 3.5.5.9 (2004/06/07)
136                headDaysCnt                     = 0;    // 3.5.5.9 (2004/06/07)
137
138                useSeqDay                       = false;        // 3.6.1.0 (2005/01/05)
139                startDay                        = null; // 3.6.1.0 (2005/01/05)
140                endDay                          = null; // 3.6.1.0 (2005/01/05)
141
142                useItd                  = false; // 5.0.0.3 (2009/09/22)
143        }
144
145        /**
146         * DBTableModel から HTML文字列を作成して返します。
147         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
148         * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
149         *
150         * @og.rev 3.5.0.0 (2003/09/17) BODY要素の noClass 属性を追加。
151         * @og.rev 3.5.0.0 (2003/09/17) &lt;tr&gt;属性は、元のフォーマットのまま使用します。
152         * @og.rev 3.5.2.0 (2003/10/20) ヘッダー繰り返し属性( headerSkipCount )を採用
153         * @og.rev 3.5.3.1 (2003/10/31) skip属性を採用。headerLine のキャッシュクリア
154         * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
155         * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応
156         * @og.rev 3.5.5.9 (2004/06/07) IEの colspan が上手く動かない対策。
157         * @og.rev 3.5.5.9 (2004/06/07) [#カラム名] , [$カラム名] に対応
158         * @og.rev 3.5.6.0 (2004/06/18) itdFormat を、BODY毎のFormatを使用するように修正
159         * @og.rev 3.5.6.0 (2004/06/18) '!' 値のみ 追加 既存の '$' は、レンデラー
160         * @og.rev 3.5.6.3 (2004/07/12) 行チェックによる編集が出来るように機能を追加
161         * @og.rev 3.5.6.4 (2004/07/16) ヘッダーとボディー部をJavaScriptで分離
162         * @og.rev 3.6.1.0 (2005/01/05) 行チェックによる編集が、検索即登録時も可能なようにします。
163         * @og.rev 4.0.0.0 (2005/01/31) 新規作成(getColumnClassName ⇒ getColumnDbType)
164         * @og.rev 3.7.0.1 (2005/01/31) E の colspan バグ対応で入れた最終行の 空タグを消す為の修正
165         * @og.rev 4.3.1.0 (2008/09/08) フォーマットが設定されていない場合のエラー追加
166         * @og.rev 4.3.7.4 (2009/07/01) tbodyタグの入れ子を解消(FireFox対応)
167         * @og.rev 5.0.0.3 (2009/09/22) itdタグの有無でcolspan対策のtdの出力個数を調整
168         * @og.rev 5.5.4.4 (2012/07/20) 二重チェック状態になってしまう対策
169         * @og.rev 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応
170         * @og.rev 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
171         * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。
172         * @og.rev 6.4.5.0 (2016/04/08) メソッド変更( getColumnDbType(int) → getClassName(int) )
173         * @og.rev 6.8.1.1 (2017/07/22) ckboxTD変数は、&lt;td&gt; から &lt;td に変更します(タグの最後が記述されていない状態でもらう)。
174         * @og.rev 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。
175         *
176         * @param  stNo   表示開始位置
177         * @param  pgSize   表示件数
178         *
179         * @return      DBTableModelから作成された HTML文字列
180         * @og.rtnNotNull
181         */
182        @Override
183        public String create( final int stNo, final int pgSize )  {
184                // ガントは、キーブレイクがあるため、全件表示します。
185                final int pageSize = getRowCount() ;
186                if( pageSize == 0 ) { return ""; }      // 暫定処置
187                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
188
189                // 4.3.1.0 (2008/09/08)
190                if( headerFormat == null ) {
191                        final String errMsg = "ViewTagで canUseFormat() = true の場合、Formatter は必須です。";
192                        throw new HybsSystemException( errMsg );
193                }
194
195                headerLine       = null;                // 3.5.3.1 (2003/10/31) キャッシュクリア
196
197                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
198                // ガントは、キーブレイクがあるため、全件表示します。
199                final int startNo  = 0;
200                final int lastNo = getLastNo( startNo, pageSize );
201                final int blc = getBackLinkCount();
202                final int hsc = getHeaderSkipCount();           // 3.5.2.0 (2003/10/20)
203                int hscCnt = 1;                                         // 3.5.2.0 (2003/10/20)
204
205                // このビューの特有な属性を初期化
206                paramInit();
207
208                final StringBuilder out = new StringBuilder( BUFFER_LARGE );
209
210                out.append( getCountForm( startNo,pageSize ) );
211
212                if( posDuration < 0 ) { ganttHeadLine = getGanttHeadNoDuration(startNo, lastNo); }
213                else {                                  ganttHeadLine = getGanttHead(startNo, lastNo); }
214
215                out.append( getHeader() );
216
217                if( bodyFormatsCount == 0 ) {
218                        bodyFormats[0] = headerFormat ;
219                        bodyFormatsCount ++ ;
220                }
221                else {
222                        for( int i=0; i<bodyFormatsCount; i++ ) {
223                                bodyFormats[i].makeFormat( getDBTableModel() );
224                                // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応
225                                setFormatNoDisplay( bodyFormats[i] );
226
227                                if( itdFormats[i] != null ) {
228                                        itdFormats[i].makeFormat( getDBTableModel() );
229                                        // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応
230                                        setFormatNoDisplay( itdFormats[i] );
231                                }
232                        }
233                }
234
235                String[] astrOldGroupKeys = new String[groupCols.length];
236                for( int nIndex =0; nIndex<astrOldGroupKeys.length; nIndex++ ) {
237                        astrOldGroupKeys[nIndex] = "";
238                }
239
240                final StringBuilder bodyBuf = new StringBuilder( BUFFER_LARGE );                // 6.1.0.0 (2014/12/26) refactoring
241                final StringBuilder itdBuf  = new StringBuilder( BUFFER_LARGE );                // 6.1.0.0 (2014/12/26) refactoring
242                boolean checked = false;                // 3.5.6.3 (2004/07/12)
243                int bgClrCnt = 0;
244                TableFormatter itdFormat = null;
245                for( int row=startNo; row<lastNo; row++ ) {
246                        // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
247                        boolean outputCheck = true;                     // 5.5.4.4. (2012/07/20) チェックボックス出力判定
248                        // ガントでは、データのスキップは行いません。
249
250                        if( !isSameGroup(row, astrOldGroupKeys) ) {
251                                // 3.5.6.3 (2004/07/12) キーブレイク時にチェック行かどうかを記録
252                                checked = getDBTableModel().isRowChecked( row );
253
254                                if( row != startNo ) {
255                                        // ヘッダー日付けが残っているのに、データがなくなった場合
256                                        while( headDays != null && headDaysCnt < headDays.length ) {
257                                                itdBuf.append( "<td></td>" );
258                                                headDaysCnt++;
259                                        }
260                                        out.append(StringUtil.replace(bodyBuf.toString(), TableFormatter.HYBS_ITD_MARKER, itdBuf.toString()));
261                                        itdBuf.setLength(0);            // 6.1.0.0 (2014/12/26) refactoring
262                                        headDaysCnt     = 0;
263                                }
264
265                                bodyBuf.setLength(0);
266
267                                // カラムのグループがブレイクするまで、BodyFormatは同一である前提
268                                for( int i=0; i<bodyFormatsCount; i++ ) {
269                                        final TableFormatter bodyFormat = bodyFormats[i];
270                                        if( ! bodyFormat.isUse( row,getDBTableModel() ) ) { continue; }         // 3.5.4.0 (2003/11/25)
271                                        itdFormat = itdFormats[i];              //
272
273                                        bodyBuf.append("<tbody")
274                                                .append( getBgColorCycleClass( bgClrCnt++ ) )
275                                                .append('>')                                                                                                    // 6.0.2.5 (2014/10/31) char を append する。
276                                                .append( bodyFormat.getTrTag() );
277
278                                        // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加
279                                        if( isNumberDisplay() ) {
280                                                final String ckboxTD = "<td" + bodyFormat.getRowspan();                 // 6.8.1.1 (2017/07/22)
281                                                bodyBuf.append( makeCheckbox( ckboxTD,row,blc,true ) );                 // 6.8.2.0 (2017/10/13)
282                                                outputCheck = false; // 5.5.4.4. (2012/07/20)
283                                        }
284
285                                        int cl = 0;
286                                        for( ; cl<bodyFormat.getLocationSize(); cl++ ) {
287                                                String fmt = bodyFormat.getFormat(cl);
288                                                final int loc = bodyFormat.getLocation(cl);     // 3.5.5.0 (2004/03/12)
289                                                // 6.1.0.0 (2014/12/26) refactoring
290                                                if( ! bodyFormat.isNoClass() && loc >= 0 ) {
291                                                        // 6.4.3.4 (2016/03/11) tdに、[カラム]が無いケースで、次の[カラム]のクラス属性が、前方すべてのtdにセットされてしまう対応。
292                                                        final int idx = fmt.lastIndexOf( "<td" );
293                                                        if( idx >= 0 ) {        // matchしてるので、あるはず
294                                                                final String tdclass = " class=\"" + getClassName(loc) + "\" ";         // 6.4.5.0 (2016/04/08)
295                                                                fmt = fmt.substring( 0,idx+3 ) + tdclass + fmt.substring( idx+3 ) ;
296                                                        }
297                                                }
298                                                bodyBuf.append( fmt );                                  // 3.5.0.0 (2003/09/17)
299                                                // 3.5.5.9 (2004/06/07) #,$ 対応
300                                                if( loc >= 0 ) {
301                                                        // 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。
302                                                        bodyBuf.append( getTypeCaseValue( bodyFormat.getType(cl),row,loc ) );
303                                                }
304                                                else {
305                                                        bodyBuf.append( bodyFormat.getSystemFormat(row,loc) );
306                                                }
307                                        }
308                                        bodyBuf.append( bodyFormat.getFormat(cl) )
309                                                .append("</tbody>").append( CR );
310                                }
311
312                                // 3.5.2.0 (2003/10/20) ヘッダー繰り返し属性( headerSkipCount )を採用
313                                if( hsc > 0 && hscCnt % hsc == 0 ) {
314                                        bodyBuf.append("<tbody class=\"row_h\" >")
315                                                .append( getHeadLine() )
316                                                .append("</tbody>");
317                                        hscCnt = 1;
318                                }
319                                else {
320                                        hscCnt ++ ;
321                                }
322                        }
323
324                        // 3.5.6.3 (2004/07/12) キーブレイク時のチェック行の状態を繰り返し行に反映
325                        // 3.6.1.0 (2005/01/05) さらに、外部でチェックを入れている場合は、個別対応する。
326                        if( (checked || isChecked( row )) && outputCheck ) {     // 5.5.4.4. (2012/07/20)
327                                getDBTableModel().setRowWritable( row,true );
328                                out.append( CHECK_ROW )
329                                        .append( row )
330                                        .append( "\" />" );
331                        }
332
333                        formatItd(row, itdFormat, itdBuf);
334                }
335
336                // 残ったデータを出力
337                if( itdBuf.length() > 0 ) {
338                        // ヘッダー日付けが残っているのに、データがなくなった場合
339                        while( headDays != null && headDaysCnt < headDays.length ) {
340                                itdBuf.append( "<td></td>" );
341                                headDaysCnt++;
342                        }
343                        out.append(StringUtil.replace(bodyBuf.toString(), TableFormatter.HYBS_ITD_MARKER, itdBuf.toString()));
344                }
345
346                // 3.5.5.9 (2004/06/07) IEの colspan が上手く動かない対策。
347                // minDuration が、1.0 以下の場合のみ実行
348                if( minDuration < 1.0d ) {
349                        // int tdCount = (int)Math.round( maxDayCnt / minDuration );
350                        // 5.0.0.3 (2009/09/22) itdタグの有無でtdCountを変える。
351                        final int tdCount = useItd ? (int) Math.round( maxDayCnt / minDuration ) : headerFormat.getLocationSize();
352
353                        // 3.7.0.1 (2005/01/31) E の colspan バグ対応で入れた最終行の 空タグを消す為の修正
354                        out.append("<tbody><tr class=\"dummy\">").append( CR );
355                        for( int i=0; i<tdCount; i++ ) {
356                                out.append( "<td/>" );
357                        }
358                        out.append("</tr></tbody>").append( CR );
359                }
360
361                if( footerFormat != null ) {
362                        // 6.3.9.0 (2015/11/06) 引数にTableFormatterを渡して、処理の共有化を図る。
363                        out.append( getTableFoot( footerFormat ) );
364                }
365
366                out.append("</table>").append( CR )
367                        .append( getScrollBarEndDiv() );        // 3.8.0.3 (2005/07/15)
368
369                return out.toString();
370        }
371
372        /**
373         * このビーに対する特別な初期化を行う。
374         *
375         * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
376         * @og.rev 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更
377         * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加
378         */
379        private void paramInit() {
380
381                final String strGroupCols       = getParam( ViewGanttTableParam.GROUP_COLUMNS_KEY       ,ViewGanttTableParam.GROUP_COLUMNS_VALUE        );
382                final String strColDuration     = getParam( ViewGanttTableParam.DURATION_COLUMN_KEY     ,null                                   );
383                final String strColDystart      = getParam( ViewGanttTableParam.DYSTART_COLUMN_KEY      ,ViewGanttTableParam.DYSTART_COLUMN_VALUE       );
384                final String strMinDuration     = getParam( ViewGanttTableParam.MIN_DURATION_KEY        ,ViewGanttTableParam.MIN_DURATION_VALUE         );
385
386                formatDystart   = getParam( ViewGanttTableParam.DYSTART_FORMAT_KEY      ,ViewGanttTableParam.DYSTART_FORMAT_VALUE       );
387                headerLocale    = getParam( ViewGanttTableParam.HEADER_LOCALE_KEY       ,ViewGanttTableParam.HEADER_LOCALE_VALUE        );
388                startDay                = getParam( ViewGanttTableParam.START_DAY_KEY           ,null                                   );      // 3.6.1.0 (2005/01/05)
389                endDay                  = getParam( ViewGanttTableParam.END_DAY_KEY                     ,null                                   );      // 3.6.1.0 (2005/01/05)
390
391                final String seqDay     = getParam( ViewGanttTableParam.USE_SEQ_DAY_KEY  ,ViewGanttTableParam.USE_SEQ_DAY_VALUE  );     // 3.6.1.0 (2005/01/05)
392                useSeqDay       = Boolean.parseBoolean( seqDay );               // 6.1.0.0 (2014/12/26) refactoring
393
394                final DBTableModel table = getDBTableModel();
395
396                // 3.5.5.9 (2004/06/07) durationColumn を指定しない場合の処理を追加
397                if( strColDuration != null ) {
398                        posDuration = table.getColumnNo( strColDuration );
399                }
400
401                posDystart  = table.getColumnNo( strColDystart );
402
403                final String[] groupKeys = StringUtil.csv2Array(strGroupCols);
404                groupCols = new int[groupKeys.length];
405                for( int nIndex=0; nIndex<groupCols.length ; nIndex++ ) {
406                        groupCols[nIndex] = table.getColumnNo( groupKeys[nIndex] );
407                }
408
409                minDuration = StringUtil.parseDouble( strMinDuration );
410                if( minDuration <= 0.0d ) {
411                        final String errMsg = "最小期間単位(minDuration)が、0 かそれ以下です。";
412                        throw new HybsSystemException( errMsg );
413                }
414
415                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
416                final String strHeadDuration= getParam( ViewGanttTableParam.HEADER_DURATION_KEY ,strMinDuration                 );
417                headerDuration = StringUtil.parseDouble( strHeadDuration );
418                if( headerDuration <= 0.0d ) {
419                        final String errMsg = "ヘッダーの表示期間(headerDuration)が、0 かそれ以下です。";
420                        throw new HybsSystemException( errMsg );
421                }
422
423                // 3.5.5.9 (2004/06/07) エラーチェックの強化
424                // 4.0.0 (2005/01/31) Equality checks with floating point numbers can lead to unexpected behavior.
425                if( posDuration < 0 && (
426                                Double.compare( minDuration,1.0d ) != 0 ||
427                                Double.compare( headerDuration,1.0d ) != 0 ) ) {
428
429                        final String errMsg = "期間カラム(durationColumn)を指定しない場合は、"
430                                        + "最小期間単位(minDuration)および、"
431                                        + "ヘッダーの表示期間(headerDuration)を '1' 以外に設定できません。";
432                        throw new HybsSystemException( errMsg );
433                }
434        }
435
436        /**
437         * 上下行のデータが同じグルプかどうかをチェックする。
438         *
439         * @param   nRowIndex テーブルモデルの行番号
440         * @param   astrOldValues 古いグループデータ配列
441         *
442         * @return  使用可能(true)/ 使用不可能 (false)
443         */
444        private boolean isSameGroup(final int nRowIndex, final String[] astrOldValues) {
445                boolean bRet = groupCols.length > 0 ;
446                if( bRet ) {
447                        for( int nIndex=0; bRet && nIndex<groupCols.length ; nIndex++ ) {
448                                bRet = astrOldValues[nIndex].equals( getValue( nRowIndex, groupCols[nIndex] ) );
449                        }
450
451                        // 不一致時に astrOldValues に 新しい値を設定しておきます。
452                        if( !bRet ) {
453                                for( int nIndex=0; nIndex<groupCols.length; nIndex++ ) {
454                                        astrOldValues[nIndex] = getValue(nRowIndex, groupCols[nIndex]);
455                                }
456                        }
457                }
458
459                return bRet;
460        }
461
462        /**
463         * DBTableModel から テーブルのタグ文字列を作成して返します。
464         *
465         * @og.rev 3.5.0.0 (2003/09/17) &lt;tr&gt;属性は、元のフォーマットのまま使用します。
466         * @og.rev 3.5.1.0 (2003/10/03) Noカラムに、numberType 属性を追加
467         * @og.rev 3.5.2.0 (2003/10/20) ヘッダー繰り返し部をgetHeadLine()へ移動
468         * @og.rev 3.5.3.1 (2003/10/31) VERCHAR2 を VARCHAR2 に修正。
469         * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
470         * @og.rev 3.5.6.5 (2004/08/09) thead に、id="header" を追加
471         * @og.rev 4.0.0.0 (2005/01/31) DBColumn の 属性(CLS_NM)から、DBTYPEに変更
472         * @og.rev 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応
473         * @og.rev 5.9.1.2 (2015/10/23) 自己終了警告対応
474         * @og.rev 6.4.4.1 (2016/03/18) NUMBER_DISPLAYを、static final 定数化します。
475         * @og.rev 6.4.4.2 (2016/04/01) StringBuilderの代わりに、OgBuilderを使用する。
476         * @og.rev 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄)
477         * @og.rev 6.4.9.1 (2016/08/05) colgroupのHTML5対応(No欄)時の対応ミス修正
478         * @og.rev 6.8.1.0 (2017/07/14) HTML5対応ヘッダー出力設定時に、ブラウザを互換設定したときの対応。
479         * @og.rev 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。
480         *
481         * @return      テーブルのタグ文字列
482         * @og.rtnNotNull
483         */
484        @Override
485        protected String getTableHead() {
486                headerFormat.makeFormat( getDBTableModel() );
487                // 6.2.0.0 (2015/02/27) フォーマット系の noDisplay 対応
488                setFormatNoDisplay( headerFormat );
489
490                // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄)
491                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
492
493                if( isNumberDisplay() ) {
494                        // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄)
495                                buf.append( NUMBER_DISPLAY );           // 6.8.1.0 (2017/07/14) HTML5ネイティブ時でも、出力します。
496                        // 6.8.1.0 (2017/07/14) HTML5対応ヘッダー出力設定時に、ブラウザを互換設定したときの対応。
497                        // 6.8.2.0 (2017/10/13) makeNthChildの廃止と、makeCheckboxで、個別にclass指定するように変更。
498        //              if( !useIE7Header ) { 
499        //                      buf.append( "<style type=\"text/css\">" ).append( CR );
500        //                      makeNthChild( buf,2,"BIT" );
501        //                      makeNthChild( buf,3,"S9"  );
502        //                      buf.append( "</style>" ).append( CR );          // 6.4.9.1 (2016/08/05)
503        //              }
504                }
505
506                // 6.4.9.0 (2016/07/23) colgroupのHTML5対応(No欄)の関係で、修正します。
507                return new OgBuilder()
508                                        .appendIf( isNumberDisplay() , buf.toString() )
509                                        .appendCR( "<thead id=\"header\">" )                            // 3.5.6.5 (2004/08/09)
510                                        .append(   getHeadLine() )
511                                        .appendCR( "</thead>" )
512                                        .toString();
513        }
514
515        /**
516         * ヘッダー繰り返し部を、getTableHead()メソッドから分離。
517         *
518         * @og.rev 6.1.2.0 (2015/01/24) キャッシュを返すのを、#getHeadLine() に移動。
519         *
520         * @return      テーブルのタグ文字列
521         * @og.rtnNotNull
522         */
523        @Override
524        protected String getHeadLine() {
525                if( headerLine == null ) {                                      // キャッシュになければ、設定する。
526                        headerLine = getHeadLine( "<th" + headerFormat.getRowspan() ) ;
527                }
528
529                return headerLine ;
530        }
531
532        /**
533         * ヘッダー繰り返し部を、getTableHead()メソッドから分離。
534         *
535         * @og.rev 3.5.2.0 (2003/10/20) 新規作成
536         * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
537         * @og.rev 3.5.4.3 (2004/01/05) useCheckControl 属性の機能を追加
538         * @og.rev 3.5.4.6 (2004/01/30) numberType="none" 時の処理を追加(Noラベルを出さない)
539         * @og.rev 3.5.4.7 (2004/02/06) ヘッダーにソート機能用のリンクを追加します。
540         * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応
541         * @og.rev 3.7.0.1 (2005/01/31) 全件チェックコントロール処理変更
542         * @og.rev 5.0.0.3 (2009/09/22) itdの有無を取得します。
543         * @og.rev 6.1.2.0 (2015/01/24) キャッシュを返すのを、#getHeadLine() に移動。
544         *
545         * @param       thTag タグの文字列
546         *
547         * @return      テーブルのタグ文字列
548         * @og.rtnNotNull
549         */
550        @Override
551        protected String getHeadLine( final String thTag ) {
552
553                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
554                        .append( headerFormat.getTrTag() ).append( CR );
555
556                // 3.5.5.0 (2004/03/12) No 欄そのものの作成判断追加
557                if( isNumberDisplay() ) {
558                        // 6.1.2.0 (2015/01/24) thTag に、headerFormat.getRowspan() を含ませて受け取る。
559                        // 3.5.4.3 (2004/01/05) 追加分
560                        if( isUseCheckControl() && "checkbox".equals( getSelectedType() ) ) {
561                                buf.append( thTag ).append( "></th>" )
562                                        .append( thTag ).append( '>' ).append( getAllCheckControl() ).append( "</th>" )         // 6.0.2.5 (2014/10/31) char を append する。
563                                        .append( thTag ).append( '>' ).append( getNumberHeader()    ).append( "</th>" );        // 6.0.2.5 (2014/10/31) char を append する。
564                        }
565                        else {
566                                buf.append( thTag ).append(" colspan=\"3\">").append( getNumberHeader() ).append( "</th>" );    // 6.0.2.5 (2014/10/31) char を append する。
567                        }
568                }
569
570                int cl = 0;
571                for( ; cl<headerFormat.getLocationSize(); cl++ ) {
572                        buf.append( headerFormat.getFormat(cl) );
573                        final int loc = headerFormat.getLocation(cl);
574                        if( loc >= 0 ) { buf.append( getSortedColumnLabel(loc) ); }
575                }
576                buf.append( headerFormat.getFormat(cl) ).append( CR );
577
578                // 5.0.0.3 (2009/09/22) ITD_MARKERの条件判断追加
579                if( buf.indexOf( TableFormatter.HYBS_ITD_MARKER ) >= 0 ){
580                        useItd = true;
581                }
582
583                return StringUtil.replace( buf.toString(), TableFormatter.HYBS_ITD_MARKER, ganttHeadLine );
584        }
585
586        /**
587         * ガントチャートヘッダー繰り返し部を、getTableHead()メソッドから分離。
588         * このメソッドは、durationColumn を利用して、連続日付けデータを作成します。
589         * データは、開始日と期間データを持ち、ヘッダーは連続日付けになります。
590         * ヘッダーの期間(headerDuration)とデータの最小期間(minDuration)が異なる為、
591         * 最初の行データより、最終日を求め、headerDuration で割り算した個数の連続日数を
592         * 表示させています。
593         *
594         * @og.rev 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更
595         * @og.rev 3.5.6.0 (2004/06/18) ithFormat 変数削除
596         *
597         * @param       startNo 開始行番号
598         * @param       lastNo  最終行番号
599         *
600         * @return      テーブルのタグ文字列
601         * @og.rtnNotNull
602         */
603        private String getGanttHead(final int startNo, final int lastNo) {
604                String[] astrOldGroupKeys = new String[groupCols.length];
605                for( int nIndex =0; nIndex<astrOldGroupKeys.length; nIndex++ ) {
606                        astrOldGroupKeys[nIndex] = "";
607                }
608
609                // 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更
610                final Locale local = new Locale( headerLocale );
611                final SimpleDateFormat fmtDate = new SimpleDateFormat( formatDystart,local );
612
613                double nSumDuration = 0.0d ;
614                Date dFirst = null ;
615
616                for( int nRowUp=startNo; nRowUp<lastNo; nRowUp++ ) {
617                        // ガントでは、データのスキップは行いません。
618
619                        // 最初の行 または、ブレイクするまで
620                        if( isSameGroup(nRowUp, astrOldGroupKeys) || nRowUp == startNo ) {
621                                nSumDuration += StringUtil.parseDouble( getValue(nRowUp, posDuration) );
622                                try {
623                                        if( dFirst == null ) {
624                                                dFirst = fmtDate.parse(getValue(nRowUp, posDystart));
625                                        }
626                                }
627                                catch( final ParseException ex) {
628                                        final String errMsg = "DYSTARTに指定した日付形式と異なるデータが存在しています。"
629                                                        + "[" + getValue(nRowUp  , posDystart) + "] => ["
630                                                        + fmtDate.toPattern() + "]" ;
631                                        throw new HybsSystemException( errMsg,ex );
632                                }
633                        }
634                        else { break; }
635                }
636
637                String thVal    = ""     ;              // <td ・・・> の <td 以下の ・・・部分の属性文字列。
638                String ymdForm  = "MM/dd" ;             // td タグの BODY 部 の 日付けフォーマット
639
640                if( headerFormat != null ) {
641                        final String format = headerFormat.getItdBody().trim() ;
642                        final Matcher matcher = TDTH_PTN.matcher(format);
643                        if( matcher.find() ) {
644                                thVal   = matcher.group(2);
645                                ymdForm = matcher.group(3);
646                        }
647                }
648
649                try {
650                        fmtDate.applyPattern(ymdForm);
651                }
652                catch( final IllegalArgumentException eArg ) {
653                        final String errMsg = "theadの内のitdの内側に指定された日付の形式が不正です。(" + ymdForm + ")";
654                        throw new HybsSystemException( errMsg,eArg );
655                }
656
657                final int colspan = (int)Math.round( headerDuration / minDuration );
658                final String th ;
659                if( colspan == 1 ) { th = "<th " + thVal + ">"; }
660                else { th = "<th colspan=\"" + colspan + "\" " + thVal + ">" ; }
661
662                final Calendar cal = Calendar.getInstance() ;
663                cal.setTime( dFirst );
664
665                maxDayCnt = (int)Math.round(nSumDuration / headerDuration) ;            // 3.5.5.9 (2004/06/07)
666                int addDate ;
667                int field   ;
668                if( headerDuration >= 1.0d ) {
669                        addDate = (int)Math.round( headerDuration );
670                        field   = Calendar.DATE ;
671                }
672                else {
673                        addDate = (int)Math.round( 24.0d * headerDuration );
674                        field   = Calendar.HOUR_OF_DAY ;
675                }
676
677                // 端数を指定すると、積算誤差がでます。
678                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
679
680                for( int nIndex=0; nIndex<maxDayCnt; nIndex++ ) {
681                        buf.append( th )
682                                .append( fmtDate.format( cal.getTime() ) )
683                                .append( "</th>" );
684                        cal.add( field ,addDate );
685                }
686
687                return buf.toString();
688        }
689
690        /**
691         * ガントチャートヘッダー繰り返し部を、getTableHead()メソッドから分離。
692         * このメソッドは、durationColumn が指定されていない場合の処理を行います。
693         * データは、すべての行に関して、同じ日付けのデータとして扱われます。
694         * よって、行間で、日付け違いの並び順になっているとずれることがあります。
695         * ヘッダーは、最初の行の日付けをそのまま表示させます。よって、データと
696         * 日付けが同期されていれば、不連続な日付けのヘッダーを表示させることも可能です。
697         * ヘッダーの期間(headerDuration)とデータの最小期間(minDuration)は、
698         * ともに、'1' であることが前提です。
699         * useSeqDay 属性に、"true" を設定すると、開始日(startDay)と終了日(endDay)
700         * の日付けを連続した日付けとします。開始日(startDay)や終了日(endDay)が指定
701         * されていない場合は、dystartColumn 属性で指定されたカラムの値の最大値、最小値を
702         * 自動セットします。
703         *
704         * @og.rev 3.5.5.9 (2004/06/07) 新規作成
705         * @og.rev 3.5.6.0 (2004/06/18) ithFormat 変数削除
706         * @og.rev 3.6.1.0 (2005/01/05) startDay,endDay,useSeqDay 属性追加
707         *
708         * @param       startNo 開始行番号
709         * @param       lastNo  最終行番号
710         *
711         * @return      テーブルのタグ文字列
712         * @og.rtnNotNull
713         */
714        private String getGanttHeadNoDuration(final int startNo, final int lastNo) {
715                String[] astrOldGroupKeys = new String[groupCols.length];
716                for( int nIndex=0; nIndex<astrOldGroupKeys.length; nIndex++ ) {
717                        astrOldGroupKeys[nIndex] = "";
718                }
719
720                String thVal    = ""     ;              // <td ・・・> の <td 以下の ・・・部分の属性文字列。
721                String ymdForm  = "MM/dd" ;             // td タグの BODY 部 の 日付けフォーマット
722
723                if( headerFormat != null ) {
724                        final String format = headerFormat.getItdBody().trim() ;
725                        final Matcher matcher = TDTH_PTN.matcher(format);
726                        if( matcher.find() ) {
727                                thVal   = matcher.group(2);
728                                ymdForm = matcher.group(3);
729                        }
730                }
731
732                // 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更
733                final Locale local = new Locale( headerLocale );
734                final SimpleDateFormat dataFmt = new SimpleDateFormat( formatDystart,local );
735
736                final TreeSet<String> daySet = new TreeSet<>();
737                for( int nRowUp=startNo; nRowUp<lastNo; nRowUp++ ) {
738                        // ガントでは、データのスキップは行いません。
739
740                        final String day = getValue(nRowUp, posDystart);
741                        daySet.add( day );
742                }
743                // 3.6.1.0 (2005/01/05)
744                if( useSeqDay ) {
745                        if( startDay == null ) { startDay = daySet.first() ; }
746                        if( endDay   == null ) { endDay   = daySet.last()  ; }
747
748                        try {
749                                final Calendar startCal = Calendar.getInstance() ;
750                                final Date dStart = dataFmt.parse( startDay );
751                                startCal.setTime( dStart );
752
753                                final Calendar endCal = Calendar.getInstance() ;
754                                final Date dEnd = dataFmt.parse( endDay );
755                                endCal.setTime( dEnd );
756                                endCal.set( Calendar.HOUR_OF_DAY,12 );  // 日付け比較する為、12時間進めておく。
757
758                                while( startCal.before( endCal ) ) {
759                                        daySet.add( dataFmt.format( startCal.getTime() ) );
760                                        startCal.add( Calendar.DATE ,1 );
761                                }
762                        }
763                        catch( final ParseException ex) {
764                                final String errMsg = "startDay,endDayに指定した日付形式と異なるデータが存在しています。"
765                                                + "[" + startDay + "],["
766                                                + "[" + endDay + "] => ["
767                                                + dataFmt.toPattern() + "]" ;
768                                throw new HybsSystemException( errMsg,ex );
769                        }
770                }
771
772                headDays = daySet.toArray( new String[daySet.size()] ); // 4.0.0 (2005/01/31)
773
774                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
775                final String th = "<th " + thVal + ">";                                 // 6.1.0.0 (2014/12/26) ソースの統一のため、一旦
776                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
777                final SimpleDateFormat headFmt = new SimpleDateFormat( ymdForm,Locale.JAPAN );
778                final Calendar cal = Calendar.getInstance() ;
779                for( int i=0; i<headDays.length; i++ ) {
780                        try {
781                                cal.setTime( dataFmt.parse(headDays[i]) );
782                                buf.append( th )
783                                        .append( headFmt.format( cal.getTime() ) )
784                                        .append( "</th>" );
785                        }
786                        catch( final ParseException ex) {
787                                final String errMsg = "DYSTARTに指定した日付形式と異なるデータが存在しています。"
788                                                + "[" + headDays[i] + "] => ["
789                                                + dataFmt.toPattern() + "]" ;
790                                throw new HybsSystemException( errMsg,ex );
791                        }
792                }
793
794                return buf.toString();
795        }
796
797        /**
798         * itaタグの中身を形式化する。
799         *
800         * @og.rev 3.5.5.0 (2004/03/12) systemFormat(例:[KEY.カラム名]形式等)の対応
801         * @og.rev 3.5.5.9 (2004/06/07) durationColumn を指定しない場合の処理を追加
802         * @og.rev 3.5.6.0 (2004/06/18) itdタグの[$xx] , [#xx]対応
803         * @og.rev 3.5.6.0 (2004/06/18) '!' 値のみ 追加 既存の '$' は、レンデラー
804         * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
805         * @og.rev 6.4.4.2 (2016/04/01) TableFormatterのタイプ別値取得処理の共通部をまとめる。
806         *
807         * @param   nTblRow             テーブルモデルの行番号
808         * @param   myIteFormat TableFormatteオブジェクト
809         * @param   strBuf              出力データバーファ(not null , 同じオブジェクトを return する)
810         *
811         */
812        private void formatItd( final int nTblRow, final TableFormatter myIteFormat, final StringBuilder strBuf ) {
813                if( myIteFormat == null ) { strBuf.setLength(0); }
814
815                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
816                final int colspan ;
817                if( posDuration >= 0 ) {
818                        // 3.7.0.0 (2005/01/18) 小数点の桁落ち対策
819                        colspan = (int)Math.round( StringUtil.parseDouble( getValue(nTblRow, posDuration) ) / minDuration );
820                }
821                else {  // 日付けヘッダー未満は、空タグを出力しておく
822                        final String today = getValue(nTblRow, posDystart);
823                        int comp = headDays[headDaysCnt].compareTo( today );
824                        headDaysCnt++ ;
825                        while( comp < 0 && headDaysCnt < headDays.length ) {
826                                strBuf.append( "<td></td>" );
827                                comp = headDays[headDaysCnt].compareTo( today );
828                                headDaysCnt++ ;
829                        }
830                        if( comp != 0 ) {       // 見つからなかった(先に日付けが無くなった)
831                                final String errMsg = "日付けヘッダーと日付けデータに矛盾が発生しています。"
832                                                + CR
833                                                + "groupColumns で日付けがユニークになっていない可能性があります。"
834                                                + "row=[" + (nTblRow +1) + "] , today=[" + today + "]" ;
835                                throw new HybsSystemException( errMsg );
836                        }
837                        colspan = 1;            // 6.3.9.1 (2015/11/27)
838                }
839
840                int cl = 0;
841                for( ; cl<myIteFormat.getLocationSize(); cl++ ) {
842                        String fmt = myIteFormat.getFormat(cl) ;
843                        final int loc = myIteFormat.getLocation(cl);    // 3.5.6.0
844                        if( cl == 0 && colspan != 1 ) {
845                                fmt = StringUtil.replace( fmt , "<td", "<td colspan=\"" + colspan + "\"" );
846                        }
847                        strBuf.append( fmt );                   // 3.5.6.0
848                        if( loc >= 0 ) {
849                                // 6.4.4.2 (2016/04/01) 処理の共通部をまとめる。
850                                strBuf.append( getTypeCaseValue( myIteFormat.getType(cl),nTblRow,loc ) );
851                        }
852                        else {
853                                strBuf.append( myIteFormat.getSystemFormat(nTblRow,loc) );
854                        }
855                }
856                strBuf.append( myIteFormat.getFormat(cl) );
857
858        }
859
860        /**
861         * フォーマットを設定します。
862         *
863         * @og.rev 3.5.4.0 (2003/11/25) 新規作成
864         * @og.rev 3.5.4.4 (2004/01/16) 配列の最大数を変更
865         * @og.rev 3.5.6.0 (2004/06/18) ithFormat ,  itdFormat 変数削除
866         *
867         * @param       list    TableFormatterのリスト
868         */
869        @Override
870        public void setFormatterList( final List<TableFormatter> list ) {               // 4.3.3.6 (2008/11/15) Generics警告対応
871                bodyFormats = new TableFormatter[BODYFORMAT_MAX_COUNT];
872
873                bodyFormatsCount = 0;
874                for( int i=0; i<list.size(); i++ ) {
875                        final TableFormatter format = list.get( i );            // 4.3.3.6 (2008/11/15) Generics警告対応
876
877                        switch( format.getFormatType() ) {
878                                case TYPE_HEAD : headerFormat = format; break;
879                                case TYPE_BODY : bodyFormats[bodyFormatsCount++] = format; break;
880                                case TYPE_FOOT : footerFormat = format; break;
881                                default : final String errMsg = "FormatterType の定義外の値が指定されました。";
882                                // 4.3.4.4 (2009/01/01)
883                                                  throw new HybsSystemException( errMsg );
884                        }
885                        // 3.5.6.0 (2004/06/18) 廃止
886                }
887
888                // 3.5.6.0 (2004/06/18) itdFormats 処理追加
889                // tbody 配列分だけ設定しておきます。
890                itdFormats = new TableFormatter[bodyFormatsCount];
891                for( int i=0; i<bodyFormatsCount; i++ ) {
892                        final String format = bodyFormats[i].getItdBody().trim();
893                        itdFormats[i] = new TableFormatter();
894                        itdFormats[i].setFormat( format );
895                }
896        }
897
898        /**
899         * フォーマットメソッドを使用できるかどうかを問い合わせます。
900         *
901         * @return  使用可能(true)/ 使用不可能 (false)
902         */
903        @Override
904        public boolean canUseFormat() {
905                return true;
906        }
907
908        /**
909         * 表示項目の編集(並び替え)が可能かどうかを返します。
910         *
911         * @og.rev 5.1.6.0 (2010/05/01) 新規追加
912         *
913         * @return      表示項目の編集(並び替え)が可能かどうか(false:不可能)
914         */
915        @Override
916        public boolean isEditable() {
917                return false;
918        }
919}