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.business; 017 018import java.util.concurrent.ConcurrentMap; // 6.4.3.3 (2016/03/04) 019import java.util.concurrent.ConcurrentHashMap; // 6.4.3.1 (2016/02/12) refactoring 020import java.util.Arrays; 021 022import org.opengion.fukurou.model.DataModel; 023import org.opengion.fukurou.model.NativeType; 024import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 025import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 026 027/** 028 * 業務ロジックを処理するためのテーブルモデルです。 029 * 030 * このテーブルモデルでは、オブジェクト生成時に、カラム配列、値配列を元に、内部データを生成し、 031 * その後は、行の追加や値の変更はできません。 032 * 033 * @og.rev 5.1.1.0 (2009/12/01) 新規作成 034 * @og.group 業務ロジック 035 * 036 * @version 5.0 037 * @author Hiroki Nakamura 038 * @since JDK1.6, 039 */ 040// 8.5.5.1 (2024/02/29) spotbugs CT_CONSTRUCTOR_THROW(コンストラクタで、Excweptionを出さない) class を final にすれば、警告は消える。 041// public class ArrayTableModel implements DataModel<String> { 042public final class ArrayTableModel implements DataModel<String> { 043 044 private final String[] names; 045 private final String[][] vals; 046 private final String[] modTypes; 047 048 /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */ 049 private final ConcurrentMap<Integer,String[]> rtnMap = new ConcurrentHashMap<>() ; // 6.4.3.3 (2016/03/04) final化で、初期から作成しておきます。 050 051 /** 052 * 引数に名前配列、値配列を指定したコンストラクター 053 * 054 * @param nms 名前配列 055 * @param vs 値2重配列 056 * @throws IllegalArgumentException 引数の配列が不正な場合 057 */ 058 public ArrayTableModel( final String[] nms, final String[][] vs ) { 059 this( nms, vs, null ); 060 } 061 062 /** 063 * 引数に名前配列、値配列、変更区分配列を指定したコンストラクター 064 * 065 * @og.rev 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。 066 * @og.rev 5.7.2.3 (2014/01/31) vsのチェック条件を戻す 067 * @og.rev 5.7.3.1 (2014/02/14) nmsのチェック条件も戻す 068 * 069 * @param nms 名前配列 070 * @param vs 値2重配列 071 * @param ms 変更区分の配列 072 * @throws IllegalArgumentException 引数の配列が不正な場合 073 */ 074 public ArrayTableModel( final String[] nms, final String[][] vs, final String[] ms ) { 075 if( nms == null || nms.length == 0 ) { 076 final String errMsg = "引数の名前配列に、null は設定できません。"; 077 throw new IllegalArgumentException( errMsg ); 078 } 079 // 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。 080 // 5.7.2.3 (2014/01/31) 結果0行でlength=0で通るようなのでvsのエラーチェック条件を戻す。 081 if( vs == null ) { 082 final String errMsg = "引数の値配列に、null は設定できません。"; 083 throw new IllegalArgumentException( errMsg ); 084 } 085 // 5.7.3.1 (2014/02/14) 5.7.2.3での戻しでは不十分だったのでこちらも戻す 086 // 6.0.0.1 (2014/04/25) These nested if statements could be combined 087 if( vs.length > 0 && ( vs[0] == null || vs[0].length == 0 || nms.length != vs[0].length ) ) { 088 final String errMsg = "名前配列と値配列のカラム数が異なります。" + CR 089 + " nms =" + Arrays.toString( nms ) + CR 090 + " vs[0] =" + Arrays.toString( vs[0] ) ; 091 throw new IllegalArgumentException( errMsg ); 092 } 093 094 final int cols = nms.length; 095 names = new String[cols]; 096 System.arraycopy( nms, 0, names, 0, cols ); 097 098 final int rows = vs.length; 099 vals = new String[rows][cols]; 100 for( int i=0; i<rows; i++ ) { 101 System.arraycopy( vs[i], 0, vals[i], 0, cols ); 102 } 103 104 if( ms != null && ms.length > 0 ) { 105 if( vs.length == ms.length ) { 106 modTypes = new String[rows]; 107 System.arraycopy( ms, 0, modTypes, 0, rows ); 108 } 109 else { 110 // 5.6.7.0 (2013/07/27) エラーメッセージを判りやすくする。 111 final String errMsg = "変更区分を指定する場合、値配列の行数と一致する必要があります。" + CR 112 + " 変更区分 行数 =" + ms.length + CR 113 + " 値配列 行数 =" + vs.length ; 114 throw new IllegalArgumentException( errMsg ); 115 } 116 } 117 else { 118 modTypes = null; 119 } 120 } 121 122 /** 123 * rowで指定された行番号(インデックス番号)に行を追加します。 124 * 125 * 値配列をセットする場合は、以下の条件を満たす必要があります。 126 * 1.行番号は、0~(rowCount-1) の範囲 127 * 2.値配列は、not null、かつ 1件以上 128 * 3.値配列の個数は、内部カラム数と同じ 129 * 130 * ここで登録した値は、内部の値配列と別管理されますので、セット後に、再びゲットしても 131 * ここでセットした値を取り出すことはできません。 132 * また、同じ行番号でセットした場合は、後でセットした値が有効です。 133 * 134 * ※ ここでの更新時に、modifyType は、設定されません。 135 * 必要であれば、setModifyType メソッドで個別に設定してください。 136 * 137 * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。 138 * よって、オリジナルのDBTableModelの行番号ではありません。 139 * 140 * @og.rev 5.6.0.3 (2012/01/24) 変更された値を、書き戻す機能を追加します。 141 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 142 * 143 * @param inVals 配列値 144 * @param rowNo 追加するインデックス 145 * @throws IllegalArgumentException 引数が1,2,3の条件を満たさない場合。 146 */ 147 @Override // DataModel 148 public void setValues( final String[] inVals, final int rowNo ) { 149 if( rowNo < 0 || rowNo > getRowCount() ) { 150 final String errMsg = "引数のインデックスは、0~" + (getRowCount()-1) + " の間で指定してください。index=[" + rowNo + "]"; 151 throw new IllegalArgumentException( errMsg ); 152 } 153 else if( inVals == null || inVals.length == 0 ) { 154 final String errMsg = "引数の値配列に、null、または 0件配列は指定できません。index=[" + rowNo + "]"; 155 throw new IllegalArgumentException( errMsg ); 156 } 157 else if( inVals.length != names.length ) { 158 final String errMsg = "引数の値配列の個数と、内部カラム数が一致しません。" 159 + " index=[" + rowNo + "] : 引数個数=[" + inVals.length + "] != 内部カラム数=[" + names.length + "]"; 160 throw new IllegalArgumentException( errMsg ); 161 } 162 163 // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 164 165 final int cols = names.length; 166 final String[] newVals = new String[cols]; 167 System.arraycopy( inVals, 0, newVals, 0, cols ); 168 169 // 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。初期化処理の場所も移動。 170 // 2.0.0 (2024/01/12) PMD 7.0.0 UnnecessaryBoxing 171// rtnMap.put( Integer.valueOf( rowNo ) , newVals ); 172 rtnMap.put( rowNo , newVals ); 173 } 174 175 /** 176 * BizLogicで、データが変更された場合は、このMapで値の配列を返します。 177 * Mapのキーは、インデックス(row)のIntegerオブジェクトです。値は、設定された String配列です。 178 * なにも変更がされていなければ、空のConcurrentMapを返しましす。 179 * 180 * ※ インデックス(row)とは、このArrayTableModel に持つ vals 配列の行のインデックスです。 181 * よって、オリジナルのDBTableModelの行番号ではありません。 182 * 183 * @og.rev 5.6.0.3 (2012/01/24) 変更された値を、書き戻すためのMap<インデックス,値配列> を返します。 184 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 185 * @og.rev 6.4.3.3 (2016/03/04) 変更が無い場合は、nulllではなく、空のConcurrentMapを返しましす。 186 * 187 * @return 書き戻すためのMap<インデックス,値配列> 188 * @see AbstractBizLogic#isRequireTable() 189 * @og.rtnNotNull 190 */ 191 public ConcurrentMap<Integer,String[]> getModifyVals() { 192 return rtnMap; 193 } 194 195 /** 196 * カラム名に対応する カラム番号を返します。 197 * 198 * 特殊なカラムが指定された場合は、負の値を返します。 199 * 例えば、[KEY.カラム名]、[I]、[ROW.ID] など、特定の負の値を返します。 200 * また、カラム名が元のデータモデルに存在しない場合も、負の値か、 201 * Exception を返します。負の値なのか、Exception なのかは、 202 * 実装に依存します。 203 * 204 * @param columnName 値が参照されるカラム名 205 * 206 * @return 指定されたセルのカラム番号。存在しなければ、-1 207 * @throws IllegalArgumentException 引数のカラム名が null の場合 208 */ 209 @Override // DataModel 210 public int getColumnNo( final String columnName ) { 211 if( columnName == null ) { 212 final String errMsg = "引数のカラム名に、null は設定できません。"; 213 throw new IllegalArgumentException( errMsg ); 214 } 215 216 int address = -1; 217 for( int i=0; i<names.length; i++ ) { 218 if( columnName.equalsIgnoreCase( names[i] ) ) { 219 address = i; 220 break; 221 } 222 } 223 224 return address; 225 } 226 227 /** 228 * カラム名配列に対応する カラム番号配列を返します。 229 * 230 * これは、#getColumnNo( String ) に対する 複数のカラム名を検索した 231 * 場合と同じです。 232 * 233 * @og.rev 6.8.6.0 (2018/01/19) 可変長引数から、通常配列に変更。 234 * 235 * @param clmNms 値が参照されるカラム名配列(可変長引数) 236 * 237 * @return 指定されたセルのカラム番号配列。 238 * @og.rtnNotNull 239 */ 240// public int[] getColumnNos( final String... clmNms ) { 241 public int[] getColumnNos( final String[] clmNms ) { 242 if( clmNms == null || clmNms.length == 0 ) { return new int[0]; } // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 243 244 final int[] clmNos = new int[clmNms.length]; // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal 245 for( int j=0; j<clmNms.length; j++ ) { 246 int address = -1; 247 for( int i=0; i<names.length; i++ ) { 248 if( clmNms[j].equalsIgnoreCase( names[i] ) ) { 249 address = i; 250 break; 251 } 252 } 253 clmNos[j] = address; 254 } 255 256 return clmNos; 257 } 258 259 /** 260 * カラム名配列を返します。 261 * 262 * @return カラム名配列 263 * @og.rtnNotNull 264 */ 265 @Override // DataModel 266 public String[] getNames() { 267 return names.clone(); 268 } 269 270 /** 271 * 指定のカラム名引数に相当するデータを2重配列で返します。 272 * 273 * @og.rev 6.8.5.0 (2018/01/09) 新規追加 274 * @og.rev 8.5.5.1 (2024/02/29) spotbugs CT_CONSTRUCTOR_THROW(コンストラクタで、Excweptionを出さない) class を final にすれば、警告は消える。 275 * 276 * @param clmNms 値が参照されるカラム名配列(可変長引数) 277 * 278 * @return 指定された名引数に相当するデータの2重配列 279 * @og.rtnNotNull 280 */ 281// protected String[][] getValues( final String... clmNms ) { 282 /* default */ String[][] getValues( final String... clmNms ) { 283 final int[] clmNos = getColumnNos( clmNms ); 284 285 final String[][] rtns = new String[vals.length][clmNos.length]; 286 287 for( int row=0; row<vals.length; row++ ) { 288 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach 289// for( int j=0; j<clmNos.length; j++ ) { 290// final int col = clmNos[j]; 291// rtns[row][col] = vals[row][col]; 292// } 293 for( final int col : clmNos ) { 294 rtns[row][col] = vals[row][col]; 295 } 296 } 297 298 return rtns; 299 } 300 301 /** 302 * row にあるセルの属性値を配列で返します。 303 * 304 * @param row 値が参照される行 305 * 306 * @return 指定されたセルの属性値配列 307 * @og.rtnNotNull 308 */ 309 @Override // DataModel 310 public String[] getValues( final int row ) { 311 return vals[row].clone(); 312 } 313 314 /** 315 * row および clm にあるセルの属性値をStringに変換して返します。 316 * 317 * @param row 値が参照される行 318 * @param clm 値が参照される列 319 * 320 * @return 指定されたセルの値 321 */ 322 @Override // DataModel 323 public String getValue( final int row, final int clm ) { 324 return vals[row][clm]; 325 } 326 327 /** 328 * row および clm にあるセルの属性値をStringに変換して返します。 329 * 330 * @param row 値が参照される行 331 * @param clm 値が参照される列(キー) 332 * 333 * @return 指定されたセルの値 334 * 335 */ 336 public String getValue( final int row, final String clm ) { 337 return vals[row][getColumnNo( clm )]; 338 } 339 340 /** 341 * データテーブル内の行の数を返します。 342 * 343 * @return モデルの行数 344 * 345 */ 346 @Override // DataModel 347 public int getRowCount() { 348 return vals.length; 349 } 350 351 /** 352 * row 単位に変更されたタイプ(追加/変更/削除)を返します。 353 * タイプは始めに一度登録するとそれ以降に変更はかかりません。 354 * つまり、始めに 追加で作成したデータは、その後変更があっても追加のままです。 355 * なにも変更されていない場合は、""(ゼロストリング)を返します。 356 * 357 * @param row 値が参照される行 358 * 359 * @return 変更されたタイプの値 360 */ 361 @Override // DataModel 362 public String getModifyType( final int row ) { 363 return modTypes == null ? "" : modTypes[row]; 364 } 365 366 /** 367 * row 単位に変更タイプ(追加/変更/削除)をセットします。 368 * このメソッドでは、データのバックアップは取りません。 369 * タイプは始めに一度登録するとそれ以降に変更はかかりません。 370 * なにも変更されていない場合は、""(ゼロストリング)の状態です。 371 * 372 * @og.rev 6.7.9.1 (2017/05/19) インターフェースの見直しにより、追加 373 * 374 * @param row 値が参照される行 375 * @param modType 変更タイプ(追加/変更/削除) 376 * 377 */ 378 @Override // DataModel 379 public void setModifyType( final int row , final String modType ) { 380 if( modTypes[row].isEmpty() ) { modTypes[row] = modType; } 381 } 382 383 /** 384 * clm のNativeタイプを返します。 385 * Nativeタイプはorg.opengion.fukurou.model.NativeTypeで定義されています。 386 * 387 * @og.rev 5.1.8.0 (2010/07/01) NativeType#getType(String) のメソッドを使用するように変更。 388 * 389 * @param clm 値が参照される列 390 * 391 * @return Nativeタイプ 392 * @see org.opengion.fukurou.model.NativeType 393 */ 394 @Override // DataModel 395 public NativeType getNativeType( final int clm ) { 396 return NativeType.getType( vals[0][clm] ); 397 } 398 399 /** 400 * このオブジェクトの文字列表記を返します。 401 * デバッグ用です。 402 * 403 * @og.rev 5.6.7.0 (2013/07/27) 新規追加 404 * 405 * @return 文字列表現 406 * @og.rtnNotNull 407 * @see java.lang.Object#toString() 408 */ 409 @Override // Object 410 public String toString() { 411 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 412 413 buf.append( "NAMES=" ).append( Arrays.toString( names ) ).append( CR ) 414 // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..; 415 .append( " COL_LEN=" ).append( names == null ? -1 : names.length ).append( CR ) 416 .append( " ROW_LEN=" ).append( vals == null ? -1 : vals.length ).append( CR ) ; 417 418 return buf.toString(); 419 } 420}