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