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