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: * DefaultKeyedValues.java 29: * ----------------------- 30: * (C) Copyright 2002-2007, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Thomas Morgner; 34: * 35: * Changes: 36: * -------- 37: * 31-Oct-2002 : Version 1 (DG); 38: * 11-Feb-2003 : Fixed bug in getValue(key) method for unrecognised key (DG); 39: * 05-Mar-2003 : Added methods to sort stored data 'by key' or 'by value' (DG); 40: * 13-Mar-2003 : Implemented Serializable (DG); 41: * 08-Apr-2003 : Modified removeValue(Comparable) method to fix bug 717049 (DG); 42: * 18-Aug-2003 : Implemented Cloneable (DG); 43: * 27-Aug-2003 : Moved SortOrder from org.jfree.data --> org.jfree.util (DG); 44: * 09-Feb-2004 : Modified getIndex() method - see bug report 893256 (DG); 45: * 15-Sep-2004 : Updated clone() method and added PublicCloneable 46: * interface (DG); 47: * 25-Nov-2004 : Small update to the clone() implementation (DG); 48: * 24-Feb-2005 : Added methods addValue(Comparable, double) and 49: * setValue(Comparable, double) for convenience (DG); 50: * ------------- JFREECHART 1.0.x --------------------------------------------- 51: * 31-Jul-2006 : Added a clear() method (DG); 52: * 01-Aug-2006 : Added argument check to getIndex() method (DG); 53: * 30-Apr-2007 : Added insertValue() methods (DG); 54: * 31-Oct-2007 : Performance improvements by using separate lists for keys and 55: * values (TM); 56: * 21-Nov-2007 : Fixed bug in removeValue() method from previous patch (DG); 57: * 58: */ 59: 60: package org.jfree.data; 61: 62: import java.io.Serializable; 63: import java.util.ArrayList; 64: import java.util.Arrays; 65: import java.util.Comparator; 66: import java.util.HashMap; 67: import java.util.List; 68: 69: import org.jfree.util.PublicCloneable; 70: import org.jfree.util.SortOrder; 71: 72: /** 73: * An ordered list of (key, value) items. This class provides a default 74: * implementation of the {@link KeyedValues} interface. 75: */ 76: public class DefaultKeyedValues implements KeyedValues, 77: Cloneable, PublicCloneable, 78: Serializable { 79: 80: /** For serialization. */ 81: private static final long serialVersionUID = 8468154364608194797L; 82: 83: /** Storage for the keys. */ 84: private ArrayList keys; 85: 86: /** Storage for the values. */ 87: private ArrayList values; 88: 89: /** 90: * Contains (key, Integer) mappings, where the Integer is the index for 91: * the key in the list. 92: */ 93: private HashMap indexMap; 94: 95: /** 96: * Creates a new collection (initially empty). 97: */ 98: public DefaultKeyedValues() { 99: this.keys = new ArrayList(); 100: this.values = new ArrayList(); 101: this.indexMap = new HashMap(); 102: } 103: 104: /** 105: * Returns the number of items (values) in the collection. 106: * 107: * @return The item count. 108: */ 109: public int getItemCount() { 110: return this.indexMap.size(); 111: } 112: 113: /** 114: * Returns a value. 115: * 116: * @param item the item of interest (zero-based index). 117: * 118: * @return The value (possibly <code>null</code>). 119: * 120: * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds. 121: */ 122: public Number getValue(int item) { 123: return (Number) this.values.get(item); 124: } 125: 126: /** 127: * Returns a key. 128: * 129: * @param index the item index (zero-based). 130: * 131: * @return The row key. 132: * 133: * @throws IndexOutOfBoundsException if <code>item</code> is out of bounds. 134: */ 135: public Comparable getKey(int index) { 136: return (Comparable) this.keys.get(index); 137: } 138: 139: /** 140: * Returns the index for a given key. 141: * 142: * @param key the key (<code>null</code> not permitted). 143: * 144: * @return The index, or <code>-1</code> if the key is not recognised. 145: * 146: * @throws IllegalArgumentException if <code>key</code> is 147: * <code>null</code>. 148: */ 149: public int getIndex(Comparable key) { 150: if (key == null) { 151: throw new IllegalArgumentException("Null 'key' argument."); 152: } 153: final Integer i = (Integer) this.indexMap.get(key); 154: if (i == null) { 155: return -1; // key not found 156: } 157: return i.intValue(); 158: } 159: 160: /** 161: * Returns the keys for the values in the collection. 162: * 163: * @return The keys (never <code>null</code>). 164: */ 165: public List getKeys() { 166: return (List) this.keys.clone(); 167: } 168: 169: /** 170: * Returns the value for a given key. 171: * 172: * @param key the key (<code>null</code> not permitted). 173: * 174: * @return The value (possibly <code>null</code>). 175: * 176: * @throws UnknownKeyException if the key is not recognised. 177: * 178: * @see #getValue(int) 179: */ 180: public Number getValue(Comparable key) { 181: int index = getIndex(key); 182: if (index < 0) { 183: throw new UnknownKeyException("Key not found: " + key); 184: } 185: return getValue(index); 186: } 187: 188: /** 189: * Updates an existing value, or adds a new value to the collection. 190: * 191: * @param key the key (<code>null</code> not permitted). 192: * @param value the value. 193: * 194: * @see #addValue(Comparable, Number) 195: */ 196: public void addValue(Comparable key, double value) { 197: addValue(key, new Double(value)); 198: } 199: 200: /** 201: * Adds a new value to the collection, or updates an existing value. 202: * This method passes control directly to the 203: * {@link #setValue(Comparable, Number)} method. 204: * 205: * @param key the key (<code>null</code> not permitted). 206: * @param value the value (<code>null</code> permitted). 207: */ 208: public void addValue(Comparable key, Number value) { 209: setValue(key, value); 210: } 211: 212: /** 213: * Updates an existing value, or adds a new value to the collection. 214: * 215: * @param key the key (<code>null</code> not permitted). 216: * @param value the value. 217: */ 218: public void setValue(Comparable key, double value) { 219: setValue(key, new Double(value)); 220: } 221: 222: /** 223: * Updates an existing value, or adds a new value to the collection. 224: * 225: * @param key the key (<code>null</code> not permitted). 226: * @param value the value (<code>null</code> permitted). 227: */ 228: public void setValue(Comparable key, Number value) { 229: if (key == null) { 230: throw new IllegalArgumentException("Null 'key' argument."); 231: } 232: int keyIndex = getIndex(key); 233: if (keyIndex >= 0) { 234: this.keys.set(keyIndex, key); 235: this.values.set(keyIndex, value); 236: } 237: else { 238: this.keys.add(key); 239: this.values.add(value); 240: this.indexMap.put(key, new Integer(this.keys.size() - 1)); 241: } 242: } 243: 244: /** 245: * Inserts a new value at the specified position in the dataset or, if 246: * there is an existing item with the specified key, updates the value 247: * for that item and moves it to the specified position. 248: * 249: * @param position the position (in the range 0 to getItemCount()). 250: * @param key the key (<code>null</code> not permitted). 251: * @param value the value. 252: * 253: * @since 1.0.6 254: */ 255: public void insertValue(int position, Comparable key, double value) { 256: insertValue(position, key, new Double(value)); 257: } 258: 259: /** 260: * Inserts a new value at the specified position in the dataset or, if 261: * there is an existing item with the specified key, updates the value 262: * for that item and moves it to the specified position. 263: * 264: * @param position the position (in the range 0 to getItemCount()). 265: * @param key the key (<code>null</code> not permitted). 266: * @param value the value (<code>null</code> permitted). 267: * 268: * @since 1.0.6 269: */ 270: public void insertValue(int position, Comparable key, Number value) { 271: if (position < 0 || position > getItemCount()) { 272: throw new IllegalArgumentException("'position' out of bounds."); 273: } 274: if (key == null) { 275: throw new IllegalArgumentException("Null 'key' argument."); 276: } 277: int pos = getIndex(key); 278: if (pos == position) { 279: this.keys.set(pos, key); 280: this.values.set(pos, value); 281: } 282: else { 283: if (pos >= 0) { 284: this.keys.remove(pos); 285: this.values.remove(pos); 286: } 287: 288: this.keys.add(position, key); 289: this.values.add(position, value); 290: rebuildIndex(); 291: } 292: } 293: 294: /** 295: * Rebuilds the key to indexed-position mapping after an positioned insert 296: * or a remove operation. 297: */ 298: private void rebuildIndex () { 299: this.indexMap.clear(); 300: for (int i = 0; i < this.keys.size(); i++) { 301: final Object key = this.keys.get(i); 302: this.indexMap.put(key, new Integer(i)); 303: } 304: } 305: 306: /** 307: * Removes a value from the collection. 308: * 309: * @param index the index of the item to remove (in the range 310: * <code>0</code> to <code>getItemCount() - 1</code>). 311: * 312: * @throws IndexOutOfBoundsException if <code>index</code> is not within 313: * the specified range. 314: */ 315: public void removeValue(int index) { 316: this.keys.remove(index); 317: this.values.remove(index); 318: rebuildIndex(); 319: } 320: 321: /** 322: * Removes a value from the collection. 323: * 324: * @param key the item key (<code>null</code> not permitted). 325: * 326: * @throws IllegalArgumentException if <code>key</code> is 327: * <code>null</code>. 328: * @throws UnknownKeyException if <code>key</code> is not recognised. 329: */ 330: public void removeValue(Comparable key) { 331: int index = getIndex(key); 332: if (index < 0) { 333: throw new UnknownKeyException("The key (" + key 334: + ") is not recognised."); 335: } 336: removeValue(index); 337: } 338: 339: /** 340: * Clears all values from the collection. 341: * 342: * @since 1.0.2 343: */ 344: public void clear() { 345: this.keys.clear(); 346: this.values.clear(); 347: this.indexMap.clear(); 348: } 349: 350: /** 351: * Sorts the items in the list by key. 352: * 353: * @param order the sort order (<code>null</code> not permitted). 354: */ 355: public void sortByKeys(SortOrder order) { 356: final int size = this.keys.size(); 357: final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 358: 359: for (int i = 0; i < size; i++) { 360: data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 361: (Number) this.values.get(i)); 362: } 363: 364: Comparator comparator = new KeyedValueComparator( 365: KeyedValueComparatorType.BY_KEY, order); 366: Arrays.sort(data, comparator); 367: clear(); 368: 369: for (int i = 0; i < data.length; i++) { 370: final DefaultKeyedValue value = data[i]; 371: addValue(value.getKey(), value.getValue()); 372: } 373: } 374: 375: /** 376: * Sorts the items in the list by value. If the list contains 377: * <code>null</code> values, they will sort to the end of the list, 378: * irrespective of the sort order. 379: * 380: * @param order the sort order (<code>null</code> not permitted). 381: */ 382: public void sortByValues(SortOrder order) { 383: final int size = this.keys.size(); 384: final DefaultKeyedValue[] data = new DefaultKeyedValue[size]; 385: for (int i = 0; i < size; i++) { 386: data[i] = new DefaultKeyedValue((Comparable) this.keys.get(i), 387: (Number) this.values.get(i)); 388: } 389: 390: Comparator comparator = new KeyedValueComparator( 391: KeyedValueComparatorType.BY_VALUE, order); 392: Arrays.sort(data, comparator); 393: 394: clear(); 395: for (int i = 0; i < data.length; i++) { 396: final DefaultKeyedValue value = data[i]; 397: addValue(value.getKey(), value.getValue()); 398: } 399: } 400: 401: /** 402: * Tests if this object is equal to another. 403: * 404: * @param obj the object (<code>null</code> permitted). 405: * 406: * @return A boolean. 407: */ 408: public boolean equals(Object obj) { 409: if (obj == this) { 410: return true; 411: } 412: 413: if (!(obj instanceof KeyedValues)) { 414: return false; 415: } 416: 417: KeyedValues that = (KeyedValues) obj; 418: int count = getItemCount(); 419: if (count != that.getItemCount()) { 420: return false; 421: } 422: 423: for (int i = 0; i < count; i++) { 424: Comparable k1 = getKey(i); 425: Comparable k2 = that.getKey(i); 426: if (!k1.equals(k2)) { 427: return false; 428: } 429: Number v1 = getValue(i); 430: Number v2 = that.getValue(i); 431: if (v1 == null) { 432: if (v2 != null) { 433: return false; 434: } 435: } 436: else { 437: if (!v1.equals(v2)) { 438: return false; 439: } 440: } 441: } 442: return true; 443: } 444: 445: /** 446: * Returns a hash code. 447: * 448: * @return A hash code. 449: */ 450: public int hashCode() { 451: return (this.keys != null ? this.keys.hashCode() : 0); 452: } 453: 454: /** 455: * Returns a clone. 456: * 457: * @return A clone. 458: * 459: * @throws CloneNotSupportedException this class will not throw this 460: * exception, but subclasses might. 461: */ 462: public Object clone() throws CloneNotSupportedException { 463: DefaultKeyedValues clone = (DefaultKeyedValues) super.clone(); 464: clone.keys = (ArrayList) this.keys.clone(); 465: clone.values = (ArrayList) this.values.clone(); 466: clone.indexMap = (HashMap) this.indexMap.clone(); 467: return clone; 468: } 469: 470: }