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