001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2005, 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 * DefaultKeyedValues2D.java 029 * ------------------------- 030 * (C) Copyright 2002-2005, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Andreas Schroeder; 034 * 035 * $Id: DefaultKeyedValues2D.java,v 1.7.2.2 2005/10/25 21:29:13 mungady Exp $ 036 * 037 * Changes 038 * ------- 039 * 28-Oct-2002 : Version 1 (DG); 040 * 21-Jan-2003 : Updated Javadocs (DG); 041 * 13-Mar-2003 : Implemented Serializable (DG); 042 * 18-Aug-2003 : Implemented Cloneable (DG); 043 * 31-Mar-2004 : Made the rows optionally sortable by a flag (AS); 044 * 01-Apr-2004 : Implemented remove method (AS); 045 * 05-Apr-2004 : Added clear() method (DG); 046 * 15-Sep-2004 : Fixed clone() method (DG); 047 * 12-Jan-2005 : Fixed bug in getValue() method (DG); 048 * 23-Mar-2005 : Implemented PublicCloneable (DG); 049 * 09-Jun-2005 : Modified getValue() method to throw exception for unknown 050 * keys (DG); 051 * 052 */ 053 054 package org.jfree.data; 055 056 import java.io.Serializable; 057 import java.util.Collections; 058 import java.util.Iterator; 059 import java.util.List; 060 061 import org.jfree.util.ObjectUtilities; 062 import org.jfree.util.PublicCloneable; 063 064 /** 065 * A data structure that stores zero, one or many values, where each value 066 * is associated with two keys (a 'row' key and a 'column' key). The keys 067 * should be (a) instances of {@link Comparable} and (b) immutable. 068 */ 069 public class DefaultKeyedValues2D implements KeyedValues2D, 070 PublicCloneable, Cloneable, 071 Serializable { 072 073 /** For serialization. */ 074 private static final long serialVersionUID = -5514169970951994748L; 075 076 /** The row keys. */ 077 private List rowKeys; 078 079 /** The column keys. */ 080 private List columnKeys; 081 082 /** The row data. */ 083 private List rows; 084 085 /** If the row keys should be sorted by their comparable order. */ 086 private boolean sortRowKeys; 087 088 /** 089 * Creates a new instance (initially empty). 090 */ 091 public DefaultKeyedValues2D() { 092 this(false); 093 } 094 095 /** 096 * Creates a new instance (initially empty). 097 * 098 * @param sortRowKeys if the row keys should be sorted. 099 */ 100 public DefaultKeyedValues2D(boolean sortRowKeys) { 101 this.rowKeys = new java.util.ArrayList(); 102 this.columnKeys = new java.util.ArrayList(); 103 this.rows = new java.util.ArrayList(); 104 this.sortRowKeys = sortRowKeys; 105 } 106 107 /** 108 * Returns the row count. 109 * 110 * @return The row count. 111 */ 112 public int getRowCount() { 113 return this.rowKeys.size(); 114 } 115 116 /** 117 * Returns the column count. 118 * 119 * @return The column count. 120 */ 121 public int getColumnCount() { 122 return this.columnKeys.size(); 123 } 124 125 /** 126 * Returns the value for a given row and column. 127 * 128 * @param row the row index. 129 * @param column the column index. 130 * 131 * @return The value. 132 */ 133 public Number getValue(int row, int column) { 134 Number result = null; 135 DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row); 136 if (rowData != null) { 137 Comparable columnKey = (Comparable) this.columnKeys.get(column); 138 // the row may not have an entry for this key, in which case the 139 // return value is null 140 int index = rowData.getIndex(columnKey); 141 if (index >= 0) { 142 result = rowData.getValue(index); 143 } 144 } 145 return result; 146 } 147 148 /** 149 * Returns the key for a given row. 150 * 151 * @param row the row index (zero based). 152 * 153 * @return The row index. 154 */ 155 public Comparable getRowKey(int row) { 156 return (Comparable) this.rowKeys.get(row); 157 } 158 159 /** 160 * Returns the row index for a given key. 161 * 162 * @param key the key (<code>null</code> not permitted). 163 * 164 * @return The row index. 165 */ 166 public int getRowIndex(Comparable key) { 167 if (key == null) { 168 throw new IllegalArgumentException("Null 'key' argument."); 169 } 170 if (this.sortRowKeys) { 171 return Collections.binarySearch(this.rowKeys, key); 172 } 173 else { 174 return this.rowKeys.indexOf(key); 175 } 176 } 177 178 /** 179 * Returns the row keys. 180 * 181 * @return The row keys. 182 */ 183 public List getRowKeys() { 184 return Collections.unmodifiableList(this.rowKeys); 185 } 186 187 /** 188 * Returns the key for a given column. 189 * 190 * @param column the column. 191 * 192 * @return The key. 193 */ 194 public Comparable getColumnKey(int column) { 195 return (Comparable) this.columnKeys.get(column); 196 } 197 198 /** 199 * Returns the column index for a given key. 200 * 201 * @param key the key (<code>null</code> not permitted). 202 * 203 * @return The column index. 204 */ 205 public int getColumnIndex(Comparable key) { 206 if (key == null) { 207 throw new IllegalArgumentException("Null 'key' argument."); 208 } 209 return this.columnKeys.indexOf(key); 210 } 211 212 /** 213 * Returns the column keys. 214 * 215 * @return The column keys. 216 */ 217 public List getColumnKeys() { 218 return Collections.unmodifiableList(this.columnKeys); 219 } 220 221 /** 222 * Returns the value for the given row and column keys. This method will 223 * throw an {@link UnknownKeyException} if either key is not defined in the 224 * data structure. 225 * 226 * @param rowKey the row key (<code>null</code> not permitted). 227 * @param columnKey the column key (<code>null</code> not permitted). 228 * 229 * @return The value (possibly <code>null</code>). 230 */ 231 public Number getValue(Comparable rowKey, Comparable columnKey) { 232 if (rowKey == null) { 233 throw new IllegalArgumentException("Null 'rowKey' argument."); 234 } 235 if (columnKey == null) { 236 throw new IllegalArgumentException("Null 'columnKey' argument."); 237 } 238 int row = getRowIndex(rowKey); 239 if (row >= 0) { 240 DefaultKeyedValues rowData 241 = (DefaultKeyedValues) this.rows.get(row); 242 return rowData.getValue(columnKey); 243 } 244 else { 245 throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 246 } 247 } 248 249 /** 250 * Adds a value to the table. Performs the same function as 251 * #setValue(Number, Comparable, Comparable). 252 * 253 * @param value the value (<code>null</code> permitted). 254 * @param rowKey the row key (<code>null</code> not permitted). 255 * @param columnKey the column key (<code>null</code> not permitted). 256 */ 257 public void addValue(Number value, Comparable rowKey, 258 Comparable columnKey) { 259 // defer argument checking 260 setValue(value, rowKey, columnKey); 261 } 262 263 /** 264 * Adds or updates a value. 265 * 266 * @param value the value (<code>null</code> permitted). 267 * @param rowKey the row key (<code>null</code> not permitted). 268 * @param columnKey the column key (<code>null</code> not permitted). 269 */ 270 public void setValue(Number value, Comparable rowKey, 271 Comparable columnKey) { 272 273 DefaultKeyedValues row; 274 int rowIndex = getRowIndex(rowKey); 275 276 if (rowIndex >= 0) { 277 row = (DefaultKeyedValues) this.rows.get(rowIndex); 278 } 279 else { 280 row = new DefaultKeyedValues(); 281 if (this.sortRowKeys) { 282 rowIndex = -rowIndex - 1; 283 this.rowKeys.add(rowIndex, rowKey); 284 this.rows.add(rowIndex, row); 285 } 286 else { 287 this.rowKeys.add(rowKey); 288 this.rows.add(row); 289 } 290 } 291 row.setValue(columnKey, value); 292 293 int columnIndex = this.columnKeys.indexOf(columnKey); 294 if (columnIndex < 0) { 295 this.columnKeys.add(columnKey); 296 } 297 } 298 299 /** 300 * Removes a value. 301 * 302 * @param rowKey the row key (<code>null</code> not permitted). 303 * @param columnKey the column key (<code>null</code> not permitted). 304 */ 305 public void removeValue(Comparable rowKey, Comparable columnKey) { 306 setValue(null, rowKey, columnKey); 307 308 // 1. check whether the row is now empty. 309 boolean allNull = true; 310 int rowIndex = getRowIndex(rowKey); 311 DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 312 313 for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 314 item++) { 315 if (row.getValue(item) != null) { 316 allNull = false; 317 break; 318 } 319 } 320 321 if (allNull) { 322 this.rowKeys.remove(rowIndex); 323 this.rows.remove(rowIndex); 324 } 325 326 // 2. check whether the column is now empty. 327 allNull = true; 328 int columnIndex = getColumnIndex(columnKey); 329 330 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 331 item++) { 332 row = (DefaultKeyedValues) this.rows.get(item); 333 if (row.getValue(columnIndex) != null) { 334 allNull = false; 335 break; 336 } 337 } 338 339 if (allNull) { 340 for (int item = 0, itemCount = this.rows.size(); item < itemCount; 341 item++) { 342 row = (DefaultKeyedValues) this.rows.get(item); 343 row.removeValue(columnIndex); 344 } 345 this.columnKeys.remove(columnIndex); 346 } 347 } 348 349 /** 350 * Removes a row. 351 * 352 * @param rowIndex the row index. 353 */ 354 public void removeRow(int rowIndex) { 355 this.rowKeys.remove(rowIndex); 356 this.rows.remove(rowIndex); 357 } 358 359 /** 360 * Removes a row. 361 * 362 * @param rowKey the row key. 363 */ 364 public void removeRow(Comparable rowKey) { 365 removeRow(getRowIndex(rowKey)); 366 } 367 368 /** 369 * Removes a column. 370 * 371 * @param columnIndex the column index. 372 */ 373 public void removeColumn(int columnIndex) { 374 Comparable columnKey = getColumnKey(columnIndex); 375 removeColumn(columnKey); 376 } 377 378 /** 379 * Removes a column. 380 * 381 * @param columnKey the column key (<code>null</code> not permitted). 382 */ 383 public void removeColumn(Comparable columnKey) { 384 Iterator iterator = this.rows.iterator(); 385 while (iterator.hasNext()) { 386 DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next(); 387 rowData.removeValue(columnKey); 388 } 389 this.columnKeys.remove(columnKey); 390 } 391 392 /** 393 * Clears all the data and associated keys. 394 */ 395 public void clear() { 396 this.rowKeys.clear(); 397 this.columnKeys.clear(); 398 this.rows.clear(); 399 } 400 401 /** 402 * Tests if this object is equal to another. 403 * 404 * @param o the other object (<code>null</code> permitted). 405 * 406 * @return A boolean. 407 */ 408 public boolean equals(Object o) { 409 410 if (o == null) { 411 return false; 412 } 413 if (o == this) { 414 return true; 415 } 416 417 if (!(o instanceof KeyedValues2D)) { 418 return false; 419 } 420 KeyedValues2D kv2D = (KeyedValues2D) o; 421 if (!getRowKeys().equals(kv2D.getRowKeys())) { 422 return false; 423 } 424 if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 425 return false; 426 } 427 int rowCount = getRowCount(); 428 if (rowCount != kv2D.getRowCount()) { 429 return false; 430 } 431 432 int colCount = getColumnCount(); 433 if (colCount != kv2D.getColumnCount()) { 434 return false; 435 } 436 437 for (int r = 0; r < rowCount; r++) { 438 for (int c = 0; c < colCount; c++) { 439 Number v1 = getValue(r, c); 440 Number v2 = kv2D.getValue(r, c); 441 if (v1 == null) { 442 if (v2 != null) { 443 return false; 444 } 445 } 446 else { 447 if (!v1.equals(v2)) { 448 return false; 449 } 450 } 451 } 452 } 453 return true; 454 } 455 456 /** 457 * Returns a hash code. 458 * 459 * @return A hash code. 460 */ 461 public int hashCode() { 462 int result; 463 result = this.rowKeys.hashCode(); 464 result = 29 * result + this.columnKeys.hashCode(); 465 result = 29 * result + this.rows.hashCode(); 466 return result; 467 } 468 469 /** 470 * Returns a clone. 471 * 472 * @return A clone. 473 * 474 * @throws CloneNotSupportedException this class will not throw this 475 * exception, but subclasses (if any) might. 476 */ 477 public Object clone() throws CloneNotSupportedException { 478 DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 479 // for the keys, a shallow copy should be fine because keys 480 // should be immutable... 481 clone.columnKeys = new java.util.ArrayList(this.columnKeys); 482 clone.rowKeys = new java.util.ArrayList(this.rowKeys); 483 484 // but the row data requires a deep copy 485 clone.rows = (List) ObjectUtilities.deepClone(this.rows); 486 return clone; 487 } 488 489 }