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