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