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