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 * KeyedObject2D.java 029 * ------------------ 030 * (C) Copyright 2003-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.data; 038 039import java.io.Serializable; 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.List; 043import org.jfree.chart.internal.Args; 044 045/** 046 * A data structure that stores zero, one or many objects, where each object is 047 * associated with two keys (a 'row' key and a 'column' key). 048 */ 049public class KeyedObjects2D<R extends Comparable<R>, C extends Comparable<C>> 050 implements Cloneable, Serializable { 051 052 /** For serialization. */ 053 private static final long serialVersionUID = -1015873563138522374L; 054 055 /** The row keys. */ 056 private List<R> rowKeys; 057 058 /** The column keys. */ 059 private List<C> columnKeys; 060 061 /** The row data. */ 062 private List<KeyedObjects> rows; 063 064 /** 065 * Creates a new instance (initially empty). 066 */ 067 public KeyedObjects2D() { 068 this.rowKeys = new ArrayList<>(); 069 this.columnKeys = new ArrayList<>(); 070 this.rows = new java.util.ArrayList(); 071 } 072 073 /** 074 * Returns the row count. 075 * 076 * @return The row count. 077 * 078 * @see #getColumnCount() 079 */ 080 public int getRowCount() { 081 return this.rowKeys.size(); 082 } 083 084 /** 085 * Returns the column count. 086 * 087 * @return The column count. 088 * 089 * @see #getRowCount() 090 */ 091 public int getColumnCount() { 092 return this.columnKeys.size(); 093 } 094 095 /** 096 * Returns the object for a given row and column. 097 * 098 * @param row the row index (in the range 0 to getRowCount() - 1). 099 * @param column the column index (in the range 0 to getColumnCount() - 1). 100 * 101 * @return The object (possibly {@code null}). 102 * 103 * @see #getObject(Comparable, Comparable) 104 */ 105 public Object getObject(int row, int column) { 106 Object result = null; 107 KeyedObjects rowData = (KeyedObjects) this.rows.get(row); 108 if (rowData != null) { 109 Comparable columnKey = (Comparable) this.columnKeys.get(column); 110 if (columnKey != null) { 111 int index = rowData.getIndex(columnKey); 112 if (index >= 0) { 113 result = rowData.getObject(columnKey); 114 } 115 } 116 } 117 return result; 118 } 119 120 /** 121 * Returns the key for a given row. 122 * 123 * @param row the row index (zero based). 124 * 125 * @return The row index. 126 * 127 * @see #getRowIndex(Comparable) 128 */ 129 public R getRowKey(int row) { 130 return this.rowKeys.get(row); 131 } 132 133 /** 134 * Returns the row index for a given key, or {@code -1} if the key 135 * is not recognised. 136 * 137 * @param key the key ({@code null} not permitted). 138 * 139 * @return The row index. 140 * 141 * @see #getRowKey(int) 142 */ 143 public int getRowIndex(R key) { 144 Args.nullNotPermitted(key, "key"); 145 return this.rowKeys.indexOf(key); 146 } 147 148 /** 149 * Returns the row keys. 150 * 151 * @return The row keys (never {@code null}). 152 * 153 * @see #getRowKeys() 154 */ 155 public List<R> getRowKeys() { 156 return Collections.unmodifiableList(this.rowKeys); 157 } 158 159 /** 160 * Returns the key for a given column. 161 * 162 * @param column the column. 163 * 164 * @return The key. 165 * 166 * @see #getColumnIndex(Comparable) 167 */ 168 public C getColumnKey(int column) { 169 return this.columnKeys.get(column); 170 } 171 172 /** 173 * Returns the column index for a given key, or {@code -1} if the key 174 * is not recognised. 175 * 176 * @param key the key ({@code null} not permitted). 177 * 178 * @return The column index. 179 * 180 * @see #getColumnKey(int) 181 */ 182 public int getColumnIndex(C key) { 183 Args.nullNotPermitted(key, "key"); 184 return this.columnKeys.indexOf(key); 185 } 186 187 /** 188 * Returns the column keys. 189 * 190 * @return The column keys (never {@code null}). 191 * 192 * @see #getRowKeys() 193 */ 194 public List<C> getColumnKeys() { 195 return Collections.unmodifiableList(this.columnKeys); 196 } 197 198 /** 199 * Returns the object for the given row and column keys. 200 * 201 * @param rowKey the row key ({@code null} not permitted). 202 * @param columnKey the column key ({@code null} not permitted). 203 * 204 * @return The object (possibly {@code null}). 205 * 206 * @throws IllegalArgumentException if {@code rowKey} or 207 * {@code columnKey} is {@code null}. 208 * @throws UnknownKeyException if {@code rowKey} or 209 * {@code columnKey} is not recognised. 210 */ 211 public Object getObject(R rowKey, C columnKey) { 212 Args.nullNotPermitted(rowKey, "rowKey"); 213 Args.nullNotPermitted(columnKey, "columnKey"); 214 int row = this.rowKeys.indexOf(rowKey); 215 if (row < 0) { 216 throw new UnknownKeyException("Row key (" + rowKey 217 + ") not recognised."); 218 } 219 int column = this.columnKeys.indexOf(columnKey); 220 if (column < 0) { 221 throw new UnknownKeyException("Column key (" + columnKey 222 + ") not recognised."); 223 } 224 KeyedObjects rowData = (KeyedObjects) this.rows.get(row); 225 int index = rowData.getIndex(columnKey); 226 if (index >= 0) { 227 return rowData.getObject(index); 228 } 229 else { 230 return null; 231 } 232 } 233 234 /** 235 * Adds an object to the table. Performs the same function as setObject(). 236 * 237 * @param object the object. 238 * @param rowKey the row key ({@code null} not permitted). 239 * @param columnKey the column key ({@code null} not permitted). 240 */ 241 public void addObject(Object object, R rowKey, C columnKey) { 242 setObject(object, rowKey, columnKey); 243 } 244 245 /** 246 * Adds or updates an object. 247 * 248 * @param object the object. 249 * @param rowKey the row key ({@code null} not permitted). 250 * @param columnKey the column key ({@code null} not permitted). 251 */ 252 public void setObject(Object object, R rowKey, C columnKey) { 253 Args.nullNotPermitted(rowKey, "rowKey"); 254 Args.nullNotPermitted(columnKey, "columnKey"); 255 KeyedObjects row; 256 int rowIndex = this.rowKeys.indexOf(rowKey); 257 if (rowIndex >= 0) { 258 row = (KeyedObjects) this.rows.get(rowIndex); 259 } 260 else { 261 this.rowKeys.add(rowKey); 262 row = new KeyedObjects(); 263 this.rows.add(row); 264 } 265 row.setObject(columnKey, object); 266 int columnIndex = this.columnKeys.indexOf(columnKey); 267 if (columnIndex < 0) { 268 this.columnKeys.add(columnKey); 269 } 270 } 271 272 /** 273 * Removes an object from the table by setting it to {@code null}. If 274 * all the objects in the specified row and/or column are now 275 * {@code null}, the row and/or column is removed from the table. 276 * 277 * @param rowKey the row key ({@code null} not permitted). 278 * @param columnKey the column key ({@code null} not permitted). 279 * 280 * @see #addObject(Object, Comparable, Comparable) 281 */ 282 public void removeObject(R rowKey, C columnKey) { 283 int rowIndex = getRowIndex(rowKey); 284 if (rowIndex < 0) { 285 throw new UnknownKeyException("Row key (" + rowKey 286 + ") not recognised."); 287 } 288 int columnIndex = getColumnIndex(columnKey); 289 if (columnIndex < 0) { 290 throw new UnknownKeyException("Column key (" + columnKey 291 + ") not recognised."); 292 } 293 setObject(null, rowKey, columnKey); 294 295 // 1. check whether the row is now empty. 296 boolean allNull = true; 297 KeyedObjects row = (KeyedObjects) this.rows.get(rowIndex); 298 299 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 300 item++) { 301 if (row.getObject(item) != null) { 302 allNull = false; 303 break; 304 } 305 } 306 307 if (allNull) { 308 this.rowKeys.remove(rowIndex); 309 this.rows.remove(rowIndex); 310 } 311 312 // 2. check whether the column is now empty. 313 allNull = true; 314 315 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 316 item++) { 317 row = (KeyedObjects) this.rows.get(item); 318 int colIndex = row.getIndex(columnKey); 319 if (colIndex >= 0 && row.getObject(colIndex) != null) { 320 allNull = false; 321 break; 322 } 323 } 324 325 if (allNull) { 326 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 327 item++) { 328 row = (KeyedObjects) this.rows.get(item); 329 int colIndex = row.getIndex(columnKey); 330 if (colIndex >= 0) { 331 row.removeValue(colIndex); 332 } 333 } 334 this.columnKeys.remove(columnKey); 335 } 336 } 337 338 /** 339 * Removes an entire row from the table. 340 * 341 * @param rowIndex the row index. 342 * 343 * @see #removeColumn(int) 344 */ 345 public void removeRow(int rowIndex) { 346 this.rowKeys.remove(rowIndex); 347 this.rows.remove(rowIndex); 348 } 349 350 /** 351 * Removes an entire row from the table. 352 * 353 * @param rowKey the row key ({@code null} not permitted). 354 * 355 * @throws UnknownKeyException if {@code rowKey} is not recognised. 356 * 357 * @see #removeColumn(Comparable) 358 */ 359 public void removeRow(R rowKey) { 360 int index = getRowIndex(rowKey); 361 if (index < 0) { 362 throw new UnknownKeyException("Row key (" + rowKey 363 + ") not recognised."); 364 } 365 removeRow(index); 366 } 367 368 /** 369 * Removes an entire column from the table. 370 * 371 * @param columnIndex the column index. 372 * 373 * @see #removeRow(int) 374 */ 375 public void removeColumn(int columnIndex) { 376 C columnKey = getColumnKey(columnIndex); 377 removeColumn(columnKey); 378 } 379 380 /** 381 * Removes an entire column from the table. 382 * 383 * @param columnKey the column key ({@code null} not permitted). 384 * 385 * @throws UnknownKeyException if {@code rowKey} is not recognised. 386 * 387 * @see #removeRow(Comparable) 388 */ 389 public void removeColumn(C columnKey) { 390 int index = getColumnIndex(columnKey); 391 if (index < 0) { 392 throw new UnknownKeyException("Column key (" + columnKey 393 + ") not recognised."); 394 } 395 for (KeyedObjects rowData : this.rows) { 396 int i = rowData.getIndex(columnKey); 397 if (i >= 0) { 398 rowData.removeValue(i); 399 } 400 } 401 this.columnKeys.remove(columnKey); 402 } 403 404 /** 405 * Clears all the data and associated keys. 406 * 407 * @since 1.0.7 408 */ 409 public void clear() { 410 this.rowKeys.clear(); 411 this.columnKeys.clear(); 412 this.rows.clear(); 413 } 414 415 /** 416 * Tests this object for equality with an arbitrary object. 417 * 418 * @param obj the object to test ({@code null} permitted). 419 * 420 * @return A boolean. 421 */ 422 @Override 423 public boolean equals(Object obj) { 424 if (obj == this) { 425 return true; 426 } 427 if (!(obj instanceof KeyedObjects2D)) { 428 return false; 429 } 430 431 KeyedObjects2D that = (KeyedObjects2D) obj; 432 if (!getRowKeys().equals(that.getRowKeys())) { 433 return false; 434 } 435 if (!getColumnKeys().equals(that.getColumnKeys())) { 436 return false; 437 } 438 int rowCount = getRowCount(); 439 if (rowCount != that.getRowCount()) { 440 return false; 441 } 442 int colCount = getColumnCount(); 443 if (colCount != that.getColumnCount()) { 444 return false; 445 } 446 for (int r = 0; r < rowCount; r++) { 447 for (int c = 0; c < colCount; c++) { 448 Object v1 = getObject(r, c); 449 Object v2 = that.getObject(r, c); 450 if (v1 == null) { 451 if (v2 != null) { 452 return false; 453 } 454 } 455 else { 456 if (!v1.equals(v2)) { 457 return false; 458 } 459 } 460 } 461 } 462 return true; 463 } 464 465 /** 466 * Returns a hashcode for this object. 467 * 468 * @return A hashcode. 469 */ 470 @Override 471 public int hashCode() { 472 int result; 473 result = this.rowKeys.hashCode(); 474 result = 29 * result + this.columnKeys.hashCode(); 475 result = 29 * result + this.rows.hashCode(); 476 return result; 477 } 478 479 /** 480 * Returns a clone. 481 * 482 * @return A clone. 483 * 484 * @throws CloneNotSupportedException this class will not throw this 485 * exception, but subclasses (if any) might. 486 */ 487 @Override 488 public Object clone() throws CloneNotSupportedException { 489 KeyedObjects2D clone = (KeyedObjects2D) super.clone(); 490 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 491 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 492 clone.rows = new java.util.ArrayList(this.rows.size()); 493 for (KeyedObjects row : this.rows) { 494 clone.rows.add(row.clone()); 495 } 496 return clone; 497 } 498 499}