001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert 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 * XIntervalSeriesCollection.java
029 * ------------------------------
030 * (C) Copyright 2006-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data.xy;
038
039import java.io.IOException;
040import java.io.ObjectInputStream;
041import java.io.ObjectOutputStream;
042import java.io.Serializable;
043import java.util.ArrayList;
044import java.util.List;
045import java.util.Objects;
046
047import org.jfree.chart.internal.Args;
048import org.jfree.chart.internal.CloneUtils;
049import org.jfree.chart.api.PublicCloneable;
050
051import org.jfree.data.general.DatasetChangeEvent;
052
053/**
054 * A collection of {@link XIntervalSeries} objects.
055 *
056 * @since 1.0.3
057 *
058 * @see XIntervalSeries
059 */
060public class XIntervalSeriesCollection<S extends Comparable<S>> 
061        extends AbstractIntervalXYDataset
062        implements IntervalXYDataset, PublicCloneable, Serializable {
063
064    /** Storage for the data series. */
065    private List<XIntervalSeries<S>> data;
066
067    /**
068     * Creates a new {@code XIntervalSeriesCollection} instance.
069     */
070    public XIntervalSeriesCollection() {
071        this.data = new ArrayList<>();
072    }
073
074    /**
075     * Adds a series to the collection and sends a {@link DatasetChangeEvent}
076     * to all registered listeners.
077     *
078     * @param series  the series ({@code null} not permitted).
079     */
080    public void addSeries(XIntervalSeries<S> series) {
081        Args.nullNotPermitted(series, "series");
082        this.data.add(series);
083        series.addChangeListener(this);
084        fireDatasetChanged();
085    }
086
087    /**
088     * Returns the number of series in the collection.
089     *
090     * @return The series count.
091     */
092    @Override
093    public int getSeriesCount() {
094        return this.data.size();
095    }
096
097    /**
098     * Returns a series from the collection.
099     *
100     * @param series  the series index (zero-based).
101     *
102     * @return The series.
103     *
104     * @throws IllegalArgumentException if {@code series} is not in the
105     *     range {@code 0} to {@code getSeriesCount() - 1}.
106     */
107    public XIntervalSeries<S> getSeries(int series) {
108        Args.requireInRange(series, "series", 0, this.data.size() - 1);
109        return this.data.get(series);
110    }
111
112    /**
113     * Returns the key for a series.
114     *
115     * @param series  the series index (in the range {@code 0} to
116     *     {@code getSeriesCount() - 1}).
117     *
118     * @return The key for a series.
119     *
120     * @throws IllegalArgumentException if {@code series} is not in the
121     *     specified range.
122     */
123    @Override
124    public S getSeriesKey(int series) {
125        // defer argument checking
126        return getSeries(series).getKey();
127    }
128
129    /**
130     * Returns the number of items in the specified series.
131     *
132     * @param series  the series (zero-based index).
133     *
134     * @return The item count.
135     *
136     * @throws IllegalArgumentException if {@code series} is not in the
137     *     range {@code 0} to {@code getSeriesCount() - 1}.
138     */
139    @Override
140    public int getItemCount(int series) {
141        // defer argument checking
142        return getSeries(series).getItemCount();
143    }
144
145    /**
146     * Returns the x-value for an item within a series.
147     *
148     * @param series  the series index.
149     * @param item  the item index.
150     *
151     * @return The x-value.
152     */
153    @Override
154    public Number getX(int series, int item) {
155        XIntervalSeries<S> s = this.data.get(series);
156        XIntervalDataItem di = (XIntervalDataItem) s.getDataItem(item);
157        return di.getX();
158    }
159
160    /**
161     * Returns the start x-value (as a double primitive) for an item within a
162     * series.
163     *
164     * @param series  the series index (zero-based).
165     * @param item  the item index (zero-based).
166     *
167     * @return The value.
168     */
169    @Override
170    public double getStartXValue(int series, int item) {
171        XIntervalSeries<S> s = this.data.get(series);
172        return s.getXLowValue(item);
173    }
174
175    /**
176     * Returns the end x-value (as a double primitive) for an item within a
177     * series.
178     *
179     * @param series  the series (zero-based index).
180     * @param item  the item (zero-based index).
181     *
182     * @return The value.
183     */
184    @Override
185    public double getEndXValue(int series, int item) {
186        XIntervalSeries<S> s = this.data.get(series);
187        return s.getXHighValue(item);
188    }
189
190    /**
191     * Returns the y-value (as a double primitive) for an item within a
192     * series.
193     *
194     * @param series  the series index (zero-based).
195     * @param item  the item index (zero-based).
196     *
197     * @return The value.
198     */
199    @Override
200    public double getYValue(int series, int item) {
201        XIntervalSeries<S> s = this.data.get(series);
202        return s.getYValue(item);
203    }
204
205    /**
206     * Returns the y-value for an item within a series.
207     *
208     * @param series  the series index.
209     * @param item  the item index.
210     *
211     * @return The y-value.
212     */
213    @Override
214    public Number getY(int series, int item) {
215        XIntervalSeries<S> s = this.data.get(series);
216        XIntervalDataItem di = (XIntervalDataItem) s.getDataItem(item);
217        return di.getYValue();
218    }
219
220    /**
221     * Returns the start x-value for an item within a series.
222     *
223     * @param series  the series index.
224     * @param item  the item index.
225     *
226     * @return The x-value.
227     */
228    @Override
229    public Number getStartX(int series, int item) {
230        XIntervalSeries<S> s = this.data.get(series);
231        XIntervalDataItem di = (XIntervalDataItem) s.getDataItem(item);
232        return di.getXLowValue();
233    }
234
235    /**
236     * Returns the end x-value for an item within a series.
237     *
238     * @param series  the series index.
239     * @param item  the item index.
240     *
241     * @return The x-value.
242     */
243    @Override
244    public Number getEndX(int series, int item) {
245        XIntervalSeries<S> s = this.data.get(series);
246        XIntervalDataItem di = (XIntervalDataItem) s.getDataItem(item);
247        return di.getXHighValue();
248    }
249
250    /**
251     * Returns the start y-value for an item within a series.  This method
252     * maps directly to {@link #getY(int, int)}.
253     *
254     * @param series  the series index.
255     * @param item  the item index.
256     *
257     * @return The start y-value.
258     */
259    @Override
260    public Number getStartY(int series, int item) {
261        return getY(series, item);
262    }
263
264    /**
265     * Returns the end y-value for an item within a series.  This method
266     * maps directly to {@link #getY(int, int)}.
267     *
268     * @param series  the series index.
269     * @param item  the item index.
270     *
271     * @return The end y-value.
272     */
273    @Override
274    public Number getEndY(int series, int item) {
275        return getY(series, item);
276    }
277
278    /**
279     * Removes a series from the collection and sends a
280     * {@link DatasetChangeEvent} to all registered listeners.
281     *
282     * @param series  the series index (zero-based).
283     *
284     * @since 1.0.10
285     */
286    public void removeSeries(int series) {
287        Args.requireInRange(series, "series", 0, this.data.size() - 1);
288        XIntervalSeries ts = this.data.get(series);
289        ts.removeChangeListener(this);
290        this.data.remove(series);
291        fireDatasetChanged();
292    }
293
294    /**
295     * Removes a series from the collection and sends a
296     * {@link DatasetChangeEvent} to all registered listeners.
297     *
298     * @param series  the series ({@code null} not permitted).
299     *
300     * @since 1.0.10
301     */
302    public void removeSeries(XIntervalSeries<S> series) {
303        Args.nullNotPermitted(series, "series");
304        if (this.data.contains(series)) {
305            series.removeChangeListener(this);
306            this.data.remove(series);
307            fireDatasetChanged();
308        }
309    }
310
311    /**
312     * Removes all the series from the collection and sends a
313     * {@link DatasetChangeEvent} to all registered listeners.
314     *
315     * @since 1.0.10
316     */
317    public void removeAllSeries() {
318        // Unregister the collection as a change listener to each series in
319        // the collection.
320        for (XIntervalSeries series : this.data) {
321          series.removeChangeListener(this);
322        }
323        this.data.clear();
324        fireDatasetChanged();
325    }
326
327    /**
328     * Tests this instance for equality with an arbitrary object.
329     *
330     * @param obj  the object ({@code null} permitted).
331     *
332     * @return A boolean.
333     */
334    @Override
335    public boolean equals(Object obj) {
336        if (obj == this) {
337            return true;
338        }
339        if (!(obj instanceof XIntervalSeriesCollection)) {
340            return false;
341        }
342        XIntervalSeriesCollection<S> that = (XIntervalSeriesCollection<S>) obj;
343        return Objects.equals(this.data, that.data);
344    }
345
346    /**
347     * Returns a clone of this instance.
348     *
349     * @return A clone.
350     *
351     * @throws CloneNotSupportedException if there is a problem.
352     */
353    @Override
354    public Object clone() throws CloneNotSupportedException {
355        XIntervalSeriesCollection clone
356                = (XIntervalSeriesCollection) super.clone();
357        clone.data = CloneUtils.cloneList(this.data);
358        return clone;
359    }
360
361
362    /**
363     * Provides serialization support.
364     *
365     * @param stream  the output stream.
366     *
367     * @throws IOException  if there is an I/O error.
368     */
369    private void writeObject(ObjectOutputStream stream) throws IOException {
370        stream.defaultWriteObject();
371    }
372
373    /**
374     * Provides serialization support.
375     *
376     * @param stream  the input stream.
377     *
378     * @throws IOException  if there is an I/O error.
379     * @throws ClassNotFoundException  if there is a classpath problem.
380     */
381    private void readObject(ObjectInputStream stream)
382            throws IOException, ClassNotFoundException {
383        stream.defaultReadObject();
384        for (XIntervalSeries<S> item : this.data) {
385            XIntervalSeries<S> series = item;
386            series.addChangeListener(this);
387        }
388    }
389}