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