    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.hayabusa.common.HybsSystem;
    019import org.opengion.hayabusa.common.HybsSystemException;
    020import org.opengion.fukurou.util.StringUtil;
    021
    022import java.util.Calendar;
    023import java.util.Map;
    024import java.util.TreeMap;
    025import java.util.Collection;
    026import java.util.Locale ;
    027import java.util.Date;
    028import java.text.DateFormat;
    029import java.text.SimpleDateFormat;
    030
    031import java.awt.Graphics2D;
    032import java.awt.Color;
    033import java.awt.FontMetrics;
    034import java.awt.Stroke;
    035import java.awt.BasicStroke;
    036import java.awt.image.BufferedImage;
    037
    038import javax.imageio.ImageIO;
    039import java.io.File;
    040import java.io.IOException;
    041
    042
    043/**
    044 * キー、日時、状況コードを持つ稼働状況の表示を行うクラスです。
    045 *
    046 * パラメータが必要な場合は、ViewTimeBarParamTag を使用してください。
    047 *
    048 * パラメータが設定されていない場合は、ViewForm_ImageTimeBar の初期値が使用されます。
    049 * (パラメータを使用するには、viewタグのuseParam 属性をtrueに設定する必要があります。)
    050 *
    051 * SELECT文は、キー、日時、状況コードが、必須項目で、カラムの並び順は、完全に固定です。
    052 * よって、カラム位置を指定する必要はありませんが、SELECT文を自由に設定することも
    053 * 出来ませんので、ご注意ください。
    054 * この固定化に伴い、WRITABLE 指定も使用できません。（そもそも書き込み不可です）
    055 * それ以降のカラムについては、内部処理としては、使用していません。
    056 * ただし、パラメータで、カラー色指定、ラベル表記部、イメージ重ね合わせ、
    057 * ポップアップ表記、リンク表記に使えます。
    058 *
    059 * データの並び順(ORDER BY)も、キー、日時順にしてください。
    060 * データは、キー単位に１レコード作成されます。（キーブレイク）その間、日時順に
    061 * データを処理します。
    062 *
    063 * データの表示は、今のレコードの日時から、次のレコードの日時までを一つの状態と
    064 * して表します。今のレコードを表示するには、次のレコードが必要になります。
    065 * 画面表示は、表示開始日時(minStartTime) から 表示期間(timeSpan)分を表示します。
    066 * 通常、開始時刻は、表示開始時刻より前より始まり、次のレコードで、終了時刻が決定
    067 * されます。最後のデータは、期間満了まで続いていると仮定されます。
    068 * データが存在しないのであれば、「存在しないデータ」を作成してください。
    069 *
    070 * ImageTimeBar では、キーでまとめた値について、各状況コードをカラー化し、積み上げ
    071 * 帯グラフ形式でPNG画像化します。
    072 * この画像を、読み込む HTML を出力することで、画面上に、積み上げ帯グラフを表示します。
    073 * 状況コードに対応する色は、標準では自動作成ですが、外部から色文字列(colorClm)を与えることで
    074 * 自由に指定する事も可能です。
    075 *
    076 * ポップアップ表記(tipsClm)、リンク表記(linkClm)は、この画像に対するエリア指定タグを出力する事で実現します。
    077 * 画像ファイルは、全データに対して、１画像だけなので、サイズは大きくなりますが、１レコード
    078 * 単位に画像を作成しないため、レスポンスは向上します。
    079 * それぞれ、viewMarker , viewLink を利用することが可能です。特に、リンク表記(linkClm) については、
    080 * linkタグの hrefTarget 属性を true に設定することで適用できます。
    081 *
    082 * 画像ファイルは、java.io.File.createTempFile( File ) で作成するため、JavaVM(=Tomcat)が
    083 * 正常終了するときに、削除されます。異常終了時には残りますが、temp フォルダを定期的に
    084 * 整理すれば、それほど大量のファイルが残ることはないと思われます。
    085 *
    086 * データは、イベント発生時に作成されると仮定しています。つまり、書き込まれた日時から、
    087 * 状況コードに対応する状況が発生し、次の状況違いのレコードまで継続していると考えます。
    088 * よって、データを途中で切り出す場合、切り出す範囲の前の状態が必要になります。
    089 * 一番最初の状態は、"不明" として扱います。（空欄＝白色）
    090 *
    091 * @og.rev 5.5.5.6 (2012/08/31) 新規追加
    092 * @og.group 画面表示
    093 *
    094 * @version  4.0
    095 * @author       Kazuhiko Hasegawa
    096 * @since    JDK5.0,
    097 */
    098public class ViewForm_ImageTimeBar extends ViewForm_HTMLTable {
    099        //* このプログラムのVERSION文字列を設定します。   {@value} */
    100        private static final String VERSION = "5.6.5.0 (2013/06/07)" ;
    101
    102        private static final Color LABEL_COLOR = Color.BLACK;           // ラベル記述時の色
    103        private static final Color NULL_COLOR  = Color.WHITE;           // 5.6.1.1 (2013/02/08) 不明(空欄)時の色
    104
    105        private long startDate          ;       // タイムテーブルの表示開始日時から求めた long 値(1=分単位)
    106        private long timeSpan           ;       // タイムテーブルの表示期間。元は時間指定であるが、分単位で持つ。(1=分単位)
    107
    108        private boolean useLegend       ;       // カラーの凡例を使用するかどうか[true/false]
    109        private int maxLabelWidth       ;       // ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ
    110        private int maxTimeWidth        ;       // タイム表記部の最大サイズをpxで指定。
    111        private int chartHeight         ;       // １レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT＋MARGIN＊２
    112        private int chartPadding        ;       // イメージ作成の 全体テーブルの隙間
    113        private int recodeMargin        ;       // 各レコード、文字等の内部の間隔
    114        private boolean useLastData     ;       // 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false]
    115
    116        private String tempDir          ;       // 画像ファイルを作成するテンポラリディレクトリ(相対パス)
    117        private String tempUrl          ;       // 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス)
    118
    119        // SELECT文は、キー、日時、状況コードが、必須項目
    120        private static final int        keyClmNo        = 0;    // ヘッダー1：キーカラムNo
    121        private static final int        dyClmNo         = 1;    // ヘッダー2：日時カラムNo
    122        private static final int        fgjClmNo        = 2;    // ヘッダー3：状況コードカラムNo
    123
    124        private int[] labelClmsNo       = null;                         // 初期値は、キーカラム
    125        private int[] maxClmWidth       = null;                         // labelClms 単位の最大文字列長
    126        private int   colClmNo          = -1;                           // カラーコード直接指定する場合に色文字列を指定するカラムNo
    127        private int   tipsClmNo         = -1;                           // マウスオーバー時のTips表示を行うカラムNo
    128        private int   linkClmNo         = -1;                           // クリッカブルリンクを設定するカラムNo
    129
    130        private int str2DateTime        ;       // getStr2Date(String)処理をおこなった時の、引数の時刻情報(分単位)をセットするテンポラリ変数。
    131        private int startTime           ;       // startDate の時刻情報。上記処理で、startDate を getStr2Date(String) 処理したときの値を持っておくための変数。
    132
    133        private int MAX_X       ;       // イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth
    134        private int MAX_Y       ;       // イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (chartHeight+recodeMargin*2)*(レコード数＋ヘッダー数)
    135
    136        /**
    137         * 内容をクリア(初期化)します。
    138         *
    139         */
    140//      @Override
    141//      public void clear() {
    142//              super.clear();
    143//      }
    144
    145        /**
    146         * DBTableModel から HTML文字列を作成して返します。
    147         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
    148         * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
    149         *
    150         * @og.rev 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
    151         * @og.rev 5.6.1.1 (2013/02/08) 初期値の色を、直接の値ではなく、static final で定義された色を使用する。色自体は変えていません
    152         * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
    153         * @og.rev 5.6.3.0 (2013/04/01) tipsのシングルクォーテーション のエスケープ
    154         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、&lt;span&gt;タグが付与される値から、spanタグを削除します。
    155         *
    156         * @param  startNo        表示開始位置
    157         * @param  pageSize   表示件数
    158         *
    159         * @return      DBTableModelから作成された HTML文字列
    160         */
    161        @Override
    162        public String create( final int startNo, final int pageSize )  {
    163                if( getRowCount() == 0 ) { return ""; } // 暫定処置
    164
    165                // パラメータの情報を初期化＆取得します。
    166                paramInit();
    167
    168                int lastNo = getLastNo( startNo, pageSize );
    169
    170                // イメージの 最大Ｘ、Ｙサイズを求め、結果を、MAX_X,MAX_Y 変数に設定する。
    171                calcImageSize( startNo,lastNo );
    172
    173                BufferedImage img = new BufferedImage( MAX_X, MAX_Y, BufferedImage.TYPE_INT_ARGB);
    174                Graphics2D g2 = img.createGraphics();
    175
    176                // chartPadding を考慮した領域のクリップ（クリップ外にDrowされても無視されます。）
    177                g2.setClip( chartPadding , chartPadding , MAX_X-chartPadding*2+1 , MAX_Y-chartPadding*2+1 );    // （x 座標,y 座標,幅,高さ) ＋１は罫線の分
    178
    179                StringBuilder out = new StringBuilder( HybsSystem.BUFFER_LARGE );
    180
    181                String oldKeyVal = "";                          // 初期値。１回目にキーブレイクさせる。
    182                long   oldTime   = 0L;                          // 日付項目の一つ前の値
    183
    184//              Color  oldColor  = Color.WHITE;         // 色の一つ前の値(初期値)
    185                Color  oldColor  = NULL_COLOR;          // 5.6.1.1 (2013/02/08) 不明(空欄)時の色(初期値)
    186
    187                ColorMap colMap = new ColorMap();       // 状況コード、ラベル、カラーを管理するクラス
    188
    189                int rowCnt = (useLegend) ? 2 : 1;               // 凡例(useLegend)を使う(true)場合は、２行分、使わない場合は、ヘッダーの１行が初期値
    190                int imgX   = 0;                                                 // イメージ出力時の X軸の左端 px数
    191                int imgY   = 0;                                                 // イメージ出力時の Y軸の上端 px数
    192                int imgW   = 0;                                                 // イメージ出力時の 幅(fillRectで使用)
    193                int imgH   = chartHeight;                               // イメージ出力時の 高さ(fillRectで使用)
    194                int rowH   = chartHeight+recodeMargin*2;        // 罫線出力時の高さ(drawRectで使用)
    195
    196                double timeScale = (double)maxTimeWidth/(double)timeSpan;
    197                boolean useTipsLink = (tipsClmNo >= 0 || linkClmNo >= 0) ;                // tipsClm か linkClm かどちらかを使用する場合、true
    198                for( int row=startNo; row<lastNo; row++ ) {
    199                        // キーブレイクの判定
    200                        String keyVal  = getValue( row,keyClmNo );
    201                        String dyVal   = getValue( row,dyClmNo );
    202
    203                        // キーブレイク判定。キーブレイクは、一番初めから来る。
    204                        if( !oldKeyVal.equals( keyVal ) ) {
    205                                // キーブレイクで最初に行うのは、前のレコードの未出力分の処理。１行目は、処理不要
    206                                if( row > startNo ) {
    207                                        imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);
    208                                        imgW = (int)((timeSpan-oldTime)*timeScale) ;
    209
    210                                        // 幅が0以上の場合のみ描画する。
    211                                        if( imgW > 0 ) {
    212                                                // 5.6.1.1 (2013/02/08) useLastData 追加
    213                                                if( useLastData ) { g2.setColor( oldColor ); }                                  // 色の設定は、旧色を使う
    214                                                else {                          g2.setColor( NULL_COLOR ); }                            // NULL色を使う
    215                                                g2.fillRect( imgX , imgY+recodeMargin , imgW, imgH );                   // (実際の状態)左端x,上端y,幅w,高さh
    216
    217                                                // tipsClm か linkClm を使用する。
    218                                                // 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
    219//                                              if( useTipsLink ) {
    220//                                              if( useTipsLink && row > 0 ) {
    221                                                if( useLastData && useTipsLink && row > 0 ) {        // 5.6.1.1 (2013/02/08) useLastData 追加
    222                                                        // tips や link は、ひとつ前のデータで作成する必要がある。(row-1)
    223                                                        String tips = (tipsClmNo >= 0) ? getValueLabel( row-1,tipsClmNo ) : getValueLabel( row-1,fgjClmNo ) ;
    224                                                        if( tips != null && tips.indexOf( "'" ) >= 0 ) {     // 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
    225                                                                tips = tips.replaceAll( "'","&#39;" );
    226                                                        }
    227                                                        tips = StringUtil.spanCut( tips );                                      // 5.6.3.1 (2013/04/05) spanタグを削除
    228
    229                                                        out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
    230                                                        if( linkClmNo >= 0 ) {
    231                                                                String hrefVal = getValueLabel( row-1,linkClmNo );
    232                                                                if( hrefVal != null && hrefVal.startsWith( "href" ) ) {         // linkClmの値の先頭が、html の場合のみ追加する。
    233                                                                        out.append( hrefVal );
    234                                                                }
    235                                                        }
    236                                                        out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );               // 左端x1,上端y1
    237                                                        out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );                      // 右端x2,下段y2
    238                                                }
    239                                        }
    240                                }
    241
    242                                // 次に、新しい行の出力（ラベルは、ブレイク時に出力しておきます。）
    243                                oldKeyVal = keyVal;                     // null は来ないはず
    244                                imgY = chartPadding+(rowH)*rowCnt ;
    245                                rowCnt++ ;
    246
    247                                // レコードのラベル分だけ、繰返し描画する。
    248                                int clmSu = labelClmsNo.length;
    249                                int posX  = chartPadding ;                                              // ラベル文字列の書き出し位置の初期値
    250                                int posY2 = imgY+chartHeight+recodeMargin ;             // ラベルのY軸基準。
    251                                g2.setColor( LABEL_COLOR );
    252                                for( int col=0; col<clmSu; col++ ) {
    253                                        String lbl = getValueLabel( row,labelClmsNo[col] );             // その行の値のRenderer値
    254                                        lbl = StringUtil.spanCut( lbl );                                                // 5.6.3.1 (2013/04/05) spanタグを削除
    255                                        g2.drawString( lbl , posX + recodeMargin, posY2 );              // 文字列,ベースラインのx座標,y座標
    256                                        posX += recodeMargin*2 + maxClmWidth[col] ;                             // 次の書き出し位置は、文字最大長＋マージン
    257                                }
    258
    259                                // レコードの枠線
    260                                g2.drawRect( chartPadding , imgY , MAX_X-chartPadding*2 , rowH );               // (レコード枠)左端x,上端y,幅w,高さh
    261
    262                                oldTime = 0L;                           // キーブレイクによる初期化
    263//                              oldColor= Color.WHITE;          // キーブレイクによる初期化
    264                                oldColor= NULL_COLOR;           // 5.6.1.1 (2013/02/08) キーブレイクによる初期化=不明(空欄)時の色
    265                        }
    266
    267                        long newTime = getStr2Date( dyVal ) - startDate;                // 次の時刻
    268                        if( newTime < oldTime ) { newTime = oldTime; }                       // 前の時刻より小さい場合は、前の時刻まで、進めておく。
    269
    270                        imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);         // old時刻から書き始める(左端x)
    271                        imgW = (int)((newTime-oldTime)*timeScale) ;                                                     // 差分幅だけ描画する。
    272
    273                        // 幅が0以上の場合のみ描画する。
    274                        if( imgW > 0 ) {
    275                                g2.setColor( oldColor );                                                                                // 色の設定
    276                                g2.fillRect( imgX , imgY+recodeMargin , imgW, chartHeight );    // (実際の状態)左端x,上端y,幅w,高さh
    277
    278                                // tipsClm か linkClm を使用する。
    279                                // 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
    280//                              if( useTipsLink ) {
    281                                if( useTipsLink && row > 0 ) {
    282                                        // tips や link は、ひとつ前のデータで作成する必要がある。(row-1)
    283                                        String tips = (tipsClmNo >= 0) ? getValueLabel( row-1,tipsClmNo ) : getValueLabel( row-1,fgjClmNo ) ;
    284                                        if( tips != null && tips.indexOf( "'" ) >= 0 ) {     // 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
    285                                                tips = tips.replaceAll( "'","&#39;" );
    286                                        }
    287                                        tips = StringUtil.spanCut( tips );                                      // 5.6.3.1 (2013/04/05) spanタグを削除
    288
    289                                        out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
    290                                        if( linkClmNo >= 0 ) {
    291                                                String hrefVal = getValueLabel( row-1,linkClmNo );
    292                                                if( hrefVal != null && hrefVal.startsWith( "href" ) ) {         // linkClmの値の先頭が、html の場合のみ追加する。
    293                                                        out.append( hrefVal );
    294                                                }
    295                                        }
    296                                        out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );               // 左端x1,上端y1
    297                                        out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );                      // 右端x2,下段y2
    298                                }
    299                        }
    300
    301                        oldTime   = newTime ;
    302
    303                        String fgjVal  = getValue( row,fgjClmNo );                      // 状況コードのキー
    304                        String fgjLbl  = getValueLabel( row,fgjClmNo );         // 状況コードのラベル
    305                        if( colClmNo >= 0 ) {
    306                                oldColor  = colMap.getColor( fgjVal,fgjLbl,getValue( row,colClmNo ) );          // 色文字列を指定する場合
    307                        }
    308                        else {
    309                                oldColor  = colMap.getColor( fgjVal,fgjLbl );                                                           // 色文字列を指定しない＝自動設定
    310                        }
    311                }
    312
    313                // レコードのいちばん最後の出力。一応、ジャストの場合(oldTime == maxEdTime)は、処理しない
    314                // 5.6.1.1 (2013/02/08) レコードのいちばん最後の出力は、NULL色を使うように変更
    315                if( oldTime < timeSpan ) {
    316                        imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);
    317                        imgW = (int)((timeSpan-oldTime)*timeScale) ;
    318                        // 5.6.1.1 (2013/02/08) useLastData 追加
    319                        if( useLastData ) { g2.setColor( oldColor ); }                                  // 色の設定は、旧色を使う
    320                        else {                          g2.setColor( NULL_COLOR ); }                            // NULL色を使う
    321                        g2.fillRect( imgX , imgY+recodeMargin , imgW, chartHeight );    // (実際の状態)左端,上端,幅,高さ
    322
    323                        // tipsClm か linkClm を使用する。
    324//                      if( useTipsLink ) {
    325                        if( useLastData && useTipsLink ) {                      // 5.6.1.1 (2013/02/08) useLastData 追加
    326                                // tips や link 最後の出力は、最後のデータなので、lastNo-1 となる。
    327                                String tips = (tipsClmNo >= 0) ? getValueLabel( lastNo-1,tipsClmNo ) : getValueLabel( lastNo-1,fgjClmNo ) ;
    328                                if( tips != null && tips.indexOf( "'" ) >= 0 ) {     // 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
    329                                        tips = tips.replaceAll( "'","&#39;" );
    330                                }
    331                                tips = StringUtil.spanCut( tips );                                      // 5.6.3.1 (2013/04/05) spanタグを削除
    332
    333                                out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
    334                                if( linkClmNo >= 0 ) {
    335                                        String hrefVal = getValueLabel( lastNo-1,linkClmNo );
    336                                        if( hrefVal != null && hrefVal.startsWith( "href" ) ) {         // linkClmの値の先頭が、html の場合のみ追加する。
    337                                                out.append( hrefVal );
    338                                        }
    339                                }
    340                                out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );               // 左端x1,上端y1
    341                                out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );                      // 右端x2,下段y2
    342                        }
    343                }
    344
    345                // ヘッダー情報のイメージを作成します。
    346                imageHeaderPaint( g2 , timeScale , colMap );
    347
    348                // イメージファイルを出力し、URLを返します。
    349                File file = null;
    350                try {
    351                        File dir = new File( tempDir );         // 画像ファイル出力先のフォルダ
    352                        dir.mkdirs();                                           // なければ作成する。
    353
    354                        file = File.createTempFile( "Img",".png", dir );        // テンポラリファイルの作成
    355                        file.deleteOnExit();                                                            // JavaVM 停止時にテンポラリファイルの削除
    356
    357                        ImageIO.write(img, "PNG", file );
    358                        g2.dispose();
    359                }
    360                catch( IOException ex ) {
    361                        System.out.println( "エラー:" + ex );
    362                }
    363
    364                // imgタグを作成します。
    365                int width  = MAX_X;
    366                int height = MAX_Y;
    367
    368                StringBuilder out2 = new StringBuilder( HybsSystem.BUFFER_LARGE );
    369                out2.append( "<img id='ImageTimeBar' alt='ImageTimeBar' border='0'" );
    370                out2.append( " width='" ).append( width ).append( "px' height='" ).append( height ).append( "px'" );
    371                out2.append( " src='" ).append( tempUrl ).append( file.getName() ).append( "'" );
    372
    373                // area タグのデータが作成されていれば、出力します。
    374                if( out.length() > 0 ) {
    375                        out2.append( " usemap='#TimeBarMap' />" );   // img タグにusemap 属性を追加して、終了部分を追記
    376
    377                        out2.append( "<map name='TimeBarMap'>" );
    378                        out2.append( out );
    379                        out2.append( "</map>" );
    380                }
    381                else {
    382                        out2.append( " />" );                                                // img タグの終了部分を追記
    383                }
    384
    385                return out2.toString();
    386        }
    387
    388        /**
    389         * パラメータ内容を初期化します。
    390         *
    391         * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
    392         */
    393        private void paramInit() {
    394                String s_startDate      = getParam( "START_DATE"                );      // タイムテーブルの表示開始日時をセットします(初期値:データの最小日時)。
    395                int timeSpanHour        = getIntParam( "TIME_SPAN"              );      // タイムテーブルの表示期間を時間で指定します(初期値:{@og.value TIME_SPAN})。
    396
    397                String[] s_labelClms    = StringUtil.csv2Array( getParam( "LABEL_CLMS" ) );     // 一覧表のラベル表示部に表示するカラム名をCSV形式で指定します。
    398                String s_colClm                 = getParam( "COLOR_CLM"         );      // レコードに付ける色を色文字列で指定する場合のカラム名を指定します。
    399                String s_tipsClm                = getParam( "TIPS_CLM"          );      // レコード単位に、マウスオーバー時のTips表示を行うカラム名を指定します。
    400                String s_linkClm                = getParam( "LINK_CLM"          );      // レコード単位に、クリッカブルリンクを設定するカラム名を指定します。
    401
    402                useLegend               = getBoolParam( "USE_LEGEND"            );      // カラーの凡例を使用するかどうか[true/false]
    403                maxLabelWidth   = getIntParam( "MAX_LABEL_WIDTH"        );      // ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ
    404                maxTimeWidth    = getIntParam( "MAX_TIME_WIDTH"         );      // タイム表記部の最大サイズをpxで指定。
    405                chartHeight             = getIntParam( "CHART_HEIGHT"           );      // １レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT＋MARGIN＊２
    406                chartPadding    = getIntParam( "CHART_PADDING"          );      // イメージ作成の 全体テーブルの隙間
    407                recodeMargin    = getIntParam( "RECODE_MARGIN"          );      // 各レコード、文字等の内部の間隔
    408                useLastData             = getBoolParam( "USE_LAST_DATA"         );      // 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false]
    409
    410                tempDir                 = getParam( "TEMP_DIR"                          );      // 画像ファイルを作成するテンポラリディレクトリ(相対パス)
    411                tempUrl                 = getParam( "TEMP_URL"                          );      // 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス)
    412
    413                startDate               = getStr2Date( s_startDate );                   // 分単位に変換する。
    414                startTime               = str2DateTime ;                                                // 開始日時の時刻情報(分単位)の値。str2DateTime は、getStr2Date(String)メソッド実行後にセットされる。
    415                timeSpan                = timeSpanHour * 60L ;                                  // 分単位に変換する。
    416
    417                int len = s_labelClms.length;
    418                if( len > 0 ) {
    419                        labelClmsNo = new int[len];
    420                        for( int i=0; i<len; i++ ) {
    421                                labelClmsNo[i] = getColumnNo( s_labelClms[i] );
    422                        }
    423                }
    424                else {
    425                        labelClmsNo = new int[] { keyClmNo };           // 初期値は、キーカラム
    426                }
    427
    428                // 指定のカラム名に対するカラム番号を取得。なければ、-1 を設定しておく。
    429                if( s_colClm  != null ) { colClmNo  = getColumnNo( s_colClm  ); }                       // レコードに付ける色を色文字列で指定する場合のカラムNo
    430                if( s_tipsClm != null ) { tipsClmNo = getColumnNo( s_tipsClm ); }                       // レコード単位に、マウスオーバー時のTips表示を行うカラムNo
    431                if( s_linkClm != null ) { linkClmNo = getColumnNo( s_linkClm ); }                       // レコード単位に、クリッカブルリンクを設定するカラムNo
    432        }
    433
    434        /**
    435         * イメージの ＸＹサイズを求め、結果を、MAX_X,MAX_Y 変数に設定します。
    436         *
    437         * また、ラベル文字列の最大長が指定されていない場合（maxLabelWidth == -1）最大長を自動計算し、maxLabelWidth変数にセットします。
    438         *
    439         * maxLabelWidth : -1 の場合は、ラベル文字列の最大長を求めて、この値を計算する。= (recodeMargin*2 + ラベル文字列の最大長)
    440         * MAX_X : イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth
    441         * MAX_Y : イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (chartHeight+recodeMargin*2)*(レコード数＋ヘッダー数)
    442         *
    443         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
    444         *
    445         * @param       startNo 計算の開始列番号
    446         * @param       lastNo  計算の終了列番号
    447         */
    448        private void calcImageSize( final int startNo , final int lastNo ) {
    449                String oldKeyVal = "";  // 初期値。１回目にキーブレイクさせる。
    450
    451                int clmSu = labelClmsNo.length;
    452                maxClmWidth = new int[clmSu];
    453
    454                int rowCnt = (useLegend) ? 2 : 1;               // 凡例(useLegend)を使う(true)場合は、２行分、使わない場合は、ヘッダーの１行が初期値
    455
    456                // ラベル領域の長さを各ラベル長を積み上げて計算する。
    457                if( maxLabelWidth < 0 ) {
    458                        // FontMetrics を取得する為だけの BufferedImage
    459                        BufferedImage img = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB);
    460                        Graphics2D g2 = img.createGraphics();
    461                        FontMetrics fontM = g2.getFontMetrics();
    462
    463                        // 初期値の計算は、ヘッダーのラベルの幅を使用する。
    464                        for( int col=0; col<clmSu; col++ ) {
    465                                String lbl = getColumnLabel( labelClmsNo[col] );        // ヘッダーのラベル
    466                                lbl = StringUtil.spanCut( lbl );                                        // 5.6.3.1 (2013/04/05) spanタグを削除
    467                                maxClmWidth[col] = fontM.stringWidth( lbl );            // 最大値の初期値として、ヘッダーラベルの幅をセットしておく。
    468                        }
    469
    470                        for( int row=startNo; row<lastNo; row++ ) {
    471                                // キーブレイク判定。キーブレイクは、一番初めから来る。
    472                                String keyVal = getValue( row,keyClmNo );
    473                                if( !oldKeyVal.equals( keyVal ) ) {
    474                                        oldKeyVal = keyVal;
    475                                        rowCnt++;                               // レコード数
    476
    477                                        // ラベルは、キーブレイク時の値のみ採用する。
    478                                        for( int col=0; col<clmSu; col++ ) {
    479                                                String lbl = getValueLabel( row,labelClmsNo[col] );     // その行の値のRenderer値
    480                                                lbl = StringUtil.spanCut( lbl );                                        // 5.6.3.1 (2013/04/05) spanタグを削除
    481                                                int fontW = fontM.stringWidth( lbl );
    482                                                if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }
    483                                        }
    484                                }
    485                        }
    486                        g2.dispose();
    487
    488                        // 最大ラベル幅は、各ラベルの最大値＋（マージン＊２）＊カラム数
    489                        maxLabelWidth = recodeMargin * 2 * clmSu ;
    490                        for( int col=0; col<clmSu; col++ ) {
    491                                maxLabelWidth += maxClmWidth[col];
    492                        }
    493                }
    494                else  {
    495                        for( int row=startNo; row<lastNo; row++ ) {
    496                                // キーブレイク判定。キーブレイクは、一番初めから来る。
    497                                String keyVal = getValue( row,keyClmNo );
    498                                if( !oldKeyVal.equals( keyVal ) ) {
    499                                        oldKeyVal = keyVal;
    500                                        rowCnt++;                               // レコード数
    501                                }
    502                        }
    503
    504                        // 最大ラベル幅は、均等割り付け。端数は無視（どうせ、ラベル部は、maxLabelWidth で計算するので。）
    505                        int clmWidth = ( maxLabelWidth - recodeMargin * 2 * clmSu ) / clmSu ;
    506                        for( int col=0; col<clmSu; col++ ) {
    507                                maxClmWidth[col] = clmWidth;
    508                        }
    509                }
    510
    511                MAX_X = chartPadding*2 + maxLabelWidth + maxTimeWidth ;
    512                MAX_Y = chartPadding*2 + (chartHeight+recodeMargin*2)*rowCnt ;
    513        }
    514
    515        /**
    516         * ヘッダー情報のイメージを作成します。
    517         *
    518         * 全体の枠もここで作成しておきます。
    519         * イメージは、キーカラムのラベルと、時間軸になります。時間軸は縦方向にすべて線を引きます。
    520         * 時間軸の間隔は、timeScale によって、切り替わります。
    521         * 凡例を使う場合（useLegend=true）は、引数の ColorMap を利用して作成します。
    522         *
    523         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
    524         * @og.rev 5.6.5.0 (2013/06/07) 年月日情報を表示します。なお、日単位の場合は、年月は省略します。
    525         *
    526         * @param       g2                      描画するGraphics2Dオブジェクト
    527         * @param       timeScale       時間(分)当たりのピクセル数
    528         * @param       colMap          状況コードに対応したカラーマップ
    529         */
    530        private void imageHeaderPaint( final Graphics2D g2 , final double timeScale , final ColorMap colMap ) {
    531
    532                int posY1 = chartPadding  ;
    533                int posY2 = chartPadding+chartHeight+recodeMargin ;
    534
    535                // 凡例を使う場合
    536                if( useLegend && colMap != null ) {
    537                        // 凡例を並べる間隔を求める。
    538                        FontMetrics fontM = g2.getFontMetrics();
    539                        int maxSize = fontM.stringWidth( colMap.getMaxLengthLabel() ) ;         // 文字列の最大長ラベルの幅
    540                        int imgW  = chartHeight ;                               // 凡例■の幅(高さと同じにしているので真四角)
    541                        int mgnW  = recodeMargin ;                              // 凡例■から文字までの間
    542                        int spanW = maxSize + recodeMargin ;    // 凡例■から凡例■までの間隔。文字最大長＋α
    543
    544                        int legX  = chartPadding ;
    545                        for( Object[] obj : colMap.values() ) {
    546                                String lbl = (String)obj[0];
    547                                Color  col = (Color)obj[1];
    548
    549                                g2.setColor( col );                                                                                             // 凡例■の色
    550                                g2.fillRect( legX , posY1+recodeMargin , imgW , chartHeight );  // (実際の状態)左端x,上端y,幅w,高さh
    551
    552                                legX += imgW + mgnW ;
    553                                g2.setColor( LABEL_COLOR );
    554                                g2.drawString( lbl , legX , posY2 );            // 文字列,ベースラインのx座標,y座標
    555
    556                                legX += spanW ;
    557                        }
    558                        posY1 += chartHeight+recodeMargin*2 ;   // １レコード分の高さを加算しておく。
    559                        posY2 += chartHeight+recodeMargin*2 ;   // １レコード分の高さを加算しておく。
    560                }
    561
    562                // まずは、全体の枠線の描画
    563                g2.setColor( LABEL_COLOR );
    564                g2.drawRect( chartPadding, posY1, MAX_X-chartPadding*2, MAX_Y-posY1-chartPadding );                             // 左端,上端,幅,高さ
    565
    566                // ヘッダーのラベル分だけ、繰返し描画する。
    567                int clmSu = labelClmsNo.length;
    568                int posX  = chartPadding ;              // ラベル文字列の書き出し位置の初期値
    569                for( int col=0; col<clmSu; col++ ) {
    570                        String lbl = getColumnLabel( labelClmsNo[col] );                        // ヘッダーのラベル
    571                        lbl = StringUtil.spanCut( lbl );                                                        // 5.6.3.1 (2013/04/05) spanタグを削除
    572                        g2.drawString( lbl , posX + recodeMargin, posY2 );                      // 文字列,ベースラインのx座標,y座標
    573                        posX += recodeMargin*2 + maxClmWidth[col] ;                                     // 次の書き出し位置は、文字最大長＋マージン*2
    574                        g2.drawLine( posX, posY1, posX, MAX_Y-chartPadding );           // 始点(x 座標,y 座標),終点(x 座標,y 座標)
    575                }
    576
    577                int step = TimeScaleStep.getStep( timeScale );                  // 時間スケールに対応したSTEP数
    578
    579                // 日付ヘッダー部の描画のためのカレンダー
    580                Calendar cal = Calendar.getInstance();
    581                cal.setTimeInMillis( startDate * 60000L );
    582
    583                int week = cal.get( Calendar.DAY_OF_WEEK );     // 開始曜日
    584
    585                String fmt = ( step < 1440 ) ? "yyyy/MM/dd(EE)" : "dd" ;             // 上段フォーマットは、時間ベースの場合は、"yyyy/MM/dd(EE)" 、日ベースの場合は、"dd"
    586                DateFormat format1 = new SimpleDateFormat( fmt,Locale.JAPAN );
    587
    588                // グラフ領域の日付ヘッダー部の描画
    589                g2.setStroke( DSAH_STROK );                             // 日付部は、破線
    590                posX = chartPadding+maxLabelWidth ;             // グラフ領域は、chartPadding+maxLabelWidth から。
    591                for( int tm = 0; tm < timeSpan; tm+=step ) {
    592
    593                        int offset = chartHeight / 2 + recodeMargin;    // ヘッダーの表示基準のオフセット（チャート幅の半分）
    594
    595                        // 上段：ヘッダー出力
    596                        if( ( tm % 1440 ) == 0 ) {
    597                                Date dt = new Date( (startDate + tm) * 60000L );
    598                                g2.drawString( format1.format( dt ) , posX + recodeMargin , posY2-offset );                     // 文字列,ベースラインのx座標,y座標
    599                                offset = 0;             // ヘッダーを表示する場合のみ上まで線を引く。
    600                        }
    601
    602                        g2.drawString( getTime2Str(startTime+tm,step,week) , posX + recodeMargin , posY2 );             // 文字列,ベースラインのx座標,y座標
    603                        g2.drawLine( posX, posY1+offset, posX, MAX_Y-chartPadding );                                                    // 始点(x 座標,y 座標),終点(x 座標,y 座標)
    604
    605//                      g2.drawString( getTime2Str(startTime+tm,step) , posX + recodeMargin , posY2 );  // 文字列,ベースラインのx座標,y座標
    606//                      g2.drawLine( posX, posY1-10, posX, MAX_Y-chartPadding-10 );                                             // 始点(x 座標,y 座標),終点(x 座標,y 座標)
    607                        posX += (int)(step*timeScale);
    608                }
    609        }
    610
    611        // imageHeaderPaintメソッドで使用する 破線の定義
    612        private static final Stroke DSAH_STROK = new BasicStroke(
    613                                1.0f                                            , // BasicStroke の幅
    614                                BasicStroke.CAP_BUTT            , // 両端の装飾
    615                                BasicStroke.JOIN_MITER          , // 輪郭線セグメントの接合部の装飾
    616                                1.0f                                            , // 接合トリミングの制限値
    617                                new float[] {2.0f, 1.0f}        , // 破線パターンを表す配列
    618                                0.0f                                              // 破線パターン開始位置のオフセット
    619        );
    620
    621        /**
    622         * 時間スケールに対応したSTEP数を管理するための内部クラス
    623         *
    624         * 時間ヘッダーを表示する場合、ある程度意味のある間隔でラベル表示したいと思います。
    625         * 全体の描画領域の長さと、時間当たりのスケールファクター（ピクセル数）から、
    626         * ラベルの描画間隔を求めます。
    627         * 意味のある間隔は、STEPS で定義し、10分,30分,60分,1/4日,1/2日,1日 まで定義しています。
    628         * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
    629         *
    630         * 一時間当たりの表示幅を、MIN_PX としていますので、この値以下の間隔では描画されません。
    631         * 初期値は、600px を 24時間表示できる 600px/24h = 25px にしています。
    632         */
    633        private static final class TimeScaleStep {
    634                                                                                                        // 分   分   時   1/4   1/2  １日
    635                private static final int[] STEPS = new int[] { 10 , 30 , 60 , 360 , 720 , 1440 };
    636                private static final int MIN_PX = 25;           // スケールに対する最小値
    637
    638                /**
    639                 * 時間を意味のある範囲の整数として返します。
    640                 *
    641                 * 全体の描画領域の長さと、時間当たりのスケールファクター（ピクセル数）から、
    642                 * 10分,30分,60分,1/4日,1/2日,1日 までの整数値で返します。
    643                 * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
    644                 *
    645                 * @param       timeScale       時間(分)当たりのピクセル数
    646                 * @return      時間スケールに対応した意味のある範囲の整数
    647                 */
    648                public static final int getStep( final double timeScale ) {
    649                        int tmStep = (int)Math.ceil(MIN_PX/(timeScale));        // 整数切り上げ
    650
    651                        for( int i=0; i<STEPS.length; i++ ) {
    652                                if( tmStep <= STEPS[i] ) { return STEPS[i]; }        // 配列の数字に切り上げ
    653                        }
    654
    655                        // 未設定の場合は、最上位の値の整数倍に切り上げ
    656                        return (int)Math.ceil( tmStep / STEPS[STEPS.length-1] ) * STEPS[STEPS.length-1];
    657                }
    658        }
    659
    660        /**
    661         * 状況コード、ラベル、色を管理するための内部クラス
    662         *
    663         * 状況に応じたコード、ラベル、色を管理します。
    664         * これは、getColor(状況コード,ラベル) または、getColor(状況コード,ラベル,色文字列) で
    665         * 要求された情報を内部で管理し、同じコードの場合に同じ色を返します。
    666         * また、凡例作成用に、最も文字数が長いラベルを管理します。
    667         * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
    668         * これは、同一状況コードで色違いを作成することができないことを意味します。
    669         * 色文字列を指定しない場合は、内部の色配列から、順番に色を割り当てます。
    670         * 色を割り当てる順番は、状況コードの発生順です。よって、検索条件によって、
    671         * 状況コードの現れる順番が異なると、色も毎回異なることになります。
    672         *
    673         * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW
    674         * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
    675         * よって、どのような色になるかは全くわかりません。
    676         */
    677        private static final class ColorMap {
    678                private static final Color[] CLR_ARY = new Color[] {
    679                                Color.BLUE      ,Color.CYAN   ,Color.GRAY ,Color.GREEN ,Color.LIGHT_GRAY ,Color.MAGENTA ,
    680                                Color.DARK_GRAY ,Color.ORANGE ,Color.PINK ,Color.RED   ,Color.YELLOW
    681                };
    682                private int lastCnt = 0;
    683                private final Map<String,Object[]> colMap = new TreeMap<String,Object[]>();
    684                private String maxLabel = "" ;          // 最大長のラベル
    685                private int    maxlen   = -1 ;          // 最大長のラベルのlength()
    686
    687                /**
    688                 * 状況コードに対応した色オブジェクトを返します。
    689                 *
    690                 * 状況コードが初めて指定された場合は、順番に内部の色を割り当てます。
    691                 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
    692                 *
    693                 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW
    694                 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
    695                 * よって、どのような色になるかは全くわかりません。
    696                 *
    697                 * @param       fgj     状況コード
    698                 * @param       lbl     状況コードのラベル
    699                 * @return      状況コードに対応した色オブジェクト
    700                 */
    701                public Color getColor( final String fgj,final String lbl ) {
    702                        return getColor( fgj,lbl,null );
    703                }
    704
    705                /**
    706                 * 状況コードに対応した色オブジェクトを返します。
    707                 *
    708                 * 状況コードが初めて指定された場合は、引数の色文字列の色を割り当てます。
    709                 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
    710                 *
    711                 * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
    712                 * これは、同一状況コードで色違いを作成することができないことを意味します。
    713                 * 色文字列 が null の場合は、自動割り当てのメソッドと同じです。
    714                 * よって、色文字列の指定と、自動割り当てが同時に発生すると、異なる状況コードで
    715                 * 同じ色が指定される可能性がありますので、混在して使用しないでください。
    716                 *
    717                 * @param       fgj             状況コード
    718                 * @param       lbl             状況コードのラベル
    719                 * @param       colStr  状況コードに対応した色文字列(nullの場合は、自動割り当て)
    720                 * @return      状況コードに対応した色オブジェクト
    721                 */
    722                public Color getColor( final String fgj,final String lbl,final String colStr ) {
    723                        if( fgj == null ) { return LABEL_COLOR; }
    724                        if( lbl != null ) {
    725                                int len = lbl.length();
    726                                if( len > maxlen ) { maxLabel = lbl; maxlen = len; }
    727                        }
    728
    729                        Object[] obj = colMap.get( fgj );
    730                        if( obj == null ) {
    731                                obj = new Object[2];
    732                                obj[0] = lbl;
    733                                obj[1] = (colStr != null) ? StringUtil.getColorInstance( colStr ) : uniqColor();
    734
    735                                colMap.put( fgj,obj );
    736                        }
    737
    738                        return (Color)obj[1] ;
    739                }
    740
    741                /**
    742                 * 内部のシーケンスに対応した、ユニークな色オブジェクトを返します。
    743                 *
    744                 * 内部カウンターを＋１ しながら、内部の色オブジェクトを返します。
    745                 *
    746                 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW
    747                 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
    748                 * よって、どのような色になるかは全くわかりません。
    749                 *
    750                 * @return      ユニークな色オブジェクト
    751                 */
    752                public Color uniqColor() {
    753                        Color col = null;
    754                        if( lastCnt < CLR_ARY.length ) {
    755                                col = CLR_ARY[lastCnt++];
    756                        }
    757                        else {
    758                                int R=(int)(Math.random()*256);
    759                                int G=(int)(Math.random()*256);
    760                                int B=(int)(Math.random()*256);
    761                                col = new Color(R,G,B);
    762                        }
    763
    764                        return col;
    765                }
    766
    767                /**
    768                 * 内部で管理している、ラベル(String)と色オブジェクト(Color)の コレクションを返します。
    769                 *
    770                 * 内部で管理しているコレクションです。
    771                 * このコレクションは、状況コードでソートされています。
    772                 * コレクションの中身は、オブジェクト配列となっており、[0]は、String型のラベル、[1]は、
    773                 * Color型の色です。
    774                 *
    775                 * @return      ラベル(String)と色オブジェクト(Color)の コレクション
    776                 */
    777                public Collection<Object[]> values() {
    778                        return colMap.values();
    779                }
    780
    781                /**
    782                 * 登録されたラベル文字列で、最も文字数が長いラベルを返します。
    783                 *
    784                 * 凡例で、ラベルの最大長を求めるのに利用できます。
    785                 * ただし、簡易的に、length() 計算しているだけなので、英語、日本語混在や、
    786                 * プロポーショナルフォント使用時の厳密な最大長の文字列ではありません。
    787                 *
    788                 * @return      最も文字数が長いラベル
    789                 */
    790                public String getMaxLengthLabel() { return maxLabel; }
    791        }
    792
    793        /**
    794         * 時間文字列を数字に変換します。
    795         *
    796         * "0800" は、480に、"2100" は、1260 に変換します。
    797         * つまり、分単位の値に変換します。秒の単位は、切り捨てます。
    798         * ここでは、時間文字列が、08:00 の様に、":" を含む場合は、削除し、
    799         * ４ケタ以上の場合は、前から４ケタ分を使います。
    800         * ４ケタ以下（１，２，３ケタ）でも、先頭に 0 埋めで、時間文字列にします。
    801         * ０ケタ、および、null の場合は、defTm 初期値 を返します。
    802         *
    803         * @param       val     時間文字列の値(0800 など)
    804         * @param       defTm   引数の時間文字列が null の場合の初期値
    805         *
    806         * @return      時間文字列を数字に変換した結果( 80 など)
    807         */
    808        private int getStr2Time( final String val , final int defTm ) {
    809                if( val == null || val.isEmpty() ) { return defTm; }
    810
    811                // 桁数によって、処理の仕方が変わるため。
    812                int rtnInt = 0;
    813                switch( val.length() ) {
    814                        case    1:
    815                        case    2:      rtnInt = Integer.parseInt( val ); break;
    816                        case    3:      rtnInt = Integer.parseInt( val.substring( 0,1 ) )*60 + Integer.parseInt( val.substring( 1,3 ) ); break;
    817                        default  :      rtnInt = Integer.parseInt( val.substring( 0,2 ) )*60 + Integer.parseInt( val.substring( 2,4 ) ); break;
    818                }
    819
    820                return rtnInt ;
    821        }
    822
    823        /**
    824         * 日時文字列を数字に変換します。
    825         *
    826         * 日時文字列は、yyyyMMdd または、yyyyMMddhhmmss 形式とします。
    827         * これを、エポックタイムからの経過時間の 分単位の値を求めます。
    828         * 具体的には、Calendar オブジェクトの getTimeInMillis() の結果を、
    829         * 60000 で割り算した値を作成します。
    830         * よって、Calendar オブジェクト作成時も、秒の単位は切り捨てます。
    831         * 引数が null の場合は、現在時刻より、作成します。
    832         *
    833         * @param       val     日時文字列の値(yyyyMMdd または、yyyyMMddhhmmss 形式 など)
    834         *
    835         * @return      日時文字列を分換算した数字
    836         */
    837        private long getStr2Date( final String val ) {
    838                Calendar cal = Calendar.getInstance();
    839                str2DateTime = 0;
    840                if( val == null ) {
    841                        cal.set( Calendar.HOUR_OF_DAY, 0 );             // 5.3.5.0 (2011/05/01) 時間の解決規則が適用されるため、｢時｣だけは、setメソッドで 0 にセットする。
    842                        cal.clear( Calendar.MINUTE );
    843                        cal.clear( Calendar.SECOND );
    844                        cal.clear( Calendar.MILLISECOND );
    845                }
    846                else if( val.length() == 8 ) {
    847                        cal.clear();
    848                        cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,                     // 年
    849                                         Integer.parseInt( val.substring( 4,6 ) ) - 1,          // 月(0から始まる)
    850                                         Integer.parseInt( val.substring( 6,8 ) )                       // 日
    851                        );
    852                }
    853                else {
    854                        cal.clear();
    855                        cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,                     // 年
    856                                         Integer.parseInt( val.substring( 4,6 ) ) - 1,          // 月(0から始まる)
    857                                         Integer.parseInt( val.substring( 6,8 ) ) ,                     // 日
    858                                         Integer.parseInt( val.substring( 8,10 ) ) ,            // 時
    859                                         Integer.parseInt( val.substring( 10,12 ) )             // 分
    860                        );
    861                        str2DateTime = Integer.parseInt( val.substring( 8,10 ) ) * 60 + Integer.parseInt( val.substring( 10,12 ) ) ;
    862                }
    863                return (cal.getTimeInMillis() / 60000L);                // 分単位に変換する。
    864        }
    865
    866        // 5.6.5.0 (2013/06/07) 曜日データを配列で持っておきます。
    867        private static final String[] DAY_OF_WEEK_ja = new String[] { "土","日","月","火","水","木","金","土" };        // [0]="土" は、1〜7 の余計算で、 7=0 になる為。
    868
    869        /**
    870         * 数字(分)を時間文字列に変換します。
    871         *
    872         * 480 は、"08" に、1260 は、"21" に変換します。
    873         * 引数の時間は、分を表す整数です。２４時間表記であれば、0 〜 1440 の範囲で収まりますが、
    874         * 期間が長い場合は、その値を超えます。また、２４時間を超える場合は、0 に戻ります。
    875         * 文字列にする場合の最小単位は、(時)なので、60(分)で割り算して、余は、切り捨てます。
    876         * step は、60(分)単位の表示時に飛ばす数です。step=1 なら、60(分)単位、step=2 なら、120(分)
    877         * 単位となります。stepが、24 以下の場合は、そのまま、24時間表記で構いませんが、
    878         * それを超えると時間ではなく、日数表記に切り替わります。
    879         *
    880         * @og.rev 5.6.5.0 (2013/06/07) 月単位の場合は、曜日を表示します。
    881         *
    882         * @param       timeVal 引数の時間整数(分)
    883         * @param       step    60分単位のステップ数( 10,30,60,720,1440 単位となるように調整)
    884         * @param       week    カレンダクラスの曜日フィールド(DAY_OF_WEEK)
    885         *
    886         * @return      数字を時間文字列に変換した結果( "08" など)
    887         */
    888//      private String getTime2Str( final int timeVal, final int step ) {
    889        private String getTime2Str( final int timeVal, final int step, final int week ) {
    890
    891                int dtm = (timeVal / 60 / 24 );                 // 日
    892                int htm = ((timeVal / 60) % 24 );               // 時(２４時間制)
    893                int mtm = (timeVal % 60 );                              // 分(６０分制)
    894
    895                StringBuilder rtn = new StringBuilder();
    896
    897                if( step >= 1440 ) {
    898//                      rtn.append( dtm ).append( "d" );                                                        // 5.6.5.0 (2013/06/07) 相対日付廃止
    899                        rtn.append( DAY_OF_WEEK_ja[ ( dtm + week ) % 7 ] );                     // 曜日を表示
    900                }
    901                else {
    902                        if( htm < 10 ) { rtn.append( "0" ); }
    903                        rtn.append( htm );
    904
    905                        if( step < 60 ) {
    906                                rtn.append( ":" );
    907                                if( mtm < 10 ) { rtn.append( "0" ); }
    908                                rtn.append( mtm );
    909                        }
    910                }
    911
    912                return rtn.toString();
    913        }
    914
    915        // 引数に、 を追加する。
    916
    917        /**
    918         * 表示項目の編集(並び替え)が可能かどうかを返します
    919         *
    920         * @return      表示項目の編集(並び替え)が可能かどうか(false:不可能)
    921         */
    922        @Override
    923        public boolean isEditable() {
    924                return false;
    925        }
    926}