001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2013, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------- 028 * DefaultKeyedValues2D.java 029 * ------------------------- 030 * (C) Copyright 2002-2013, 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 * 03-Jul-2013 : Use ParamChecks (DG); 056 * 057 */ 058 059package org.jfree.data; 060 061import java.io.Serializable; 062import java.util.Collections; 063import java.util.Iterator; 064import java.util.List; 065import org.jfree.chart.util.ParamChecks; 066 067import org.jfree.util.ObjectUtilities; 068import org.jfree.util.PublicCloneable; 069 070/** 071 * A data structure that stores zero, one or many values, where each value 072 * is associated with two keys (a 'row' key and a 'column' key). The keys 073 * should be (a) instances of {@link Comparable} and (b) immutable. 074 */ 075public class DefaultKeyedValues2D implements KeyedValues2D, PublicCloneable, 076 Cloneable, Serializable { 077 078 /** For serialization. */ 079 private static final long serialVersionUID = -5514169970951994748L; 080 081 /** The row keys. */ 082 private List rowKeys; 083 084 /** The column keys. */ 085 private List columnKeys; 086 087 /** The row data. */ 088 private List rows; 089 090 /** If the row keys should be sorted by their comparable order. */ 091 private boolean sortRowKeys; 092 093 /** 094 * Creates a new instance (initially empty). 095 */ 096 public DefaultKeyedValues2D() { 097 this(false); 098 } 099 100 /** 101 * Creates a new instance (initially empty). 102 * 103 * @param sortRowKeys if the row keys should be sorted. 104 */ 105 public DefaultKeyedValues2D(boolean sortRowKeys) { 106 this.rowKeys = new java.util.ArrayList(); 107 this.columnKeys = new java.util.ArrayList(); 108 this.rows = new java.util.ArrayList(); 109 this.sortRowKeys = sortRowKeys; 110 } 111 112 /** 113 * Returns the row count. 114 * 115 * @return The row count. 116 * 117 * @see #getColumnCount() 118 */ 119 @Override 120 public int getRowCount() { 121 return this.rowKeys.size(); 122 } 123 124 /** 125 * Returns the column count. 126 * 127 * @return The column count. 128 * 129 * @see #getRowCount() 130 */ 131 @Override 132 public int getColumnCount() { 133 return this.columnKeys.size(); 134 } 135 136 /** 137 * Returns the value for a given row and column. 138 * 139 * @param row the row index. 140 * @param column the column index. 141 * 142 * @return The value. 143 * 144 * @see #getValue(Comparable, Comparable) 145 */ 146 @Override 147 public Number getValue(int row, int column) { 148 Number result = null; 149 DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row); 150 if (rowData != null) { 151 Comparable columnKey = (Comparable) this.columnKeys.get(column); 152 // the row may not have an entry for this key, in which case the 153 // return value is null 154 int index = rowData.getIndex(columnKey); 155 if (index >= 0) { 156 result = rowData.getValue(index); 157 } 158 } 159 return result; 160 } 161 162 /** 163 * Returns the key for a given row. 164 * 165 * @param row the row index (in the range 0 to {@link #getRowCount()} - 1). 166 * 167 * @return The row key. 168 * 169 * @see #getRowIndex(Comparable) 170 * @see #getColumnKey(int) 171 */ 172 @Override 173 public Comparable getRowKey(int row) { 174 return (Comparable) this.rowKeys.get(row); 175 } 176 177 /** 178 * Returns the row index for a given key. 179 * 180 * @param key the key (<code>null</code> not permitted). 181 * 182 * @return The row index. 183 * 184 * @see #getRowKey(int) 185 * @see #getColumnIndex(Comparable) 186 */ 187 @Override 188 public int getRowIndex(Comparable key) { 189 ParamChecks.nullNotPermitted(key, "key"); 190 if (this.sortRowKeys) { 191 return Collections.binarySearch(this.rowKeys, key); 192 } 193 else { 194 return this.rowKeys.indexOf(key); 195 } 196 } 197 198 /** 199 * Returns the row keys in an unmodifiable list. 200 * 201 * @return The row keys. 202 * 203 * @see #getColumnKeys() 204 */ 205 @Override 206 public List getRowKeys() { 207 return Collections.unmodifiableList(this.rowKeys); 208 } 209 210 /** 211 * Returns the key for a given column. 212 * 213 * @param column the column (in the range 0 to {@link #getColumnCount()} 214 * - 1). 215 * 216 * @return The key. 217 * 218 * @see #getColumnIndex(Comparable) 219 * @see #getRowKey(int) 220 */ 221 @Override 222 public Comparable getColumnKey(int column) { 223 return (Comparable) this.columnKeys.get(column); 224 } 225 226 /** 227 * Returns the column index for a given key. 228 * 229 * @param key the key (<code>null</code> not permitted). 230 * 231 * @return The column index. 232 * 233 * @see #getColumnKey(int) 234 * @see #getRowIndex(Comparable) 235 */ 236 @Override 237 public int getColumnIndex(Comparable key) { 238 ParamChecks.nullNotPermitted(key, "key"); 239 return this.columnKeys.indexOf(key); 240 } 241 242 /** 243 * Returns the column keys in an unmodifiable list. 244 * 245 * @return The column keys. 246 * 247 * @see #getRowKeys() 248 */ 249 @Override 250 public List getColumnKeys() { 251 return Collections.unmodifiableList(this.columnKeys); 252 } 253 254 /** 255 * Returns the value for the given row and column keys. This method will 256 * throw an {@link UnknownKeyException} if either key is not defined in the 257 * data structure. 258 * 259 * @param rowKey the row key (<code>null</code> not permitted). 260 * @param columnKey the column key (<code>null</code> not permitted). 261 * 262 * @return The value (possibly <code>null</code>). 263 * 264 * @see #addValue(Number, Comparable, Comparable) 265 * @see #removeValue(Comparable, Comparable) 266 */ 267 @Override 268 public Number getValue(Comparable rowKey, Comparable columnKey) { 269 ParamChecks.nullNotPermitted(rowKey, "rowKey"); 270 ParamChecks.nullNotPermitted(columnKey, "columnKey"); 271 272 // check that the column key is defined in the 2D structure 273 if (!(this.columnKeys.contains(columnKey))) { 274 throw new UnknownKeyException("Unrecognised columnKey: " 275 + columnKey); 276 } 277 278 // now fetch the row data - need to bear in mind that the row 279 // structure may not have an entry for the column key, but that we 280 // have already checked that the key is valid for the 2D structure 281 int row = getRowIndex(rowKey); 282 if (row >= 0) { 283 DefaultKeyedValues rowData 284 = (DefaultKeyedValues) this.rows.get(row); 285 int col = rowData.getIndex(columnKey); 286 return (col >= 0 ? rowData.getValue(col) : null); 287 } 288 else { 289 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 290 } 291 } 292 293 /** 294 * Adds a value to the table. Performs the same function as 295 * #setValue(Number, Comparable, Comparable). 296 * 297 * @param value the value (<code>null</code> permitted). 298 * @param rowKey the row key (<code>null</code> not permitted). 299 * @param columnKey the column key (<code>null</code> not permitted). 300 * 301 * @see #setValue(Number, Comparable, Comparable) 302 * @see #removeValue(Comparable, Comparable) 303 */ 304 public void addValue(Number value, Comparable rowKey, 305 Comparable columnKey) { 306 // defer argument checking 307 setValue(value, rowKey, columnKey); 308 } 309 310 /** 311 * Adds or updates a value. 312 * 313 * @param value the value (<code>null</code> permitted). 314 * @param rowKey the row key (<code>null</code> not permitted). 315 * @param columnKey the column key (<code>null</code> not permitted). 316 * 317 * @see #addValue(Number, Comparable, Comparable) 318 * @see #removeValue(Comparable, Comparable) 319 */ 320 public void setValue(Number value, Comparable rowKey, 321 Comparable columnKey) { 322 323 DefaultKeyedValues row; 324 int rowIndex = getRowIndex(rowKey); 325 326 if (rowIndex >= 0) { 327 row = (DefaultKeyedValues) this.rows.get(rowIndex); 328 } 329 else { 330 row = new DefaultKeyedValues(); 331 if (this.sortRowKeys) { 332 rowIndex = -rowIndex - 1; 333 this.rowKeys.add(rowIndex, rowKey); 334 this.rows.add(rowIndex, row); 335 } 336 else { 337 this.rowKeys.add(rowKey); 338 this.rows.add(row); 339 } 340 } 341 row.setValue(columnKey, value); 342 343 int columnIndex = this.columnKeys.indexOf(columnKey); 344 if (columnIndex < 0) { 345 this.columnKeys.add(columnKey); 346 } 347 } 348 349 /** 350 * Removes a value from the table by setting it to <code>null</code>. If 351 * all the values in the specified row and/or column are now 352 * <code>null</code>, the row and/or column is removed from the table. 353 * 354 * @param rowKey the row key (<code>null</code> not permitted). 355 * @param columnKey the column key (<code>null</code> not permitted). 356 * 357 * @see #addValue(Number, Comparable, Comparable) 358 */ 359 public void removeValue(Comparable rowKey, Comparable columnKey) { 360 setValue(null, rowKey, columnKey); 361 362 // 1. check whether the row is now empty. 363 boolean allNull = true; 364 int rowIndex = getRowIndex(rowKey); 365 DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 366 367 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 368 item++) { 369 if (row.getValue(item) != null) { 370 allNull = false; 371 break; 372 } 373 } 374 375 if (allNull) { 376 this.rowKeys.remove(rowIndex); 377 this.rows.remove(rowIndex); 378 } 379 380 // 2. check whether the column is now empty. 381 allNull = true; 382 //int columnIndex = getColumnIndex(columnKey); 383 384 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 385 item++) { 386 row = (DefaultKeyedValues) this.rows.get(item); 387 int columnIndex = row.getIndex(columnKey); 388 if (columnIndex >= 0 && row.getValue(columnIndex) != null) { 389 allNull = false; 390 break; 391 } 392 } 393 394 if (allNull) { 395 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 396 item++) { 397 row = (DefaultKeyedValues) this.rows.get(item); 398 int columnIndex = row.getIndex(columnKey); 399 if (columnIndex >= 0) { 400 row.removeValue(columnIndex); 401 } 402 } 403 this.columnKeys.remove(columnKey); 404 } 405 } 406 407 /** 408 * Removes a row. 409 * 410 * @param rowIndex the row index. 411 * 412 * @see #removeRow(Comparable) 413 * @see #removeColumn(int) 414 */ 415 public void removeRow(int rowIndex) { 416 this.rowKeys.remove(rowIndex); 417 this.rows.remove(rowIndex); 418 } 419 420 /** 421 * Removes a row from the table. 422 * 423 * @param rowKey the row key (<code>null</code> not permitted). 424 * 425 * @see #removeRow(int) 426 * @see #removeColumn(Comparable) 427 * 428 * @throws UnknownKeyException if <code>rowKey</code> is not defined in the 429 * table. 430 */ 431 public void removeRow(Comparable rowKey) { 432 ParamChecks.nullNotPermitted(rowKey, "rowKey"); 433 int index = getRowIndex(rowKey); 434 if (index >= 0) { 435 removeRow(index); 436 } 437 else { 438 throw new UnknownKeyException("Unknown key: " + rowKey); 439 } 440 } 441 442 /** 443 * Removes a column. 444 * 445 * @param columnIndex the column index. 446 * 447 * @see #removeColumn(Comparable) 448 * @see #removeRow(int) 449 */ 450 public void removeColumn(int columnIndex) { 451 Comparable columnKey = getColumnKey(columnIndex); 452 removeColumn(columnKey); 453 } 454 455 /** 456 * Removes a column from the table. 457 * 458 * @param columnKey the column key (<code>null</code> not permitted). 459 * 460 * @throws UnknownKeyException if the table does not contain a column with 461 * the specified key. 462 * @throws IllegalArgumentException if <code>columnKey</code> is 463 * <code>null</code>. 464 * 465 * @see #removeColumn(int) 466 * @see #removeRow(Comparable) 467 */ 468 public void removeColumn(Comparable columnKey) { 469 ParamChecks.nullNotPermitted(columnKey, "columnKey"); 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 @Override 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 @Override 555 public int hashCode() { 556 int result; 557 result = this.rowKeys.hashCode(); 558 result = 29 * result + this.columnKeys.hashCode(); 559 result = 29 * result + this.rows.hashCode(); 560 return result; 561 } 562 563 /** 564 * Returns a clone. 565 * 566 * @return A clone. 567 * 568 * @throws CloneNotSupportedException this class will not throw this 569 * exception, but subclasses (if any) might. 570 */ 571 @Override 572 public Object clone() throws CloneNotSupportedException { 573 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 574 // for the keys, a shallow copy should be fine because keys 575 // should be immutable... 576 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 577 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 578 579 // but the row data requires a deep copy 580 clone.rows = (List) ObjectUtilities.deepClone(this.rows); 581 return clone; 582 } 583 584}