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