    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.fukurou.model;
    017
    018import java.util.ArrayList;
    019import java.util.List;
    020
    021import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    022import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
    023
    024/**
    025 * [PN],[OYA] などの [] で指定されたカラムで表されたフォーマットデータに対して、
    026 * DataModel オブジェクトを適用して 各カラムに実データを割り当てるオブジェクトです。
    027 *
    028 * カラム名には、特殊カラム名が使用できます。これは、DataModel に存在しないカラム名
    029 * ですが、値を返すことが出来ます。
    030 * <pre>
    031 * [KEY.カラム名] : 行番号付きカラム名
    032 * [I]            : 行番号
    033 * [ROW.ID]       : 行毎のチェックボックスのID
    034 * [ROW.JSON]     : 行毎の全データのJavaScriptオブジェクト形式 { key:val,key:val,... }
    035 * カラムの前に修飾記号(#,$,!)を付けるとフォーマットを変更できます。
    036 * ただし、FormatTextField 系 と FormatTable 系で、出力される形式が異なります。
    037 *                  FormatTextField 系               FormatTable 系
    038 * [#カラム名]    : TDなしのラベルと入力フィールド   ラベルを出力
    039 * [$カラム名]    : TDなしの入力フィールドのみ       レンデラーを出力
    040 * [!カラム名]    : TDなしの値のみ                   値を出力
    041 *
    042 * </pre>
    043 * @og.group 画面表示
    044 *
    045 * @version  4.0
    046 * @author   Kazuhiko Hasegawa
    047 * @since    JDK5.0,
    048 */
    049public class Formatter {
    050        /** カラムID(連結文字列)行番号の連結文字列を定義 {@value} */
    051        public static final String JOINT_STRING = "__" ;
    052
    053        /** テーブル表示のチェックボックスを特定する id の 名称( id は、この名称＋行番号) {@value} */
    054        public static final String ROW_ID_KEY = "cb";   // 3.6.0.0 (2004/09/17)
    055
    056//      /** 特殊カラム名の定義: 行番号 [I]  */
    057//      public static final int SYS_ROWNUM      = -1;           // [KEY.カラム名],[I],[ROW.ID]
    058        /** 特殊カラム名の定義: [ROW.JSON]       */
    059        public static final int SYS_JSON        = -2;           // [ROW.JSON]
    060        /** 6.9.5.0 (2018/04/23) 特殊カラム名の定義: 行番号 [I]     */
    061        public static final int SYS_ROWNUM      = -3;           // [KEY.カラム名],[I],[ROW.ID] 6.9.5.0 (2018/04/23) -1 は、カラム無しと同じなので、-3 に変更
    062        /** 特殊カラム名の定義: 非表示              */
    063        public static final int NO_DISPLAY      = -9;           // 6.2.0.1 (2015/03/06) 非表示のマーカー
    064
    065        private final DataModel<?>      model   ;                       // 4.3.3.6 (2008/11/15) Generics警告対応
    066        private int[]                           clmNos  ;                       // フォーマットのカラム番号配列
    067        private String[]                        format  ;
    068        private String[]                        clmKeys ;                       // フォーマットのカラム名配列
    069        private String[]                        clmPrms ;                       // 6.8.3.1 (2017/12/01) フォーマットのカラムのパラメータ
    070        private char[]                          type    ;                       // '#':ラベルのみ  '$':レンデラー '!':値のみ  その他:通常
    071
    072        /**
    073         * データモデルとフォーマットを指定してフォーマッターを構築します。
    074         *
    075         * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
    076         *
    077         * @param       model データモデル
    078         * @param       fmt  [カラム名]形式のフォーマットデータ
    079         */
    080        public Formatter( final DataModel<?> model , final String fmt ) {
    081                this.model = model;
    082                makeFormatList( fmt );
    083                advanceFormat();
    084        }
    085
    086        /**
    087         * フォーマットをセットします。
    088         *
    089         * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    090         *
    091         * @param       fmt  [カラム名]形式のフォーマットデータ
    092         */
    093        private void makeFormatList( final String fmt ) {
    094                int start = 0;
    095                int index = fmt.indexOf( '[' );
    096                final List<String> formatList = new ArrayList<>();
    097                final List<String> clmKeyList = new ArrayList<>();
    098                while( index >= 0 ) {
    099                        final int end = fmt.indexOf( ']',index );
    100                        if( end < 0 ) {
    101                                final String errMsg = "[ と ] との対応関係がずれています。"
    102                                                                + "format=[" + fmt + "] : index=" + index ;
    103                                throw new OgRuntimeException( errMsg );
    104                        }
    105
    106                        // [ より前方の文字列は、formatList へ
    107                        if( index > 0 ) { formatList.add( fmt.substring( start,index ) ); }
    108                        else                    { formatList.add( "" ); }       // ][ と連続しているケース
    109
    110                        // [XXXX] の XXXX部分を処理
    111                        clmKeyList.add( fmt.substring( index+1,end ) );
    112
    113                        start = end+1 ;
    114                        index = fmt.indexOf( '[',start );
    115                }
    116                // ] の後方部分は、formatList へ
    117                formatList.add( fmt.substring( start ) );
    118
    119                format  = formatList.toArray( new String[formatList.size()] );
    120                clmKeys = clmKeyList.toArray( new String[clmKeyList.size()] );
    121                clmPrms = new String[clmKeyList.size()];                // 6.8.3.1 (2017/12/01)
    122        }
    123
    124        /**
    125         * 追加機能フォーマットを作成します。
    126         *
    127         * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    128         */
    129        private void advanceFormat() {
    130                final int size = clmKeys.length ;
    131                clmNos = new int[size];
    132                type   = new char[size];
    133
    134                // カラム番号の設定と、特殊カラム名処理
    135                String clm ;
    136                for( int i=0; i<size; i++ ) {
    137                        clm = clmKeys[i];
    138                        final char ch = clm.charAt(0);
    139                        if( ch == '#' || ch == '$' || ch == '!' ) {
    140                                type[i] = ch;
    141                                clm = clm.substring(1);
    142                                // 6.8.3.1 (2017/12/01) [$XXXX param] 対応。
    143                                final int sp = clm.indexOf( ' ' );              // スペース分割
    144                                if( sp > 0 ) {
    145                                        clmPrms[i] = clm.substring( sp+1 );     // 先にパラメータを取得
    146                                        clm = clm.substring( 0,sp );
    147                                }
    148                                clmKeys[i] = clm;
    149                                clmNos[i]  = model.getColumnNo( clm );  // 指定されたセルのカラム番号。存在しなければ、-1
    150                        }
    151                        // [KEY.カラム名] 機能追加
    152                        else if( clm.startsWith( "KEY." ) ) {
    153                                clmNos[i] = SYS_ROWNUM;
    154                                format[i] = format[i] + clm.substring(4) + JOINT_STRING ;
    155                        }
    156                        // [I] 機能追加
    157                        else if( "I".equals( clm ) ) {
    158                                clmNos[i] = SYS_ROWNUM;
    159                        }
    160                        // [ROW.ID] 機能追加
    161                        else if( "ROW.ID".equals( clm ) ) {
    162                                clmNos[i] = SYS_ROWNUM;
    163                                format[i] = format[i] + ROW_ID_KEY ;
    164                        }
    165                        // [ROW.JSON] 機能追加
    166                        else if( "ROW.JSON".equals( clm ) ) {
    167                                clmNos[i] = SYS_JSON;
    168                        }
    169                        else {
    170                                clmNos[i] = model.getColumnNo( clm );   // 指定されたセルのカラム番号。存在しなければ、-1
    171                        }
    172                }
    173        }
    174
    175        /**
    176         * column にあるセルの属性値をStringに変換して返します。
    177         *
    178         * @og.rev 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    179         *
    180         * @param       row     処理中の行番号
    181         * @param       clm     値が参照されるカラム番号
    182         *
    183         * @return      指定されたセルの値
    184         *
    185         */
    186        public String getValue( final int row,final int clm ) {
    187                final String rtn ;
    188                if( clm >= 0 ) {
    189                        // 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    190                        final Object obj = model.getValue( row,clm );
    191                        rtn = obj == null ? "" : String.valueOf( obj );
    192                }
    193                else if( clm == SYS_ROWNUM ) {
    194                        rtn = String.valueOf( row );
    195                }
    196                else if( clm == SYS_JSON ) {
    197                        rtn = getJson( row );
    198                }
    199                else {
    200                        final String errMsg = "指定のカラム番号に該当する処理が見つかりません。"
    201                                                        + "clm=[" + clm + "]" ;
    202                        throw new OgRuntimeException( errMsg );
    203                }
    204
    205                return rtn ;
    206        }
    207
    208        /**
    209         * 指定の 行番号に対する、DataModel を元に作成したフォーマット文字列を返します。
    210         *
    211         * @param       row     行番号( [I]フォーマット処理用 )
    212         *
    213         * @return  指定のObject配列を元に作成したフォーマット文字列
    214         */
    215        public String getFormatString( final int row ) {
    216                return getFormatString( row, null );
    217        }
    218
    219        /**
    220         * 指定の 行番号に対する、DataModel を元に作成したフォーマット文字列を返します。
    221         * データはseparatorで指定された区切り文字で囲まれて返されます。
    222         *
    223         * @og.rev 4.3.1.1 (2008/08/23) switch に、default label が存在しないため、追加
    224         * @og.rev 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    225         *
    226         * @param       row     行番号( [I]フォーマット処理用 )
    227         * @param       separator       セパレーター
    228         *
    229         * @return  内部のDataModelを元に作成したフォーマット文字列
    230         * @og.rtnNotNull
    231         */
    232        public String getFormatString( final int row, final String separator ) {
    233                final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE );
    234
    235                final int count = clmNos.length;
    236                for( int i=0; i<count; i++ ) {
    237                        rtnStr.append( format[i] );
    238                        if( clmNos[i] == SYS_ROWNUM ) {
    239                                rtnStr.append( row );
    240                        }
    241                        else if( clmNos[i] == SYS_JSON ) {
    242                                rtnStr.append( getJson( row ) );
    243                        }
    244                        else {
    245                                // 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    246                                final Object obj = model.getValue( row,clmNos[i] );
    247                                final String val = obj == null ? "" : String.valueOf( obj );
    248
    249                                if( separator == null || separator.isEmpty() ) {
    250                                        rtnStr.append( val );
    251                                }
    252                                else {
    253                                        // 4.3.1.1 (2008/08/23) default label が存在しないため、追加
    254                                        switch( model.getNativeType( clmNos[i] ) ) {
    255                                                case INT:
    256                                                case LONG:
    257                                                case DOUBLE:
    258                                                        rtnStr.append( val );
    259                                                        break;
    260                                                case STRING:
    261                                                case CALENDAR:
    262                                                        rtnStr.append( separator ).append( val ).append( separator );
    263                                                        break;
    264                                                default:
    265                                                        throw new AssertionError( "Unexpected enumrated value! " + model.getNativeType( clmNos[i] ) );
    266                                        }
    267                                }
    268                        }
    269                }
    270                rtnStr.append( format[count] );
    271
    272                return rtnStr.toString();
    273        }
    274
    275        /**
    276         * 引数の DataModel を元に作成したフォーマット文字列を返します。
    277         * これは、簡易処理で、DataModel オブジェクトは、実質的には、LineModel です。
    278         * パッケージの関連から、引数が、DataModel オブジェクトになっています。
    279         *
    280         * @og.rev 6.3.2.0 (2015/07/10) LineModelで、Formatter処理できるように、対応します。
    281         *
    282         * @param       model   DataModelオブジェクト(実質的には、LineModelオブジェクト)
    283         *
    284         * @return  引数のDataModelを元に作成したフォーマット文字列
    285         * @og.rtnNotNull
    286         */
    287        public String getLineFormatString( final DataModel<Object> model ) {
    288                final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE );
    289
    290                final int count = clmNos.length;
    291                for( int i=0; i<count; i++ ) {
    292                        rtnStr.append( format[i] )
    293                                .append( model.getValue( 0,clmNos[i] ) );               // 行番号は、0 にしておきます。
    294                }
    295                rtnStr.append( format[count] );
    296
    297                return rtnStr.toString();
    298        }
    299
    300        /**
    301         * 先のフォーマット情報の［カラム名］を、クエスチョンマークに置き換えたフォーマットを返します。
    302         *
    303         * これは、java.sql.PreparedStatement 形式のQuery文字列を合成する場合に使用します。
    304         *
    305         * @return  PreparedStatement形式のQuery文字列
    306         * @og.rtnNotNull
    307         */
    308        public String getQueryFormatString() {
    309                final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE );
    310
    311                final int count = clmKeys.length;
    312                for( int i=0; i<count; i++ ) {
    313                        rtnStr.append( format[i] ).append( '?' );
    314                }
    315                rtnStr.append( format[count] );
    316
    317                return rtnStr.toString();
    318        }
    319
    320        /**
    321         * フォーマットのカラム名配列を返します。
    322         *
    323         * @return      フォーマットのカラム名配列
    324         * @og.rtnNotNull
    325         */
    326        public String[] getClmKeys() {
    327                return clmKeys.clone();
    328        }
    329
    330        /**
    331         * フォーマットのカラムのパラメータ配列を返します。
    332         *
    333         * [#XXX] 、[$XXX]、[!XXX] で、カラムオブジェクトに渡すパラメータを、[$XXX param]形式で
    334         * 指定できます。param 部分を、カラム配列と関連付けて、返します。
    335         * 未設定のパラメータは、null が設定されています。
    336         *
    337         * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    338         *
    339         * @return      フォーマットのパラメータ名配列
    340         * @og.rtnNotNull
    341         */
    342        public String[] getClmPrms() {
    343                return clmPrms.clone();
    344        }
    345
    346        /**
    347         * フォーマットの指定の位置のカラムのパラメータを返します。
    348         *
    349         * [#XXX] 、[$XXX]、[!XXX] で、カラムオブジェクトに渡すパラメータを、[$XXX param]形式で
    350         * 指定できます。param 部分を、カラム番号を指定することで、返します。
    351         * 未設定のパラメータは、null が設定されています。
    352         *
    353         * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    354         *
    355         * @param       ad パラメータのアドレス(カラムと同じ位置)
    356         * @return      フォーマットのパラメータ
    357         * @og.rtnNotNull
    358         */
    359        public String getClmParam( final int ad ) {
    360                return ad >= 0 && ad < clmPrms.length ? clmPrms[ad] : null;
    361        }
    362
    363        /**
    364         * フォーマットのカラム番号配列を返します。
    365         *
    366         * @return      フォーマットのカラム番号配列
    367         * @og.rtnNotNull
    368         */
    369        public int[] getClmNos() {
    370                return clmNos.clone();
    371        }
    372
    373        /**
    374         * フォーマット配列を返します。
    375         *
    376         * @return      フォーマット配列
    377         * @og.rtnNotNull
    378         */
    379        public String[] getFormat() {
    380                return format.clone();
    381        }
    382
    383        /**
    384         * タイプ文字列配列を返します。
    385         * タイプとは、[XXX] の記述で、[#XXX] は、XXXカラムのラベルを、[$XXX]は、XXXカラムの
    386         * レンデラーを、[!XXX] は、値のみ取り出す指定を行います。
    387         * 主に、TextField系のフォーマットとTable系では、意味合いが異なりますので、
    388         * ご注意ください。
    389         *
    390         * @return タイプ文字列配列 '#':ラベルのみ  '$':レンデラー '!':値のみ  その他:通常
    391         * @og.rtnNotNull
    392         */
    393        public char[] getType() {
    394                return type.clone();
    395        }
    396
    397        /**
    398         * 行毎の全データのJavaScriptオブジェクト形式 を返します。
    399         *
    400         * JavaScriptオブジェクト形式とは、{ key:val,key:val,... }の形式で、
    401         * ここでは、内部設定された DataModel のすべてのカラム名をキーに、
    402         * 引数で渡された 配列を 値として使用します。
    403         *
    404         * @og.rev 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    405         *
    406         * @param       row     (DataModelの)行番号
    407         *
    408         * @return  指定の行番号に対応する全データのJSON形式データ
    409         * @og.rtnNotNull
    410         */
    411        public String getJson( final int row ) {
    412                final String[] names = model.getNames();
    413                final Object[] vals  = model.getValues( row );
    414
    415                final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE );
    416
    417                rtnStr.append( "{'I':'" ).append( row ).append( '\'' ); // 行番号
    418
    419                for( int i=0; i<names.length; i++ ) {
    420                        // 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。
    421                        rtnStr.append( ",'" ).append( names[i] ).append( "':'" )
    422                                .append( vals[i] == null ? "" : vals[i] ).append( '\'' );
    423                }
    424                rtnStr.append( '}' );           // 6.0.2.5 (2014/10/31) char を append する。
    425
    426                return rtnStr.toString();
    427        }
    428}