001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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, PublicCloneable, 074 Cloneable, Serializable { 075 076 /** For serialization. */ 077 private static final long serialVersionUID = -5514169970951994748L; 078 079 /** The row keys. */ 080 private List rowKeys; 081 082 /** The column keys. */ 083 private List columnKeys; 084 085 /** The row data. */ 086 private List rows; 087 088 /** If the row keys should be sorted by their comparable order. */ 089 private boolean sortRowKeys; 090 091 /** 092 * Creates a new instance (initially empty). 093 */ 094 public DefaultKeyedValues2D() { 095 this(false); 096 } 097 098 /** 099 * Creates a new instance (initially empty). 100 * 101 * @param sortRowKeys if the row keys should be sorted. 102 */ 103 public DefaultKeyedValues2D(boolean sortRowKeys) { 104 this.rowKeys = new java.util.ArrayList(); 105 this.columnKeys = new java.util.ArrayList(); 106 this.rows = new java.util.ArrayList(); 107 this.sortRowKeys = sortRowKeys; 108 } 109 110 /** 111 * Returns the row count. 112 * 113 * @return The row count. 114 * 115 * @see #getColumnCount() 116 */ 117 public int getRowCount() { 118 return this.rowKeys.size(); 119 } 120 121 /** 122 * Returns the column count. 123 * 124 * @return The column count. 125 * 126 * @see #getRowCount() 127 */ 128 public int getColumnCount() { 129 return this.columnKeys.size(); 130 } 131 132 /** 133 * Returns the value for a given row and column. 134 * 135 * @param row the row index. 136 * @param column the column index. 137 * 138 * @return The value. 139 * 140 * @see #getValue(Comparable, Comparable) 141 */ 142 public Number getValue(int row, int column) { 143 Number result = null; 144 DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row); 145 if (rowData != null) { 146 Comparable columnKey = (Comparable) this.columnKeys.get(column); 147 // the row may not have an entry for this key, in which case the 148 // return value is null 149 int index = rowData.getIndex(columnKey); 150 if (index >= 0) { 151 result = rowData.getValue(index); 152 } 153 } 154 return result; 155 } 156 157 /** 158 * Returns the key for a given row. 159 * 160 * @param row the row index (in the range 0 to {@link #getRowCount()} - 1). 161 * 162 * @return The row key. 163 * 164 * @see #getRowIndex(Comparable) 165 * @see #getColumnKey(int) 166 */ 167 public Comparable getRowKey(int row) { 168 return (Comparable) this.rowKeys.get(row); 169 } 170 171 /** 172 * Returns the row index for a given key. 173 * 174 * @param key the key (<code>null</code> not permitted). 175 * 176 * @return The row index. 177 * 178 * @see #getRowKey(int) 179 * @see #getColumnIndex(Comparable) 180 */ 181 public int getRowIndex(Comparable key) { 182 if (key == null) { 183 throw new IllegalArgumentException("Null 'key' argument."); 184 } 185 if (this.sortRowKeys) { 186 return Collections.binarySearch(this.rowKeys, key); 187 } 188 else { 189 return this.rowKeys.indexOf(key); 190 } 191 } 192 193 /** 194 * Returns the row keys in an unmodifiable list. 195 * 196 * @return The row keys. 197 * 198 * @see #getColumnKeys() 199 */ 200 public List getRowKeys() { 201 return Collections.unmodifiableList(this.rowKeys); 202 } 203 204 /** 205 * Returns the key for a given column. 206 * 207 * @param column the column (in the range 0 to {@link #getColumnCount()} 208 * - 1). 209 * 210 * @return The key. 211 * 212 * @see #getColumnIndex(Comparable) 213 * @see #getRowKey(int) 214 */ 215 public Comparable getColumnKey(int column) { 216 return (Comparable) this.columnKeys.get(column); 217 } 218 219 /** 220 * Returns the column index for a given key. 221 * 222 * @param key the key (<code>null</code> not permitted). 223 * 224 * @return The column index. 225 * 226 * @see #getColumnKey(int) 227 * @see #getRowIndex(Comparable) 228 */ 229 public int getColumnIndex(Comparable key) { 230 if (key == null) { 231 throw new IllegalArgumentException("Null 'key' argument."); 232 } 233 return this.columnKeys.indexOf(key); 234 } 235 236 /** 237 * Returns the column keys in an unmodifiable list. 238 * 239 * @return The column keys. 240 * 241 * @see #getRowKeys() 242 */ 243 public List getColumnKeys() { 244 return Collections.unmodifiableList(this.columnKeys); 245 } 246 247 /** 248 * Returns the value for the given row and column keys. This method will 249 * throw an {@link UnknownKeyException} if either key is not defined in the 250 * data structure. 251 * 252 * @param rowKey the row key (<code>null</code> not permitted). 253 * @param columnKey the column key (<code>null</code> not permitted). 254 * 255 * @return The value (possibly <code>null</code>). 256 * 257 * @see #addValue(Number, Comparable, Comparable) 258 * @see #removeValue(Comparable, Comparable) 259 */ 260 public Number getValue(Comparable rowKey, Comparable columnKey) { 261 if (rowKey == null) { 262 throw new IllegalArgumentException("Null 'rowKey' argument."); 263 } 264 if (columnKey == null) { 265 throw new IllegalArgumentException("Null 'columnKey' argument."); 266 } 267 268 // check that the column key is defined in the 2D structure 269 if (!(this.columnKeys.contains(columnKey))) { 270 throw new UnknownKeyException("Unrecognised columnKey: " 271 + columnKey); 272 } 273 274 // now fetch the row data - need to bear in mind that the row 275 // structure may not have an entry for the column key, but that we 276 // have already checked that the key is valid for the 2D structure 277 int row = getRowIndex(rowKey); 278 if (row >= 0) { 279 DefaultKeyedValues rowData 280 = (DefaultKeyedValues) this.rows.get(row); 281 int col = rowData.getIndex(columnKey); 282 return (col >= 0 ? rowData.getValue(col) : null); 283 } 284 else { 285 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 286 } 287 } 288 289 /** 290 * Adds a value to the table. Performs the same function as 291 * #setValue(Number, Comparable, Comparable). 292 * 293 * @param value the value (<code>null</code> permitted). 294 * @param rowKey the row key (<code>null</code> not permitted). 295 * @param columnKey the column key (<code>null</code> not permitted). 296 * 297 * @see #setValue(Number, Comparable, Comparable) 298 * @see #removeValue(Comparable, Comparable) 299 */ 300 public void addValue(Number value, Comparable rowKey, 301 Comparable columnKey) { 302 // defer argument checking 303 setValue(value, rowKey, columnKey); 304 } 305 306 /** 307 * Adds or updates a value. 308 * 309 * @param value the value (<code>null</code> permitted). 310 * @param rowKey the row key (<code>null</code> not permitted). 311 * @param columnKey the column key (<code>null</code> not permitted). 312 * 313 * @see #addValue(Number, Comparable, Comparable) 314 * @see #removeValue(Comparable, Comparable) 315 */ 316 public void setValue(Number value, Comparable rowKey, 317 Comparable columnKey) { 318 319 DefaultKeyedValues row; 320 int rowIndex = getRowIndex(rowKey); 321 322 if (rowIndex >= 0) { 323 row = (DefaultKeyedValues) this.rows.get(rowIndex); 324 } 325 else { 326 row = new DefaultKeyedValues(); 327 if (this.sortRowKeys) { 328 rowIndex = -rowIndex - 1; 329 this.rowKeys.add(rowIndex, rowKey); 330 this.rows.add(rowIndex, row); 331 } 332 else { 333 this.rowKeys.add(rowKey); 334 this.rows.add(row); 335 } 336 } 337 row.setValue(columnKey, value); 338 339 int columnIndex = this.columnKeys.indexOf(columnKey); 340 if (columnIndex < 0) { 341 this.columnKeys.add(columnKey); 342 } 343 } 344 345 /** 346 * Removes a value from the table by setting it to <code>null</code>. If 347 * all the values in the specified row and/or column are now 348 * <code>null</code>, the row and/or column is removed from the table. 349 * 350 * @param rowKey the row key (<code>null</code> not permitted). 351 * @param columnKey the column key (<code>null</code> not permitted). 352 * 353 * @see #addValue(Number, Comparable, Comparable) 354 */ 355 public void removeValue(Comparable rowKey, Comparable columnKey) { 356 setValue(null, rowKey, columnKey); 357 358 // 1. check whether the row is now empty. 359 boolean allNull = true; 360 int rowIndex = getRowIndex(rowKey); 361 DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 362 363 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 364 item++) { 365 if (row.getValue(item) != null) { 366 allNull = false; 367 break; 368 } 369 } 370 371 if (allNull) { 372 this.rowKeys.remove(rowIndex); 373 this.rows.remove(rowIndex); 374 } 375 376 // 2. check whether the column is now empty. 377 allNull = true; 378 //int columnIndex = getColumnIndex(columnKey); 379 380 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 381 item++) { 382 row = (DefaultKeyedValues) this.rows.get(item); 383 int columnIndex = row.getIndex(columnKey); 384 if (columnIndex >= 0 && row.getValue(columnIndex) != null) { 385 allNull = false; 386 break; 387 } 388 } 389 390 if (allNull) { 391 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 392 item++) { 393 row = (DefaultKeyedValues) this.rows.get(item); 394 int columnIndex = row.getIndex(columnKey); 395 if (columnIndex >= 0) { 396 row.removeValue(columnIndex); 397 } 398 } 399 this.columnKeys.remove(columnKey); 400 } 401 } 402 403 /** 404 * Removes a row. 405 * 406 * @param rowIndex the row index. 407 * 408 * @see #removeRow(Comparable) 409 * @see #removeColumn(int) 410 */ 411 public void removeRow(int rowIndex) { 412 this.rowKeys.remove(rowIndex); 413 this.rows.remove(rowIndex); 414 } 415 416 /** 417 * Removes a row from the table. 418 * 419 * @param rowKey the row key (<code>null</code> not permitted). 420 * 421 * @see #removeRow(int) 422 * @see #removeColumn(Comparable) 423 * 424 * @throws UnknownKeyException if <code>rowKey</code> is not defined in the 425 * table. 426 */ 427 public void removeRow(Comparable rowKey) { 428 if (rowKey == null) { 429 throw new IllegalArgumentException("Null 'rowKey' argument."); 430 } 431 int index = getRowIndex(rowKey); 432 if (index >= 0) { 433 removeRow(index); 434 } 435 else { 436 throw new UnknownKeyException("Unknown key: " + rowKey); 437 } 438 } 439 440 /** 441 * Removes a column. 442 * 443 * @param columnIndex the column index. 444 * 445 * @see #removeColumn(Comparable) 446 * @see #removeRow(int) 447 */ 448 public void removeColumn(int columnIndex) { 449 Comparable columnKey = getColumnKey(columnIndex); 450 removeColumn(columnKey); 451 } 452 453 /** 454 * Removes a column from the table. 455 * 456 * @param columnKey the column key (<code>null</code> not permitted). 457 * 458 * @throws UnknownKeyException if the table does not contain a column with 459 * the specified key. 460 * @throws IllegalArgumentException if <code>columnKey</code> is 461 * <code>null</code>. 462 * 463 * @see #removeColumn(int) 464 * @see #removeRow(Comparable) 465 */ 466 public void removeColumn(Comparable columnKey) { 467 if (columnKey == null) { 468 throw new IllegalArgumentException("Null 'columnKey' argument."); 469 } 470 if (!this.columnKeys.contains(columnKey)) { 471 throw new UnknownKeyException("Unknown key: " + columnKey); 472 } 473 Iterator iterator = this.rows.iterator(); 474 while (iterator.hasNext()) { 475 DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next(); 476 int index = rowData.getIndex(columnKey); 477 if (index >= 0) { 478 rowData.removeValue(columnKey); 479 } 480 } 481 this.columnKeys.remove(columnKey); 482 } 483 484 /** 485 * Clears all the data and associated keys. 486 */ 487 public void clear() { 488 this.rowKeys.clear(); 489 this.columnKeys.clear(); 490 this.rows.clear(); 491 } 492 493 /** 494 * Tests if this object is equal to another. 495 * 496 * @param o the other object (<code>null</code> permitted). 497 * 498 * @return A boolean. 499 */ 500 public boolean equals(Object o) { 501 502 if (o == null) { 503 return false; 504 } 505 if (o == this) { 506 return true; 507 } 508 509 if (!(o instanceof KeyedValues2D)) { 510 return false; 511 } 512 KeyedValues2D kv2D = (KeyedValues2D) o; 513 if (!getRowKeys().equals(kv2D.getRowKeys())) { 514 return false; 515 } 516 if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 517 return false; 518 } 519 int rowCount = getRowCount(); 520 if (rowCount != kv2D.getRowCount()) { 521 return false; 522 } 523 524 int colCount = getColumnCount(); 525 if (colCount != kv2D.getColumnCount()) { 526 return false; 527 } 528 529 for (int r = 0; r < rowCount; r++) { 530 for (int c = 0; c < colCount; c++) { 531 Number v1 = getValue(r, c); 532 Number v2 = kv2D.getValue(r, c); 533 if (v1 == null) { 534 if (v2 != null) { 535 return false; 536 } 537 } 538 else { 539 if (!v1.equals(v2)) { 540 return false; 541 } 542 } 543 } 544 } 545 return true; 546 } 547 548 /** 549 * Returns a hash code. 550 * 551 * @return A hash code. 552 */ 553 public int hashCode() { 554 int result; 555 result = this.rowKeys.hashCode(); 556 result = 29 * result + this.columnKeys.hashCode(); 557 result = 29 * result + this.rows.hashCode(); 558 return result; 559 } 560 561 /** 562 * Returns a clone. 563 * 564 * @return A clone. 565 * 566 * @throws CloneNotSupportedException this class will not throw this 567 * exception, but subclasses (if any) might. 568 */ 569 public Object clone() throws CloneNotSupportedException { 570 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 571 // for the keys, a shallow copy should be fine because keys 572 // should be immutable... 573 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 574 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 575 576 // but the row data requires a deep copy 577 clone.rows = (List) ObjectUtilities.deepClone(this.rows); 578 return clone; 579 } 580 581 }