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