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