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