Source for org.jfree.data.DefaultKeyedValues

   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: }