Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ------------------------- 28: * DefaultKeyedValues2D.java 29: * ------------------------- 30: * (C) Copyright 2002-2007, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Andreas Schroeder; 34: * 35: * $Id: DefaultKeyedValues2D.java,v 1.7.2.4 2007/02/26 15:14:11 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 28-Oct-2002 : Version 1 (DG); 40: * 21-Jan-2003 : Updated Javadocs (DG); 41: * 13-Mar-2003 : Implemented Serializable (DG); 42: * 18-Aug-2003 : Implemented Cloneable (DG); 43: * 31-Mar-2004 : Made the rows optionally sortable by a flag (AS); 44: * 01-Apr-2004 : Implemented remove method (AS); 45: * 05-Apr-2004 : Added clear() method (DG); 46: * 15-Sep-2004 : Fixed clone() method (DG); 47: * 12-Jan-2005 : Fixed bug in getValue() method (DG); 48: * 23-Mar-2005 : Implemented PublicCloneable (DG); 49: * 09-Jun-2005 : Modified getValue() method to throw exception for unknown 50: * keys (DG); 51: * ------------- JFREECHART 1.0.x --------------------------------------------- 52: * 18-Jan-2007 : Fixed bug in getValue() method (DG); 53: * 54: */ 55: 56: package org.jfree.data; 57: 58: import java.io.Serializable; 59: import java.util.Collections; 60: import java.util.Iterator; 61: import java.util.List; 62: 63: import org.jfree.util.ObjectUtilities; 64: import org.jfree.util.PublicCloneable; 65: 66: /** 67: * A data structure that stores zero, one or many values, where each value 68: * is associated with two keys (a 'row' key and a 'column' key). The keys 69: * should be (a) instances of {@link Comparable} and (b) immutable. 70: */ 71: public class DefaultKeyedValues2D implements KeyedValues2D, 72: PublicCloneable, Cloneable, 73: Serializable { 74: 75: /** For serialization. */ 76: private static final long serialVersionUID = -5514169970951994748L; 77: 78: /** The row keys. */ 79: private List rowKeys; 80: 81: /** The column keys. */ 82: private List columnKeys; 83: 84: /** The row data. */ 85: private List rows; 86: 87: /** If the row keys should be sorted by their comparable order. */ 88: private boolean sortRowKeys; 89: 90: /** 91: * Creates a new instance (initially empty). 92: */ 93: public DefaultKeyedValues2D() { 94: this(false); 95: } 96: 97: /** 98: * Creates a new instance (initially empty). 99: * 100: * @param sortRowKeys if the row keys should be sorted. 101: */ 102: public DefaultKeyedValues2D(boolean sortRowKeys) { 103: this.rowKeys = new java.util.ArrayList(); 104: this.columnKeys = new java.util.ArrayList(); 105: this.rows = new java.util.ArrayList(); 106: this.sortRowKeys = sortRowKeys; 107: } 108: 109: /** 110: * Returns the row count. 111: * 112: * @return The row count. 113: * 114: * @see #getColumnCount() 115: */ 116: public int getRowCount() { 117: return this.rowKeys.size(); 118: } 119: 120: /** 121: * Returns the column count. 122: * 123: * @return The column count. 124: * 125: * @see #getRowCount() 126: */ 127: public int getColumnCount() { 128: return this.columnKeys.size(); 129: } 130: 131: /** 132: * Returns the value for a given row and column. 133: * 134: * @param row the row index. 135: * @param column the column index. 136: * 137: * @return The value. 138: * 139: * @see #getValue(Comparable, Comparable) 140: */ 141: public Number getValue(int row, int column) { 142: Number result = null; 143: DefaultKeyedValues rowData = (DefaultKeyedValues) this.rows.get(row); 144: if (rowData != null) { 145: Comparable columnKey = (Comparable) this.columnKeys.get(column); 146: // the row may not have an entry for this key, in which case the 147: // return value is null 148: int index = rowData.getIndex(columnKey); 149: if (index >= 0) { 150: result = rowData.getValue(index); 151: } 152: } 153: return result; 154: } 155: 156: /** 157: * Returns the key for a given row. 158: * 159: * @param row the row index (in the range 0 to {@link #getRowCount()} - 1). 160: * 161: * @return The row key. 162: * 163: * @see #getRowIndex(Comparable) 164: * @see #getColumnKey(int) 165: */ 166: public Comparable getRowKey(int row) { 167: return (Comparable) this.rowKeys.get(row); 168: } 169: 170: /** 171: * Returns the row index for a given key. 172: * 173: * @param key the key (<code>null</code> not permitted). 174: * 175: * @return The row index. 176: * 177: * @see #getRowKey(int) 178: * @see #getColumnIndex(Comparable) 179: */ 180: public int getRowIndex(Comparable key) { 181: if (key == null) { 182: throw new IllegalArgumentException("Null 'key' argument."); 183: } 184: if (this.sortRowKeys) { 185: return Collections.binarySearch(this.rowKeys, key); 186: } 187: else { 188: return this.rowKeys.indexOf(key); 189: } 190: } 191: 192: /** 193: * Returns the row keys in an unmodifiable list. 194: * 195: * @return The row keys. 196: * 197: * @see #getColumnKeys() 198: */ 199: public List getRowKeys() { 200: return Collections.unmodifiableList(this.rowKeys); 201: } 202: 203: /** 204: * Returns the key for a given column. 205: * 206: * @param column the column (in the range 0 to {@link #getColumnCount()} 207: * - 1). 208: * 209: * @return The key. 210: * 211: * @see #getColumnIndex(Comparable) 212: * @see #getRowKey(int) 213: */ 214: public Comparable getColumnKey(int column) { 215: return (Comparable) this.columnKeys.get(column); 216: } 217: 218: /** 219: * Returns the column index for a given key. 220: * 221: * @param key the key (<code>null</code> not permitted). 222: * 223: * @return The column index. 224: * 225: * @see #getColumnKey(int) 226: * @see #getRowIndex(Comparable) 227: */ 228: public int getColumnIndex(Comparable key) { 229: if (key == null) { 230: throw new IllegalArgumentException("Null 'key' argument."); 231: } 232: return this.columnKeys.indexOf(key); 233: } 234: 235: /** 236: * Returns the column keys in an unmodifiable list. 237: * 238: * @return The column keys. 239: * 240: * @see #getRowKeys() 241: */ 242: public List getColumnKeys() { 243: return Collections.unmodifiableList(this.columnKeys); 244: } 245: 246: /** 247: * Returns the value for the given row and column keys. This method will 248: * throw an {@link UnknownKeyException} if either key is not defined in the 249: * data structure. 250: * 251: * @param rowKey the row key (<code>null</code> not permitted). 252: * @param columnKey the column key (<code>null</code> not permitted). 253: * 254: * @return The value (possibly <code>null</code>). 255: * 256: * @see #addValue(Number, Comparable, Comparable) 257: * @see #removeValue(Comparable, Comparable) 258: */ 259: public Number getValue(Comparable rowKey, Comparable columnKey) { 260: if (rowKey == null) { 261: throw new IllegalArgumentException("Null 'rowKey' argument."); 262: } 263: if (columnKey == null) { 264: throw new IllegalArgumentException("Null 'columnKey' argument."); 265: } 266: 267: // check that the column key is defined in the 2D structure 268: if (!(this.columnKeys.contains(columnKey))) { 269: throw new UnknownKeyException("Unrecognised columnKey: " 270: + columnKey); 271: } 272: 273: // now fetch the row data - need to bear in mind that the row 274: // structure may not have an entry for the column key, but that we 275: // have already checked that the key is valid for the 2D structure 276: int row = getRowIndex(rowKey); 277: if (row >= 0) { 278: DefaultKeyedValues rowData 279: = (DefaultKeyedValues) this.rows.get(row); 280: int col = rowData.getIndex(columnKey); 281: return (col >= 0 ? rowData.getValue(col) : null); 282: } 283: else { 284: throw new UnknownKeyException("Unrecognised rowKey: " + rowKey); 285: } 286: } 287: 288: /** 289: * Adds a value to the table. Performs the same function as 290: * #setValue(Number, Comparable, Comparable). 291: * 292: * @param value the value (<code>null</code> permitted). 293: * @param rowKey the row key (<code>null</code> not permitted). 294: * @param columnKey the column key (<code>null</code> not permitted). 295: * 296: * @see #setValue(Number, Comparable, Comparable) 297: * @see #removeValue(Comparable, Comparable) 298: */ 299: public void addValue(Number value, Comparable rowKey, 300: Comparable columnKey) { 301: // defer argument checking 302: setValue(value, rowKey, columnKey); 303: } 304: 305: /** 306: * Adds or updates a value. 307: * 308: * @param value the value (<code>null</code> permitted). 309: * @param rowKey the row key (<code>null</code> not permitted). 310: * @param columnKey the column key (<code>null</code> not permitted). 311: * 312: * @see #addValue(Number, Comparable, Comparable) 313: * @see #removeValue(Comparable, Comparable) 314: */ 315: public void setValue(Number value, Comparable rowKey, 316: Comparable columnKey) { 317: 318: DefaultKeyedValues row; 319: int rowIndex = getRowIndex(rowKey); 320: 321: if (rowIndex >= 0) { 322: row = (DefaultKeyedValues) this.rows.get(rowIndex); 323: } 324: else { 325: row = new DefaultKeyedValues(); 326: if (this.sortRowKeys) { 327: rowIndex = -rowIndex - 1; 328: this.rowKeys.add(rowIndex, rowKey); 329: this.rows.add(rowIndex, row); 330: } 331: else { 332: this.rowKeys.add(rowKey); 333: this.rows.add(row); 334: } 335: } 336: row.setValue(columnKey, value); 337: 338: int columnIndex = this.columnKeys.indexOf(columnKey); 339: if (columnIndex < 0) { 340: this.columnKeys.add(columnKey); 341: } 342: } 343: 344: /** 345: * Removes a value. 346: * 347: * @param rowKey the row key (<code>null</code> not permitted). 348: * @param columnKey the column key (<code>null</code> not permitted). 349: * 350: * @see #addValue(Number, Comparable, Comparable) 351: */ 352: public void removeValue(Comparable rowKey, Comparable columnKey) { 353: setValue(null, rowKey, columnKey); 354: 355: // 1. check whether the row is now empty. 356: boolean allNull = true; 357: int rowIndex = getRowIndex(rowKey); 358: DefaultKeyedValues row = (DefaultKeyedValues) this.rows.get(rowIndex); 359: 360: for (int item = 0, itemCount = row.getItemCount(); item < itemCount; 361: item++) { 362: if (row.getValue(item) != null) { 363: allNull = false; 364: break; 365: } 366: } 367: 368: if (allNull) { 369: this.rowKeys.remove(rowIndex); 370: this.rows.remove(rowIndex); 371: } 372: 373: // 2. check whether the column is now empty. 374: allNull = true; 375: int columnIndex = getColumnIndex(columnKey); 376: 377: for (int item = 0, itemCount = this.rows.size(); item < itemCount; 378: item++) { 379: row = (DefaultKeyedValues) this.rows.get(item); 380: if (row.getValue(columnIndex) != null) { 381: allNull = false; 382: break; 383: } 384: } 385: 386: if (allNull) { 387: for (int item = 0, itemCount = this.rows.size(); item < itemCount; 388: item++) { 389: row = (DefaultKeyedValues) this.rows.get(item); 390: row.removeValue(columnIndex); 391: } 392: this.columnKeys.remove(columnIndex); 393: } 394: } 395: 396: /** 397: * Removes a row. 398: * 399: * @param rowIndex the row index. 400: * 401: * @see #removeRow(Comparable) 402: * @see #removeColumn(int) 403: */ 404: public void removeRow(int rowIndex) { 405: this.rowKeys.remove(rowIndex); 406: this.rows.remove(rowIndex); 407: } 408: 409: /** 410: * Removes a row. 411: * 412: * @param rowKey the row key (<code>null</code> not permitted). 413: * 414: * @see #removeRow(int) 415: * @see #removeColumn(Comparable) 416: */ 417: public void removeRow(Comparable rowKey) { 418: removeRow(getRowIndex(rowKey)); 419: } 420: 421: /** 422: * Removes a column. 423: * 424: * @param columnIndex the column index. 425: * 426: * @see #removeColumn(Comparable) 427: * @see #removeRow(int) 428: */ 429: public void removeColumn(int columnIndex) { 430: Comparable columnKey = getColumnKey(columnIndex); 431: removeColumn(columnKey); 432: } 433: 434: /** 435: * Removes a column. 436: * 437: * @param columnKey the column key (<code>null</code> not permitted). 438: * 439: * @see #removeColumn(int) 440: * @see #removeRow(Comparable) 441: */ 442: public void removeColumn(Comparable columnKey) { 443: Iterator iterator = this.rows.iterator(); 444: while (iterator.hasNext()) { 445: DefaultKeyedValues rowData = (DefaultKeyedValues) iterator.next(); 446: rowData.removeValue(columnKey); 447: } 448: this.columnKeys.remove(columnKey); 449: } 450: 451: /** 452: * Clears all the data and associated keys. 453: */ 454: public void clear() { 455: this.rowKeys.clear(); 456: this.columnKeys.clear(); 457: this.rows.clear(); 458: } 459: 460: /** 461: * Tests if this object is equal to another. 462: * 463: * @param o the other object (<code>null</code> permitted). 464: * 465: * @return A boolean. 466: */ 467: public boolean equals(Object o) { 468: 469: if (o == null) { 470: return false; 471: } 472: if (o == this) { 473: return true; 474: } 475: 476: if (!(o instanceof KeyedValues2D)) { 477: return false; 478: } 479: KeyedValues2D kv2D = (KeyedValues2D) o; 480: if (!getRowKeys().equals(kv2D.getRowKeys())) { 481: return false; 482: } 483: if (!getColumnKeys().equals(kv2D.getColumnKeys())) { 484: return false; 485: } 486: int rowCount = getRowCount(); 487: if (rowCount != kv2D.getRowCount()) { 488: return false; 489: } 490: 491: int colCount = getColumnCount(); 492: if (colCount != kv2D.getColumnCount()) { 493: return false; 494: } 495: 496: for (int r = 0; r < rowCount; r++) { 497: for (int c = 0; c < colCount; c++) { 498: Number v1 = getValue(r, c); 499: Number v2 = kv2D.getValue(r, c); 500: if (v1 == null) { 501: if (v2 != null) { 502: return false; 503: } 504: } 505: else { 506: if (!v1.equals(v2)) { 507: return false; 508: } 509: } 510: } 511: } 512: return true; 513: } 514: 515: /** 516: * Returns a hash code. 517: * 518: * @return A hash code. 519: */ 520: public int hashCode() { 521: int result; 522: result = this.rowKeys.hashCode(); 523: result = 29 * result + this.columnKeys.hashCode(); 524: result = 29 * result + this.rows.hashCode(); 525: return result; 526: } 527: 528: /** 529: * Returns a clone. 530: * 531: * @return A clone. 532: * 533: * @throws CloneNotSupportedException this class will not throw this 534: * exception, but subclasses (if any) might. 535: */ 536: public Object clone() throws CloneNotSupportedException { 537: DefaultKeyedValues2D clone = (DefaultKeyedValues2D) super.clone(); 538: // for the keys, a shallow copy should be fine because keys 539: // should be immutable... 540: clone.columnKeys = new java.util.ArrayList(this.columnKeys); 541: clone.rowKeys = new java.util.ArrayList(this.rowKeys); 542: 543: // but the row data requires a deep copy 544: clone.rows = (List) ObjectUtilities.deepClone(this.rows); 545: return clone; 546: } 547: 548: }