Source for org.jfree.data.ComparableObjectSeries

   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:  * ComparableObjectSeries.java
  29:  * ---------------------------
  30:  * (C) Copyright 2006, 2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 19-Oct-2006 : New class (DG);
  38:  * 31-Oct-2007 : Implemented faster hashCode() (DG);
  39:  *
  40:  */
  41: 
  42: package org.jfree.data;
  43: 
  44: import java.io.Serializable;
  45: import java.util.Collections;
  46: import java.util.List;
  47: 
  48: import org.jfree.data.general.Series;
  49: import org.jfree.data.general.SeriesChangeEvent;
  50: import org.jfree.data.general.SeriesException;
  51: import org.jfree.util.ObjectUtilities;
  52: 
  53: /**
  54:  * A (possibly ordered) list of (Comparable, Object) data items.
  55:  *
  56:  * @since 1.0.3
  57:  */
  58: public class ComparableObjectSeries extends Series 
  59:         implements Cloneable, Serializable {
  60:     
  61:     /** Storage for the data items in the series. */
  62:     protected List data;
  63: 
  64:     /** The maximum number of items for the series. */
  65:     private int maximumItemCount = Integer.MAX_VALUE;
  66: 
  67:     /** A flag that controls whether the items are automatically sorted. */
  68:     private boolean autoSort;
  69:     
  70:     /** A flag that controls whether or not duplicate x-values are allowed. */
  71:     private boolean allowDuplicateXValues;
  72: 
  73:     /**
  74:      * Creates a new empty series.  By default, items added to the series will 
  75:      * be sorted into ascending order by x-value, and duplicate x-values will 
  76:      * be allowed (these defaults can be modified with another constructor.
  77:      *
  78:      * @param key  the series key (<code>null</code> not permitted).
  79:      */
  80:     public ComparableObjectSeries(Comparable key) {
  81:         this(key, true, true);
  82:     }
  83:     
  84:     /**
  85:      * Constructs a new series that contains no data.  You can specify 
  86:      * whether or not duplicate x-values are allowed for the series.
  87:      *
  88:      * @param key  the series key (<code>null</code> not permitted).
  89:      * @param autoSort  a flag that controls whether or not the items in the 
  90:      *                  series are sorted.
  91:      * @param allowDuplicateXValues  a flag that controls whether duplicate 
  92:      *                               x-values are allowed.
  93:      */
  94:     public ComparableObjectSeries(Comparable key, boolean autoSort, 
  95:             boolean allowDuplicateXValues) {
  96:         super(key);
  97:         this.data = new java.util.ArrayList();
  98:         this.autoSort = autoSort;
  99:         this.allowDuplicateXValues = allowDuplicateXValues;
 100:     }
 101: 
 102:     /**
 103:      * Returns the flag that controls whether the items in the series are 
 104:      * automatically sorted.  There is no setter for this flag, it must be 
 105:      * defined in the series constructor.
 106:      * 
 107:      * @return A boolean.
 108:      */
 109:     public boolean getAutoSort() {
 110:         return this.autoSort;
 111:     }
 112:     
 113:     /**
 114:      * Returns a flag that controls whether duplicate x-values are allowed.  
 115:      * This flag can only be set in the constructor.
 116:      *
 117:      * @return A boolean.
 118:      */
 119:     public boolean getAllowDuplicateXValues() {
 120:         return this.allowDuplicateXValues;
 121:     }
 122: 
 123:     /**
 124:      * Returns the number of items in the series.
 125:      *
 126:      * @return The item count.
 127:      */
 128:     public int getItemCount() {
 129:         return this.data.size();
 130:     }
 131: 
 132:     /**
 133:      * Returns the maximum number of items that will be retained in the series.
 134:      * The default value is <code>Integer.MAX_VALUE</code>.
 135:      *
 136:      * @return The maximum item count.
 137:      * @see #setMaximumItemCount(int)
 138:      */
 139:     public int getMaximumItemCount() {
 140:         return this.maximumItemCount;
 141:     }
 142: 
 143:     /**
 144:      * Sets the maximum number of items that will be retained in the series.  
 145:      * If you add a new item to the series such that the number of items will 
 146:      * exceed the maximum item count, then the first element in the series is 
 147:      * automatically removed, ensuring that the maximum item count is not 
 148:      * exceeded.
 149:      * <p>
 150:      * Typically this value is set before the series is populated with data,
 151:      * but if it is applied later, it may cause some items to be removed from
 152:      * the series (in which case a {@link SeriesChangeEvent} will be sent to
 153:      * all registered listeners.
 154:      *
 155:      * @param maximum  the maximum number of items for the series.
 156:      */
 157:     public void setMaximumItemCount(int maximum) {
 158:         this.maximumItemCount = maximum;
 159:         boolean dataRemoved = false;
 160:         while (this.data.size() > maximum) {
 161:             this.data.remove(0);   
 162:             dataRemoved = true;
 163:         }
 164:         if (dataRemoved) {
 165:             fireSeriesChanged();
 166:         }
 167:     }
 168:     
 169:     /**
 170:      * Adds new data to the series and sends a {@link SeriesChangeEvent} to 
 171:      * all registered listeners.
 172:      * <P>
 173:      * Throws an exception if the x-value is a duplicate AND the 
 174:      * allowDuplicateXValues flag is false.
 175:      *
 176:      * @param x  the x-value (<code>null</code> not permitted).
 177:      * @param y  the y-value (<code>null</code> permitted).
 178:      */
 179:     protected void add(Comparable x, Object y) {
 180:         // argument checking delegated...
 181:         add(x, y, true);
 182:     }
 183:     
 184:     /**
 185:      * Adds new data to the series and, if requested, sends a 
 186:      * {@link SeriesChangeEvent} to all registered listeners.
 187:      * <P>
 188:      * Throws an exception if the x-value is a duplicate AND the 
 189:      * allowDuplicateXValues flag is false.
 190:      *
 191:      * @param x  the x-value (<code>null</code> not permitted).
 192:      * @param y  the y-value (<code>null</code> permitted).
 193:      * @param notify  a flag the controls whether or not a 
 194:      *                {@link SeriesChangeEvent} is sent to all registered 
 195:      *                listeners.
 196:      */
 197:     protected void add(Comparable x, Object y, boolean notify) {
 198:         // delegate argument checking to XYDataItem...
 199:         ComparableObjectItem item = new ComparableObjectItem(x, y);
 200:         add(item, notify);
 201:     }
 202: 
 203:     /**
 204:      * Adds a data item to the series and, if requested, sends a 
 205:      * {@link SeriesChangeEvent} to all registered listeners.
 206:      *
 207:      * @param item  the (x, y) item (<code>null</code> not permitted).
 208:      * @param notify  a flag that controls whether or not a 
 209:      *                {@link SeriesChangeEvent} is sent to all registered 
 210:      *                listeners.
 211:      */
 212:     protected void add(ComparableObjectItem item, boolean notify) {
 213: 
 214:         if (item == null) {
 215:             throw new IllegalArgumentException("Null 'item' argument.");
 216:         }
 217: 
 218:         if (this.autoSort) {
 219:             int index = Collections.binarySearch(this.data, item);
 220:             if (index < 0) {
 221:                 this.data.add(-index - 1, item);
 222:             }
 223:             else {
 224:                 if (this.allowDuplicateXValues) {
 225:                     // need to make sure we are adding *after* any duplicates
 226:                     int size = this.data.size();
 227:                     while (index < size 
 228:                            && item.compareTo(this.data.get(index)) == 0) {
 229:                         index++;
 230:                     }
 231:                     if (index < this.data.size()) {
 232:                         this.data.add(index, item);
 233:                     }
 234:                     else {
 235:                         this.data.add(item);
 236:                     }
 237:                 }
 238:                 else {
 239:                     throw new SeriesException("X-value already exists.");
 240:                 }
 241:             }
 242:         }
 243:         else {
 244:             if (!this.allowDuplicateXValues) {
 245:                 // can't allow duplicate values, so we need to check whether
 246:                 // there is an item with the given x-value already
 247:                 int index = indexOf(item.getComparable());
 248:                 if (index >= 0) {
 249:                     throw new SeriesException("X-value already exists.");      
 250:                 }
 251:             }
 252:             this.data.add(item);
 253:         }
 254:         if (getItemCount() > this.maximumItemCount) {
 255:             this.data.remove(0);
 256:         }                    
 257:         if (notify) {
 258:             fireSeriesChanged();
 259:         }
 260:     }
 261:     
 262:     /**
 263:      * Returns the index of the item with the specified x-value, or a negative 
 264:      * index if the series does not contain an item with that x-value.  Be 
 265:      * aware that for an unsorted series, the index is found by iterating 
 266:      * through all items in the series.
 267:      * 
 268:      * @param x  the x-value (<code>null</code> not permitted).
 269:      * 
 270:      * @return The index.
 271:      */
 272:     public int indexOf(Comparable x) {
 273:         if (this.autoSort) {
 274:             return Collections.binarySearch(this.data, new ComparableObjectItem(
 275:                     x, null));   
 276:         }
 277:         else {
 278:             for (int i = 0; i < this.data.size(); i++) {
 279:                 ComparableObjectItem item = (ComparableObjectItem) 
 280:                         this.data.get(i);
 281:                 if (item.getComparable().equals(x)) {
 282:                     return i;   
 283:                 }
 284:             }
 285:             return -1;
 286:         }
 287:     } 
 288: 
 289:     /**
 290:      * Updates an item in the series.
 291:      * 
 292:      * @param x  the x-value (<code>null</code> not permitted).
 293:      * @param y  the y-value (<code>null</code> permitted).
 294:      * 
 295:      * @throws SeriesException if there is no existing item with the specified
 296:      *         x-value.
 297:      */
 298:     protected void update(Comparable x, Object y) {
 299:         int index = indexOf(x);
 300:         if (index < 0) {
 301:             throw new SeriesException("No observation for x = " + x);
 302:         }
 303:         else {
 304:             ComparableObjectItem item = getDataItem(index);
 305:             item.setObject(y);
 306:             fireSeriesChanged();
 307:         }
 308:     }
 309: 
 310:     /**
 311:      * Updates the value of an item in the series and sends a 
 312:      * {@link SeriesChangeEvent} to all registered listeners.
 313:      * 
 314:      * @param index  the item (zero based index).
 315:      * @param y  the new value (<code>null</code> permitted).
 316:      */
 317:     protected void updateByIndex(int index, Object y) {
 318:         ComparableObjectItem item = getDataItem(index);
 319:         item.setObject(y);
 320:         fireSeriesChanged();
 321:     }
 322:     
 323:     /**
 324:      * Return the data item with the specified index.
 325:      *
 326:      * @param index  the index.
 327:      *
 328:      * @return The data item with the specified index.
 329:      */
 330:     protected ComparableObjectItem getDataItem(int index) {
 331:         return (ComparableObjectItem) this.data.get(index);
 332:     }
 333: 
 334:     /**
 335:      * Deletes a range of items from the series and sends a 
 336:      * {@link SeriesChangeEvent} to all registered listeners.
 337:      *
 338:      * @param start  the start index (zero-based).
 339:      * @param end  the end index (zero-based).
 340:      */
 341:     protected void delete(int start, int end) {
 342:         for (int i = start; i <= end; i++) {
 343:             this.data.remove(start);
 344:         }
 345:         fireSeriesChanged();
 346:     }
 347:     
 348:     /**
 349:      * Removes all data items from the series.
 350:      */
 351:     protected void clear() {
 352:         if (this.data.size() > 0) {
 353:             this.data.clear();
 354:             fireSeriesChanged();
 355:         }
 356:     }
 357: 
 358:     /**
 359:      * Removes the item at the specified index and sends a 
 360:      * {@link SeriesChangeEvent} to all registered listeners.
 361:      * 
 362:      * @param index  the index.
 363:      * 
 364:      * @return The item removed.
 365:      */
 366:     protected ComparableObjectItem remove(int index) {
 367:         ComparableObjectItem result = (ComparableObjectItem) this.data.remove(
 368:                 index);
 369:         fireSeriesChanged();
 370:         return result;
 371:     }
 372:     
 373:     /**
 374:      * Removes the item with the specified x-value and sends a 
 375:      * {@link SeriesChangeEvent} to all registered listeners.
 376:      * 
 377:      * @param x  the x-value.
 378: 
 379:      * @return The item removed.
 380:      */
 381:     public ComparableObjectItem remove(Comparable x) {
 382:         return remove(indexOf(x));
 383:     }
 384:     
 385:     /**
 386:      * Tests this series for equality with an arbitrary object.
 387:      *
 388:      * @param obj  the object to test against for equality 
 389:      *             (<code>null</code> permitted).
 390:      *
 391:      * @return A boolean.
 392:      */
 393:     public boolean equals(Object obj) {
 394:         if (obj == this) {
 395:             return true;
 396:         }
 397:         if (!(obj instanceof ComparableObjectSeries)) {
 398:             return false;
 399:         }
 400:         if (!super.equals(obj)) {
 401:             return false;
 402:         }
 403:         ComparableObjectSeries that = (ComparableObjectSeries) obj;
 404:         if (this.maximumItemCount != that.maximumItemCount) {
 405:             return false;
 406:         }
 407:         if (this.autoSort != that.autoSort) {
 408:             return false;
 409:         }
 410:         if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
 411:             return false;
 412:         }
 413:         if (!ObjectUtilities.equal(this.data, that.data)) {
 414:             return false;
 415:         }
 416:         return true;
 417:     }
 418:     
 419:     /**
 420:      * Returns a hash code.
 421:      * 
 422:      * @return A hash code.
 423:      */
 424:     public int hashCode() {
 425:         int result = super.hashCode();
 426:         // it is too slow to look at every data item, so let's just look at
 427:         // the first, middle and last items...
 428:         int count = getItemCount();
 429:         if (count > 0) {
 430:             ComparableObjectItem item = getDataItem(0);
 431:             result = 29 * result + item.hashCode();
 432:         }
 433:         if (count > 1) {
 434:             ComparableObjectItem item = getDataItem(count - 1);
 435:             result = 29 * result + item.hashCode();
 436:         }
 437:         if (count > 2) {
 438:             ComparableObjectItem item = getDataItem(count / 2);
 439:             result = 29 * result + item.hashCode();
 440:         }
 441:         result = 29 * result + this.maximumItemCount;
 442:         result = 29 * result + (this.autoSort ? 1 : 0);
 443:         result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
 444:         return result;
 445:     }
 446:     
 447: }