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.hayabusa.db; 017 018import java.math.BigDecimal; 019import java.sql.ResultSet; 020import java.sql.ResultSetMetaData; 021import java.sql.SQLException; 022import java.text.DecimalFormat; 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029 030import org.opengion.fukurou.db.DBUtil; 031import org.opengion.fukurou.util.StringUtil; 032import org.opengion.hayabusa.common.HybsSystem; 033import org.opengion.hayabusa.common.HybsSystemException; 034import org.opengion.hayabusa.resource.LabelData; 035import org.opengion.hayabusa.resource.ResourceManager; 036 037/** 038 * DBTableModelを継承した TableModelの編集設定による変換を行うための実装クラスです。 039 * 040 * このクラスでは、オブジェクト初期化後は、通常のDBTableModelと同じ振る舞いをします。 041 * オブジェクト初期化時(createメソッド呼び出し時)に、検索結果オブジェクトから直接、編集設定に 042 * 応じて変換されたDBTableModelを生成します。 043 * 044 * このような実装を行う理由は、メモリ使用量を節約するためです。 045 * この編集設定では、集計機能を備えていますが、一旦DBTableModel作成後に集計処理を行うと、 046 * メモリを大量に使用する恐れがあるため、検索結果オブジェクトから直接集計処理を行い、DBTableModelを 047 * 生成しています。 048 * 049 * DBTableModel インターフェースは,データベースの検索結果(Resultset)をラップする 050 * インターフェースとして使用して下さい。 051 * 052 * @og.rev 5.3.6.0 (2011/06/01) 新規作成 053 * @og.group テーブル管理 054 * 055 * @version 5.0 056 * @author Hiroki Nakamura 057 * @since JDK6.0, 058 */ 059public class DBTableModelEditor extends DBTableModelImpl { 060 private static final String JS = HybsSystem.JOINT_STRING; 061 private static final DecimalFormat FORMAT = new DecimalFormat( "0.#########" ); 062 063 private int rowCountColumn = -1; 064 private DBEditConfig config; 065 066 /** 067 * DBTableModel を設定し、このオブジェクトを初期化します。 068 * 069 * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更 070 * 071 * @param result 検索結果オブジェクト 072 * @param skipRowCount 読み飛ばし件数 073 * @param maxRowCount 最大検索件数 074 * @param resource ResourceManagerオブジェクト 075 * @param config エディット設定オブジェクト 076 * @throws SQLException データベースアクセスエラー 077 */ 078 public void create( final ResultSet result, final int skipRowCount, final int maxRowCount, final ResourceManager resource, final DBEditConfig config ) throws SQLException { 079 if( result == null || config == null || resource == null ) { 080 String errMsg = "DBTableModelまたは、DBEditConfigが設定されていません。"; 081 throw new HybsSystemException( errMsg ); // 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更 082 } 083 084 this.config = config; 085 086 /********************************************************************** 087 * 各パラメーターの初期化処理 088 **********************************************************************/ 089 ResultSetMetaData metaData = result.getMetaData(); 090 int colCnt = metaData.getColumnCount(); 091 if( config.useGroup() || config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) { 092 rowCountColumn = colCnt; 093 colCnt++; 094 } 095 init( colCnt ); 096 097 DBColumn[] dbColumn = new DBColumn[numberOfColumns]; 098 int[] types = new int[numberOfColumns]; 099 boolean[] sumFilter = new boolean[numberOfColumns]; 100 boolean[] groupFilter = new boolean[numberOfColumns]; 101 boolean[] subTotalFilter = new boolean[numberOfColumns]; 102 boolean[] totalFilter = new boolean[numberOfColumns]; 103 for( int column=0; column<numberOfColumns; column++ ) { 104 String name = null; 105 if( column != rowCountColumn ) { 106 name = metaData.getColumnLabel(column+1).toUpperCase(Locale.JAPAN); 107 types[column] = metaData.getColumnType(column+1); 108 dbColumn[column] = resource.getDBColumn( name ); 109 if( dbColumn[column] == null ) { 110 LabelData labelData = resource.getLabelData( name ); 111 dbColumn[column] = DBTableModelUtil.makeDBColumn( name,labelData,metaData,column,resource.getLang() ); 112 } 113 } 114 else { 115 name = "rowCount"; 116 dbColumn[column] = resource.makeDBColumn( name ); 117 } 118 119 setDBColumn( column,dbColumn[column] ); 120 121 sumFilter[column] = config.isSumClm( name ); 122 groupFilter[column] = config.isGroupClm( name ); 123 subTotalFilter[column] = config.isSubTotalClm( name ); 124 totalFilter[column] = config.isTotalClm( name ); 125 } 126 127 /********************************************************************** 128 * 集計、ソート、合計処理 129 **********************************************************************/ 130 // 集計キーに基づく集計処理を行いデータを追加します。 131 if( config.useGroup() ) { 132 addGroupRows( result, types, skipRowCount, maxRowCount, sumFilter, groupFilter ); 133 } 134 // 通常と同じように結果カーソルからデータを読込みデータを追加します。 135 else { 136 // 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。 137 addPlainRows( result, skipRowCount, maxRowCount ); 138 } 139 140 // ソート処理 141 if( getRowCount() > 0 && config.useOrderBy() ) { 142 sort(); 143 } 144 145 // 小計・合計行を追加します。 146 if( getRowCount() > 0 && !isOverflow() 147 && ( config.useSubTotal() || config.useTotal() || config.useGrandTotal() ) ) { 148 addTotalRows( maxRowCount, resource, sumFilter, groupFilter, subTotalFilter, totalFilter ); 149 } 150 } 151 152 /** 153 * 集計キーの設定に基づき、DBTableModelの行を追加します。 154 * 内部的には、キーブレイクではなく、内部マップにより集計処理を行っているため、 155 * 集計キーが検索順により散在した場合でも1まとまりで集計されます。 156 * 157 * @og.rev 5.3.9.0 (2011/09/01) 値がNULLの場合にエラーになるバグを修正 158 * @og.rev 5.6.1.0 (2013/02/01) doubleをBigDecimalに 159 * 160 * @param result 検索結果オブジェクト 161 * @param types カラムタイプの配列 162 * @param skipRowCount 読み飛ばし件数 163 * @param maxRowCount 最大検索件数 164 * @param sumFilter 集計項目フィルター 165 * @param groupFilter グループキーフィルター 166 * @throws SQLException データベースアクセスエラー 167 */ 168 private void addGroupRows( final ResultSet result, final int[] types, final int skipRowCount, final int maxRowCount 169 , final boolean[] sumFilter, final boolean[] groupFilter ) throws SQLException { 170 int numberOfRows = 0; 171 while( numberOfRows < skipRowCount && result.next() ) { 172 // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。 173 numberOfRows ++ ; 174 } 175 numberOfRows = 0; 176 177 Map<String,String[]> groupLinkedMap = new LinkedHashMap<String,String[]>(); 178 Map<String,Integer> groupCountMap = new HashMap<String,Integer>(); 179 Map<String,BigDecimal[]> sumMap = new HashMap<String,BigDecimal[]>(); // 5.6.1.0 (2013/02/01) 180 while( numberOfRows < maxRowCount && result.next() ) { 181 StringBuilder groupKey = new StringBuilder(); 182 BigDecimal[] sumVals = new BigDecimal[config.getSumClmCount()]; // 5.6.1.0 (2013/02/01) 183 String[] groupVals = new String[config.getGroupClmCount()]; 184 int sc = 0; 185 int gc = 0; 186 for( int column=0; column<numberOfColumns; column++ ) { 187 if( column != rowCountColumn ) { 188 String val = DBUtil.getValue( result, column, types[column] ); 189 if( sumFilter[column] ) { 190 // 5.3.9.0 (2011/09/01) 値がNULLの場合の対応漏れ 191 // sumVals[sc++] = ( val != null && val.length() > 0 ? Double.valueOf( val ) : 0 ); 192 sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); // 5.6.1.0 (2013/02/01) 193 } 194 if( groupFilter[column] ) { 195 groupVals[gc++] = val; 196 groupKey.append( val ).append( JS ); 197 } 198 } 199 } 200 201 String key = groupKey.toString(); 202 int groupCount = 0; 203 if( groupLinkedMap.containsKey( key ) ) { 204 BigDecimal[] eSumVals = sumMap.get( key ); // 5.6.1.0 (2013/02/01) 205 for( int i=0; i<config.getSumClmCount(); i++ ) { 206 sumVals[i] = sumVals[i] == null ? BigDecimal.ZERO : sumVals[i].add( eSumVals[i] ); // 5.6.1.0 (2013/02/01) 207 } 208 sumMap.put( key, sumVals ); 209 groupCount = groupCountMap.get( key ).intValue() + 1; 210 } 211 else { 212 groupLinkedMap.put( key, groupVals ); 213 groupCount = 1; 214 numberOfRows++; 215 } 216 sumMap.put( key, sumVals ); 217 groupCountMap.put( key, Integer.valueOf( groupCount ) ); 218 } 219 220 for( Map.Entry<String, String[]> entry : groupLinkedMap.entrySet() ) { 221 String key = entry.getKey(); 222 addRow( groupFilter, entry.getValue(), groupCountMap.get( key ), sumFilter, sumMap.get( key ) ); 223 } 224 225 // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー 226 if( numberOfRows >= maxRowCount && result.next() ) { 227 setOverflow( true ); 228 } 229 } 230 231 /** 232 * 検索結果オブジェクトを順に読み取り、そのままDBTableModelの行を追加します。 233 * 234 * @og.rev 5.5.2.4 (2012/05/16) int[] types は使われていないので、削除します。 235 * 236 * @param result 検索結果オブジェクト 237 * @param skipRowCount 読み飛ばし件数 238 * @param maxRowCount 最大検索件数 239 * @throws SQLException データベースアクセスエラー 240 */ 241 private void addPlainRows( final ResultSet result, final int skipRowCount, final int maxRowCount ) throws SQLException { 242 int numberOfRows = 0; 243 while( numberOfRows < skipRowCount && result.next() ) { 244 // 注意 resultSet.next() を先に判定すると必ず1件読み飛ばしてしまう。 245 numberOfRows ++ ; 246 } 247 numberOfRows = 0; 248 249 while( numberOfRows < maxRowCount && result.next() ) { 250 numberOfRows++ ; 251 String[] columnValues = new String[numberOfColumns]; 252 for( int column=0; column<numberOfColumns; column++ ) { 253 if( column != rowCountColumn ) { 254 Object obj = result.getObject(column+1); 255 columnValues[column] = ( obj == null ? "" : String.valueOf( obj ) ); 256 } 257 else { 258 columnValues[column] = ""; 259 } 260 } 261 addColumnValues( columnValues ); 262 } 263 264 // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー 265 if( numberOfRows >= maxRowCount && result.next() ) { 266 setOverflow( true ); 267 } 268 } 269 270 /** 271 * DBTableModelのソート処理を行います。 272 * 273 */ 274 private void sort() { 275 // orderByClmsによる並び替え 276 DBTableModelSorter temp = new DBTableModelSorter(); 277 temp.setModel( this ); 278 String[] oClms = StringUtil.csv2Array( config.getOrderByClms() ); 279 for( int i=oClms.length-1; i>=0; i-- ) { 280 String oc = oClms[i]; 281 boolean ascending = true; 282 if( oc.startsWith( "!" ) ) { 283 oc = oc.substring( 1 ); 284 ascending = false; 285 } 286 int clmNo = getColumnNo( oc ); 287 temp.sortByColumn( clmNo, ascending ); 288 } 289 this.data = temp.data; 290 this.rowHeader = temp.rowHeader; 291 } 292 293 /** 294 * DBTableModelからデータを読み取り、エディット設定情報を元に合計行の追加処理を行います。 295 * 合計行の追加は、キーブレイクにより行われますので、同じキーが複数回出現した場合は、 296 * それぞれの行に対して、合計行が付加されます。 297 * 298 * @og.rev 5.3.7.0 (2011/07/01) 小計、合計行追加処理でオーバーフローフラグがセットされないバグを修正 299 * @og.rev 5.6.1.0 (2013/02/01) 誤差回避のため、doubleではなくdecimalで計算する 300 * @og.rev 5.6.8.1 (2013/09/13) 1行目が合計されていなかったので修正 301 * 302 * @param maxRowCount 最大検索件数 303 * @param resource リソースマネージャー 304 * @param sumFilter 集計項目フィルター 305 * @param groupFilter グループキーフィルター 306 * @param subTotalFilter 小計キーフィルター 307 * @param totalFilter 合計キーフィルター 308 * 309 * @return オーバーフローしたかどうか(最大件数が超えた場合でかつ次のデータがある場合は、true) 310 */ 311 private boolean addTotalRows( final int maxRowCount, final ResourceManager resource, final boolean[] sumFilter 312 , final boolean[] groupFilter, final boolean[] subTotalFilter, final boolean[] totalFilter ) { 313 314 String subTotalLabel = ( config.useSubTotal() ? resource.makeDBColumn( "EDIT_SUBTOTAL_VALUE" ).getLongLabel() : null ); 315 String totalLabel = ( config.useTotal() ? resource.makeDBColumn( "EDIT_TOTAL_VALUE" ).getLongLabel() : null ); 316 String grandTotalLabel = ( config.useGrandTotal() ? resource.makeDBColumn( "EDIT_GRANDTOTAL_VALUE" ).getLongLabel() : null ); 317 318 int numberOfRows = getRowCount(); 319 int sumClmCount = config.getSumClmCount(); 320 BigDecimal subTotalSum[] = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01) 321 BigDecimal totalSum[] = new BigDecimal[sumClmCount]; 322 BigDecimal grandTotalSum[] = new BigDecimal[sumClmCount]; 323 324 String lastSubTotalKey = null; 325 String lastTotalKey = null; 326 327 int subTotalCount = 0; 328 int totalCount = 0; 329 int grandTotalCount = 0; 330 int rowCount =0; 331 332 int tblIdx = 0; 333 while( numberOfRows < maxRowCount && tblIdx < getRowCount() ) { 334 BigDecimal[] sumVals = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01) 335 StringBuilder groupKey = new StringBuilder(); 336 StringBuilder subTotalKey = new StringBuilder(); 337 StringBuilder totalKey = new StringBuilder(); 338 339 int sc = 0; 340 for( int column=0; column<numberOfColumns; column++ ) { 341 String val = getValue( tblIdx, column ); 342 if( groupFilter[column] ) { groupKey.append( val ).append( JS ); } 343 if( sumFilter[column] ) { sumVals[sc++] = ( val != null && val.length() > 0 ? new BigDecimal( val ) : BigDecimal.ZERO ); } // 5.6.1.0 (2013/02/01) 344 if( subTotalFilter[column] ) { subTotalKey.append( val ).append( JS ); } 345 if( totalFilter[column] ) { totalKey.append( val ).append( JS ); } 346 if( column == rowCountColumn ) { rowCount = ( val != null && val.length() > 0 ? Integer.valueOf( val ) : 0 ); } 347 } 348 349 // 小計キーブレイク処理 350 if( numberOfRows < maxRowCount && config.useSubTotal() && lastSubTotalKey != null && lastSubTotalKey.length() > 0 351 && !lastSubTotalKey.equals( subTotalKey.toString() ) ) { 352 addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx ); 353 subTotalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01) 354 subTotalCount = 0; 355 numberOfRows++; 356 tblIdx++; 357 } 358 359 // 合計キーブレイク処理 360 if( numberOfRows < maxRowCount && config.useTotal() && lastTotalKey != null && lastTotalKey.length() > 0 361 && !lastTotalKey.equals( totalKey.toString() ) ) { 362 addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx ); 363 totalSum = new BigDecimal[sumClmCount]; // 5.6.1.0 (2013/02/01) 364 totalCount = 0; 365 numberOfRows++; 366 tblIdx++; 367 } 368 369 // 小計、合計、総合計単位に集計項目の合計値を加算します。 370 for( int cnt=0; cnt<sumClmCount; cnt++ ) { 371 subTotalSum[cnt] = subTotalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : subTotalSum[cnt].add(sumVals[cnt]); // 5.6.8.1 (2013/09/13) 372 totalSum[cnt] = totalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : totalSum[cnt].add(sumVals[cnt]); 373 grandTotalSum[cnt] = grandTotalSum[cnt] == null ? BigDecimal.ZERO.add(sumVals[cnt]) : grandTotalSum[cnt].add(sumVals[cnt]); 374 } 375 376 lastSubTotalKey = subTotalKey.toString(); 377 lastTotalKey = totalKey.toString(); 378 379 // グループ集計時はグルーピングした行数を加算する。 380 int gcnt = 1; 381 if( config.useGroup() && rowCountColumn >= 0 && rowCount > 0 ) { 382 gcnt = rowCount; 383 } 384 subTotalCount += gcnt; 385 totalCount += gcnt; 386 grandTotalCount += gcnt; 387 388 tblIdx++; 389 } 390 391 // 最大件数が、超えた場合でかつ次のデータがある場合は、オーバーフロー 392 boolean isOverFlow = tblIdx < getRowCount() ; 393 394 // 小計キー最終行処理 395 if( config.useSubTotal() && lastSubTotalKey != null ) { 396 if( numberOfRows < maxRowCount ) { 397 addRow( subTotalFilter, subTotalLabel, subTotalCount, sumFilter, subTotalSum, tblIdx ); 398 numberOfRows++; 399 tblIdx++; 400 } 401 else { 402 isOverFlow = true; 403 } 404 } 405 406 // 合計キー最終行処理 407 if( config.useTotal() && lastTotalKey != null ) { 408 if( numberOfRows < maxRowCount ) { 409 addRow( totalFilter, totalLabel, totalCount, sumFilter, totalSum, tblIdx ); 410 numberOfRows++; 411 tblIdx++; 412 } 413 else { 414 isOverFlow = true; 415 } 416 } 417 418 // 総合計処理 419 if( config.useGrandTotal() && numberOfRows > 0 ) { 420 if( numberOfRows < maxRowCount ) { 421 boolean[] grandTotalFilter = new boolean[numberOfColumns]; 422 // 総合計のラベル表示廃止 423 // grandTotalFilter[0] = true; 424 addRow( grandTotalFilter, grandTotalLabel, grandTotalCount, sumFilter, grandTotalSum, tblIdx ); 425 numberOfRows++; 426 tblIdx++; 427 } 428 else { 429 isOverFlow = true; 430 } 431 } 432 433 if( isOverFlow ) { 434 setOverflow( true ); 435 } 436 437 return isOverFlow; 438 } 439 440 /** 441 * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。 442 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。 443 * 444 * @og.rev 5.6.1.0 (2013/02/01) doubleをdecimalに 445 * 446 * @param keyFilter キーフィルタ 447 * @param keyVals キーの値配列 448 * @param keyCount 集計した行のカウント 449 * @param sumFilter 集計フィルタ 450 * @param sumVals 集計値配列 451 * @param aRow 挿入する行番号 452 */ 453 private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount 454 , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) { 455 String[] columnValues = new String[numberOfColumns]; 456 int sc = 0; 457 int kc = 0; 458 for( int column=0; column<numberOfColumns; column++ ) { 459 String val = ""; 460 if( keyFilter[column] ) { 461 val = keyVals[kc++]; 462 } 463 if( sumFilter[column] ) { 464 val = FORMAT.format( sumVals[sc++] ); 465 } 466 if( column == rowCountColumn ) { 467 val = String.valueOf( keyCount ); 468 } 469 columnValues[column] = val; 470 } 471 472 if( aRow < 0 ) { 473 addColumnValues( columnValues ); 474 } 475 else { 476 addValues( columnValues, aRow, false ); 477 } 478 } 479 480 /** 481 * キーの値配列、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。 482 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。 483 * 484 * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimal 485 * 486 * @param keyFilter キーフィルタ 487 * @param keyVals キーの値配列 488 * @param keyCount 集計した行のカウント 489 * @param sumFilter 集計フィルタ 490 * @param sumVals 集計値配列 491 */ 492 private void addRow( final boolean[] keyFilter, final String[] keyVals, final int keyCount 493 , final boolean[] sumFilter, final BigDecimal[] sumVals ) { 494 addRow( keyFilter, keyVals, keyCount, sumFilter, sumVals, -1 ); 495 } 496 497 /** 498 * キーの値、集計値の配列を引数として、追加行を生成し、DBTableModelに追加します。 499 * キー、及び集計値がDBTableModel上のどのカラムに位置するかは、キーフィルタ、集計フィルタで指定します。 500 * 501 * @og.rev 5.6.1.0 (2013/02/01) doubleをbigDecimalに 502 * 503 * @param keyFilter キーフィルタ 504 * @param keyVal キーの値 505 * @param keyCount 集計した行のカウント 506 * @param sumFilter 集計フィルタ 507 * @param sumVals 集計値配列 508 * @param aRow 挿入する行番号 509 */ 510 private void addRow( final boolean[] keyFilter, final String keyVal, final int keyCount 511 , final boolean[] sumFilter, final BigDecimal[] sumVals, final int aRow ) { 512 List<String> tmp = new ArrayList<String>(); 513 for( int column=0; column<numberOfColumns; column++ ) { 514 if( keyFilter[column] ) { 515 tmp.add( keyVal ); 516 } 517 } 518 addRow( keyFilter, tmp.toArray( new String[tmp.size()] ), keyCount, sumFilter, sumVals, aRow ); 519 } 520}