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