001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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-2022, 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.ArrayList; 041import java.util.Collections; 042import java.util.List; 043 044import org.jfree.chart.internal.CloneUtils; 045import org.jfree.chart.internal.Args; 046import org.jfree.chart.api.PublicCloneable; 047 048/** 049 * A data structure that stores zero, one or many values, where each value 050 * is associated with two keys (a 'row' key and a 'column' key). The keys 051 * should be (a) instances of {@link Comparable} and (b) immutable. 052 */ 053public class DefaultKeyedValues2D<R extends Comparable<R>, C extends Comparable<C>> 054 implements KeyedValues2D<R, C>, PublicCloneable, Cloneable, Serializable { 055 056 /** For serialization. */ 057 private static final long serialVersionUID = -5514169970951994748L; 058 059 /** The row keys. */ 060 private List<R> rowKeys; 061 062 /** The column keys. */ 063 private List<C> columnKeys; 064 065 /** The row data. */ 066 private List<DefaultKeyedValues<C>> rows; 067 068 /** If the row keys should be sorted by their comparable order. */ 069 private final boolean sortRowKeys; 070 071 /** 072 * Creates a new instance (initially empty). 073 */ 074 public DefaultKeyedValues2D() { 075 this(false); 076 } 077 078 /** 079 * Creates a new instance (initially empty). 080 * 081 * @param sortRowKeys if the row keys should be sorted. 082 */ 083 public DefaultKeyedValues2D(boolean sortRowKeys) { 084 this.rowKeys = new ArrayList<>(); 085 this.columnKeys = new ArrayList<>(); 086 this.rows = new ArrayList<>(); 087 this.sortRowKeys = sortRowKeys; 088 } 089 090 /** 091 * Returns the row count. 092 * 093 * @return The row count. 094 * 095 * @see #getColumnCount() 096 */ 097 @Override 098 public int getRowCount() { 099 return this.rowKeys.size(); 100 } 101 102 /** 103 * Returns the column count. 104 * 105 * @return The column count. 106 * 107 * @see #getRowCount() 108 */ 109 @Override 110 public int getColumnCount() { 111 return this.columnKeys.size(); 112 } 113 114 /** 115 * Returns the value for a given row and column. 116 * 117 * @param row the row index. 118 * @param column the column index. 119 * 120 * @return The value. 121 * 122 * @see #getValue(Comparable, Comparable) 123 */ 124 @Override 125 public Number getValue(int row, int column) { 126 Number result = null; 127 DefaultKeyedValues<C> rowData = this.rows.get(row); 128 if (rowData != null) { 129 C columnKey = this.columnKeys.get(column); 130 // the row may not have an entry for this key, in which case the 131 // return value is null 132 int index = rowData.getIndex(columnKey); 133 if (index >= 0) { 134 result = rowData.getValue(index); 135 } 136 } 137 return result; 138 } 139 140 /** 141 * Returns the key for a given row. 142 * 143 * @param row the row index (in the range 0 to {@link #getRowCount()} - 1). 144 * 145 * @return The row key. 146 * 147 * @see #getRowIndex(Comparable) 148 * @see #getColumnKey(int) 149 */ 150 @Override 151 public R getRowKey(int row) { 152 return this.rowKeys.get(row); 153 } 154 155 /** 156 * Returns the row index for a given key. 157 * 158 * @param key the key ({@code null} not permitted). 159 * 160 * @return The row index. 161 * 162 * @see #getRowKey(int) 163 * @see #getColumnIndex(Comparable) 164 */ 165 @Override 166 public int getRowIndex(R key) { 167 Args.nullNotPermitted(key, "key"); 168 if (this.sortRowKeys) { 169 return Collections.<R>binarySearch(this.rowKeys, key); 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<R> 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 C getColumnKey(int column) { 200 return 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(C 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<C> 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(R rowKey, C 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 = this.rows.get(row); 261 int col = rowData.getIndex(columnKey); 262 return (col >= 0 ? rowData.getValue(col) : null); 263 } 264 else { 265 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 266 } 267 } 268 269 /** 270 * Adds a value to the table. Performs the same function as 271 * #setValue(Number, Comparable, Comparable). 272 * 273 * @param value the value ({@code null} permitted). 274 * @param rowKey the row key ({@code null} not permitted). 275 * @param columnKey the column key ({@code null} not permitted). 276 * 277 * @see #setValue(Number, Comparable, Comparable) 278 * @see #removeValue(Comparable, Comparable) 279 */ 280 public void addValue(Number value, R rowKey, C columnKey) { 281 // defer argument checking 282 setValue(value, rowKey, columnKey); 283 } 284 285 /** 286 * Adds or updates a value. 287 * 288 * @param value the value ({@code null} permitted). 289 * @param rowKey the row key ({@code null} not permitted). 290 * @param columnKey the column key ({@code null} not permitted). 291 * 292 * @see #addValue(Number, Comparable, Comparable) 293 * @see #removeValue(Comparable, Comparable) 294 */ 295 public void setValue(Number value, R rowKey, C columnKey) { 296 297 DefaultKeyedValues row; 298 int rowIndex = getRowIndex(rowKey); 299 300 if (rowIndex >= 0) { 301 row = this.rows.get(rowIndex); 302 } 303 else { 304 row = new DefaultKeyedValues(); 305 if (this.sortRowKeys) { 306 rowIndex = -rowIndex - 1; 307 this.rowKeys.add(rowIndex, rowKey); 308 this.rows.add(rowIndex, row); 309 } 310 else { 311 this.rowKeys.add(rowKey); 312 this.rows.add(row); 313 } 314 } 315 row.setValue(columnKey, value); 316 317 int columnIndex = this.columnKeys.indexOf(columnKey); 318 if (columnIndex < 0) { 319 this.columnKeys.add(columnKey); 320 } 321 } 322 323 /** 324 * Removes a value from the table by setting it to {@code null}. If 325 * all the values in the specified row and/or column are now 326 * {@code null}, the row and/or column is removed from the table. 327 * 328 * @param rowKey the row key ({@code null} not permitted). 329 * @param columnKey the column key ({@code null} not permitted). 330 * 331 * @see #addValue(Number, Comparable, Comparable) 332 */ 333 public void removeValue(R rowKey, C columnKey) { 334 setValue(null, rowKey, columnKey); 335 336 // 1. check whether the row is now empty. 337 boolean allNull = true; 338 int rowIndex = getRowIndex(rowKey); 339 DefaultKeyedValues row = this.rows.get(rowIndex); 340 341 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 342 item++) { 343 if (row.getValue(item) != null) { 344 allNull = false; 345 break; 346 } 347 } 348 349 if (allNull) { 350 this.rowKeys.remove(rowIndex); 351 this.rows.remove(rowIndex); 352 } 353 354 // 2. check whether the column is now empty. 355 allNull = true; 356 //int columnIndex = getColumnIndex(columnKey); 357 358 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 359 item++) { 360 row = this.rows.get(item); 361 int columnIndex = row.getIndex(columnKey); 362 if (columnIndex >= 0 && 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 = this.rows.get(item); 372 int columnIndex = row.getIndex(columnKey); 373 if (columnIndex >= 0) { 374 row.removeValue(columnIndex); 375 } 376 } 377 this.columnKeys.remove(columnKey); 378 } 379 } 380 381 /** 382 * Removes a row. 383 * 384 * @param rowIndex the row index. 385 * 386 * @see #removeRow(Comparable) 387 * @see #removeColumn(int) 388 */ 389 public void removeRow(int rowIndex) { 390 this.rowKeys.remove(rowIndex); 391 this.rows.remove(rowIndex); 392 } 393 394 /** 395 * Removes a row from the table. 396 * 397 * @param rowKey the row key ({@code null} not permitted). 398 * 399 * @see #removeRow(int) 400 * @see #removeColumn(Comparable) 401 * 402 * @throws UnknownKeyException if {@code rowKey} is not defined in the 403 * table. 404 */ 405 public void removeRow(R rowKey) { 406 Args.nullNotPermitted(rowKey, "rowKey"); 407 int index = getRowIndex(rowKey); 408 if (index >= 0) { 409 removeRow(index); 410 } 411 else { 412 throw new UnknownKeyException("Unknown key: " + rowKey); 413 } 414 } 415 416 /** 417 * Removes a column. 418 * 419 * @param columnIndex the column index. 420 * 421 * @see #removeColumn(Comparable) 422 * @see #removeRow(int) 423 */ 424 public void removeColumn(int columnIndex) { 425 C columnKey = getColumnKey(columnIndex); 426 removeColumn(columnKey); 427 } 428 429 /** 430 * Removes a column from the table. 431 * 432 * @param columnKey the column key ({@code null} not permitted). 433 * 434 * @throws UnknownKeyException if the table does not contain a column with 435 * the specified key. 436 * @throws IllegalArgumentException if {@code columnKey} is 437 * {@code null}. 438 * 439 * @see #removeColumn(int) 440 * @see #removeRow(Comparable) 441 */ 442 public void removeColumn(C columnKey) { 443 Args.nullNotPermitted(columnKey, "columnKey"); 444 if (!this.columnKeys.contains(columnKey)) { 445 throw new UnknownKeyException("Unknown key: " + columnKey); 446 } 447 for (DefaultKeyedValues rowData : this.rows) { 448 int index = rowData.getIndex(columnKey); 449 if (index >= 0) { 450 rowData.removeValue(columnKey); 451 } 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} permitted). 469 * 470 * @return A boolean. 471 */ 472 @Override 473 public boolean equals(Object o) { 474 475 if (o == null) { 476 return false; 477 } 478 if (o == this) { 479 return true; 480 } 481 482 if (!(o instanceof KeyedValues2D)) { 483 return false; 484 } 485 KeyedValues2D kv2D = (KeyedValues2D) o; 486 if (!getRowKeys().equals(kv2D.getRowKeys())) { 487 return false; 488 } 489 if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 490 return false; 491 } 492 int rowCount = getRowCount(); 493 if (rowCount != kv2D.getRowCount()) { 494 return false; 495 } 496 497 int colCount = getColumnCount(); 498 if (colCount != kv2D.getColumnCount()) { 499 return false; 500 } 501 502 for (int r = 0; r < rowCount; r++) { 503 for (int c = 0; c < colCount; c++) { 504 Number v1 = getValue(r, c); 505 Number v2 = kv2D.getValue(r, c); 506 if (v1 == null) { 507 if (v2 != null) { 508 return false; 509 } 510 } 511 else { 512 if (!v1.equals(v2)) { 513 return false; 514 } 515 } 516 } 517 } 518 return true; 519 } 520 521 /** 522 * Returns a hash code. 523 * 524 * @return A hash code. 525 */ 526 @Override 527 public int hashCode() { 528 int result; 529 result = this.rowKeys.hashCode(); 530 result = 29 * result + this.columnKeys.hashCode(); 531 result = 29 * result + this.rows.hashCode(); 532 return result; 533 } 534 535 /** 536 * Returns a clone. 537 * 538 * @return A clone. 539 * 540 * @throws CloneNotSupportedException this class will not throw this 541 * exception, but subclasses (if any) might. 542 */ 543 @Override 544 public Object clone() throws CloneNotSupportedException { 545 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 546 // for the keys, a shallow copy should be fine because keys 547 // should be immutable... 548 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 549 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 550 551 // but the row data requires a deep copy 552 clone.rows = CloneUtils.cloneList(this.rows); 553 return clone; 554 } 555 556}