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