001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------- 028 * DefaultKeyedValues2D.java 029 * ------------------------- 030 * (C) Copyright 2002-2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Andreas Schroeder; 034 * 035 * Changes 036 * ------- 037 * 28-Oct-2002 : Version 1 (DG); 038 * 21-Jan-2003 : Updated Javadocs (DG); 039 * 13-Mar-2003 : Implemented Serializable (DG); 040 * 18-Aug-2003 : Implemented Cloneable (DG); 041 * 31-Mar-2004 : Made the rows optionally sortable by a flag (AS); 042 * 01-Apr-2004 : Implemented remove method (AS); 043 * 05-Apr-2004 : Added clear() method (DG); 044 * 15-Sep-2004 : Fixed clone() method (DG); 045 * 12-Jan-2005 : Fixed bug in getValue() method (DG); 046 * 23-Mar-2005 : Implemented PublicCloneable (DG); 047 * 09-Jun-2005 : Modified getValue() method to throw exception for unknown 048 * keys (DG); 049 * ------------- JFREECHART 1.0.x --------------------------------------------- 050 * 18-Jan-2007 : Fixed bug in getValue() method (DG); 051 * 30-Mar-2007 : Fixed bug 1690654, problem with removeValue() (DG); 052 * 21-Nov-2007 : Fixed bug (1835955) in removeColumn(Comparable) method (DG); 053 * 23-Nov-2007 : Added argument checks to removeRow(Comparable) to make it 054 * consistent with the removeRow(Comparable) method (DG); 055 * 056 */ 057 058 package org.jfree.data; 059 060 import java.io.Serializable; 061 import java.util.Collections; 062 import java.util.Iterator; 063 import java.util.List; 064 065 import org.jfree.util.ObjectUtilities; 066 import org.jfree.util.PublicCloneable; 067 068 /** 069 * A data structure that stores zero, one or many values, where each value 070 * is associated with two keys (a 'row' key and a 'column' key). The keys 071 * should be (a) instances of {@link Comparable} and (b) immutable. 072 */ 073 public class DefaultKeyedValues2D implements KeyedValues2D, 074 PublicCloneable, Cloneable, 075 Serializable { 076 077 /** For serialization. */ 078 private static final long serialVersionUID = -5514169970951994748L; 079 080 /** The row keys. */ 081 private List rowKeys; 082 083 /** The column keys. */ 084 private List columnKeys; 085 086 /** The row data. */ 087 private List rows; 088 089 /** If the row keys should be sorted by their comparable order. */ 090 private boolean sortRowKeys; 091 092 /** 093 * Creates a new instance (initially empty). 094 */ 095 public DefaultKeyedValues2D() { 096 this(false); 097 } 098 099 /** 100 * Creates a new instance (initially empty). 101 * 102 * @param sortRowKeys if the row keys should be sorted. 103 */ 104 public DefaultKeyedValues2D(boolean sortRowKeys) { 105 this.rowKeys = new java.util.ArrayList(); 106 this.columnKeys = new java.util.ArrayList(); 107 this.rows = new java.util.ArrayList(); 108 this.sortRowKeys = sortRowKeys; 109 } 110 111 /** 112 * Returns the row count. 113 * 114 * @return The row count. 115 * 116 * @see #getColumnCount() 117 */ 118 public int getRowCount() { 119 return this.rowKeys.size(); 120 } 121 122 /** 123 * Returns the column count. 124 * 125 * @return The column count. 126 * 127 * @see #getRowCount() 128 */ 129 public int getColumnCount() { 130 return this.columnKeys.size(); 131 } 132 133 /** 134 * Returns the value for a given row and column. 135 * 136 * @param row the row index. 137 * @param column the column index. 138 * 139 * @return The value. 140 * 141 * @see #getValue(Comparable, Comparable) 142 */ 143 public Number getValue(int row, int column) { 144 Number result = null; 145 DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row); 146 if (rowData != null) { 147 Comparable columnKey = (Comparable) this.columnKeys.get(column); 148 // the row may not have an entry for this key, in which case the 149 // return value is null 150 int index = rowData.getIndex(columnKey); 151 if (index >= 0) { 152 result = rowData.getValue(index); 153 } 154 } 155 return result; 156 } 157 158 /** 159 * Returns the key for a given row. 160 * 161 * @param row the row index (in the range 0 to {@link #getRowCount()} - 1). 162 * 163 * @return The row key. 164 * 165 * @see #getRowIndex(Comparable) 166 * @see #getColumnKey(int) 167 */ 168 public Comparable getRowKey(int row) { 169 return (Comparable) this.rowKeys.get(row); 170 } 171 172 /** 173 * Returns the row index for a given key. 174 * 175 * @param key the key (<code>null</code> not permitted). 176 * 177 * @return The row index. 178 * 179 * @see #getRowKey(int) 180 * @see #getColumnIndex(Comparable) 181 */ 182 public int getRowIndex(Comparable key) { 183 if (key == null) { 184 throw new IllegalArgumentException("Null 'key' argument."); 185 } 186 if (this.sortRowKeys) { 187 return Collections.binarySearch(this.rowKeys, key); 188 } 189 else { 190 return this.rowKeys.indexOf(key); 191 } 192 } 193 194 /** 195 * Returns the row keys in an unmodifiable list. 196 * 197 * @return The row keys. 198 * 199 * @see #getColumnKeys() 200 */ 201 public List getRowKeys() { 202 return Collections.unmodifiableList(this.rowKeys); 203 } 204 205 /** 206 * Returns the key for a given column. 207 * 208 * @param column the column (in the range 0 to {@link #getColumnCount()} 209 * - 1). 210 * 211 * @return The key. 212 * 213 * @see #getColumnIndex(Comparable) 214 * @see #getRowKey(int) 215 */ 216 public Comparable getColumnKey(int column) { 217 return (Comparable) this.columnKeys.get(column); 218 } 219 220 /** 221 * Returns the column index for a given key. 222 * 223 * @param key the key (<code>null</code> not permitted). 224 * 225 * @return The column index. 226 * 227 * @see #getColumnKey(int) 228 * @see #getRowIndex(Comparable) 229 */ 230 public int getColumnIndex(Comparable key) { 231 if (key == null) { 232 throw new IllegalArgumentException("Null 'key' argument."); 233 } 234 return this.columnKeys.indexOf(key); 235 } 236 237 /** 238 * Returns the column keys in an unmodifiable list. 239 * 240 * @return The column keys. 241 * 242 * @see #getRowKeys() 243 */ 244 public List getColumnKeys() { 245 return Collections.unmodifiableList(this.columnKeys); 246 } 247 248 /** 249 * Returns the value for the given row and column keys. This method will 250 * throw an {@link UnknownKeyException} if either key is not defined in the 251 * data structure. 252 * 253 * @param rowKey the row key (<code>null</code> not permitted). 254 * @param columnKey the column key (<code>null</code> not permitted). 255 * 256 * @return The value (possibly <code>null</code>). 257 * 258 * @see #addValue(Number, Comparable, Comparable) 259 * @see #removeValue(Comparable, Comparable) 260 */ 261 public Number getValue(Comparable rowKey, Comparable columnKey) { 262 if (rowKey == null) { 263 throw new IllegalArgumentException("Null 'rowKey' argument."); 264 } 265 if (columnKey == null) { 266 throw new IllegalArgumentException("Null 'columnKey' argument."); 267 } 268 269 // check that the column key is defined in the 2D structure 270 if (!(this.columnKeys.contains(columnKey))) { 271 throw new UnknownKeyException("Unrecognised columnKey: " 272 + columnKey); 273 } 274 275 // now fetch the row data - need to bear in mind that the row 276 // structure may not have an entry for the column key, but that we 277 // have already checked that the key is valid for the 2D structure 278 int row = getRowIndex(rowKey); 279 if (row >= 0) { 280 DefaultKeyedValues rowData 281 = (DefaultKeyedValues) this.rows.get(row); 282 int col = rowData.getIndex(columnKey); 283 return (col >= 0 ? rowData.getValue(col) : null); 284 } 285 else { 286 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 287 } 288 } 289 290 /** 291 * Adds a value to the table. Performs the same function as 292 * #setValue(Number, Comparable, Comparable). 293 * 294 * @param value the value (<code>null</code> permitted). 295 * @param rowKey the row key (<code>null</code> not permitted). 296 * @param columnKey the column key (<code>null</code> not permitted). 297 * 298 * @see #setValue(Number, Comparable, Comparable) 299 * @see #removeValue(Comparable, Comparable) 300 */ 301 public void addValue(Number value, Comparable rowKey, 302 Comparable columnKey) { 303 // defer argument checking 304 setValue(value, rowKey, columnKey); 305 } 306 307 /** 308 * Adds or updates a value. 309 * 310 * @param value the value (<code>null</code> permitted). 311 * @param rowKey the row key (<code>null</code> not permitted). 312 * @param columnKey the column key (<code>null</code> not permitted). 313 * 314 * @see #addValue(Number, Comparable, Comparable) 315 * @see #removeValue(Comparable, Comparable) 316 */ 317 public void setValue(Number value, Comparable rowKey, 318 Comparable columnKey) { 319 320 DefaultKeyedValues row; 321 int rowIndex = getRowIndex(rowKey); 322 323 if (rowIndex >= 0) { 324 row = (DefaultKeyedValues) this.rows.get(rowIndex); 325 } 326 else { 327 row = new DefaultKeyedValues(); 328 if (this.sortRowKeys) { 329 rowIndex = -rowIndex - 1; 330 this.rowKeys.add(rowIndex, rowKey); 331 this.rows.add(rowIndex, row); 332 } 333 else { 334 this.rowKeys.add(rowKey); 335 this.rows.add(row); 336 } 337 } 338 row.setValue(columnKey, value); 339 340 int columnIndex = this.columnKeys.indexOf(columnKey); 341 if (columnIndex < 0) { 342 this.columnKeys.add(columnKey); 343 } 344 } 345 346 /** 347 * Removes a value from the table by setting it to <code>null</code>. If 348 * all the values in the specified row and/or column are now 349 * <code>null</code>, the row and/or column is removed from the table. 350 * 351 * @param rowKey the row key (<code>null</code> not permitted). 352 * @param columnKey the column key (<code>null</code> not permitted). 353 * 354 * @see #addValue(Number, Comparable, Comparable) 355 */ 356 public void removeValue(Comparable rowKey, Comparable columnKey) { 357 setValue(null, rowKey, columnKey); 358 359 // 1. check whether the row is now empty. 360 boolean allNull = true; 361 int rowIndex = getRowIndex(rowKey); 362 DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 363 364 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 365 item++) { 366 if (row.getValue(item) != null) { 367 allNull = false; 368 break; 369 } 370 } 371 372 if (allNull) { 373 this.rowKeys.remove(rowIndex); 374 this.rows.remove(rowIndex); 375 } 376 377 // 2. check whether the column is now empty. 378 allNull = true; 379 //int columnIndex = getColumnIndex(columnKey); 380 381 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 382 item++) { 383 row = (DefaultKeyedValues) this.rows.get(item); 384 int columnIndex = row.getIndex(columnKey); 385 if (columnIndex >= 0 && row.getValue(columnIndex) != null) { 386 allNull = false; 387 break; 388 } 389 } 390 391 if (allNull) { 392 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 393 item++) { 394 row = (DefaultKeyedValues) this.rows.get(item); 395 int columnIndex = row.getIndex(columnKey); 396 if (columnIndex >= 0) { 397 row.removeValue(columnIndex); 398 } 399 } 400 this.columnKeys.remove(columnKey); 401 } 402 } 403 404 /** 405 * Removes a row. 406 * 407 * @param rowIndex the row index. 408 * 409 * @see #removeRow(Comparable) 410 * @see #removeColumn(int) 411 */ 412 public void removeRow(int rowIndex) { 413 this.rowKeys.remove(rowIndex); 414 this.rows.remove(rowIndex); 415 } 416 417 /** 418 * Removes a row from the table. 419 * 420 * @param rowKey the row key (<code>null</code> not permitted). 421 * 422 * @see #removeRow(int) 423 * @see #removeColumn(Comparable) 424 * 425 * @throws UnknownKeyException if <code>rowKey</code> is not defined in the 426 * table. 427 */ 428 public void removeRow(Comparable rowKey) { 429 if (rowKey == null) { 430 throw new IllegalArgumentException("Null 'rowKey' argument."); 431 } 432 int index = getRowIndex(rowKey); 433 if (index >= 0) { 434 removeRow(index); 435 } 436 else { 437 throw new UnknownKeyException("Unknown key: " + rowKey); 438 } 439 } 440 441 /** 442 * Removes a column. 443 * 444 * @param columnIndex the column index. 445 * 446 * @see #removeColumn(Comparable) 447 * @see #removeRow(int) 448 */ 449 public void removeColumn(int columnIndex) { 450 Comparable columnKey = getColumnKey(columnIndex); 451 removeColumn(columnKey); 452 } 453 454 /** 455 * Removes a column from the table. 456 * 457 * @param columnKey the column key (<code>null</code> not permitted). 458 * 459 * @throws UnknownKeyException if the table does not contain a column with 460 * the specified key. 461 * @throws IllegalArgumentException if <code>columnKey</code> is 462 * <code>null</code>. 463 * 464 * @see #removeColumn(int) 465 * @see #removeRow(Comparable) 466 */ 467 public void removeColumn(Comparable columnKey) { 468 if (columnKey == null) { 469 throw new IllegalArgumentException("Null 'columnKey' argument."); 470 } 471 if (!this.columnKeys.contains(columnKey)) { 472 throw new UnknownKeyException("Unknown key: " + columnKey); 473 } 474 Iterator iterator = this.rows.iterator(); 475 while (iterator.hasNext()) { 476 DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next(); 477 int index = rowData.getIndex(columnKey); 478 if (index >= 0) { 479 rowData.removeValue(columnKey); 480 } 481 } 482 this.columnKeys.remove(columnKey); 483 } 484 485 /** 486 * Clears all the data and associated keys. 487 */ 488 public void clear() { 489 this.rowKeys.clear(); 490 this.columnKeys.clear(); 491 this.rows.clear(); 492 } 493 494 /** 495 * Tests if this object is equal to another. 496 * 497 * @param o the other object (<code>null</code> permitted). 498 * 499 * @return A boolean. 500 */ 501 public boolean equals(Object o) { 502 503 if (o == null) { 504 return false; 505 } 506 if (o == this) { 507 return true; 508 } 509 510 if (!(o instanceof KeyedValues2D)) { 511 return false; 512 } 513 KeyedValues2D kv2D = (KeyedValues2D) o; 514 if (!getRowKeys().equals(kv2D.getRowKeys())) { 515 return false; 516 } 517 if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 518 return false; 519 } 520 int rowCount = getRowCount(); 521 if (rowCount != kv2D.getRowCount()) { 522 return false; 523 } 524 525 int colCount = getColumnCount(); 526 if (colCount != kv2D.getColumnCount()) { 527 return false; 528 } 529 530 for (int r = 0; r < rowCount; r++) { 531 for (int c = 0; c < colCount; c++) { 532 Number v1 = getValue(r, c); 533 Number v2 = kv2D.getValue(r, c); 534 if (v1 == null) { 535 if (v2 != null) { 536 return false; 537 } 538 } 539 else { 540 if (!v1.equals(v2)) { 541 return false; 542 } 543 } 544 } 545 } 546 return true; 547 } 548 549 /** 550 * Returns a hash code. 551 * 552 * @return A hash code. 553 */ 554 public int hashCode() { 555 int result; 556 result = this.rowKeys.hashCode(); 557 result = 29 * result + this.columnKeys.hashCode(); 558 result = 29 * result + this.rows.hashCode(); 559 return result; 560 } 561 562 /** 563 * Returns a clone. 564 * 565 * @return A clone. 566 * 567 * @throws CloneNotSupportedException this class will not throw this 568 * exception, but subclasses (if any) might. 569 */ 570 public Object clone() throws CloneNotSupportedException { 571 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 572 // for the keys, a shallow copy should be fine because keys 573 // should be immutable... 574 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 575 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 576 577 // but the row data requires a deep copy 578 clone.rows = (List) ObjectUtilities.deepClone(this.rows); 579 return clone; 580 } 581 582 }