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.3 2007/01/18 09:25:21 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 (zero based). 160 * 161 * @return The row key. 162 * 163 * @see #getRowIndex(Comparable) 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 */ 178 public int getRowIndex(Comparable key) { 179 if (key == null) { 180 throw new IllegalArgumentException("Null 'key' argument."); 181 } 182 if (this.sortRowKeys) { 183 return Collections.binarySearch(this.rowKeys, key); 184 } 185 else { 186 return this.rowKeys.indexOf(key); 187 } 188 } 189 190 /** 191 * Returns the row keys in an unmodifiable list. 192 * 193 * @return The row keys. 194 */ 195 public List getRowKeys() { 196 return Collections.unmodifiableList(this.rowKeys); 197 } 198 199 /** 200 * Returns the key for a given column. 201 * 202 * @param column the column. 203 * 204 * @return The key. 205 * 206 * @see #getColumnIndex(Comparable) 207 */ 208 public Comparable getColumnKey(int column) { 209 return (Comparable) this.columnKeys.get(column); 210 } 211 212 /** 213 * Returns the column index for a given key. 214 * 215 * @param key the key (<code>null</code> not permitted). 216 * 217 * @return The column index. 218 * 219 * @see #getColumnKey(int) 220 */ 221 public int getColumnIndex(Comparable key) { 222 if (key == null) { 223 throw new IllegalArgumentException("Null 'key' argument."); 224 } 225 return this.columnKeys.indexOf(key); 226 } 227 228 /** 229 * Returns the column keys in an unmodifiable list. 230 * 231 * @return The column keys. 232 * 233 * @see #getRowKeys() 234 */ 235 public List getColumnKeys() { 236 return Collections.unmodifiableList(this.columnKeys); 237 } 238 239 /** 240 * Returns the value for the given row and column keys. This method will 241 * throw an {@link UnknownKeyException} if either key is not defined in the 242 * data structure. 243 * 244 * @param rowKey the row key (<code>null</code> not permitted). 245 * @param columnKey the column key (<code>null</code> not permitted). 246 * 247 * @return The value (possibly <code>null</code>). 248 */ 249 public Number getValue(Comparable rowKey, Comparable columnKey) { 250 if (rowKey == null) { 251 throw new IllegalArgumentException("Null 'rowKey' argument."); 252 } 253 if (columnKey == null) { 254 throw new IllegalArgumentException("Null 'columnKey' argument."); 255 } 256 257 // check that the column key is defined in the 2D structure 258 if (!(this.columnKeys.contains(columnKey))) { 259 throw new UnknownKeyException("Unrecognised columnKey: " 260 + columnKey); 261 } 262 263 // now fetch the row data - need to bear in mind that the row 264 // structure may not have an entry for the column key, but that we 265 // have already checked that the key is valid for the 2D structure 266 int row = getRowIndex(rowKey); 267 if (row >= 0) { 268 DefaultKeyedValues rowData 269 = (DefaultKeyedValues) this.rows.get(row); 270 int col = rowData.getIndex(columnKey); 271 return (col >= 0 ? rowData.getValue(col) : null); 272 } 273 else { 274 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 275 } 276 } 277 278 /** 279 * Adds a value to the table. Performs the same function as 280 * #setValue(Number, Comparable, Comparable). 281 * 282 * @param value the value (<code>null</code> permitted). 283 * @param rowKey the row key (<code>null</code> not permitted). 284 * @param columnKey the column key (<code>null</code> not permitted). 285 */ 286 public void addValue(Number value, Comparable rowKey, 287 Comparable columnKey) { 288 // defer argument checking 289 setValue(value, rowKey, columnKey); 290 } 291 292 /** 293 * Adds or updates a value. 294 * 295 * @param value the value (<code>null</code> permitted). 296 * @param rowKey the row key (<code>null</code> not permitted). 297 * @param columnKey the column key (<code>null</code> not permitted). 298 */ 299 public void setValue(Number value, Comparable rowKey, 300 Comparable columnKey) { 301 302 DefaultKeyedValues row; 303 int rowIndex = getRowIndex(rowKey); 304 305 if (rowIndex >= 0) { 306 row = (DefaultKeyedValues) this.rows.get(rowIndex); 307 } 308 else { 309 row = new DefaultKeyedValues(); 310 if (this.sortRowKeys) { 311 rowIndex = -rowIndex - 1; 312 this.rowKeys.add(rowIndex, rowKey); 313 this.rows.add(rowIndex, row); 314 } 315 else { 316 this.rowKeys.add(rowKey); 317 this.rows.add(row); 318 } 319 } 320 row.setValue(columnKey, value); 321 322 int columnIndex = this.columnKeys.indexOf(columnKey); 323 if (columnIndex < 0) { 324 this.columnKeys.add(columnKey); 325 } 326 } 327 328 /** 329 * Removes a value. 330 * 331 * @param rowKey the row key (<code>null</code> not permitted). 332 * @param columnKey the column key (<code>null</code> not permitted). 333 */ 334 public void removeValue(Comparable rowKey, Comparable columnKey) { 335 setValue(null, rowKey, columnKey); 336 337 // 1. check whether the row is now empty. 338 boolean allNull = true; 339 int rowIndex = getRowIndex(rowKey); 340 DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 341 342 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 343 item++) { 344 if (row.getValue(item) != null) { 345 allNull = false; 346 break; 347 } 348 } 349 350 if (allNull) { 351 this.rowKeys.remove(rowIndex); 352 this.rows.remove(rowIndex); 353 } 354 355 // 2. check whether the column is now empty. 356 allNull = true; 357 int columnIndex = getColumnIndex(columnKey); 358 359 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 360 item++) { 361 row = (DefaultKeyedValues) this.rows.get(item); 362 if (row.getValue(columnIndex) != null) { 363 allNull = false; 364 break; 365 } 366 } 367 368 if (allNull) { 369 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 370 item++) { 371 row = (DefaultKeyedValues) this.rows.get(item); 372 row.removeValue(columnIndex); 373 } 374 this.columnKeys.remove(columnIndex); 375 } 376 } 377 378 /** 379 * Removes a row. 380 * 381 * @param rowIndex the row index. 382 * 383 * @see #removeRow(Comparable) 384 */ 385 public void removeRow(int rowIndex) { 386 this.rowKeys.remove(rowIndex); 387 this.rows.remove(rowIndex); 388 } 389 390 /** 391 * Removes a row. 392 * 393 * @param rowKey the row key (<code>null</code> not permitted). 394 * 395 * @see #removeRow(int) 396 */ 397 public void removeRow(Comparable rowKey) { 398 removeRow(getRowIndex(rowKey)); 399 } 400 401 /** 402 * Removes a column. 403 * 404 * @param columnIndex the column index. 405 * 406 * @see #removeColumn(Comparable) 407 */ 408 public void removeColumn(int columnIndex) { 409 Comparable columnKey = getColumnKey(columnIndex); 410 removeColumn(columnKey); 411 } 412 413 /** 414 * Removes a column. 415 * 416 * @param columnKey the column key (<code>null</code> not permitted). 417 * 418 * @see #removeColumn(int) 419 */ 420 public void removeColumn(Comparable columnKey) { 421 Iterator iterator = this.rows.iterator(); 422 while (iterator.hasNext()) { 423 DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next(); 424 rowData.removeValue(columnKey); 425 } 426 this.columnKeys.remove(columnKey); 427 } 428 429 /** 430 * Clears all the data and associated keys. 431 */ 432 public void clear() { 433 this.rowKeys.clear(); 434 this.columnKeys.clear(); 435 this.rows.clear(); 436 } 437 438 /** 439 * Tests if this object is equal to another. 440 * 441 * @param o the other object (<code>null</code> permitted). 442 * 443 * @return A boolean. 444 */ 445 public boolean equals(Object o) { 446 447 if (o == null) { 448 return false; 449 } 450 if (o == this) { 451 return true; 452 } 453 454 if (!(o instanceof KeyedValues2D)) { 455 return false; 456 } 457 KeyedValues2D kv2D = (KeyedValues2D) o; 458 if (!getRowKeys().equals(kv2D.getRowKeys())) { 459 return false; 460 } 461 if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 462 return false; 463 } 464 int rowCount = getRowCount(); 465 if (rowCount != kv2D.getRowCount()) { 466 return false; 467 } 468 469 int colCount = getColumnCount(); 470 if (colCount != kv2D.getColumnCount()) { 471 return false; 472 } 473 474 for (int r = 0; r < rowCount; r++) { 475 for (int c = 0; c < colCount; c++) { 476 Number v1 = getValue(r, c); 477 Number v2 = kv2D.getValue(r, c); 478 if (v1 == null) { 479 if (v2 != null) { 480 return false; 481 } 482 } 483 else { 484 if (!v1.equals(v2)) { 485 return false; 486 } 487 } 488 } 489 } 490 return true; 491 } 492 493 /** 494 * Returns a hash code. 495 * 496 * @return A hash code. 497 */ 498 public int hashCode() { 499 int result; 500 result = this.rowKeys.hashCode(); 501 result = 29 * result + this.columnKeys.hashCode(); 502 result = 29 * result + this.rows.hashCode(); 503 return result; 504 } 505 506 /** 507 * Returns a clone. 508 * 509 * @return A clone. 510 * 511 * @throws CloneNotSupportedException this class will not throw this 512 * exception, but subclasses (if any) might. 513 */ 514 public Object clone() throws CloneNotSupportedException { 515 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 516 // for the keys, a shallow copy should be fine because keys 517 // should be immutable... 518 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 519 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 520 521 // but the row data requires a deep copy 522 clone.rows = (List) ObjectUtilities.deepClone(this.rows); 523 return clone; 524 } 525 526 }