    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------------------
    028 * TimeSeriesCollection.java
    029 * -------------------------
    030 * (C) Copyright 2001-2009, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 11-Oct-2001 : Version 1 (DG);
    038 * 18-Oct-2001 : Added implementation of IntervalXYDataSource so that bar plots
    039 *               (using numerical axes) can be plotted from time series
    040 *               data (DG);
    041 * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
    042 * 15-Nov-2001 : Added getSeries() method.  Changed name from TimeSeriesDataset
    043 *               to TimeSeriesCollection (DG);
    044 * 07-Dec-2001 : TimeSeries --> BasicTimeSeries (DG);
    045 * 01-Mar-2002 : Added a time zone offset attribute, to enable fast calculation
    046 *               of the time period start and end values (DG);
    047 * 29-Mar-2002 : The collection now registers itself with all the time series
    048 *               objects as a SeriesChangeListener.  Removed redundant
    049 *               calculateZoneOffset method (DG);
    050 * 06-Jun-2002 : Added a setting to control whether the x-value supplied in the
    051 *               getXValue() method comes from the START, MIDDLE, or END of the
    052 *               time period.  This is a workaround for JFreeChart, where the
    053 *               current date axis always labels the start of a time
    054 *               period (DG);
    055 * 24-Jun-2002 : Removed unnecessary import (DG);
    056 * 24-Aug-2002 : Implemented DomainInfo interface, and added the
    057 *               DomainIsPointsInTime flag (DG);
    058 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    059 * 16-Oct-2002 : Added remove methods (DG);
    060 * 10-Jan-2003 : Changed method names in RegularTimePeriod class (DG);
    061 * 13-Mar-2003 : Moved to com.jrefinery.data.time package and implemented
    062 *               Serializable (DG);
    063 * 04-Sep-2003 : Added getSeries(String) method (DG);
    064 * 15-Sep-2003 : Added a removeAllSeries() method to match
    065 *               XYSeriesCollection (DG);
    066 * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
    067 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    068 *               getYValue() (DG);
    069 * 06-Oct-2004 : Updated for changed in DomainInfo interface (DG);
    070 * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
    071 *               release (DG);
    072 * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
    073 * ------------- JFREECHART 1.0.x ---------------------------------------------
    074 * 13-Dec-2005 : Deprecated the 'domainIsPointsInTime' flag as it is
    075 *               redundant.  Fixes bug 1243050 (DG);
    076 * 04-May-2007 : Override getDomainOrder() to indicate that items are sorted
    077 *               by x-value (ascending) (DG);
    078 * 08-May-2007 : Added indexOf(TimeSeries) method (DG);
    079 * 18-Jan-2008 : Changed getSeries(String) to getSeries(Comparable) (DG);
    080 * 19-May-2009 : Implemented XYDomainInfo (DG);
    081 * 26-May-2009 : Implemented XYRangeInfo (DG);
    082 * 09-Jun-2009 : Apply some short-cuts to series value lookups (DG);
    083 * 26-Jun-2009 : Fixed clone() (DG);
    084 *
    085 */
    086
    087package org.jfree.data.time;
    088
    089import java.io.Serializable;
    090import java.util.ArrayList;
    091import java.util.Calendar;
    092import java.util.Collections;
    093import java.util.Iterator;
    094import java.util.List;
    095import java.util.TimeZone;
    096
    097import org.jfree.data.DomainInfo;
    098import org.jfree.data.DomainOrder;
    099import org.jfree.data.Range;
    100import org.jfree.data.general.DatasetChangeEvent;
    101import org.jfree.data.xy.AbstractIntervalXYDataset;
    102import org.jfree.data.xy.IntervalXYDataset;
    103import org.jfree.data.xy.XYDataset;
    104import org.jfree.data.xy.XYDomainInfo;
    105import org.jfree.data.xy.XYRangeInfo;
    106import org.jfree.util.ObjectUtilities;
    107
    108/**
    109 * A collection of time series objects.  This class implements the
    110 * {@link XYDataset} interface, as well as the extended
    111 * {@link IntervalXYDataset} interface.  This makes it a convenient dataset for
    112 * use with the {@link org.jfree.chart.plot.XYPlot} class.
    113 */
    114public class TimeSeriesCollection extends AbstractIntervalXYDataset
    115        implements XYDataset, IntervalXYDataset, DomainInfo, XYDomainInfo,
    116        XYRangeInfo, Serializable {
    117
    118    /** For serialization. */
    119    private static final long serialVersionUID = 834149929022371137L;
    120
    121    /** Storage for the time series. */
    122    private List data;
    123
    124    /** A working calendar (to recycle) */
    125    private Calendar workingCalendar;
    126
    127    /**
    128     * The point within each time period that is used for the X value when this
    129     * collection is used as an {@link org.jfree.data.xy.XYDataset}.  This can
    130     * be the start, middle or end of the time period.
    131     */
    132    private TimePeriodAnchor xPosition;
    133
    134    /**
    135     * A flag that indicates that the domain is 'points in time'.  If this
    136     * flag is true, only the x-value is used to determine the range of values
    137     * in the domain, the start and end x-values are ignored.
    138     *
    139     * @deprecated No longer used (as of 1.0.1).
    140     */
    141    private boolean domainIsPointsInTime;
    142
    143    /**
    144     * Constructs an empty dataset, tied to the default timezone.
    145     */
    146    public TimeSeriesCollection() {
    147        this(null, TimeZone.getDefault());
    148    }
    149
    150    /**
    151     * Constructs an empty dataset, tied to a specific timezone.
    152     *
    153     * @param zone  the timezone (<code>null</code> permitted, will use
    154     *              <code>TimeZone.getDefault()</code> in that case).
    155     */
    156    public TimeSeriesCollection(TimeZone zone) {
    157        // FIXME: need a locale as well as a timezone
    158        this(null, zone);
    159    }
    160
    161    /**
    162     * Constructs a dataset containing a single series (more can be added),
    163     * tied to the default timezone.
    164     *
    165     * @param series the series (<code>null</code> permitted).
    166     */
    167    public TimeSeriesCollection(TimeSeries series) {
    168        this(series, TimeZone.getDefault());
    169    }
    170
    171    /**
    172     * Constructs a dataset containing a single series (more can be added),
    173     * tied to a specific timezone.
    174     *
    175     * @param series  a series to add to the collection (<code>null</code>
    176     *                permitted).
    177     * @param zone  the timezone (<code>null</code> permitted, will use
    178     *              <code>TimeZone.getDefault()</code> in that case).
    179     */
    180    public TimeSeriesCollection(TimeSeries series, TimeZone zone) {
    181        // FIXME:  need a locale as well as a timezone
    182        if (zone == null) {
    183            zone = TimeZone.getDefault();
    184        }
    185        this.workingCalendar = Calendar.getInstance(zone);
    186        this.data = new ArrayList();
    187        if (series != null) {
    188            this.data.add(series);
    189            series.addChangeListener(this);
    190        }
    191        this.xPosition = TimePeriodAnchor.START;
    192        this.domainIsPointsInTime = true;
    193
    194    }
    195
    196    /**
    197     * Returns a flag that controls whether the domain is treated as 'points in
    198     * time'.  This flag is used when determining the max and min values for
    199     * the domain.  If <code>true</code>, then only the x-values are considered
    200     * for the max and min values.  If <code>false</code>, then the start and
    201     * end x-values will also be taken into consideration.
    202     *
    203     * @return The flag.
    204     *
    205     * @deprecated This flag is no longer used (as of 1.0.1).
    206     */
    207    public boolean getDomainIsPointsInTime() {
    208        return this.domainIsPointsInTime;
    209    }
    210
    211    /**
    212     * Sets a flag that controls whether the domain is treated as 'points in
    213     * time', or time periods.
    214     *
    215     * @param flag  the flag.
    216     *
    217     * @deprecated This flag is no longer used, as of 1.0.1.  The
    218     *             <code>includeInterval</code> flag in methods such as
    219     *             {@link #getDomainBounds(boolean)} makes this unnecessary.
    220     */
    221    public void setDomainIsPointsInTime(boolean flag) {
    222        this.domainIsPointsInTime = flag;
    223        notifyListeners(new DatasetChangeEvent(this, this));
    224    }
    225
    226    /**
    227     * Returns the order of the domain values in this dataset.
    228     *
    229     * @return {@link DomainOrder#ASCENDING}
    230     */
    231    public DomainOrder getDomainOrder() {
    232        return DomainOrder.ASCENDING;
    233    }
    234
    235    /**
    236     * Returns the position within each time period that is used for the X
    237     * value when the collection is used as an
    238     * {@link org.jfree.data.xy.XYDataset}.
    239     *
    240     * @return The anchor position (never <code>null</code>).
    241     */
    242    public TimePeriodAnchor getXPosition() {
    243        return this.xPosition;
    244    }
    245
    246    /**
    247     * Sets the position within each time period that is used for the X values
    248     * when the collection is used as an {@link XYDataset}, then sends a
    249     * {@link DatasetChangeEvent} is sent to all registered listeners.
    250     *
    251     * @param anchor  the anchor position (<code>null</code> not permitted).
    252     */
    253    public void setXPosition(TimePeriodAnchor anchor) {
    254        if (anchor == null) {
    255            throw new IllegalArgumentException("Null 'anchor' argument.");
    256        }
    257        this.xPosition = anchor;
    258        notifyListeners(new DatasetChangeEvent(this, this));
    259    }
    260
    261    /**
    262     * Returns a list of all the series in the collection.
    263     *
    264     * @return The list (which is unmodifiable).
    265     */
    266    public List getSeries() {
    267        return Collections.unmodifiableList(this.data);
    268    }
    269
    270    /**
    271     * Returns the number of series in the collection.
    272     *
    273     * @return The series count.
    274     */
    275    public int getSeriesCount() {
    276        return this.data.size();
    277    }
    278
    279    /**
    280     * Returns the index of the specified series, or -1 if that series is not
    281     * present in the dataset.
    282     *
    283     * @param series  the series (<code>null</code> not permitted).
    284     *
    285     * @return The series index.
    286     *
    287     * @since 1.0.6
    288     */
    289    public int indexOf(TimeSeries series) {
    290        if (series == null) {
    291            throw new IllegalArgumentException("Null 'series' argument.");
    292        }
    293        return this.data.indexOf(series);
    294    }
    295
    296    /**
    297     * Returns a series.
    298     *
    299     * @param series  the index of the series (zero-based).
    300     *
    301     * @return The series.
    302     */
    303    public TimeSeries getSeries(int series) {
    304        if ((series < 0) || (series >= getSeriesCount())) {
    305            throw new IllegalArgumentException(
    306                "The 'series' argument is out of bounds (" + series + ").");
    307        }
    308        return (TimeSeries) this.data.get(series);
    309    }
    310
    311    /**
    312     * Returns the series with the specified key, or <code>null</code> if
    313     * there is no such series.
    314     *
    315     * @param key  the series key (<code>null</code> permitted).
    316     *
    317     * @return The series with the given key.
    318     */
    319    public TimeSeries getSeries(Comparable key) {
    320        TimeSeries result = null;
    321        Iterator iterator = this.data.iterator();
    322        while (iterator.hasNext()) {
    323            TimeSeries series = (TimeSeries) iterator.next();
    324            Comparable k = series.getKey();
    325            if (k != null && k.equals(key)) {
    326                result = series;
    327            }
    328        }
    329        return result;
    330    }
    331
    332    /**
    333     * Returns the key for a series.
    334     *
    335     * @param series  the index of the series (zero-based).
    336     *
    337     * @return The key for a series.
    338     */
    339    public Comparable getSeriesKey(int series) {
    340        // check arguments...delegated
    341        // fetch the series name...
    342        return getSeries(series).getKey();
    343    }
    344
    345    /**
    346     * Adds a series to the collection and sends a {@link DatasetChangeEvent} to
    347     * all registered listeners.
    348     *
    349     * @param series  the series (<code>null</code> not permitted).
    350     */
    351    public void addSeries(TimeSeries series) {
    352        if (series == null) {
    353            throw new IllegalArgumentException("Null 'series' argument.");
    354        }
    355        this.data.add(series);
    356        series.addChangeListener(this);
    357        fireDatasetChanged();
    358    }
    359
    360    /**
    361     * Removes the specified series from the collection and sends a
    362     * {@link DatasetChangeEvent} to all registered listeners.
    363     *
    364     * @param series  the series (<code>null</code> not permitted).
    365     */
    366    public void removeSeries(TimeSeries series) {
    367        if (series == null) {
    368            throw new IllegalArgumentException("Null 'series' argument.");
    369        }
    370        this.data.remove(series);
    371        series.removeChangeListener(this);
    372        fireDatasetChanged();
    373    }
    374
    375    /**
    376     * Removes a series from the collection.
    377     *
    378     * @param index  the series index (zero-based).
    379     */
    380    public void removeSeries(int index) {
    381        TimeSeries series = getSeries(index);
    382        if (series != null) {
    383            removeSeries(series);
    384        }
    385    }
    386
    387    /**
    388     * Removes all the series from the collection and sends a
    389     * {@link DatasetChangeEvent} to all registered listeners.
    390     */
    391    public void removeAllSeries() {
    392
    393        // deregister the collection as a change listener to each series in the
    394        // collection
    395        for (int i = 0; i < this.data.size(); i++) {
    396            TimeSeries series = (TimeSeries) this.data.get(i);
    397            series.removeChangeListener(this);
    398        }
    399
    400        // remove all the series from the collection and notify listeners.
    401        this.data.clear();
    402        fireDatasetChanged();
    403
    404    }
    405
    406    /**
    407     * Returns the number of items in the specified series.  This method is
    408     * provided for convenience.
    409     *
    410     * @param series  the series index (zero-based).
    411     *
    412     * @return The item count.
    413     */
    414    public int getItemCount(int series) {
    415        return getSeries(series).getItemCount();
    416    }
    417
    418    /**
    419     * Returns the x-value (as a double primitive) for an item within a series.
    420     *
    421     * @param series  the series (zero-based index).
    422     * @param item  the item (zero-based index).
    423     *
    424     * @return The x-value.
    425     */
    426    public double getXValue(int series, int item) {
    427        TimeSeries s = (TimeSeries) this.data.get(series);
    428        RegularTimePeriod period = s.getTimePeriod(item);
    429        return getX(period);
    430    }
    431
    432    /**
    433     * Returns the x-value for the specified series and item.
    434     *
    435     * @param series  the series (zero-based index).
    436     * @param item  the item (zero-based index).
    437     *
    438     * @return The value.
    439     */
    440    public Number getX(int series, int item) {
    441        TimeSeries ts = (TimeSeries) this.data.get(series);
    442        RegularTimePeriod period = ts.getTimePeriod(item);
    443        return new Long(getX(period));
    444    }
    445
    446    /**
    447     * Returns the x-value for a time period.
    448     *
    449     * @param period  the time period (<code>null</code> not permitted).
    450     *
    451     * @return The x-value.
    452     */
    453    protected synchronized long getX(RegularTimePeriod period) {
    454        long result = 0L;
    455        if (this.xPosition == TimePeriodAnchor.START) {
    456            result = period.getFirstMillisecond(this.workingCalendar);
    457        }
    458        else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
    459            result = period.getMiddleMillisecond(this.workingCalendar);
    460        }
    461        else if (this.xPosition == TimePeriodAnchor.END) {
    462            result = period.getLastMillisecond(this.workingCalendar);
    463        }
    464        return result;
    465    }
    466
    467    /**
    468     * Returns the starting X value for the specified series and item.
    469     *
    470     * @param series  the series (zero-based index).
    471     * @param item  the item (zero-based index).
    472     *
    473     * @return The value.
    474     */
    475    public synchronized Number getStartX(int series, int item) {
    476        TimeSeries ts = (TimeSeries) this.data.get(series);
    477        return new Long(ts.getTimePeriod(item).getFirstMillisecond(
    478                this.workingCalendar));
    479    }
    480
    481    /**
    482     * Returns the ending X value for the specified series and item.
    483     *
    484     * @param series The series (zero-based index).
    485     * @param item  The item (zero-based index).
    486     *
    487     * @return The value.
    488     */
    489    public synchronized Number getEndX(int series, int item) {
    490        TimeSeries ts = (TimeSeries) this.data.get(series);
    491        return new Long(ts.getTimePeriod(item).getLastMillisecond(
    492                this.workingCalendar));
    493    }
    494
    495    /**
    496     * Returns the y-value for the specified series and item.
    497     *
    498     * @param series  the series (zero-based index).
    499     * @param item  the item (zero-based index).
    500     *
    501     * @return The value (possibly <code>null</code>).
    502     */
    503    public Number getY(int series, int item) {
    504        TimeSeries ts = (TimeSeries) this.data.get(series);
    505        return ts.getValue(item);
    506    }
    507
    508    /**
    509     * Returns the starting Y value for the specified series and item.
    510     *
    511     * @param series  the series (zero-based index).
    512     * @param item  the item (zero-based index).
    513     *
    514     * @return The value (possibly <code>null</code>).
    515     */
    516    public Number getStartY(int series, int item) {
    517        return getY(series, item);
    518    }
    519
    520    /**
    521     * Returns the ending Y value for the specified series and item.
    522     *
    523     * @param series  te series (zero-based index).
    524     * @param item  the item (zero-based index).
    525     *
    526     * @return The value (possibly <code>null</code>).
    527     */
    528    public Number getEndY(int series, int item) {
    529        return getY(series, item);
    530    }
    531
    532
    533    /**
    534     * Returns the indices of the two data items surrounding a particular
    535     * millisecond value.
    536     *
    537     * @param series  the series index.
    538     * @param milliseconds  the time.
    539     *
    540     * @return An array containing the (two) indices of the items surrounding
    541     *         the time.
    542     */
    543    public int[] getSurroundingItems(int series, long milliseconds) {
    544        int[] result = new int[] {-1, -1};
    545        TimeSeries timeSeries = getSeries(series);
    546        for (int i = 0; i < timeSeries.getItemCount(); i++) {
    547            Number x = getX(series, i);
    548            long m = x.longValue();
    549            if (m <= milliseconds) {
    550                result[0] = i;
    551            }
    552            if (m >= milliseconds) {
    553                result[1] = i;
    554                break;
    555            }
    556        }
    557        return result;
    558    }
    559
    560    /**
    561     * Returns the minimum x-value in the dataset.
    562     *
    563     * @param includeInterval  a flag that determines whether or not the
    564     *                         x-interval is taken into account.
    565     *
    566     * @return The minimum value.
    567     */
    568    public double getDomainLowerBound(boolean includeInterval) {
    569        double result = Double.NaN;
    570        Range r = getDomainBounds(includeInterval);
    571        if (r != null) {
    572            result = r.getLowerBound();
    573        }
    574        return result;
    575    }
    576
    577    /**
    578     * Returns the maximum x-value in the dataset.
    579     *
    580     * @param includeInterval  a flag that determines whether or not the
    581     *                         x-interval is taken into account.
    582     *
    583     * @return The maximum value.
    584     */
    585    public double getDomainUpperBound(boolean includeInterval) {
    586        double result = Double.NaN;
    587        Range r = getDomainBounds(includeInterval);
    588        if (r != null) {
    589            result = r.getUpperBound();
    590        }
    591        return result;
    592    }
    593
    594    /**
    595     * Returns the range of the values in this dataset's domain.
    596     *
    597     * @param includeInterval  a flag that determines whether or not the
    598     *                         x-interval is taken into account.
    599     *
    600     * @return The range.
    601     */
    602    public Range getDomainBounds(boolean includeInterval) {
    603        Range result = null;
    604        Iterator iterator = this.data.iterator();
    605        while (iterator.hasNext()) {
    606            TimeSeries series = (TimeSeries) iterator.next();
    607            int count = series.getItemCount();
    608            if (count > 0) {
    609                RegularTimePeriod start = series.getTimePeriod(0);
    610                RegularTimePeriod end = series.getTimePeriod(count - 1);
    611                Range temp;
    612                if (!includeInterval) {
    613                    temp = new Range(getX(start), getX(end));
    614                }
    615                else {
    616                    temp = new Range(
    617                            start.getFirstMillisecond(this.workingCalendar),
    618                            end.getLastMillisecond(this.workingCalendar));
    619                }
    620                result = Range.combine(result, temp);
    621            }
    622        }
    623        return result;
    624    }
    625
    626    /**
    627     * Returns the bounds of the domain values for the specified series.
    628     *
    629     * @param visibleSeriesKeys  a list of keys for the visible series.
    630     * @param includeInterval  include the x-interval?
    631     *
    632     * @return A range.
    633     *
    634     * @since 1.0.13
    635     */
    636    public Range getDomainBounds(List visibleSeriesKeys,
    637            boolean includeInterval) {
    638        Range result = null;
    639        Iterator iterator = visibleSeriesKeys.iterator();
    640        while (iterator.hasNext()) {
    641            Comparable seriesKey = (Comparable) iterator.next();
    642            TimeSeries series = getSeries(seriesKey);
    643            int count = series.getItemCount();
    644            if (count > 0) {
    645                RegularTimePeriod start = series.getTimePeriod(0);
    646                RegularTimePeriod end = series.getTimePeriod(count - 1);
    647                Range temp;
    648                if (!includeInterval) {
    649                    temp = new Range(getX(start), getX(end));
    650                }
    651                else {
    652                    temp = new Range(
    653                            start.getFirstMillisecond(this.workingCalendar),
    654                            end.getLastMillisecond(this.workingCalendar));
    655                }
    656                result = Range.combine(result, temp);
    657            }
    658        }
    659        return result;
    660    }
    661
    662    /**
    663     * Returns the bounds for the y-values in the dataset.
    664     *
    665     * @param visibleSeriesKeys  the visible series keys.
    666     * @param xRange  the x-range (<code>null</code> not permitted).
    667     * @param includeInterval  ignored.
    668     *
    669     * @return The bounds.
    670     *
    671     * @since 1.0.14
    672     */
    673    public Range getRangeBounds(List visibleSeriesKeys, Range xRange,
    674            boolean includeInterval) {
    675        Range result = null;
    676        Iterator iterator = visibleSeriesKeys.iterator();
    677        while (iterator.hasNext()) {
    678            Comparable seriesKey = (Comparable) iterator.next();
    679            TimeSeries series = getSeries(seriesKey);
    680            Range r = null;
    681            r = new Range(series.getMinY(), series.getMaxY());
    682            // FIXME: Here we are ignoring the xRange
    683            result = Range.combine(result, r);
    684        }
    685        return result;
    686    }
    687
    688    /**
    689     * Tests this time series collection for equality with another object.
    690     *
    691     * @param obj  the other object.
    692     *
    693     * @return A boolean.
    694     */
    695    public boolean equals(Object obj) {
    696        if (obj == this) {
    697            return true;
    698        }
    699        if (!(obj instanceof TimeSeriesCollection)) {
    700            return false;
    701        }
    702        TimeSeriesCollection that = (TimeSeriesCollection) obj;
    703        if (this.xPosition != that.xPosition) {
    704            return false;
    705        }
    706        if (this.domainIsPointsInTime != that.domainIsPointsInTime) {
    707            return false;
    708        }
    709        if (!ObjectUtilities.equal(this.data, that.data)) {
    710            return false;
    711        }
    712        return true;
    713    }
    714
    715    /**
    716     * Returns a hash code value for the object.
    717     *
    718     * @return The hashcode
    719     */
    720    public int hashCode() {
    721        int result;
    722        result = this.data.hashCode();
    723        result = 29 * result + (this.workingCalendar != null
    724                ? this.workingCalendar.hashCode() : 0);
    725        result = 29 * result + (this.xPosition != null
    726                ? this.xPosition.hashCode() : 0);
    727        result = 29 * result + (this.domainIsPointsInTime ? 1 : 0);
    728        return result;
    729    }
    730
    731    /**
    732     * Returns a clone of this time series collection.
    733     *
    734     * @return A clone.
    735     *
    736     * @throws java.lang.CloneNotSupportedException
    737     */
    738    public Object clone() throws CloneNotSupportedException {
    739        TimeSeriesCollection clone = (TimeSeriesCollection) super.clone();
    740        clone.data = (List) ObjectUtilities.deepClone(this.data);
    741        clone.workingCalendar = (Calendar) this.workingCalendar.clone();
    742        return clone;
    743    }
    744
    745}