0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * ---------------
0028: * TimeSeries.java
0029: * ---------------
0030: * (C) Copyright 2001-2007, by Object Refinery Limited.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Bryan Scott;
0034: * Nick Guenther;
0035: *
0036: * $Id: TimeSeries.java,v 1.10.2.11 2007/03/22 08:11:46 mungady Exp $
0037: *
0038: * Changes
0039: * -------
0040: * 11-Oct-2001 : Version 1 (DG);
0041: * 14-Nov-2001 : Added listener mechanism (DG);
0042: * 15-Nov-2001 : Updated argument checking and exceptions in add() method (DG);
0043: * 29-Nov-2001 : Added properties to describe the domain and range (DG);
0044: * 07-Dec-2001 : Renamed TimeSeries --> BasicTimeSeries (DG);
0045: * 01-Mar-2002 : Updated import statements (DG);
0046: * 28-Mar-2002 : Added a method add(TimePeriod, double) (DG);
0047: * 27-Aug-2002 : Changed return type of delete method to void (DG);
0048: * 04-Oct-2002 : Added itemCount and historyCount attributes, fixed errors
0049: * reported by Checkstyle (DG);
0050: * 29-Oct-2002 : Added series change notification to addOrUpdate() method (DG);
0051: * 28-Jan-2003 : Changed name back to TimeSeries (DG);
0052: * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented
0053: * Serializable (DG);
0054: * 01-May-2003 : Updated equals() method (see bug report 727575) (DG);
0055: * 14-Aug-2003 : Added ageHistoryCountItems method (copied existing code for
0056: * contents) made a method and added to addOrUpdate. Made a
0057: * public method to enable ageing against a specified time
0058: * (eg now) as opposed to lastest time in series (BS);
0059: * 15-Oct-2003 : Added fix for setItemCount method - see bug report 804425.
0060: * Modified exception message in add() method to be more
0061: * informative (DG);
0062: * 13-Apr-2004 : Added clear() method (DG);
0063: * 21-May-2004 : Added an extra addOrUpdate() method (DG);
0064: * 15-Jun-2004 : Fixed NullPointerException in equals() method (DG);
0065: * 29-Nov-2004 : Fixed bug 1075255 (DG);
0066: * 17-Nov-2005 : Renamed historyCount --> maximumItemAge (DG);
0067: * 28-Nov-2005 : Changed maximumItemAge from int to long (DG);
0068: * 01-Dec-2005 : New add methods accept notify flag (DG);
0069: * ------------- JFREECHART 1.0.x ---------------------------------------------
0070: * 24-May-2006 : Improved error handling in createCopy() methods (DG);
0071: * 01-Sep-2006 : Fixed bugs in removeAgedItems() methods - see bug report
0072: * 1550045 (DG);
0073: * 22-Mar-2007 : Simplified getDataItem(RegularTimePeriod) - see patch 1685500
0074: * by Nick Guenther (DG);
0075: *
0076: */
0077:
0078: package org.jfree.data.time;
0079:
0080: import java.io.Serializable;
0081: import java.lang.reflect.InvocationTargetException;
0082: import java.lang.reflect.Method;
0083: import java.util.Collection;
0084: import java.util.Collections;
0085: import java.util.Date;
0086: import java.util.List;
0087: import java.util.TimeZone;
0088:
0089: import org.jfree.data.general.Series;
0090: import org.jfree.data.general.SeriesChangeEvent;
0091: import org.jfree.data.general.SeriesException;
0092: import org.jfree.util.ObjectUtilities;
0093:
0094: /**
0095: * Represents a sequence of zero or more data items in the form (period, value).
0096: */
0097: public class TimeSeries extends Series implements Cloneable,
0098: Serializable {
0099:
0100: /** For serialization. */
0101: private static final long serialVersionUID = -5032960206869675528L;
0102:
0103: /** Default value for the domain description. */
0104: protected static final String DEFAULT_DOMAIN_DESCRIPTION = "Time";
0105:
0106: /** Default value for the range description. */
0107: protected static final String DEFAULT_RANGE_DESCRIPTION = "Value";
0108:
0109: /** A description of the domain. */
0110: private String domain;
0111:
0112: /** A description of the range. */
0113: private String range;
0114:
0115: /** The type of period for the data. */
0116: protected Class timePeriodClass;
0117:
0118: /** The list of data items in the series. */
0119: protected List data;
0120:
0121: /** The maximum number of items for the series. */
0122: private int maximumItemCount;
0123:
0124: /**
0125: * The maximum age of items for the series, specified as a number of
0126: * time periods.
0127: */
0128: private long maximumItemAge;
0129:
0130: /**
0131: * Creates a new (empty) time series. By default, a daily time series is
0132: * created. Use one of the other constructors if you require a different
0133: * time period.
0134: *
0135: * @param name the series name (<code>null</code> not permitted).
0136: */
0137: public TimeSeries(String name) {
0138: this (name, DEFAULT_DOMAIN_DESCRIPTION,
0139: DEFAULT_RANGE_DESCRIPTION, Day.class);
0140: }
0141:
0142: /**
0143: * Creates a new (empty) time series with the specified name and class
0144: * of {@link RegularTimePeriod}.
0145: *
0146: * @param name the series name (<code>null</code> not permitted).
0147: * @param timePeriodClass the type of time period (<code>null</code> not
0148: * permitted).
0149: */
0150: public TimeSeries(String name, Class timePeriodClass) {
0151: this (name, DEFAULT_DOMAIN_DESCRIPTION,
0152: DEFAULT_RANGE_DESCRIPTION, timePeriodClass);
0153: }
0154:
0155: /**
0156: * Creates a new time series that contains no data.
0157: * <P>
0158: * Descriptions can be specified for the domain and range. One situation
0159: * where this is helpful is when generating a chart for the time series -
0160: * axis labels can be taken from the domain and range description.
0161: *
0162: * @param name the name of the series (<code>null</code> not permitted).
0163: * @param domain the domain description (<code>null</code> permitted).
0164: * @param range the range description (<code>null</code> permitted).
0165: * @param timePeriodClass the type of time period (<code>null</code> not
0166: * permitted).
0167: */
0168: public TimeSeries(String name, String domain, String range,
0169: Class timePeriodClass) {
0170: super (name);
0171: this .domain = domain;
0172: this .range = range;
0173: this .timePeriodClass = timePeriodClass;
0174: this .data = new java.util.ArrayList();
0175: this .maximumItemCount = Integer.MAX_VALUE;
0176: this .maximumItemAge = Long.MAX_VALUE;
0177: }
0178:
0179: /**
0180: * Returns the domain description.
0181: *
0182: * @return The domain description (possibly <code>null</code>).
0183: *
0184: * @see #setDomainDescription(String)
0185: */
0186: public String getDomainDescription() {
0187: return this .domain;
0188: }
0189:
0190: /**
0191: * Sets the domain description and sends a <code>PropertyChangeEvent</code>
0192: * (with the property name <code>Domain</code>) to all registered
0193: * property change listeners.
0194: *
0195: * @param description the description (<code>null</code> permitted).
0196: *
0197: * @see #getDomainDescription()
0198: */
0199: public void setDomainDescription(String description) {
0200: String old = this .domain;
0201: this .domain = description;
0202: firePropertyChange("Domain", old, description);
0203: }
0204:
0205: /**
0206: * Returns the range description.
0207: *
0208: * @return The range description (possibly <code>null</code>).
0209: *
0210: * @see #setRangeDescription(String)
0211: */
0212: public String getRangeDescription() {
0213: return this .range;
0214: }
0215:
0216: /**
0217: * Sets the range description and sends a <code>PropertyChangeEvent</code>
0218: * (with the property name <code>Range</code>) to all registered listeners.
0219: *
0220: * @param description the description (<code>null</code> permitted).
0221: *
0222: * @see #getRangeDescription()
0223: */
0224: public void setRangeDescription(String description) {
0225: String old = this .range;
0226: this .range = description;
0227: firePropertyChange("Range", old, description);
0228: }
0229:
0230: /**
0231: * Returns the number of items in the series.
0232: *
0233: * @return The item count.
0234: */
0235: public int getItemCount() {
0236: return this .data.size();
0237: }
0238:
0239: /**
0240: * Returns the list of data items for the series (the list contains
0241: * {@link TimeSeriesDataItem} objects and is unmodifiable).
0242: *
0243: * @return The list of data items.
0244: */
0245: public List getItems() {
0246: return Collections.unmodifiableList(this .data);
0247: }
0248:
0249: /**
0250: * Returns the maximum number of items that will be retained in the series.
0251: * The default value is <code>Integer.MAX_VALUE</code>.
0252: *
0253: * @return The maximum item count.
0254: *
0255: * @see #setMaximumItemCount(int)
0256: */
0257: public int getMaximumItemCount() {
0258: return this .maximumItemCount;
0259: }
0260:
0261: /**
0262: * Sets the maximum number of items that will be retained in the series.
0263: * If you add a new item to the series such that the number of items will
0264: * exceed the maximum item count, then the FIRST element in the series is
0265: * automatically removed, ensuring that the maximum item count is not
0266: * exceeded.
0267: *
0268: * @param maximum the maximum (requires >= 0).
0269: *
0270: * @see #getMaximumItemCount()
0271: */
0272: public void setMaximumItemCount(int maximum) {
0273: if (maximum < 0) {
0274: throw new IllegalArgumentException(
0275: "Negative 'maximum' argument.");
0276: }
0277: this .maximumItemCount = maximum;
0278: int count = this .data.size();
0279: if (count > maximum) {
0280: delete(0, count - maximum - 1);
0281: }
0282: }
0283:
0284: /**
0285: * Returns the maximum item age (in time periods) for the series.
0286: *
0287: * @return The maximum item age.
0288: *
0289: * @see #setMaximumItemAge(long)
0290: */
0291: public long getMaximumItemAge() {
0292: return this .maximumItemAge;
0293: }
0294:
0295: /**
0296: * Sets the number of time units in the 'history' for the series. This
0297: * provides one mechanism for automatically dropping old data from the
0298: * time series. For example, if a series contains daily data, you might set
0299: * the history count to 30. Then, when you add a new data item, all data
0300: * items more than 30 days older than the latest value are automatically
0301: * dropped from the series.
0302: *
0303: * @param periods the number of time periods.
0304: *
0305: * @see #getMaximumItemAge()
0306: */
0307: public void setMaximumItemAge(long periods) {
0308: if (periods < 0) {
0309: throw new IllegalArgumentException(
0310: "Negative 'periods' argument.");
0311: }
0312: this .maximumItemAge = periods;
0313: removeAgedItems(true); // remove old items and notify if necessary
0314: }
0315:
0316: /**
0317: * Returns the time period class for this series.
0318: * <p>
0319: * Only one time period class can be used within a single series (enforced).
0320: * If you add a data item with a {@link Year} for the time period, then all
0321: * subsequent data items must also have a {@link Year} for the time period.
0322: *
0323: * @return The time period class (never <code>null</code>).
0324: */
0325: public Class getTimePeriodClass() {
0326: return this .timePeriodClass;
0327: }
0328:
0329: /**
0330: * Returns a data item for the series.
0331: *
0332: * @param index the item index (zero-based).
0333: *
0334: * @return The data item.
0335: *
0336: * @see #getDataItem(RegularTimePeriod)
0337: */
0338: public TimeSeriesDataItem getDataItem(int index) {
0339: return (TimeSeriesDataItem) this .data.get(index);
0340: }
0341:
0342: /**
0343: * Returns the data item for a specific period.
0344: *
0345: * @param period the period of interest (<code>null</code> not allowed).
0346: *
0347: * @return The data item matching the specified period (or
0348: * <code>null</code> if there is no match).
0349: *
0350: * @see #getDataItem(int)
0351: */
0352: public TimeSeriesDataItem getDataItem(RegularTimePeriod period) {
0353: int index = getIndex(period);
0354: if (index >= 0) {
0355: return (TimeSeriesDataItem) this .data.get(index);
0356: } else {
0357: return null;
0358: }
0359: }
0360:
0361: /**
0362: * Returns the time period at the specified index.
0363: *
0364: * @param index the index of the data item.
0365: *
0366: * @return The time period.
0367: */
0368: public RegularTimePeriod getTimePeriod(int index) {
0369: return getDataItem(index).getPeriod();
0370: }
0371:
0372: /**
0373: * Returns a time period that would be the next in sequence on the end of
0374: * the time series.
0375: *
0376: * @return The next time period.
0377: */
0378: public RegularTimePeriod getNextTimePeriod() {
0379: RegularTimePeriod last = getTimePeriod(getItemCount() - 1);
0380: return last.next();
0381: }
0382:
0383: /**
0384: * Returns a collection of all the time periods in the time series.
0385: *
0386: * @return A collection of all the time periods.
0387: */
0388: public Collection getTimePeriods() {
0389: Collection result = new java.util.ArrayList();
0390: for (int i = 0; i < getItemCount(); i++) {
0391: result.add(getTimePeriod(i));
0392: }
0393: return result;
0394: }
0395:
0396: /**
0397: * Returns a collection of time periods in the specified series, but not in
0398: * this series, and therefore unique to the specified series.
0399: *
0400: * @param series the series to check against this one.
0401: *
0402: * @return The unique time periods.
0403: */
0404: public Collection getTimePeriodsUniqueToOtherSeries(
0405: TimeSeries series) {
0406:
0407: Collection result = new java.util.ArrayList();
0408: for (int i = 0; i < series.getItemCount(); i++) {
0409: RegularTimePeriod period = series.getTimePeriod(i);
0410: int index = getIndex(period);
0411: if (index < 0) {
0412: result.add(period);
0413: }
0414: }
0415: return result;
0416:
0417: }
0418:
0419: /**
0420: * Returns the index for the item (if any) that corresponds to a time
0421: * period.
0422: *
0423: * @param period the time period (<code>null</code> not permitted).
0424: *
0425: * @return The index.
0426: */
0427: public int getIndex(RegularTimePeriod period) {
0428: if (period == null) {
0429: throw new IllegalArgumentException(
0430: "Null 'period' argument.");
0431: }
0432: TimeSeriesDataItem dummy = new TimeSeriesDataItem(period,
0433: Integer.MIN_VALUE);
0434: return Collections.binarySearch(this .data, dummy);
0435: }
0436:
0437: /**
0438: * Returns the value at the specified index.
0439: *
0440: * @param index index of a value.
0441: *
0442: * @return The value (possibly <code>null</code>).
0443: */
0444: public Number getValue(int index) {
0445: return getDataItem(index).getValue();
0446: }
0447:
0448: /**
0449: * Returns the value for a time period. If there is no data item with the
0450: * specified period, this method will return <code>null</code>.
0451: *
0452: * @param period time period (<code>null</code> not permitted).
0453: *
0454: * @return The value (possibly <code>null</code>).
0455: */
0456: public Number getValue(RegularTimePeriod period) {
0457:
0458: int index = getIndex(period);
0459: if (index >= 0) {
0460: return getValue(index);
0461: } else {
0462: return null;
0463: }
0464:
0465: }
0466:
0467: /**
0468: * Adds a data item to the series and sends a
0469: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0470: * listeners.
0471: *
0472: * @param item the (timeperiod, value) pair (<code>null</code> not
0473: * permitted).
0474: */
0475: public void add(TimeSeriesDataItem item) {
0476: add(item, true);
0477: }
0478:
0479: /**
0480: * Adds a data item to the series and sends a
0481: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0482: * listeners.
0483: *
0484: * @param item the (timeperiod, value) pair (<code>null</code> not
0485: * permitted).
0486: * @param notify notify listeners?
0487: */
0488: public void add(TimeSeriesDataItem item, boolean notify) {
0489: if (item == null) {
0490: throw new IllegalArgumentException("Null 'item' argument.");
0491: }
0492: if (!item.getPeriod().getClass().equals(this .timePeriodClass)) {
0493: StringBuffer b = new StringBuffer();
0494: b
0495: .append("You are trying to add data where the time period class ");
0496: b.append("is ");
0497: b.append(item.getPeriod().getClass().getName());
0498: b
0499: .append(", but the TimeSeries is expecting an instance of ");
0500: b.append(this .timePeriodClass.getName());
0501: b.append(".");
0502: throw new SeriesException(b.toString());
0503: }
0504:
0505: // make the change (if it's not a duplicate time period)...
0506: boolean added = false;
0507: int count = getItemCount();
0508: if (count == 0) {
0509: this .data.add(item);
0510: added = true;
0511: } else {
0512: RegularTimePeriod last = getTimePeriod(getItemCount() - 1);
0513: if (item.getPeriod().compareTo(last) > 0) {
0514: this .data.add(item);
0515: added = true;
0516: } else {
0517: int index = Collections.binarySearch(this .data, item);
0518: if (index < 0) {
0519: this .data.add(-index - 1, item);
0520: added = true;
0521: } else {
0522: StringBuffer b = new StringBuffer();
0523: b
0524: .append("You are attempting to add an observation for ");
0525: b.append("the time period ");
0526: b.append(item.getPeriod().toString());
0527: b
0528: .append(" but the series already contains an observation");
0529: b
0530: .append(" for that time period. Duplicates are not ");
0531: b
0532: .append("permitted. Try using the addOrUpdate() method.");
0533: throw new SeriesException(b.toString());
0534: }
0535: }
0536: }
0537: if (added) {
0538: // check if this addition will exceed the maximum item count...
0539: if (getItemCount() > this .maximumItemCount) {
0540: this .data.remove(0);
0541: }
0542:
0543: removeAgedItems(false); // remove old items if necessary, but
0544: // don't notify anyone, because that
0545: // happens next anyway...
0546: if (notify) {
0547: fireSeriesChanged();
0548: }
0549: }
0550:
0551: }
0552:
0553: /**
0554: * Adds a new data item to the series and sends a {@link SeriesChangeEvent}
0555: * to all registered listeners.
0556: *
0557: * @param period the time period (<code>null</code> not permitted).
0558: * @param value the value.
0559: */
0560: public void add(RegularTimePeriod period, double value) {
0561: // defer argument checking...
0562: add(period, value, true);
0563: }
0564:
0565: /**
0566: * Adds a new data item to the series and sends a {@link SeriesChangeEvent}
0567: * to all registered listeners.
0568: *
0569: * @param period the time period (<code>null</code> not permitted).
0570: * @param value the value.
0571: * @param notify notify listeners?
0572: */
0573: public void add(RegularTimePeriod period, double value,
0574: boolean notify) {
0575: // defer argument checking...
0576: TimeSeriesDataItem item = new TimeSeriesDataItem(period, value);
0577: add(item, notify);
0578: }
0579:
0580: /**
0581: * Adds a new data item to the series and sends
0582: * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0583: * listeners.
0584: *
0585: * @param period the time period (<code>null</code> not permitted).
0586: * @param value the value (<code>null</code> permitted).
0587: */
0588: public void add(RegularTimePeriod period, Number value) {
0589: // defer argument checking...
0590: add(period, value, true);
0591: }
0592:
0593: /**
0594: * Adds a new data item to the series and sends
0595: * a {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0596: * listeners.
0597: *
0598: * @param period the time period (<code>null</code> not permitted).
0599: * @param value the value (<code>null</code> permitted).
0600: * @param notify notify listeners?
0601: */
0602: public void add(RegularTimePeriod period, Number value,
0603: boolean notify) {
0604: // defer argument checking...
0605: TimeSeriesDataItem item = new TimeSeriesDataItem(period, value);
0606: add(item, notify);
0607: }
0608:
0609: /**
0610: * Updates (changes) the value for a time period. Throws a
0611: * {@link SeriesException} if the period does not exist.
0612: *
0613: * @param period the period (<code>null</code> not permitted).
0614: * @param value the value (<code>null</code> permitted).
0615: */
0616: public void update(RegularTimePeriod period, Number value) {
0617: TimeSeriesDataItem temp = new TimeSeriesDataItem(period, value);
0618: int index = Collections.binarySearch(this .data, temp);
0619: if (index >= 0) {
0620: TimeSeriesDataItem pair = (TimeSeriesDataItem) this .data
0621: .get(index);
0622: pair.setValue(value);
0623: fireSeriesChanged();
0624: } else {
0625: throw new SeriesException(
0626: "TimeSeries.update(TimePeriod, Number): period does not exist.");
0627: }
0628:
0629: }
0630:
0631: /**
0632: * Updates (changes) the value of a data item.
0633: *
0634: * @param index the index of the data item.
0635: * @param value the new value (<code>null</code> permitted).
0636: */
0637: public void update(int index, Number value) {
0638: TimeSeriesDataItem item = getDataItem(index);
0639: item.setValue(value);
0640: fireSeriesChanged();
0641: }
0642:
0643: /**
0644: * Adds or updates data from one series to another. Returns another series
0645: * containing the values that were overwritten.
0646: *
0647: * @param series the series to merge with this.
0648: *
0649: * @return A series containing the values that were overwritten.
0650: */
0651: public TimeSeries addAndOrUpdate(TimeSeries series) {
0652: TimeSeries overwritten = new TimeSeries(
0653: "Overwritten values from: " + getKey(), series
0654: .getTimePeriodClass());
0655: for (int i = 0; i < series.getItemCount(); i++) {
0656: TimeSeriesDataItem item = series.getDataItem(i);
0657: TimeSeriesDataItem oldItem = addOrUpdate(item.getPeriod(),
0658: item.getValue());
0659: if (oldItem != null) {
0660: overwritten.add(oldItem);
0661: }
0662: }
0663: return overwritten;
0664: }
0665:
0666: /**
0667: * Adds or updates an item in the times series and sends a
0668: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0669: * listeners.
0670: *
0671: * @param period the time period to add/update (<code>null</code> not
0672: * permitted).
0673: * @param value the new value.
0674: *
0675: * @return A copy of the overwritten data item, or <code>null</code> if no
0676: * item was overwritten.
0677: */
0678: public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period,
0679: double value) {
0680: return this .addOrUpdate(period, new Double(value));
0681: }
0682:
0683: /**
0684: * Adds or updates an item in the times series and sends a
0685: * {@link org.jfree.data.general.SeriesChangeEvent} to all registered
0686: * listeners.
0687: *
0688: * @param period the time period to add/update (<code>null</code> not
0689: * permitted).
0690: * @param value the new value (<code>null</code> permitted).
0691: *
0692: * @return A copy of the overwritten data item, or <code>null</code> if no
0693: * item was overwritten.
0694: */
0695: public TimeSeriesDataItem addOrUpdate(RegularTimePeriod period,
0696: Number value) {
0697:
0698: if (period == null) {
0699: throw new IllegalArgumentException(
0700: "Null 'period' argument.");
0701: }
0702: TimeSeriesDataItem overwritten = null;
0703:
0704: TimeSeriesDataItem key = new TimeSeriesDataItem(period, value);
0705: int index = Collections.binarySearch(this .data, key);
0706: if (index >= 0) {
0707: TimeSeriesDataItem existing = (TimeSeriesDataItem) this .data
0708: .get(index);
0709: overwritten = (TimeSeriesDataItem) existing.clone();
0710: existing.setValue(value);
0711: removeAgedItems(false); // remove old items if necessary, but
0712: // don't notify anyone, because that
0713: // happens next anyway...
0714: fireSeriesChanged();
0715: } else {
0716: this .data.add(-index - 1, new TimeSeriesDataItem(period,
0717: value));
0718:
0719: // check if this addition will exceed the maximum item count...
0720: if (getItemCount() > this .maximumItemCount) {
0721: this .data.remove(0);
0722: }
0723:
0724: removeAgedItems(false); // remove old items if necessary, but
0725: // don't notify anyone, because that
0726: // happens next anyway...
0727: fireSeriesChanged();
0728: }
0729: return overwritten;
0730:
0731: }
0732:
0733: /**
0734: * Age items in the series. Ensure that the timespan from the youngest to
0735: * the oldest record in the series does not exceed maximumItemAge time
0736: * periods. Oldest items will be removed if required.
0737: *
0738: * @param notify controls whether or not a {@link SeriesChangeEvent} is
0739: * sent to registered listeners IF any items are removed.
0740: */
0741: public void removeAgedItems(boolean notify) {
0742: // check if there are any values earlier than specified by the history
0743: // count...
0744: if (getItemCount() > 1) {
0745: long latest = getTimePeriod(getItemCount() - 1)
0746: .getSerialIndex();
0747: boolean removed = false;
0748: while ((latest - getTimePeriod(0).getSerialIndex()) > this .maximumItemAge) {
0749: this .data.remove(0);
0750: removed = true;
0751: }
0752: if (removed && notify) {
0753: fireSeriesChanged();
0754: }
0755: }
0756: }
0757:
0758: /**
0759: * Age items in the series. Ensure that the timespan from the supplied
0760: * time to the oldest record in the series does not exceed history count.
0761: * oldest items will be removed if required.
0762: *
0763: * @param latest the time to be compared against when aging data
0764: * (specified in milliseconds).
0765: * @param notify controls whether or not a {@link SeriesChangeEvent} is
0766: * sent to registered listeners IF any items are removed.
0767: */
0768: public void removeAgedItems(long latest, boolean notify) {
0769:
0770: // find the serial index of the period specified by 'latest'
0771: long index = Long.MAX_VALUE;
0772: try {
0773: Method m = RegularTimePeriod.class.getDeclaredMethod(
0774: "createInstance", new Class[] { Class.class,
0775: Date.class, TimeZone.class });
0776: RegularTimePeriod newest = (RegularTimePeriod) m.invoke(
0777: this .timePeriodClass, new Object[] {
0778: this .timePeriodClass, new Date(latest),
0779: TimeZone.getDefault() });
0780: index = newest.getSerialIndex();
0781: } catch (NoSuchMethodException e) {
0782: e.printStackTrace();
0783: } catch (IllegalAccessException e) {
0784: e.printStackTrace();
0785: } catch (InvocationTargetException e) {
0786: e.printStackTrace();
0787: }
0788:
0789: // check if there are any values earlier than specified by the history
0790: // count...
0791: boolean removed = false;
0792: while (getItemCount() > 0
0793: && (index - getTimePeriod(0).getSerialIndex()) > this .maximumItemAge) {
0794: this .data.remove(0);
0795: removed = true;
0796: }
0797: if (removed && notify) {
0798: fireSeriesChanged();
0799: }
0800: }
0801:
0802: /**
0803: * Removes all data items from the series and sends a
0804: * {@link SeriesChangeEvent} to all registered listeners.
0805: */
0806: public void clear() {
0807: if (this .data.size() > 0) {
0808: this .data.clear();
0809: fireSeriesChanged();
0810: }
0811: }
0812:
0813: /**
0814: * Deletes the data item for the given time period and sends a
0815: * {@link SeriesChangeEvent} to all registered listeners. If there is no
0816: * item with the specified time period, this method does nothing.
0817: *
0818: * @param period the period of the item to delete (<code>null</code> not
0819: * permitted).
0820: */
0821: public void delete(RegularTimePeriod period) {
0822: int index = getIndex(period);
0823: if (index >= 0) {
0824: this .data.remove(index);
0825: fireSeriesChanged();
0826: }
0827: }
0828:
0829: /**
0830: * Deletes data from start until end index (end inclusive).
0831: *
0832: * @param start the index of the first period to delete.
0833: * @param end the index of the last period to delete.
0834: */
0835: public void delete(int start, int end) {
0836: if (end < start) {
0837: throw new IllegalArgumentException("Requires start <= end.");
0838: }
0839: for (int i = 0; i <= (end - start); i++) {
0840: this .data.remove(start);
0841: }
0842: fireSeriesChanged();
0843: }
0844:
0845: /**
0846: * Returns a clone of the time series.
0847: * <P>
0848: * Notes:
0849: * <ul>
0850: * <li>no need to clone the domain and range descriptions, since String
0851: * object is immutable;</li>
0852: * <li>we pass over to the more general method clone(start, end).</li>
0853: * </ul>
0854: *
0855: * @return A clone of the time series.
0856: *
0857: * @throws CloneNotSupportedException not thrown by this class, but
0858: * subclasses may differ.
0859: */
0860: public Object clone() throws CloneNotSupportedException {
0861: Object clone = createCopy(0, getItemCount() - 1);
0862: return clone;
0863: }
0864:
0865: /**
0866: * Creates a new timeseries by copying a subset of the data in this time
0867: * series.
0868: *
0869: * @param start the index of the first time period to copy.
0870: * @param end the index of the last time period to copy.
0871: *
0872: * @return A series containing a copy of this times series from start until
0873: * end.
0874: *
0875: * @throws CloneNotSupportedException if there is a cloning problem.
0876: */
0877: public TimeSeries createCopy(int start, int end)
0878: throws CloneNotSupportedException {
0879:
0880: if (start < 0) {
0881: throw new IllegalArgumentException("Requires start >= 0.");
0882: }
0883: if (end < start) {
0884: throw new IllegalArgumentException("Requires start <= end.");
0885: }
0886: TimeSeries copy = (TimeSeries) super .clone();
0887:
0888: copy.data = new java.util.ArrayList();
0889: if (this .data.size() > 0) {
0890: for (int index = start; index <= end; index++) {
0891: TimeSeriesDataItem item = (TimeSeriesDataItem) this .data
0892: .get(index);
0893: TimeSeriesDataItem clone = (TimeSeriesDataItem) item
0894: .clone();
0895: try {
0896: copy.add(clone);
0897: } catch (SeriesException e) {
0898: e.printStackTrace();
0899: }
0900: }
0901: }
0902: return copy;
0903: }
0904:
0905: /**
0906: * Creates a new timeseries by copying a subset of the data in this time
0907: * series.
0908: *
0909: * @param start the first time period to copy.
0910: * @param end the last time period to copy.
0911: *
0912: * @return A time series containing a copy of this time series from start
0913: * until end.
0914: *
0915: * @throws CloneNotSupportedException if there is a cloning problem.
0916: */
0917: public TimeSeries createCopy(RegularTimePeriod start,
0918: RegularTimePeriod end) throws CloneNotSupportedException {
0919:
0920: if (start == null) {
0921: throw new IllegalArgumentException("Null 'start' argument.");
0922: }
0923: if (end == null) {
0924: throw new IllegalArgumentException("Null 'end' argument.");
0925: }
0926: if (start.compareTo(end) > 0) {
0927: throw new IllegalArgumentException(
0928: "Requires start on or before end.");
0929: }
0930: boolean emptyRange = false;
0931: int startIndex = getIndex(start);
0932: if (startIndex < 0) {
0933: startIndex = -(startIndex + 1);
0934: if (startIndex == this .data.size()) {
0935: emptyRange = true; // start is after last data item
0936: }
0937: }
0938: int endIndex = getIndex(end);
0939: if (endIndex < 0) { // end period is not in original series
0940: endIndex = -(endIndex + 1); // this is first item AFTER end period
0941: endIndex = endIndex - 1; // so this is last item BEFORE end
0942: }
0943: if (endIndex < 0) {
0944: emptyRange = true;
0945: }
0946: if (emptyRange) {
0947: TimeSeries copy = (TimeSeries) super .clone();
0948: copy.data = new java.util.ArrayList();
0949: return copy;
0950: } else {
0951: return createCopy(startIndex, endIndex);
0952: }
0953:
0954: }
0955:
0956: /**
0957: * Tests the series for equality with an arbitrary object.
0958: *
0959: * @param object the object to test against (<code>null</code> permitted).
0960: *
0961: * @return A boolean.
0962: */
0963: public boolean equals(Object object) {
0964: if (object == this ) {
0965: return true;
0966: }
0967: if (!(object instanceof TimeSeries) || !super .equals(object)) {
0968: return false;
0969: }
0970: TimeSeries s = (TimeSeries) object;
0971: if (!ObjectUtilities.equal(getDomainDescription(), s
0972: .getDomainDescription())) {
0973: return false;
0974: }
0975:
0976: if (!ObjectUtilities.equal(getRangeDescription(), s
0977: .getRangeDescription())) {
0978: return false;
0979: }
0980:
0981: if (!getClass().equals(s.getClass())) {
0982: return false;
0983: }
0984:
0985: if (getMaximumItemAge() != s.getMaximumItemAge()) {
0986: return false;
0987: }
0988:
0989: if (getMaximumItemCount() != s.getMaximumItemCount()) {
0990: return false;
0991: }
0992:
0993: int count = getItemCount();
0994: if (count != s.getItemCount()) {
0995: return false;
0996: }
0997: for (int i = 0; i < count; i++) {
0998: if (!getDataItem(i).equals(s.getDataItem(i))) {
0999: return false;
1000: }
1001: }
1002: return true;
1003: }
1004:
1005: /**
1006: * Returns a hash code value for the object.
1007: *
1008: * @return The hashcode
1009: */
1010: public int hashCode() {
1011: int result;
1012: result = (this .domain != null ? this .domain.hashCode() : 0);
1013: result = 29 * result
1014: + (this .range != null ? this .range.hashCode() : 0);
1015: result = 29
1016: * result
1017: + (this .timePeriodClass != null ? this .timePeriodClass
1018: .hashCode() : 0);
1019: result = 29 * result + this .data.hashCode();
1020: result = 29 * result + this .maximumItemCount;
1021: result = 29 * result + (int) this.maximumItemAge;
1022: return result;
1023: }
1024:
1025: }
|