Source for org.jfree.data.ComparableObjectSeries

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, 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, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: ComparableObjectSeries.java,v 1.1.2.2 2006/10/23 09:18:54 mungady Exp $
  36:  *
  37:  * Changes
  38:  * -------
  39:  * 19-Oct-2006 : New class, based on XYDataItem (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(x, null));   
 276:         }
 277:         else {
 278:             for (int i = 0; i < this.data.size(); i++) {
 279:                 ComparableObjectItem item = (ComparableObjectItem) this.data.get(i);
 280:                 if (item.getComparable().equals(x)) {
 281:                     return i;   
 282:                 }
 283:             }
 284:             return -1;
 285:         }
 286:     } 
 287: 
 288:     /**
 289:      * Updates an item in the series.
 290:      * 
 291:      * @param x  the x-value (<code>null</code> not permitted).
 292:      * @param y  the y-value (<code>null</code> permitted).
 293:      * 
 294:      * @throws SeriesException if there is no existing item with the specified
 295:      *         x-value.
 296:      */
 297:     protected void update(Comparable x, Object y) {
 298:         int index = indexOf(x);
 299:         if (index < 0) {
 300:             throw new SeriesException("No observation for x = " + x);
 301:         }
 302:         else {
 303:             ComparableObjectItem item = getDataItem(index);
 304:             item.setObject(y);
 305:             fireSeriesChanged();
 306:         }
 307:     }
 308: 
 309:     /**
 310:      * Updates the value of an item in the series and sends a 
 311:      * {@link SeriesChangeEvent} to all registered listeners.
 312:      * 
 313:      * @param index  the item (zero based index).
 314:      * @param y  the new value (<code>null</code> permitted).
 315:      */
 316:     protected void updateByIndex(int index, Object y) {
 317:         ComparableObjectItem item = getDataItem(index);
 318:         item.setObject(y);
 319:         fireSeriesChanged();
 320:     }
 321:     
 322:     /**
 323:      * Return the data item with the specified index.
 324:      *
 325:      * @param index  the index.
 326:      *
 327:      * @return The data item with the specified index.
 328:      */
 329:     protected ComparableObjectItem getDataItem(int index) {
 330:         return (ComparableObjectItem) this.data.get(index);
 331:     }
 332: 
 333:     /**
 334:      * Deletes a range of items from the series and sends a 
 335:      * {@link SeriesChangeEvent} to all registered listeners.
 336:      *
 337:      * @param start  the start index (zero-based).
 338:      * @param end  the end index (zero-based).
 339:      */
 340:     protected void delete(int start, int end) {
 341:         for (int i = start; i <= end; i++) {
 342:             this.data.remove(start);
 343:         }
 344:         fireSeriesChanged();
 345:     }
 346:     
 347:     /**
 348:      * Removes all data items from the series.
 349:      */
 350:     protected void clear() {
 351:         if (this.data.size() > 0) {
 352:             this.data.clear();
 353:             fireSeriesChanged();
 354:         }
 355:     }
 356: 
 357:     /**
 358:      * Removes the item at the specified index and sends a 
 359:      * {@link SeriesChangeEvent} to all registered listeners.
 360:      * 
 361:      * @param index  the index.
 362:      * 
 363:      * @return The item removed.
 364:      */
 365:     protected ComparableObjectItem remove(int index) {
 366:         ComparableObjectItem result = (ComparableObjectItem) this.data.remove(index);
 367:         fireSeriesChanged();
 368:         return result;
 369:     }
 370:     
 371:     /**
 372:      * Removes the item with the specified x-value and sends a 
 373:      * {@link SeriesChangeEvent} to all registered listeners.
 374:      * 
 375:      * @param x  the x-value.
 376: 
 377:      * @return The item removed.
 378:      */
 379:     public ComparableObjectItem remove(Comparable x) {
 380:         return remove(indexOf(x));
 381:     }
 382:     
 383:     /**
 384:      * Tests this series for equality with an arbitrary object.
 385:      *
 386:      * @param obj  the object to test against for equality 
 387:      *             (<code>null</code> permitted).
 388:      *
 389:      * @return A boolean.
 390:      */
 391:     public boolean equals(Object obj) {
 392:         if (obj == this) {
 393:             return true;
 394:         }
 395:         if (!(obj instanceof ComparableObjectSeries)) {
 396:             return false;
 397:         }
 398:         if (!super.equals(obj)) {
 399:             return false;
 400:         }
 401:         ComparableObjectSeries that = (ComparableObjectSeries) obj;
 402:         if (this.maximumItemCount != that.maximumItemCount) {
 403:             return false;
 404:         }
 405:         if (this.autoSort != that.autoSort) {
 406:             return false;
 407:         }
 408:         if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
 409:             return false;
 410:         }
 411:         if (!ObjectUtilities.equal(this.data, that.data)) {
 412:             return false;
 413:         }
 414:         return true;
 415:     }
 416:     
 417:     /**
 418:      * Returns a hash code.
 419:      * 
 420:      * @return A hash code.
 421:      */
 422:     public int hashCode() {
 423:         int result = super.hashCode();
 424:         result = 29 * result + (this.data != null ? this.data.hashCode() : 0);
 425:         result = 29 * result + this.maximumItemCount;
 426:         result = 29 * result + (this.autoSort ? 1 : 0);
 427:         result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
 428:         return result;
 429:     }
 430:     
 431: }