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