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 * @og.rev 7.2.9.0 (2020/10/12) type(#,$,!)を考慮した関数型I/F対応 212 * 213 * @param row 行番号( [I]フォーマット処理用 ) 214 * 215 * @return 指定のObject配列を元に作成したフォーマット文字列 216 * @og.rtnNotNull 217 */ 218 public String getFormatString( final int row ) { 219// return getFormatString( row, null ); 220 return getFormatString( row, null, (val,typ,prm) -> val ); 221 } 222 223 /** 224 * 指定の 行番号に対する、DataModel を元に作成したフォーマット文字列を返します。 225 * データはseparatorで指定された区切り文字で囲まれて返されます。 226 * 227 * @og.rev 7.2.9.0 (2020/10/12) type(#,$,!)を考慮した関数型I/F対応 228 * 229 * @param row 行番号( [I]フォーマット処理用 ) 230 * @param separator セパレーター 231 * 232 * @return 指定のObject配列を元に作成したフォーマット文字列 233 * @og.rtnNotNull 234 */ 235 public String getFormatString( final int row, final String separator ) { 236// return getFormatString( row, null ); 237 return getFormatString( row, separator, (val,typ,prm) -> val ); 238 } 239 240 /** 241 * 指定の 行番号に対する、DataModel を元に作成したフォーマット文字列を返します。 242 * データはseparatorで指定された区切り文字で囲まれて返されます。 243 * 244 * @og.rev 4.3.1.1 (2008/08/23) switch に、default label が存在しないため、追加 245 * @og.rev 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。 246 * @og.rev 7.2.9.0 (2020/10/12) type(#,$,!)を考慮した関数型I/F対応 247 * 248 * @param row 行番号( [I]フォーマット処理用 ) 249 * @param separator セパレーター 250 * @param triFunc TriFunction関数 251 * 252 * @return 内部のDataModelを元に作成したフォーマット文字列 253 * @og.rtnNotNull 254 */ 255// public String getFormatString( final int row, final String separator ) { 256 public String getFormatString( final int row, final String separator, final TriFunction<String,Character,String,String> triFunc ) { 257 final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE ); 258 259 final int count = clmNos.length; 260 for( int i=0; i<count; i++ ) { 261 rtnStr.append( format[i] ); 262 if( clmNos[i] == SYS_ROWNUM ) { 263 rtnStr.append( row ); 264 } 265 else if( clmNos[i] == SYS_JSON ) { 266 rtnStr.append( getJson( row ) ); 267 } 268 else { 269 // 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。 270 final Object obj = model.getValue( row,clmNos[i] ); 271 final String val = obj == null ? "" : String.valueOf( obj ); 272 273 if( separator == null || separator.isEmpty() ) { 274// rtnStr.append( val ); 275 // separator の使い方がおかしい。 276 rtnStr.append( triFunc.apply( val,type[i],clmPrms[i] ) ); // 7.2.9.0 (2020/10/12) 277 } 278 else { 279 // 4.3.1.1 (2008/08/23) default label が存在しないため、追加 280 switch( model.getNativeType( clmNos[i] ) ) { 281 case INT: 282 case LONG: 283 case DOUBLE: 284 rtnStr.append( val ); 285 break; 286 case STRING: 287 case CALENDAR: 288 rtnStr.append( separator ).append( val ).append( separator ); 289 break; 290 default: 291 throw new AssertionError( "Unexpected enumrated value! " + model.getNativeType( clmNos[i] ) ); 292 } 293 } 294 } 295 } 296 rtnStr.append( format[count] ); 297 298 return rtnStr.toString(); 299 } 300 301 /** 302 * 引数を3つ取る Function の 関数型インターフェースを定義します。 303 * 304 * @og.rev 7.2.9.0 (2020/10/12) type(#,$,!)を考慮した関数型I/F対応 305 * 306 * @param <V> 引数1の総称型 変換前の値を想定 307 * @param <T> 引数2の総称型 タイプ(#,$,!)を想定 308 * @param <P> 引数3の総称型 パラメータ(%L,%Sなど)を想定 309 * @param <R> returnの総称型 Format後の値を想定 310 */ 311 public interface TriFunction<V,T,P,R> { 312// public static interface TriFunction<V,T,P,R> { 313 /** 314 * 指定された引数にこの関数を適用します。 315 * 316 * @og.rev 7.2.9.0 (2020/10/12) type(#,$,!)を考慮した関数型I/F対応 317 * 318 * @param val 引数1の総称型 変換前の値を想定 319 * @param typ 引数2の総称型 タイプ(#,$,!)を想定 320 * @param prm 引数3の総称型 パラメータ(%L,%Sなど)を想定 321 * @return returnの総称型 Format後の値を想定 322 */ 323 R apply( V val,T typ,P prm ); 324 } 325 326 /** 327 * 引数の DataModel を元に作成したフォーマット文字列を返します。 328 * これは、簡易処理で、DataModel オブジェクトは、実質的には、LineModel です。 329 * パッケージの関連から、引数が、DataModel オブジェクトになっています。 330 * 331 * @og.rev 6.3.2.0 (2015/07/10) LineModelで、Formatter処理できるように、対応します。 332 * 333 * @param model DataModelオブジェクト(実質的には、LineModelオブジェクト) 334 * 335 * @return 引数のDataModelを元に作成したフォーマット文字列 336 * @og.rtnNotNull 337 */ 338 public String getLineFormatString( final DataModel<Object> model ) { 339 final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE ); 340 341 final int count = clmNos.length; 342 for( int i=0; i<count; i++ ) { 343 rtnStr.append( format[i] ) 344 .append( model.getValue( 0,clmNos[i] ) ); // 行番号は、0 にしておきます。 345 } 346 rtnStr.append( format[count] ); 347 348 return rtnStr.toString(); 349 } 350 351 /** 352 * 先のフォーマット情報の[カラム名]を、クエスチョンマークに置き換えたフォーマットを返します。 353 * 354 * これは、java.sql.PreparedStatement 形式のQuery文字列を合成する場合に使用します。 355 * 356 * @return PreparedStatement形式のQuery文字列 357 * @og.rtnNotNull 358 */ 359 public String getQueryFormatString() { 360 final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE ); 361 362 final int count = clmKeys.length; 363 for( int i=0; i<count; i++ ) { 364 rtnStr.append( format[i] ).append( '?' ); 365 } 366 rtnStr.append( format[count] ); 367 368 return rtnStr.toString(); 369 } 370 371 /** 372 * フォーマットのカラム名配列を返します。 373 * 374 * @return フォーマットのカラム名配列 375 * @og.rtnNotNull 376 */ 377 public String[] getClmKeys() { 378 return clmKeys.clone(); 379 } 380 381 /** 382 * フォーマットのカラムのパラメータ配列を返します。 383 * 384 * [#XXX] 、[$XXX]、[!XXX] で、カラムオブジェクトに渡すパラメータを、[$XXX param]形式で 385 * 指定できます。param 部分を、カラム配列と関連付けて、返します。 386 * 未設定のパラメータは、null が設定されています。 387 * 388 * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。 389 * 390 * @return フォーマットのパラメータ名配列 391 * @og.rtnNotNull 392 */ 393 public String[] getClmPrms() { 394 return clmPrms.clone(); 395 } 396 397 /** 398 * フォーマットの指定の位置のカラムのパラメータを返します。 399 * 400 * [#XXX] 、[$XXX]、[!XXX] で、カラムオブジェクトに渡すパラメータを、[$XXX param]形式で 401 * 指定できます。param 部分を、カラム番号を指定することで、返します。 402 * 未設定のパラメータは、null が設定されています。 403 * 404 * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。 405 * 406 * @param ad パラメータのアドレス(カラムと同じ位置) 407 * @return フォーマットのパラメータ 408 * @og.rtnNotNull 409 */ 410 public String getClmParam( final int ad ) { 411 return ad >= 0 && ad < clmPrms.length ? clmPrms[ad] : null; 412 } 413 414 /** 415 * フォーマットのカラム番号配列を返します。 416 * 417 * @return フォーマットのカラム番号配列 418 * @og.rtnNotNull 419 */ 420 public int[] getClmNos() { 421 return clmNos.clone(); 422 } 423 424 /** 425 * フォーマット配列を返します。 426 * 427 * @return フォーマット配列 428 * @og.rtnNotNull 429 */ 430 public String[] getFormat() { 431 return format.clone(); 432 } 433 434 /** 435 * タイプ文字列配列を返します。 436 * タイプとは、[XXX] の記述で、[#XXX] は、XXXカラムのラベルを、[$XXX]は、XXXカラムの 437 * レンデラーを、[!XXX] は、値のみ取り出す指定を行います。 438 * 主に、TextField系のフォーマットとTable系では、意味合いが異なりますので、 439 * ご注意ください。 440 * 441 * @return タイプ文字列配列 '#':ラベルのみ '$':レンデラー '!':値のみ その他:通常 442 * @og.rtnNotNull 443 */ 444 public char[] getType() { 445 return type.clone(); 446 } 447 448 /** 449 * 行毎の全データのJavaScriptオブジェクト形式 を返します。 450 * 451 * JavaScriptオブジェクト形式とは、{ key:val,key:val,... }の形式で、 452 * ここでは、内部設定された DataModel のすべてのカラム名をキーに、 453 * 引数で渡された 配列を 値として使用します。 454 * 455 * @og.rev 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。 456 * 457 * @param row (DataModelの)行番号 458 * 459 * @return 指定の行番号に対応する全データのJSON形式データ 460 * @og.rtnNotNull 461 */ 462 public String getJson( final int row ) { 463 final String[] names = model.getNames(); 464 final Object[] vals = model.getValues( row ); 465 466 final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE ); 467 468 rtnStr.append( "{'I':'" ).append( row ).append( '\'' ); // 行番号 469 470 for( int i=0; i<names.length; i++ ) { 471 // 6.4.3.2 (2016/02/19) 値が null の場合は、ゼロ文字列を設定する。 472 rtnStr.append( ",'" ).append( names[i] ).append( "':'" ) 473 .append( vals[i] == null ? "" : vals[i] ).append( '\'' ); 474 } 475 rtnStr.append( '}' ); // 6.0.2.5 (2014/10/31) char を append する。 476 477 return rtnStr.toString(); 478 } 479}