001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, 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 * OHLCSeriesCollection.java
029 * -------------------------
030 * (C) Copyright 2006-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   -;
034 *
035 */
036
037package org.jfree.data.time.ohlc;
038
039import java.io.Serializable;
040import java.util.List;
041import java.util.Objects;
042
043import org.jfree.chart.HashUtils;
044import org.jfree.chart.util.ObjectUtils;
045import org.jfree.chart.util.Args;
046import org.jfree.data.general.DatasetChangeEvent;
047import org.jfree.data.time.RegularTimePeriod;
048import org.jfree.data.time.TimePeriodAnchor;
049import org.jfree.data.xy.AbstractXYDataset;
050import org.jfree.data.xy.OHLCDataset;
051import org.jfree.data.xy.XYDataset;
052
053/**
054 * A collection of {@link OHLCSeries} objects.
055 *
056 * @see OHLCSeries
057 */
058public class OHLCSeriesCollection extends AbstractXYDataset
059                                implements OHLCDataset, Serializable {
060
061    /** Storage for the data series. */
062    private List data;
063
064    private TimePeriodAnchor xPosition = TimePeriodAnchor.MIDDLE;
065
066    /**
067     * Creates a new instance of {@code OHLCSeriesCollection}.
068     */
069    public OHLCSeriesCollection() {
070        this.data = new java.util.ArrayList();
071    }
072
073    /**
074     * Returns the position within each time period that is used for the X
075     * value when the collection is used as an {@link XYDataset}.
076     *
077     * @return The anchor position (never {@code null}).
078     */
079    public TimePeriodAnchor getXPosition() {
080        return this.xPosition;
081    }
082
083    /**
084     * Sets the position within each time period that is used for the X values
085     * when the collection is used as an {@link XYDataset}, then sends a
086     * {@link DatasetChangeEvent} is sent to all registered listeners.
087     *
088     * @param anchor  the anchor position ({@code null} not permitted).
089     */
090    public void setXPosition(TimePeriodAnchor anchor) {
091        Args.nullNotPermitted(anchor, "anchor");
092        this.xPosition = anchor;
093        notifyListeners(new DatasetChangeEvent(this, this));
094    }
095
096    /**
097     * Adds a series to the collection and sends a {@link DatasetChangeEvent}
098     * to all registered listeners.
099     *
100     * @param series  the series ({@code null} not permitted).
101     */
102    public void addSeries(OHLCSeries series) {
103        Args.nullNotPermitted(series, "series");
104        this.data.add(series);
105        series.addChangeListener(this);
106        fireDatasetChanged();
107    }
108
109    /**
110     * Returns the number of series in the collection.
111     *
112     * @return The series count.
113     */
114    @Override
115    public int getSeriesCount() {
116        return this.data.size();
117    }
118
119    /**
120     * Returns a series from the collection.
121     *
122     * @param series  the series index (zero-based).
123     *
124     * @return The series.
125     *
126     * @throws IllegalArgumentException if {@code series} is not in the
127     *     range {@code 0} to {@code getSeriesCount() - 1}.
128     */
129    public OHLCSeries getSeries(int series) {
130        if ((series < 0) || (series >= getSeriesCount())) {
131            throw new IllegalArgumentException("Series index out of bounds");
132        }
133        return (OHLCSeries) this.data.get(series);
134    }
135
136    /**
137     * Returns the key for a series.
138     *
139     * @param series  the series index (in the range {@code 0} to
140     *     {@code getSeriesCount() - 1}).
141     *
142     * @return The key for a series.
143     *
144     * @throws IllegalArgumentException if {@code series} is not in the
145     *     specified range.
146     */
147    @Override
148    public Comparable getSeriesKey(int series) {
149        // defer argument checking
150        return getSeries(series).getKey();
151    }
152
153    /**
154     * Returns the number of items in the specified series.
155     *
156     * @param series  the series (zero-based index).
157     *
158     * @return The item count.
159     *
160     * @throws IllegalArgumentException if {@code series} is not in the
161     *     range {@code 0} to {@code getSeriesCount() - 1}.
162     */
163    @Override
164    public int getItemCount(int series) {
165        // defer argument checking
166        return getSeries(series).getItemCount();
167    }
168
169    /**
170     * Returns the x-value for a time period.
171     *
172     * @param period  the time period ({@code null} not permitted).
173     *
174     * @return The x-value.
175     */
176    protected synchronized long getX(RegularTimePeriod period) {
177        long result = 0L;
178        if (this.xPosition == TimePeriodAnchor.START) {
179            result = period.getFirstMillisecond();
180        }
181        else if (this.xPosition == TimePeriodAnchor.MIDDLE) {
182            result = period.getMiddleMillisecond();
183        }
184        else if (this.xPosition == TimePeriodAnchor.END) {
185            result = period.getLastMillisecond();
186        }
187        return result;
188    }
189
190    /**
191     * Returns the x-value for an item within a series.
192     *
193     * @param series  the series index.
194     * @param item  the item index.
195     *
196     * @return The x-value.
197     */
198    @Override
199    public double getXValue(int series, int item) {
200        OHLCSeries s = (OHLCSeries) this.data.get(series);
201        OHLCItem di = (OHLCItem) s.getDataItem(item);
202        RegularTimePeriod period = di.getPeriod();
203        return getX(period);
204    }
205
206    /**
207     * Returns the x-value for an item within a series.
208     *
209     * @param series  the series index.
210     * @param item  the item index.
211     *
212     * @return The x-value.
213     */
214    @Override
215    public Number getX(int series, int item) {
216        return getXValue(series, item);
217    }
218
219    /**
220     * Returns the y-value for an item within a series.
221     *
222     * @param series  the series index.
223     * @param item  the item index.
224     *
225     * @return The y-value.
226     */
227    @Override
228    public Number getY(int series, int item) {
229        OHLCSeries s = (OHLCSeries) this.data.get(series);
230        OHLCItem di = (OHLCItem) s.getDataItem(item);
231        return di.getYValue();
232    }
233
234    /**
235     * Returns the open-value for an item within a series.
236     *
237     * @param series  the series index.
238     * @param item  the item index.
239     *
240     * @return The open-value.
241     */
242    @Override
243    public double getOpenValue(int series, int item) {
244        OHLCSeries s = (OHLCSeries) this.data.get(series);
245        OHLCItem di = (OHLCItem) s.getDataItem(item);
246        return di.getOpenValue();
247    }
248
249    /**
250     * Returns the open-value for an item within a series.
251     *
252     * @param series  the series index.
253     * @param item  the item index.
254     *
255     * @return The open-value.
256     */
257    @Override
258    public Number getOpen(int series, int item) {
259        return getOpenValue(series, item);
260    }
261
262    /**
263     * Returns the close-value for an item within a series.
264     *
265     * @param series  the series index.
266     * @param item  the item index.
267     *
268     * @return The close-value.
269     */
270    @Override
271    public double getCloseValue(int series, int item) {
272        OHLCSeries s = (OHLCSeries) this.data.get(series);
273        OHLCItem di = (OHLCItem) s.getDataItem(item);
274        return di.getCloseValue();
275    }
276
277    /**
278     * Returns the close-value for an item within a series.
279     *
280     * @param series  the series index.
281     * @param item  the item index.
282     *
283     * @return The close-value.
284     */
285    @Override
286    public Number getClose(int series, int item) {
287        return getCloseValue(series, item);
288    }
289
290    /**
291     * Returns the high-value for an item within a series.
292     *
293     * @param series  the series index.
294     * @param item  the item index.
295     *
296     * @return The high-value.
297     */
298    @Override
299    public double getHighValue(int series, int item) {
300        OHLCSeries s = (OHLCSeries) this.data.get(series);
301        OHLCItem di = (OHLCItem) s.getDataItem(item);
302        return di.getHighValue();
303    }
304
305    /**
306     * Returns the high-value for an item within a series.
307     *
308     * @param series  the series index.
309     * @param item  the item index.
310     *
311     * @return The high-value.
312     */
313    @Override
314    public Number getHigh(int series, int item) {
315        return getHighValue(series, item);
316    }
317
318    /**
319     * Returns the low-value for an item within a series.
320     *
321     * @param series  the series index.
322     * @param item  the item index.
323     *
324     * @return The low-value.
325     */
326    @Override
327    public double getLowValue(int series, int item) {
328        OHLCSeries s = (OHLCSeries) this.data.get(series);
329        OHLCItem di = (OHLCItem) s.getDataItem(item);
330        return di.getLowValue();
331    }
332
333    /**
334     * Returns the low-value for an item within a series.
335     *
336     * @param series  the series index.
337     * @param item  the item index.
338     *
339     * @return The low-value.
340     */
341    @Override
342    public Number getLow(int series, int item) {
343        return getLowValue(series, item);
344    }
345
346    /**
347     * Returns {@code null} always, because this dataset doesn't record
348     * any volume data.
349     *
350     * @param series  the series index (ignored).
351     * @param item  the item index (ignored).
352     *
353     * @return {@code null}.
354     */
355    @Override
356    public Number getVolume(int series, int item) {
357        return null;
358    }
359
360    /**
361     * Returns {@code Double.NaN} always, because this dataset doesn't
362     * record any volume data.
363     *
364     * @param series  the series index (ignored).
365     * @param item  the item index (ignored).
366     *
367     * @return {@code Double.NaN}.
368     */
369    @Override
370    public double getVolumeValue(int series, int item) {
371        return Double.NaN;
372    }
373
374    /**
375     * Removes the series with the specified index and sends a
376     * {@link DatasetChangeEvent} to all registered listeners.
377     *
378     * @param index  the series index.
379     */
380    public void removeSeries(int index) {
381        OHLCSeries series = getSeries(index);
382        if (series != null) {
383            removeSeries(series);
384        }
385    }
386
387    /**
388     * Removes the specified series from the dataset and sends a
389     * {@link DatasetChangeEvent} to all registered listeners.
390     *
391     * @param series  the series ({@code null} not permitted).
392     *
393     * @return {@code true} if the series was removed, and
394     *     {@code false} otherwise.
395     */
396    public boolean removeSeries(OHLCSeries series) {
397        Args.nullNotPermitted(series, "series");
398        boolean removed = this.data.remove(series);
399        if (removed) {
400            series.removeChangeListener(this);
401            fireDatasetChanged();
402        }
403        return removed;
404    }
405
406    /**
407     * Removes all the series from the collection and sends a
408     * {@link DatasetChangeEvent} to all registered listeners.
409     */
410    public void removeAllSeries() {
411
412        if (this.data.isEmpty()) {
413            return;  // nothing to do
414        }
415
416        // deregister the collection as a change listener to each series in the
417        // collection
418        for (int i = 0; i < this.data.size(); i++) {
419            OHLCSeries series = (OHLCSeries) this.data.get(i);
420            series.removeChangeListener(this);
421        }
422
423        // remove all the series from the collection and notify listeners.
424        this.data.clear();
425        fireDatasetChanged();
426
427    }
428
429    /**
430     * Tests this instance for equality with an arbitrary object.
431     *
432     * @param obj  the object ({@code null} permitted).
433     *
434     * @return A boolean.
435     */
436    @Override
437    public boolean equals(Object obj) {
438        if (obj == this) {
439            return true;
440        }
441        if (!(obj instanceof OHLCSeriesCollection)) {
442            return false;
443        }
444        OHLCSeriesCollection that = (OHLCSeriesCollection) obj;
445        if (!this.xPosition.equals(that.xPosition)) {
446            return false;
447        }
448        return Objects.equals(this.data, that.data);
449    }
450
451    /**
452     * Returns a hash code for this instance.
453     *
454     * @return A hash code.
455     */
456    @Override
457    public int hashCode() {
458        int result = 137;
459        result = HashUtils.hashCode(result, this.xPosition);
460        for (int i = 0; i < this.data.size(); i++) {
461            result = HashUtils.hashCode(result, this.data.get(i));
462        }
463        return result;
464    }
465
466    /**
467     * Returns a clone of this instance.
468     *
469     * @return A clone.
470     *
471     * @throws CloneNotSupportedException if there is a problem.
472     */
473    @Override
474    public Object clone() throws CloneNotSupportedException {
475        OHLCSeriesCollection clone
476                = (OHLCSeriesCollection) super.clone();
477        clone.data = (List) ObjectUtils.deepClone(this.data);
478        return clone;
479    }
480
481}