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: * TimeSeries.java 29: * --------------- 30: * (C) Copyright 2001-2006, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Bryan Scott; 34: * 35: * $Id: TimeSeries.java,v 1.10.2.8 2006/07/25 15:55:48 mungady Exp $ 36: * 37: * Changes 38: * ------- 39: * 11-Oct-2001 : Version 1 (DG); 40: * 14-Nov-2001 : Added listener mechanism (DG); 41: * 15-Nov-2001 : Updated argument checking and exceptions in add() method (DG); 42: * 29-Nov-2001 : Added properties to describe the domain and range (DG); 43: * 07-Dec-2001 : Renamed TimeSeries --> BasicTimeSeries (DG); 44: * 01-Mar-2002 : Updated import statements (DG); 45: * 28-Mar-2002 : Added a method add(TimePeriod, double) (DG); 46: * 27-Aug-2002 : Changed return type of delete method to void (DG); 47: * 04-Oct-2002 : Added itemCount and historyCount attributes, fixed errors 48: * reported by Checkstyle (DG); 49: * 29-Oct-2002 : Added series change notification to addOrUpdate() method (DG); 50: * 28-Jan-2003 : Changed name back to TimeSeries (DG); 51: * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented 52: * Serializable (DG); 53: * 01-May-2003 : Updated equals() method (see bug report 727575) (DG); 54: * 14-Aug-2003 : Added ageHistoryCountItems method (copied existing code for 55: * contents) made a method and added to addOrUpdate. Made a 56: * public method to enable ageing against a specified time 57: * (eg now) as opposed to lastest time in series (BS); 58: * 15-Oct-2003 : Added fix for setItemCount method - see bug report 804425. 59: * Modified exception message in add() method to be more 60: * informative (DG); 61: * 13-Apr-2004 : Added clear() method (DG); 62: * 21-May-2004 : Added an extra addOrUpdate() method (DG); 63: * 15-Jun-2004 : Fixed NullPointerException in equals() method (DG); 64: * 29-Nov-2004 : Fixed bug 1075255 (DG); 65: * 17-Nov-2005 : Renamed historyCount --> maximumItemAge (DG); 66: * 28-Nov-2005 : Changed maximumItemAge from int to long (DG); 67: * 01-Dec-2005 : New add methods accept notify flag (DG); 68: * ------------- JFREECHART 1.0.0 --------------------------------------------- 69: * 24-May-2006 : Improved error handling in createCopy() methods (DG); 70: * 71: */ 72: 73: package org.jfree.data.time; 74: 75: import java.io.Serializable; 76: import java.util.Collection; 77: import java.util.Collections; 78: import java.util.List; 79: 80: import org.jfree.data.general.Series; 81: import org.jfree.data.general.SeriesChangeEvent; 82: import org.jfree.data.general.SeriesException; 83: import org.jfree.util.ObjectUtilities; 84: 85: /** 86: * Represents a sequence of zero or more data items in the form (period, value). 87: */ 88: public class TimeSeries extends Series implements Cloneable, Serializable { 89: 90: /** For serialization. */ 91: private static final long serialVersionUID = -5032960206869675528L; 92: 93: /** Default value for the domain description. */ 94: protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time"; 95: 96: /** Default value for the range description. */ 97: protected static final String DEFAULT_RANGE_DESCRIPTION = "Value"; 98: 99: /** A description of the domain. */ 100: private String domain; 101: 102: /** A description of the range. */ 103: private String range; 104: 105: /** The type of period for the data. */ 106: protected Class timePeriodClass; 107: 108: /** The list of data items in the series. */ 109: protected List data; 110: 111: /** The maximum number of items for the series. */ 112: private int maximumItemCount; 113: 114: /** The maximum age of items for the series. */ 115: private long maximumItemAge; 116: 117: /** 118: * Creates a new (empty) time series. By default, a daily time series is 119: * created. Use one of the other constructors if you require a different 120: * time period. 121: * 122: * @param name the series name (<code>null</code> not permitted). 123: */ 124: public TimeSeries(String name) { 125: this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION, 126: Day.class); 127: } 128: 129: /** 130: * Creates a new (empty) time series with the specified name and class 131: * of {@link RegularTimePeriod}. 132: * 133: * @param name the series name (<code>null</code> not permitted). 134: * @param timePeriodClass the type of time period (<code>null</code> not 135: * permitted). 136: */ 137: public TimeSeries(String name, Class timePeriodClass) { 138: this(name, DEFAULT_DOMAIN_DESCRIPTION, DEFAULT_RANGE_DESCRIPTION, 139: timePeriodClass); 140: } 141: 142: /** 143: * Creates a new time series that contains no data. 144: * <P> 145: * Descriptions can be specified for the domain and range. One situation 146: * where this is helpful is when generating a chart for the time series - 147: * axis labels can be taken from the domain and range description. 148: * 149: * @param name the name of the series (<code>null</code> not permitted). 150: * @param domain the domain description (<code>null</code> permitted). 151: * @param range the range description (<code>null</code> permitted). 152: * @param timePeriodClass the type of time period (<code>null</code> not 153: * permitted). 154: */ 155: public TimeSeries(String name, String domain, String range, 156: Class timePeriodClass) { 157: super(name); 158: this.domain = domain; 159: this.range = range; 160: this.timePeriodClass = timePeriodClass; 161: this.data = new java.util.ArrayList(); 162: this.maximumItemCount = Integer.MAX_VALUE; 163: this.maximumItemAge = Long.MAX_VALUE; 164: } 165: 166: /** 167: * Returns the domain description. 168: * 169: * @return The domain description (possibly <code>null</code>). 170: * 171: * @see #setDomainDescription(String) 172: */ 173: public String getDomainDescription() { 174: return this.domain; 175: } 176: 177: /** 178: * Sets the domain description and sends a <code>PropertyChangeEvent</code> 179: * (with the property name <code>Domain</code>) to all registered 180: * property change listeners. 181: * 182: * @param description the description (<code>null</code> permitted). 183: * 184: * @see #getDomainDescription() 185: */ 186: public void setDomainDescription(String description) { 187: String old = this.domain; 188: this.domain = description; 189: firePropertyChange("Domain", old, description); 190: } 191: 192: /** 193: * Returns the range description. 194: * 195: * @return The range description (possibly <code>null</code>). 196: * 197: * @see #setRangeDescription(String) 198: */ 199: public String getRangeDescription() { 200: return this.range; 201: } 202: 203: /** 204: * Sets the range description and sends a <code>PropertyChangeEvent</code> 205: * (with the property name <code>Range</code>) to all registered listeners. 206: * 207: * @param description the description (<code>null</code> permitted). 208: * 209: * @see #getRangeDescription() 210: */ 211: public void setRangeDescription(String description) { 212: String old = this.range; 213: this.range = description; 214: firePropertyChange("Range", old, description); 215: } 216: 217: /** 218: * Returns the number of items in the series. 219: * 220: * @return The item count. 221: */ 222: public int getItemCount() { 223: return this.data.size(); 224: } 225: 226: /** 227: * Returns the list of data items for the series (the list contains 228: * {@link TimeSeriesDataItem} objects and is unmodifiable). 229: * 230: * @return The list of data items. 231: */ 232: public List getItems() { 233: return Collections.unmodifiableList(this.data); 234: } 235: 236: /** 237: * Returns the maximum number of items that will be retained in the series. 238: * The default value is <code>Integer.MAX_VALUE</code>. 239: * 240: * @return The maximum item count. 241: * 242: * @see #setMaximumItemCount(int) 243: */ 244: public int getMaximumItemCount() { 245: return this.maximumItemCount; 246: } 247: 248: /** 249: * Sets the maximum number of items that will be retained in the series. 250: * If you add a new item to the series such that the number of items will 251: * exceed the maximum item count, then the FIRST element in the series is 252: * automatically removed, ensuring that the maximum item count is not 253: * exceeded. 254: * 255: * @param maximum the maximum (requires >= 0). 256: * 257: * @see #getMaximumItemCount() 258: */ 259: public void setMaximumItemCount(int maximum) { 260: if (maximum < 0) { 261: throw new IllegalArgumentException("Negative 'maximum' argument."); 262: } 263: this.maximumItemCount = maximum; 264: int count = this.data.size(); 265: if (count > maximum) { 266: delete(0, count - maximum - 1); 267: } 268: } 269: 270: /** 271: * Returns the maximum item age (in time periods) for the series. 272: * 273: * @return The maximum item age. 274: * 275: * @see #setMaximumItemAge(long) 276: */ 277: public long getMaximumItemAge() { 278: return this.maximumItemAge; 279: } 280: 281: /** 282: * Sets the number of time units in the 'history' for the series. This 283: * provides one mechanism for automatically dropping old data from the 284: * time series. For example, if a series contains daily data, you might set 285: * the history count to 30. Then, when you add a new data item, all data 286: * items more than 30 days older than the latest value are automatically 287: * dropped from the series. 288: * 289: * @param periods the number of time periods. 290: * 291: * @see #getMaximumItemAge() 292: */ 293: public void setMaximumItemAge(long periods) { 294: if (periods < 0) { 295: throw new IllegalArgumentException("Negative 'periods' argument."); 296: } 297: this.maximumItemAge = periods; 298: removeAgedItems(true); // remove old items and notify if necessary 299: } 300: 301: /** 302: * Returns the time period class for this series. 303: * <p> 304: * Only one time period class can be used within a single series (enforced). 305: * If you add a data item with a {@link Year} for the time period, then all 306: * subsequent data items must also have a {@link Year} for the time period. 307: * 308: * @return The time period class (never <code>null</code>). 309: */ 310: public Class getTimePeriodClass() { 311: return this.timePeriodClass; 312: } 313: 314: /** 315: * Returns a data item for the series. 316: * 317: * @param index the item index (zero-based). 318: * 319: * @return The data item. 320: * 321: * @see #getDataItem(RegularTimePeriod) 322: */ 323: public TimeSeriesDataItem getDataItem(int index) { 324: return (TimeSeriesDataItem) this.data.get(index); 325: } 326: 327: /** 328: * Returns the data item for a specific period. 329: * 330: * @param period the period of interest (<code>null</code> not allowed). 331: * 332: * @return The data item matching the specified period (or 333: * <code>null</code> if there is no match). 334: * 335: * @see #getDataItem(int) 336: */ 337: public TimeSeriesDataItem getDataItem(RegularTimePeriod period) { 338: if (period == null) { 339: throw new IllegalArgumentException("Null 'period' argument"); 340: } 341: TimeSeriesDataItem dummy = new TimeSeriesDataItem(period, 342: Integer.MIN_VALUE); 343: int index = Collections.binarySearch(this.data, dummy); 344: if (index >= 0) { 345: return (TimeSeriesDataItem) this.data.get(index); 346: } 347: else { 348: return null; 349: } 350: } 351: 352: /** 353: * Returns the time period at the specified index. 354: * 355: * @param index the index of the data item. 356: * 357: * @return The time period. 358: */ 359: public RegularTimePeriod getTimePeriod(int index) { 360: return getDataItem(index).getPeriod(); 361: } 362: 363: /** 364: * Returns a time period that would be the next in sequence on the end of 365: * the time series. 366: * 367: * @return The next time period. 368: */ 369: public RegularTimePeriod getNextTimePeriod() { 370: RegularTimePeriod last = getTimePeriod(getItemCount() - 1); 371: return last.next(); 372: } 373: 374: /** 375: * Returns a collection of all the time periods in the time series. 376: * 377: * @return A collection of all the time periods. 378: */ 379: public Collection getTimePeriods() { 380: Collection result = new java.util.ArrayList(); 381: for (int i = 0; i < getItemCount(); i++) { 382: result.add(getTimePeriod(i)); 383: } 384: return result; 385: } 386: 387: /** 388: * Returns a collection of time periods in the specified series, but not in 389: * this series, and therefore unique to the specified series. 390: * 391: * @param series the series to check against this one. 392: * 393: * @return The unique time periods. 394: */ 395: public Collection getTimePeriodsUniqueToOtherSeries(TimeSeries series) { 396: 397: Collection result = new java.util.ArrayList(); 398: for (int i = 0; i < series.getItemCount(); i++) { 399: RegularTimePeriod period = series.getTimePeriod(i); 400: int index = getIndex(period); 401: if (index < 0) { 402: result.add(period); 403: } 404: } 405: return result; 406: 407: } 408: 409: /** 410: * Returns the index for the item (if any) that corresponds to a time 411: * period. 412: * 413: * @param period the time period (<code>null</code> not permitted). 414: * 415: * @return The index. 416: */ 417: public int getIndex(RegularTimePeriod period) { 418: if (period == null) { 419: throw new IllegalArgumentException("Null 'period' argument."); 420: } 421: TimeSeriesDataItem dummy = new TimeSeriesDataItem( 422: period, Integer.MIN_VALUE); 423: return Collections.binarySearch(this.data, dummy); 424: } 425: 426: /** 427: * Returns the value at the specified index. 428: * 429: * @param index index of a value. 430: * 431: * @return The value (possibly <code>null</code>). 432: */ 433: public Number getValue(int index) { 434: return getDataItem(index).getValue(); 435: } 436: 437: /** 438: * Returns the value for a time period. If there is no data item with the 439: * specified period, this method will return <code>null</code>. 440: * 441: * @param period time period (<code>null</code> not permitted). 442: * 443: * @return The value (possibly <code>null</code>). 444: */ 445: public Number getValue(RegularTimePeriod period) { 446: 447: int index = getIndex(period); 448: if (index >= 0) { 449: return getValue(index); 450: } 451: else { 452: return null; 453: } 454: 455: } 456: 457: /** 458: * Adds a data item to the series and sends a 459: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 460: * listeners. 461: * 462: * @param item the (timeperiod, value) pair (<code>null</code> not 463: * permitted). 464: */ 465: public void add(TimeSeriesDataItem item) { 466: add(item, true); 467: } 468: 469: /** 470: * Adds a data item to the series and sends a 471: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 472: * listeners. 473: * 474: * @param item the (timeperiod, value) pair (<code>null</code> not 475: * permitted). 476: * @param notify notify listeners? 477: */ 478: public void add(TimeSeriesDataItem item, boolean notify) { 479: if (item == null) { 480: throw new IllegalArgumentException("Null 'item' argument."); 481: } 482: if (!item.getPeriod().getClass().equals(this.timePeriodClass)) { 483: StringBuffer b = new StringBuffer(); 484: b.append("You are trying to add data where the time period class "); 485: b.append("is "); 486: b.append(item.getPeriod().getClass().getName()); 487: b.append(", but the TimeSeries is expecting an instance of "); 488: b.append(this.timePeriodClass.getName()); 489: b.append("."); 490: throw new SeriesException(b.toString()); 491: } 492: 493: // make the change (if it's not a duplicate time period)... 494: boolean added = false; 495: int count = getItemCount(); 496: if (count == 0) { 497: this.data.add(item); 498: added = true; 499: } 500: else { 501: RegularTimePeriod last = getTimePeriod(getItemCount() - 1); 502: if (item.getPeriod().compareTo(last) > 0) { 503: this.data.add(item); 504: added = true; 505: } 506: else { 507: int index = Collections.binarySearch(this.data, item); 508: if (index < 0) { 509: this.data.add(-index - 1, item); 510: added = true; 511: } 512: else { 513: StringBuffer b = new StringBuffer(); 514: b.append("You are attempting to add an observation for "); 515: b.append("the time period "); 516: b.append(item.getPeriod().toString()); 517: b.append(" but the series already contains an observation"); 518: b.append(" for that time period. Duplicates are not "); 519: b.append("permitted. Try using the addOrUpdate() method."); 520: throw new SeriesException(b.toString()); 521: } 522: } 523: } 524: if (added) { 525: // check if this addition will exceed the maximum item count... 526: if (getItemCount() > this.maximumItemCount) { 527: this.data.remove(0); 528: } 529: 530: removeAgedItems(false); // remove old items if necessary, but 531: // don't notify anyone, because that 532: // happens next anyway... 533: if (notify) { 534: fireSeriesChanged(); 535: } 536: } 537: 538: } 539: 540: /** 541: * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 542: * to all registered listeners. 543: * 544: * @param period the time period (<code>null</code> not permitted). 545: * @param value the value. 546: */ 547: public void add(RegularTimePeriod period, double value) { 548: // defer argument checking... 549: add(period, value, true); 550: } 551: 552: /** 553: * Adds a new data item to the series and sends a {@link SeriesChangeEvent} 554: * to all registered listeners. 555: * 556: * @param period the time period (<code>null</code> not permitted). 557: * @param value the value. 558: * @param notify notify listeners? 559: */ 560: public void add(RegularTimePeriod period, double value, boolean notify) { 561: // defer argument checking... 562: TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); 563: add(item, notify); 564: } 565: 566: /** 567: * Adds a new data item to the series and sends 568: * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 569: * listeners. 570: * 571: * @param period the time period (<code>null</code> not permitted). 572: * @param value the value (<code>null</code> permitted). 573: */ 574: public void add(RegularTimePeriod period, Number value) { 575: // defer argument checking... 576: add(period, value, true); 577: } 578: 579: /** 580: * Adds a new data item to the series and sends 581: * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered 582: * listeners. 583: * 584: * @param period the time period (<code>null</code> not permitted). 585: * @param value the value (<code>null</code> permitted). 586: * @param notify notify listeners? 587: */ 588: public void add(RegularTimePeriod period, Number value, boolean notify) { 589: // defer argument checking... 590: TimeSeriesDataItem item = new TimeSeriesDataItem(period, value); 591: add(item, notify); 592: } 593: 594: /** 595: * Updates (changes) the value for a time period. Throws a 596: * {@link SeriesException} if the period does not exist. 597: * 598: * @param period the period (<code>null</code> not permitted). 599: * @param value the value (<code>null</code> permitted). 600: */ 601: public void update(RegularTimePeriod period, Number value) { 602: TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value); 603: int index = Collections.binarySearch(this.data, temp); 604: if (index >= 0) { 605: TimeSeriesDataItem pair = (TimeSeriesDataItem) this.data.get(index); 606: pair.setValue(value); 607: fireSeriesChanged(); 608: } 609: else { 610: throw new SeriesException( 611: "TimeSeries.update(TimePeriod, Number): period does not exist." 612: ); 613: } 614: 615: } 616: 617: /** 618: * Updates (changes) the value of a data item. 619: * 620: * @param index the index of the data item. 621: * @param value the new value (<code>null</code> permitted). 622: */ 623: public void update(int index, Number value) { 624: TimeSeriesDataItem item = getDataItem(index); 625: item.setValue(value); 626: fireSeriesChanged(); 627: } 628: 629: /** 630: * Adds or updates data from one series to another. Returns another series 631: * containing the values that were overwritten. 632: * 633: * @param series the series to merge with this. 634: * 635: * @return A series containing the values that were overwritten. 636: */ 637: public TimeSeries addAndOrUpdate(TimeSeries series) { 638: TimeSeries overwritten = new TimeSeries("Overwritten values from: " 639: + getKey(), series.getTimePeriodClass()); 640: for (int i = 0; i < series.getItemCount(); i++) { 641: TimeSeriesDataItem item = series.getDataItem(i); 642: TimeSeriesDataItem oldItem = addOrUpdate(item.getPeriod(), 643: item.getValue()); 644: if (oldItem != null) { 645: overwritten.add(oldItem); 646: } 647: } 648: return overwritten; 649: } 650: 651: /** 652: * Adds or updates an item in the times series and sends a 653: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 654: * listeners. 655: * 656: * @param period the time period to add/update (<code>null</code> not 657: * permitted). 658: * @param value the new value. 659: * 660: * @return A copy of the overwritten data item, or <code>null</code> if no 661: * item was overwritten. 662: */ 663: public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, 664: double value) { 665: return this.addOrUpdate(period, new Double(value)); 666: } 667: 668: /** 669: * Adds or updates an item in the times series and sends a 670: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered 671: * listeners. 672: * 673: * @param period the time period to add/update (<code>null</code> not 674: * permitted). 675: * @param value the new value (<code>null</code> permitted). 676: * 677: * @return A copy of the overwritten data item, or <code>null</code> if no 678: * item was overwritten. 679: */ 680: public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period, 681: Number value) { 682: 683: if (period == null) { 684: throw new IllegalArgumentException("Null 'period' argument."); 685: } 686: TimeSeriesDataItem overwritten = null; 687: 688: TimeSeriesDataItem key = new TimeSeriesDataItem(period, value); 689: int index = Collections.binarySearch(this.data, key); 690: if (index >= 0) { 691: TimeSeriesDataItem existing 692: = (TimeSeriesDataItem) this.data.get(index); 693: overwritten = (TimeSeriesDataItem) existing.clone(); 694: existing.setValue(value); 695: removeAgedItems(false); // remove old items if necessary, but 696: // don't notify anyone, because that 697: // happens next anyway... 698: fireSeriesChanged(); 699: } 700: else { 701: this.data.add(-index - 1, new TimeSeriesDataItem(period, value)); 702: 703: // check if this addition will exceed the maximum item count... 704: if (getItemCount() > this.maximumItemCount) { 705: this.data.remove(0); 706: } 707: 708: removeAgedItems(false); // remove old items if necessary, but 709: // don't notify anyone, because that 710: // happens next anyway... 711: fireSeriesChanged(); 712: } 713: return overwritten; 714: 715: } 716: 717: /** 718: * Age items in the series. Ensure that the timespan from the youngest to 719: * the oldest record in the series does not exceed maximumItemAge time 720: * periods. Oldest items will be removed if required. 721: * 722: * @param notify controls whether or not a {@link SeriesChangeEvent} is 723: * sent to registered listeners IF any items are removed. 724: */ 725: public void removeAgedItems(boolean notify) { 726: // check if there are any values earlier than specified by the history 727: // count... 728: if (getItemCount() > 1) { 729: long latest = getTimePeriod(getItemCount() - 1).getSerialIndex(); 730: boolean removed = false; 731: while ((latest - getTimePeriod(0).getSerialIndex()) 732: >= this.maximumItemAge) { 733: this.data.remove(0); 734: removed = true; 735: } 736: if (removed && notify) { 737: fireSeriesChanged(); 738: } 739: } 740: } 741: 742: /** 743: * Age items in the series. Ensure that the timespan from the supplied 744: * time to the oldest record in the series does not exceed history count. 745: * oldest items will be removed if required. 746: * 747: * @param latest the time to be compared against when aging data. 748: * @param notify controls whether or not a {@link SeriesChangeEvent} is 749: * sent to registered listeners IF any items are removed. 750: */ 751: public void removeAgedItems(long latest, boolean notify) { 752: // check if there are any values earlier than specified by the history 753: // count... 754: if (getItemCount() > 1) { 755: while ((latest - getTimePeriod(0).getSerialIndex()) 756: >= this.maximumItemAge) { 757: this.data.remove(0); 758: } 759: } 760: } 761: 762: /** 763: * Removes all data items from the series and sends a 764: * {@link SeriesChangeEvent} to all registered listeners. 765: */ 766: public void clear() { 767: if (this.data.size() > 0) { 768: this.data.clear(); 769: fireSeriesChanged(); 770: } 771: } 772: 773: /** 774: * Deletes the data item for the given time period and sends a 775: * {@link SeriesChangeEvent} to all registered listeners. If there is no 776: * item with the specified time period, this method does nothing. 777: * 778: * @param period the period of the item to delete (<code>null</code> not 779: * permitted). 780: */ 781: public void delete(RegularTimePeriod period) { 782: int index = getIndex(period); 783: if (index >= 0) { 784: this.data.remove(index); 785: fireSeriesChanged(); 786: } 787: } 788: 789: /** 790: * Deletes data from start until end index (end inclusive). 791: * 792: * @param start the index of the first period to delete. 793: * @param end the index of the last period to delete. 794: */ 795: public void delete(int start, int end) { 796: if (end < start) { 797: throw new IllegalArgumentException("Requires start <= end."); 798: } 799: for (int i = 0; i <= (end - start); i++) { 800: this.data.remove(start); 801: } 802: fireSeriesChanged(); 803: } 804: 805: /** 806: * Returns a clone of the time series. 807: * <P> 808: * Notes: 809: * <ul> 810: * <li>no need to clone the domain and range descriptions, since String 811: * object is immutable;</li> 812: * <li>we pass over to the more general method clone(start, end).</li> 813: * </ul> 814: * 815: * @return A clone of the time series. 816: * 817: * @throws CloneNotSupportedException not thrown by this class, but 818: * subclasses may differ. 819: */ 820: public Object clone() throws CloneNotSupportedException { 821: Object clone = createCopy(0, getItemCount() - 1); 822: return clone; 823: } 824: 825: /** 826: * Creates a new timeseries by copying a subset of the data in this time 827: * series. 828: * 829: * @param start the index of the first time period to copy. 830: * @param end the index of the last time period to copy. 831: * 832: * @return A series containing a copy of this times series from start until 833: * end. 834: * 835: * @throws CloneNotSupportedException if there is a cloning problem. 836: */ 837: public TimeSeries createCopy(int start, int end) 838: throws CloneNotSupportedException { 839: 840: if (start < 0) { 841: throw new IllegalArgumentException("Requires start >= 0."); 842: } 843: if (end < start) { 844: throw new IllegalArgumentException("Requires start <= end."); 845: } 846: TimeSeries copy = (TimeSeries) super.clone(); 847: 848: copy.data = new java.util.ArrayList(); 849: if (this.data.size() > 0) { 850: for (int index = start; index <= end; index++) { 851: TimeSeriesDataItem item 852: = (TimeSeriesDataItem) this.data.get(index); 853: TimeSeriesDataItem clone = (TimeSeriesDataItem) item.clone(); 854: try { 855: copy.add(clone); 856: } 857: catch (SeriesException e) { 858: e.printStackTrace(); 859: } 860: } 861: } 862: return copy; 863: } 864: 865: /** 866: * Creates a new timeseries by copying a subset of the data in this time 867: * series. 868: * 869: * @param start the first time period to copy. 870: * @param end the last time period to copy. 871: * 872: * @return A time series containing a copy of this time series from start 873: * until end. 874: * 875: * @throws CloneNotSupportedException if there is a cloning problem. 876: */ 877: public TimeSeries createCopy(RegularTimePeriod start, RegularTimePeriod end) 878: throws CloneNotSupportedException { 879: 880: if (start == null) { 881: throw new IllegalArgumentException("Null 'start' argument."); 882: } 883: if (end == null) { 884: throw new IllegalArgumentException("Null 'end' argument."); 885: } 886: if (start.compareTo(end) > 0) { 887: throw new IllegalArgumentException( 888: "Requires start on or before end."); 889: } 890: boolean emptyRange = false; 891: int startIndex = getIndex(start); 892: if (startIndex < 0) { 893: startIndex = -(startIndex + 1); 894: if (startIndex == this.data.size()) { 895: emptyRange = true; // start is after last data item 896: } 897: } 898: int endIndex = getIndex(end); 899: if (endIndex < 0) { // end period is not in original series 900: endIndex = -(endIndex + 1); // this is first item AFTER end period 901: endIndex = endIndex - 1; // so this is last item BEFORE end 902: } 903: if (endIndex < 0) { 904: emptyRange = true; 905: } 906: if (emptyRange) { 907: TimeSeries copy = (TimeSeries) super.clone(); 908: copy.data = new java.util.ArrayList(); 909: return copy; 910: } 911: else { 912: return createCopy(startIndex, endIndex); 913: } 914: 915: } 916: 917: /** 918: * Tests the series for equality with an arbitrary object. 919: * 920: * @param object the object to test against (<code>null</code> permitted). 921: * 922: * @return A boolean. 923: */ 924: public boolean equals(Object object) { 925: if (object == this) { 926: return true; 927: } 928: if (!(object instanceof TimeSeries) || !super.equals(object)) { 929: return false; 930: } 931: TimeSeries s = (TimeSeries) object; 932: if (!ObjectUtilities.equal( 933: getDomainDescription(), s.getDomainDescription() 934: )) { 935: return false; 936: } 937: 938: if (!ObjectUtilities.equal( 939: getRangeDescription(), s.getRangeDescription() 940: )) { 941: return false; 942: } 943: 944: if (!getClass().equals(s.getClass())) { 945: return false; 946: } 947: 948: if (getMaximumItemAge() != s.getMaximumItemAge()) { 949: return false; 950: } 951: 952: if (getMaximumItemCount() != s.getMaximumItemCount()) { 953: return false; 954: } 955: 956: int count = getItemCount(); 957: if (count != s.getItemCount()) { 958: return false; 959: } 960: for (int i = 0; i < count; i++) { 961: if (!getDataItem(i).equals(s.getDataItem(i))) { 962: return false; 963: } 964: } 965: return true; 966: } 967: 968: /** 969: * Returns a hash code value for the object. 970: * 971: * @return The hashcode 972: */ 973: public int hashCode() { 974: int result; 975: result = (this.domain != null ? this.domain.hashCode() : 0); 976: result = 29 * result + (this.range != null ? this.range.hashCode() : 0); 977: result = 29 * result + (this.timePeriodClass != null 978: ? this.timePeriodClass.hashCode() : 0); 979: result = 29 * result + this.data.hashCode(); 980: result = 29 * result + this.maximumItemCount; 981: result = 29 * result + (int) this.maximumItemAge; 982: return result; 983: } 984: 985: }