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