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