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