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