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