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