001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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 * CombinedDataset.java
029 * --------------------
030 * (C) Copyright 2001-2009, by Bill Kelemen and Contributors.
031 *
032 * Original Author:  Bill Kelemen;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * Changes
036 * -------
037 * 06-Dec-2001 : Version 1 (BK);
038 * 27-Dec-2001 : Fixed bug in getChildPosition method (BK);
039 * 29-Dec-2001 : Fixed bug in getChildPosition method with complex
040 *               CombinePlot (BK);
041 * 05-Feb-2002 : Small addition to the interface HighLowDataset, as requested
042 *               by Sylvain Vieujot (DG);
043 * 14-Feb-2002 : Added bug fix for IntervalXYDataset methods, submitted by
044 *               Gyula Kun-Szabo (DG);
045 * 11-Jun-2002 : Updated for change in event constructor (DG);
046 * 04-Oct-2002 : Fixed errors reported by Checkstyle (DG);
047 * 06-May-2004 : Now extends AbstractIntervalXYDataset and added other methods
048 *               that return double primitives (DG);
049 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
050 *               getYValue() (DG);
051 * ------------- JFREECHART 1.0.x ---------------------------------------------
052 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG);
053 * 04-Feb-2009 : Deprecated the class (DG);
054 * 
055 */
056
057package org.jfree.data.general;
058
059import java.util.List;
060
061import org.jfree.data.xy.AbstractIntervalXYDataset;
062import org.jfree.data.xy.IntervalXYDataset;
063import org.jfree.data.xy.OHLCDataset;
064import org.jfree.data.xy.XYDataset;
065
066/**
067 * This class can combine instances of {@link XYDataset}, {@link OHLCDataset}
068 * and {@link IntervalXYDataset} together exposing the union of all the series
069 * under one dataset.
070 *
071 * @deprecated As of version 1.0.13.  This class will be removed from
072 *     JFreeChart 1.2.0 onwards.  Anyone needing this facility will need to
073 *     maintain it outside of JFreeChart.
074 */
075public class CombinedDataset extends AbstractIntervalXYDataset
076        implements XYDataset, OHLCDataset, IntervalXYDataset,
077        CombinationDataset {
078
079    /** Storage for the datasets we combine. */
080    private List datasetInfo = new java.util.ArrayList();
081
082    /**
083     * Default constructor for an empty combination.
084     */
085    public CombinedDataset() {
086        super();
087    }
088
089    /**
090     * Creates a CombinedDataset initialized with an array of SeriesDatasets.
091     *
092     * @param data  array of SeriesDataset that contains the SeriesDatasets to
093     *              combine.
094     */
095    public CombinedDataset(SeriesDataset[] data) {
096        add(data);
097    }
098
099    /**
100     * Adds one SeriesDataset to the combination. Listeners are notified of the
101     * change.
102     *
103     * @param data  the SeriesDataset to add.
104     */
105    public void add(SeriesDataset data) {
106        fastAdd(data);
107        DatasetChangeEvent event = new DatasetChangeEvent(this, this);
108        notifyListeners(event);
109    }
110
111    /**
112     * Adds an array of SeriesDataset's to the combination. Listeners are
113     * notified of the change.
114     *
115     * @param data  array of SeriesDataset to add
116     */
117    public void add(SeriesDataset[] data) {
118
119        for (int i = 0; i < data.length; i++) {
120            fastAdd(data[i]);
121        }
122        DatasetChangeEvent event = new DatasetChangeEvent(this, this);
123        notifyListeners(event);
124
125    }
126
127    /**
128     * Adds one series from a SeriesDataset to the combination. Listeners are
129     * notified of the change.
130     *
131     * @param data  the SeriesDataset where series is contained
132     * @param series  series to add
133     */
134    public void add(SeriesDataset data, int series) {
135        add(new SubSeriesDataset(data, series));
136    }
137
138    /**
139     * Fast add of a SeriesDataset. Does not notify listeners of the change.
140     *
141     * @param data  SeriesDataset to add
142     */
143    private void fastAdd(SeriesDataset data) {
144        for (int i = 0; i < data.getSeriesCount(); i++) {
145            this.datasetInfo.add(new DatasetInfo(data, i));
146        }
147    }
148
149    ///////////////////////////////////////////////////////////////////////////
150    // From SeriesDataset
151    ///////////////////////////////////////////////////////////////////////////
152
153    /**
154     * Returns the number of series in the dataset.
155     *
156     * @return The number of series in the dataset.
157     */
158    @Override
159    public int getSeriesCount() {
160        return this.datasetInfo.size();
161    }
162
163    /**
164     * Returns the key for a series.
165     *
166     * @param series  the series (zero-based index).
167     *
168     * @return The key for a series.
169     */
170    @Override
171    public Comparable getSeriesKey(int series) {
172        DatasetInfo di = getDatasetInfo(series);
173        return di.data.getSeriesKey(di.series);
174    }
175
176    ///////////////////////////////////////////////////////////////////////////
177    // From XYDataset
178    ///////////////////////////////////////////////////////////////////////////
179
180    /**
181     * Returns the X-value for the specified series and item.
182     * <P>
183     * Note:  throws <code>ClassCastException</code> if the series is not from
184     * a {@link XYDataset}.
185     *
186     * @param series  the index of the series of interest (zero-based).
187     * @param item  the index of the item of interest (zero-based).
188     *
189     * @return The X-value for the specified series and item.
190     */
191    @Override
192    public Number getX(int series, int item) {
193        DatasetInfo di = getDatasetInfo(series);
194        return ((XYDataset) di.data).getX(di.series, item);
195    }
196
197    /**
198     * Returns the Y-value for the specified series and item.
199     * <P>
200     * Note:  throws <code>ClassCastException</code> if the series is not from
201     * a {@link XYDataset}.
202     *
203     * @param series  the index of the series of interest (zero-based).
204     * @param item  the index of the item of interest (zero-based).
205     *
206     * @return The Y-value for the specified series and item.
207     */
208    @Override
209    public Number getY(int series, int item) {
210        DatasetInfo di = getDatasetInfo(series);
211        return ((XYDataset) di.data).getY(di.series, item);
212    }
213
214    /**
215     * Returns the number of items in a series.
216     * <P>
217     * Note:  throws <code>ClassCastException</code> if the series is not from
218     * a {@link XYDataset}.
219     *
220     * @param series  the index of the series of interest (zero-based).
221     *
222     * @return The number of items in a series.
223     */
224    @Override
225    public int getItemCount(int series) {
226        DatasetInfo di = getDatasetInfo(series);
227        return ((XYDataset) di.data).getItemCount(di.series);
228    }
229
230    ///////////////////////////////////////////////////////////////////////////
231    // From HighLowDataset
232    ///////////////////////////////////////////////////////////////////////////
233
234    /**
235     * Returns the high-value for the specified series and item.
236     * <P>
237     * Note:  throws <code>ClassCastException</code> if the series is not from a
238     * {@link OHLCDataset}.
239     *
240     * @param series  the index of the series of interest (zero-based).
241     * @param item  the index of the item of interest (zero-based).
242     *
243     * @return The high-value for the specified series and item.
244     */
245    @Override
246    public Number getHigh(int series, int item) {
247        DatasetInfo di = getDatasetInfo(series);
248        return ((OHLCDataset) di.data).getHigh(di.series, item);
249    }
250
251    /**
252     * Returns the high-value (as a double primitive) for an item within a
253     * series.
254     *
255     * @param series  the series (zero-based index).
256     * @param item  the item (zero-based index).
257     *
258     * @return The high-value.
259     */
260    @Override
261    public double getHighValue(int series, int item) {
262        double result = Double.NaN;
263        Number high = getHigh(series, item);
264        if (high != null) {
265            result = high.doubleValue();
266        }
267        return result;
268    }
269
270    /**
271     * Returns the low-value for the specified series and item.
272     * <P>
273     * Note:  throws <code>ClassCastException</code> if the series is not from a
274     * {@link OHLCDataset}.
275     *
276     * @param series  the index of the series of interest (zero-based).
277     * @param item  the index of the item of interest (zero-based).
278     *
279     * @return The low-value for the specified series and item.
280     */
281    @Override
282    public Number getLow(int series, int item) {
283        DatasetInfo di = getDatasetInfo(series);
284        return ((OHLCDataset) di.data).getLow(di.series, item);
285    }
286
287    /**
288     * Returns the low-value (as a double primitive) for an item within a
289     * series.
290     *
291     * @param series  the series (zero-based index).
292     * @param item  the item (zero-based index).
293     *
294     * @return The low-value.
295     */
296    @Override
297    public double getLowValue(int series, int item) {
298        double result = Double.NaN;
299        Number low = getLow(series, item);
300        if (low != null) {
301            result = low.doubleValue();
302        }
303        return result;
304    }
305
306    /**
307     * Returns the open-value for the specified series and item.
308     * <P>
309     * Note:  throws <code>ClassCastException</code> if the series is not from a
310     * {@link OHLCDataset}.
311     *
312     * @param series  the index of the series of interest (zero-based).
313     * @param item  the index of the item of interest (zero-based).
314     *
315     * @return The open-value for the specified series and item.
316     */
317    @Override
318    public Number getOpen(int series, int item) {
319        DatasetInfo di = getDatasetInfo(series);
320        return ((OHLCDataset) di.data).getOpen(di.series, item);
321    }
322
323    /**
324     * Returns the open-value (as a double primitive) for an item within a
325     * series.
326     *
327     * @param series  the series (zero-based index).
328     * @param item  the item (zero-based index).
329     *
330     * @return The open-value.
331     */
332    @Override
333    public double getOpenValue(int series, int item) {
334        double result = Double.NaN;
335        Number open = getOpen(series, item);
336        if (open != null) {
337            result = open.doubleValue();
338        }
339        return result;
340    }
341
342    /**
343     * Returns the close-value for the specified series and item.
344     * <P>
345     * Note:  throws <code>ClassCastException</code> if the series is not from a
346     * {@link OHLCDataset}.
347     *
348     * @param series  the index of the series of interest (zero-based).
349     * @param item  the index of the item of interest (zero-based).
350     *
351     * @return The close-value for the specified series and item.
352     */
353    @Override
354    public Number getClose(int series, int item) {
355        DatasetInfo di = getDatasetInfo(series);
356        return ((OHLCDataset) di.data).getClose(di.series, item);
357    }
358
359    /**
360     * Returns the close-value (as a double primitive) for an item within a
361     * series.
362     *
363     * @param series  the series (zero-based index).
364     * @param item  the item (zero-based index).
365     *
366     * @return The close-value.
367     */
368    @Override
369    public double getCloseValue(int series, int item) {
370        double result = Double.NaN;
371        Number close = getClose(series, item);
372        if (close != null) {
373            result = close.doubleValue();
374        }
375        return result;
376    }
377
378    /**
379     * Returns the volume value for the specified series and item.
380     * <P>
381     * Note:  throws <code>ClassCastException</code> if the series is not from a
382     * {@link OHLCDataset}.
383     *
384     * @param series  the index of the series of interest (zero-based).
385     * @param item  the index of the item of interest (zero-based).
386     *
387     * @return The volume value for the specified series and item.
388     */
389    @Override
390    public Number getVolume(int series, int item) {
391        DatasetInfo di = getDatasetInfo(series);
392        return ((OHLCDataset) di.data).getVolume(di.series, item);
393    }
394
395    /**
396     * Returns the volume-value (as a double primitive) for an item within a
397     * series.
398     *
399     * @param series  the series (zero-based index).
400     * @param item  the item (zero-based index).
401     *
402     * @return The volume-value.
403     */
404    @Override
405    public double getVolumeValue(int series, int item) {
406        double result = Double.NaN;
407        Number volume = getVolume(series, item);
408        if (volume != null) {
409            result = volume.doubleValue();
410        }
411        return result;
412    }
413
414    ///////////////////////////////////////////////////////////////////////////
415    // From IntervalXYDataset
416    ///////////////////////////////////////////////////////////////////////////
417
418    /**
419     * Returns the starting X value for the specified series and item.
420     *
421     * @param series  the index of the series of interest (zero-based).
422     * @param item  the index of the item of interest (zero-based).
423     *
424     * @return The value.
425     */
426    @Override
427    public Number getStartX(int series, int item) {
428        DatasetInfo di = getDatasetInfo(series);
429        if (di.data instanceof IntervalXYDataset) {
430            return ((IntervalXYDataset) di.data).getStartX(di.series, item);
431        }
432        else {
433            return getX(series, item);
434        }
435    }
436
437    /**
438     * Returns the ending X value for the specified series and item.
439     *
440     * @param series  the index of the series of interest (zero-based).
441     * @param item  the index of the item of interest (zero-based).
442     *
443     * @return The value.
444     */
445    @Override
446    public Number getEndX(int series, int item) {
447        DatasetInfo di = getDatasetInfo(series);
448        if (di.data instanceof IntervalXYDataset) {
449            return ((IntervalXYDataset) di.data).getEndX(di.series, item);
450        }
451        else {
452            return getX(series, item);
453        }
454    }
455
456    /**
457     * Returns the starting Y value for the specified series and item.
458     *
459     * @param series  the index of the series of interest (zero-based).
460     * @param item  the index of the item of interest (zero-based).
461     *
462     * @return The starting Y value for the specified series and item.
463     */
464    @Override
465    public Number getStartY(int series, int item) {
466        DatasetInfo di = getDatasetInfo(series);
467        if (di.data instanceof IntervalXYDataset) {
468            return ((IntervalXYDataset) di.data).getStartY(di.series, item);
469        }
470        else {
471            return getY(series, item);
472        }
473    }
474
475    /**
476     * Returns the ending Y value for the specified series and item.
477     *
478     * @param series  the index of the series of interest (zero-based).
479     * @param item  the index of the item of interest (zero-based).
480     *
481     * @return The ending Y value for the specified series and item.
482     */
483    @Override
484    public Number getEndY(int series, int item) {
485        DatasetInfo di = getDatasetInfo(series);
486        if (di.data instanceof IntervalXYDataset) {
487            return ((IntervalXYDataset) di.data).getEndY(di.series, item);
488        }
489        else {
490            return getY(series, item);
491        }
492    }
493
494    ///////////////////////////////////////////////////////////////////////////
495    // New methods from CombinationDataset
496    ///////////////////////////////////////////////////////////////////////////
497
498    /**
499     * Returns the parent Dataset of this combination. If there is more than
500     * one parent, or a child is found that is not a CombinationDataset, then
501     * returns <code>null</code>.
502     *
503     * @return The parent Dataset of this combination or <code>null</code>.
504     */
505    @Override
506    public SeriesDataset getParent() {
507
508        SeriesDataset parent = null;
509        for (int i = 0; i < this.datasetInfo.size(); i++) {
510            SeriesDataset child = getDatasetInfo(i).data;
511            if (child instanceof CombinationDataset) {
512                SeriesDataset childParent
513                    = ((CombinationDataset) child).getParent();
514                if (parent == null) {
515                    parent = childParent;
516                }
517                else if (parent != childParent) {
518                    return null;
519                }
520            }
521            else {
522                return null;
523            }
524        }
525        return parent;
526
527    }
528
529    /**
530     * Returns a map or indirect indexing form our series into parent's series.
531     * Prior to calling this method, the client should check getParent() to make
532     * sure the CombinationDataset uses the same parent. If not, the map
533     * returned by this method will be invalid or null.
534     *
535     * @return A map or indirect indexing form our series into parent's series.
536     *
537     * @see #getParent()
538     */
539    @Override
540    public int[] getMap() {
541
542        int[] map = null;
543        for (int i = 0; i < this.datasetInfo.size(); i++) {
544            SeriesDataset child = getDatasetInfo(i).data;
545            if (child instanceof CombinationDataset) {
546                int[] childMap = ((CombinationDataset) child).getMap();
547                if (childMap == null) {
548                    return null;
549                }
550                map = joinMap(map, childMap);
551            }
552            else {
553                return null;
554            }
555        }
556        return map;
557    }
558
559    ///////////////////////////////////////////////////////////////////////////
560    // New Methods
561    ///////////////////////////////////////////////////////////////////////////
562
563    /**
564     * Returns the child position.
565     *
566     * @param child  the child dataset.
567     *
568     * @return The position.
569     */
570    public int getChildPosition(Dataset child) {
571
572        int n = 0;
573        for (int i = 0; i < this.datasetInfo.size(); i++) {
574            SeriesDataset childDataset = getDatasetInfo(i).data;
575            if (childDataset instanceof CombinedDataset) {
576                int m = ((CombinedDataset) childDataset)
577                    .getChildPosition(child);
578                if (m >= 0) {
579                    return n + m;
580                }
581                n++;
582            }
583            else {
584                if (child == childDataset) {
585                    return n;
586                }
587                n++;
588            }
589        }
590        return -1;
591    }
592
593    ///////////////////////////////////////////////////////////////////////////
594    // Private
595    ///////////////////////////////////////////////////////////////////////////
596
597    /**
598     * Returns the DatasetInfo object associated with the series.
599     *
600     * @param series  the index of the series.
601     *
602     * @return The DatasetInfo object associated with the series.
603     */
604    private DatasetInfo getDatasetInfo(int series) {
605        return (DatasetInfo) this.datasetInfo.get(series);
606    }
607
608    /**
609     * Joins two map arrays (int[]) together.
610     *
611     * @param a  the first array.
612     * @param b  the second array.
613     *
614     * @return A copy of { a[], b[] }.
615     */
616    private int[] joinMap(int[] a, int[] b) {
617        if (a == null) {
618            return b;
619        }
620        if (b == null) {
621            return a;
622        }
623        int[] result = new int[a.length + b.length];
624        System.arraycopy(a, 0, result, 0, a.length);
625        System.arraycopy(b, 0, result, a.length, b.length);
626        return result;
627    }
628
629    /**
630     * Private class to store as pairs (SeriesDataset, series) for all combined
631     * series.
632     */
633    private class DatasetInfo {
634
635        /** The dataset. */
636        private SeriesDataset data;
637
638        /** The series. */
639        private int series;
640
641        /**
642         * Creates a new dataset info record.
643         *
644         * @param data  the dataset.
645         * @param series  the series.
646         */
647        DatasetInfo(SeriesDataset data, int series) {
648            this.data = data;
649            this.series = series;
650        }
651    }
652
653}