    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     * DefaultIntervalCategoryDataset.java
    029     * -----------------------------------
    030     * (C) Copyright 2002-2008, by Jeremy Bowman and Contributors.
    031     *
    032     * Original Author:  Jeremy Bowman;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * Changes
    036     * -------
    037     * 29-Apr-2002 : Version 1, contributed by Jeremy Bowman (DG);
    038     * 24-Oct-2002 : Amendments for changes made to the dataset interface (DG);
    039     * ------------- JFREECHART 1.0.x ---------------------------------------------
    040     * 08-Mar-2007 : Added equals() and clone() overrides (DG);
    041     * 25-Feb-2008 : Fix for the special case where the dataset is empty, see bug
    042     *               1897580 (DG)
    043     * 18-Dec-2008 : Use ResourceBundleWrapper - see patch 1607918 by
    044     *               Jess Thrysoee (DG);
    045     *
    046     */
    047
    048    package org.jfree.data.category;
    049
    050    import java.util.ArrayList;
    051    import java.util.Arrays;
    052    import java.util.Collections;
    053    import java.util.List;
    054    import java.util.ResourceBundle;
    055
    056    import org.jfree.chart.util.ResourceBundleWrapper;
    057    import org.jfree.data.DataUtilities;
    058    import org.jfree.data.UnknownKeyException;
    059    import org.jfree.data.general.AbstractSeriesDataset;
    060
    061    /**
    062     * A convenience class that provides a default implementation of the
    063     * {@link IntervalCategoryDataset} interface.
    064     * <p>
    065     * The standard constructor accepts data in a two dimensional array where the
    066     * first dimension is the series, and the second dimension is the category.
    067     */
    068    public class DefaultIntervalCategoryDataset extends AbstractSeriesDataset
    069            implements IntervalCategoryDataset {
    070
    071        /** The series keys. */
    072        private Comparable[] seriesKeys;
    073
    074        /** The category keys. */
    075        private Comparable[] categoryKeys;
    076
    077        /** Storage for the start value data. */
    078        private Number[][] startData;
    079
    080        /** Storage for the end value data. */
    081        private Number[][] endData;
    082
    083        /**
    084         * Creates a new dataset using the specified data values and automatically
    085         * generated series and category keys.
    086         *
    087         * @param starts  the starting values for the intervals (<code>null</code>
    088         *                not permitted).
    089         * @param ends  the ending values for the intervals (<code>null</code> not
    090         *                permitted).
    091         */
    092        public DefaultIntervalCategoryDataset(double[][] starts, double[][] ends) {
    093            this(DataUtilities.createNumberArray2D(starts),
    094                    DataUtilities.createNumberArray2D(ends));
    095        }
    096
    097        /**
    098         * Constructs a dataset and populates it with data from the array.
    099         * <p>
    100         * The arrays are indexed as data[series][category].  Series and category
    101         * names are automatically generated - you can change them using the
    102         * {@link #setSeriesKeys(Comparable[])} and
    103         * {@link #setCategoryKeys(Comparable[])} methods.
    104         *
    105         * @param starts  the start values data.
    106         * @param ends  the end values data.
    107         */
    108        public DefaultIntervalCategoryDataset(Number[][] starts, Number[][] ends) {
    109            this(null, null, starts, ends);
    110        }
    111
    112        /**
    113         * Constructs a DefaultIntervalCategoryDataset, populates it with data
    114         * from the arrays, and uses the supplied names for the series.
    115         * <p>
    116         * Category names are generated automatically ("Category 1", "Category 2",
    117         * etc).
    118         *
    119         * @param seriesNames  the series names (if <code>null</code>, series names
    120         *         will be generated automatically).
    121         * @param starts  the start values data, indexed as data[series][category].
    122         * @param ends  the end values data, indexed as data[series][category].
    123         */
    124        public DefaultIntervalCategoryDataset(String[] seriesNames,
    125                                              Number[][] starts,
    126                                              Number[][] ends) {
    127
    128            this(seriesNames, null, starts, ends);
    129
    130        }
    131
    132        /**
    133         * Constructs a DefaultIntervalCategoryDataset, populates it with data
    134         * from the arrays, and uses the supplied names for the series and the
    135         * supplied objects for the categories.
    136         *
    137         * @param seriesKeys  the series keys (if <code>null</code>, series keys
    138         *         will be generated automatically).
    139         * @param categoryKeys  the category keys (if <code>null</code>, category
    140         *         keys will be generated automatically).
    141         * @param starts  the start values data, indexed as data[series][category].
    142         * @param ends  the end values data, indexed as data[series][category].
    143         */
    144        public DefaultIntervalCategoryDataset(Comparable[] seriesKeys,
    145                                              Comparable[] categoryKeys,
    146                                              Number[][] starts,
    147                                              Number[][] ends) {
    148
    149            this.startData = starts;
    150            this.endData = ends;
    151
    152            if (starts != null && ends != null) {
    153
    154                String baseName = "org.jfree.data.resources.DataPackageResources";
    155                ResourceBundle resources = ResourceBundleWrapper.getBundle(
    156                        baseName);
    157
    158                int seriesCount = starts.length;
    159                if (seriesCount != ends.length) {
    160                    String errMsg = "DefaultIntervalCategoryDataset: the number "
    161                        + "of series in the start value dataset does "
    162                        + "not match the number of series in the end "
    163                        + "value dataset.";
    164                    throw new IllegalArgumentException(errMsg);
    165                }
    166                if (seriesCount > 0) {
    167
    168                    // set up the series names...
    169                    if (seriesKeys != null) {
    170
    171                        if (seriesKeys.length != seriesCount) {
    172                            throw new IllegalArgumentException(
    173                                    "The number of series keys does not "
    174                                    + "match the number of series in the data.");
    175                        }
    176
    177                        this.seriesKeys = seriesKeys;
    178                    }
    179                    else {
    180                        String prefix = resources.getString(
    181                                "series.default-prefix") + " ";
    182                        this.seriesKeys = generateKeys(seriesCount, prefix);
    183                    }
    184
    185                    // set up the category names...
    186                    int categoryCount = starts[0].length;
    187                    if (categoryCount != ends[0].length) {
    188                        String errMsg = "DefaultIntervalCategoryDataset: the "
    189                                    + "number of categories in the start value "
    190                                    + "dataset does not match the number of "
    191                                    + "categories in the end value dataset.";
    192                        throw new IllegalArgumentException(errMsg);
    193                    }
    194                    if (categoryKeys != null) {
    195                        if (categoryKeys.length != categoryCount) {
    196                            throw new IllegalArgumentException(
    197                                    "The number of category keys does not match "
    198                                    + "the number of categories in the data.");
    199                        }
    200                        this.categoryKeys = categoryKeys;
    201                    }
    202                    else {
    203                        String prefix = resources.getString(
    204                                "categories.default-prefix") + " ";
    205                        this.categoryKeys = generateKeys(categoryCount, prefix);
    206                    }
    207
    208                }
    209                else {
    210                    this.seriesKeys = new Comparable[0];
    211                    this.categoryKeys = new Comparable[0];
    212                }
    213            }
    214
    215        }
    216
    217        /**
    218         * Returns the number of series in the dataset (possibly zero).
    219         *
    220         * @return The number of series in the dataset.
    221         *
    222         * @see #getRowCount()
    223         * @see #getCategoryCount()
    224         */
    225        public int getSeriesCount() {
    226            int result = 0;
    227            if (this.startData != null) {
    228                result = this.startData.length;
    229            }
    230            return result;
    231        }
    232
    233        /**
    234         * Returns a series index.
    235         *
    236         * @param seriesKey  the series key.
    237         *
    238         * @return The series index.
    239         *
    240         * @see #getRowIndex(Comparable)
    241         * @see #getSeriesKey(int)
    242         */
    243        public int getSeriesIndex(Comparable seriesKey) {
    244            int result = -1;
    245            for (int i = 0; i < this.seriesKeys.length; i++) {
    246                if (seriesKey.equals(this.seriesKeys[i])) {
    247                    result = i;
    248                    break;
    249                }
    250            }
    251            return result;
    252        }
    253
    254        /**
    255         * Returns the name of the specified series.
    256         *
    257         * @param series  the index of the required series (zero-based).
    258         *
    259         * @return The name of the specified series.
    260         *
    261         * @see #getSeriesIndex(Comparable)
    262         */
    263        public Comparable getSeriesKey(int series) {
    264            if ((series >= getSeriesCount()) || (series < 0)) {
    265                throw new IllegalArgumentException("No such series : " + series);
    266            }
    267            return this.seriesKeys[series];
    268        }
    269
    270        /**
    271         * Sets the names of the series in the dataset.
    272         *
    273         * @param seriesKeys  the new keys (<code>null</code> not permitted, the
    274         *         length of the array must match the number of series in the
    275         *         dataset).
    276         *
    277         * @see #setCategoryKeys(Comparable[])
    278         */
    279        public void setSeriesKeys(Comparable[] seriesKeys) {
    280            if (seriesKeys == null) {
    281                throw new IllegalArgumentException("Null 'seriesKeys' argument.");
    282            }
    283            if (seriesKeys.length != getSeriesCount()) {
    284                throw new IllegalArgumentException(
    285                        "The number of series keys does not match the data.");
    286            }
    287            this.seriesKeys = seriesKeys;
    288            fireDatasetChanged();
    289        }
    290
    291        /**
    292         * Returns the number of categories in the dataset.
    293         *
    294         * @return The number of categories in the dataset.
    295         *
    296         * @see #getColumnCount()
    297         */
    298        public int getCategoryCount() {
    299            int result = 0;
    300            if (this.startData != null) {
    301                if (getSeriesCount() > 0) {
    302                    result = this.startData[0].length;
    303                }
    304            }
    305            return result;
    306        }
    307
    308        /**
    309         * Returns a list of the categories in the dataset.  This method supports
    310         * the {@link CategoryDataset} interface.
    311         *
    312         * @return A list of the categories in the dataset.
    313         *
    314         * @see #getRowKeys()
    315         */
    316        public List getColumnKeys() {
    317            // the CategoryDataset interface expects a list of categories, but
    318            // we've stored them in an array...
    319            if (this.categoryKeys == null) {
    320                return new ArrayList();
    321            }
    322            else {
    323                return Collections.unmodifiableList(Arrays.asList(
    324                        this.categoryKeys));
    325            }
    326        }
    327
    328        /**
    329         * Sets the categories for the dataset.
    330         *
    331         * @param categoryKeys  an array of objects representing the categories in
    332         *                      the dataset.
    333         *
    334         * @see #getRowKeys()
    335         * @see #setSeriesKeys(Comparable[])
    336         */
    337        public void setCategoryKeys(Comparable[] categoryKeys) {
    338            if (categoryKeys == null) {
    339                throw new IllegalArgumentException("Null 'categoryKeys' argument.");
    340            }
    341            if (categoryKeys.length != getCategoryCount()) {
    342                throw new IllegalArgumentException(
    343                        "The number of categories does not match the data.");
    344            }
    345            for (int i = 0; i < categoryKeys.length; i++) {
    346                if (categoryKeys[i] == null) {
    347                    throw new IllegalArgumentException(
    348                        "DefaultIntervalCategoryDataset.setCategoryKeys(): "
    349                        + "null category not permitted.");
    350                }
    351            }
    352            this.categoryKeys = categoryKeys;
    353            fireDatasetChanged();
    354        }
    355
    356        /**
    357         * Returns the data value for one category in a series.
    358         * <P>
    359         * This method is part of the CategoryDataset interface.  Not particularly
    360         * meaningful for this class...returns the end value.
    361         *
    362         * @param series    The required series (zero based index).
    363         * @param category  The required category.
    364         *
    365         * @return The data value for one category in a series (null possible).
    366         *
    367         * @see #getEndValue(Comparable, Comparable)
    368         */
    369        public Number getValue(Comparable series, Comparable category) {
    370            int seriesIndex = getSeriesIndex(series);
    371            if (seriesIndex < 0) {
    372                throw new UnknownKeyException("Unknown 'series' key.");
    373            }
    374            int itemIndex = getColumnIndex(category);
    375            if (itemIndex < 0) {
    376                throw new UnknownKeyException("Unknown 'category' key.");
    377            }
    378            return getValue(seriesIndex, itemIndex);
    379        }
    380
    381        /**
    382         * Returns the data value for one category in a series.
    383         * <P>
    384         * This method is part of the CategoryDataset interface.  Not particularly
    385         * meaningful for this class...returns the end value.
    386         *
    387         * @param series  the required series (zero based index).
    388         * @param category  the required category.
    389         *
    390         * @return The data value for one category in a series (null possible).
    391         *
    392         * @see #getEndValue(int, int)
    393         */
    394        public Number getValue(int series, int category) {
    395            return getEndValue(series, category);
    396        }
    397
    398        /**
    399         * Returns the start data value for one category in a series.
    400         *
    401         * @param series  the required series.
    402         * @param category  the required category.
    403         *
    404         * @return The start data value for one category in a series
    405         *         (possibly <code>null</code>).
    406         *
    407         * @see #getStartValue(int, int)
    408         */
    409        public Number getStartValue(Comparable series, Comparable category) {
    410            int seriesIndex = getSeriesIndex(series);
    411            if (seriesIndex < 0) {
    412                throw new UnknownKeyException("Unknown 'series' key.");
    413            }
    414            int itemIndex = getColumnIndex(category);
    415            if (itemIndex < 0) {
    416                throw new UnknownKeyException("Unknown 'category' key.");
    417            }
    418            return getStartValue(seriesIndex, itemIndex);
    419        }
    420
    421        /**
    422         * Returns the start data value for one category in a series.
    423         *
    424         * @param series  the required series (zero based index).
    425         * @param category  the required category.
    426         *
    427         * @return The start data value for one category in a series
    428         *         (possibly <code>null</code>).
    429         *
    430         * @see #getStartValue(Comparable, Comparable)
    431         */
    432        public Number getStartValue(int series, int category) {
    433
    434            // check arguments...
    435            if ((series < 0) || (series >= getSeriesCount())) {
    436                throw new IllegalArgumentException(
    437                    "DefaultIntervalCategoryDataset.getValue(): "
    438                    + "series index out of range.");
    439            }
    440
    441            if ((category < 0) || (category >= getCategoryCount())) {
    442                throw new IllegalArgumentException(
    443                    "DefaultIntervalCategoryDataset.getValue(): "
    444                    + "category index out of range.");
    445            }
    446
    447            // fetch the value...
    448            return this.startData[series][category];
    449
    450        }
    451
    452        /**
    453         * Returns the end data value for one category in a series.
    454         *
    455         * @param series  the required series.
    456         * @param category  the required category.
    457         *
    458         * @return The end data value for one category in a series (null possible).
    459         *
    460         * @see #getEndValue(int, int)
    461         */
    462        public Number getEndValue(Comparable series, Comparable category) {
    463            int seriesIndex = getSeriesIndex(series);
    464            if (seriesIndex < 0) {
    465                throw new UnknownKeyException("Unknown 'series' key.");
    466            }
    467            int itemIndex = getColumnIndex(category);
    468            if (itemIndex < 0) {
    469                throw new UnknownKeyException("Unknown 'category' key.");
    470            }
    471            return getEndValue(seriesIndex, itemIndex);
    472        }
    473
    474        /**
    475         * Returns the end data value for one category in a series.
    476         *
    477         * @param series  the required series (zero based index).
    478         * @param category  the required category.
    479         *
    480         * @return The end data value for one category in a series (null possible).
    481         *
    482         * @see #getEndValue(Comparable, Comparable)
    483         */
    484        public Number getEndValue(int series, int category) {
    485            if ((series < 0) || (series >= getSeriesCount())) {
    486                throw new IllegalArgumentException(
    487                    "DefaultIntervalCategoryDataset.getValue(): "
    488                    + "series index out of range.");
    489            }
    490
    491            if ((category < 0) || (category >= getCategoryCount())) {
    492                throw new IllegalArgumentException(
    493                    "DefaultIntervalCategoryDataset.getValue(): "
    494                    + "category index out of range.");
    495            }
    496
    497            return this.endData[series][category];
    498        }
    499
    500        /**
    501         * Sets the start data value for one category in a series.
    502         *
    503         * @param series  the series (zero-based index).
    504         * @param category  the category.
    505         *
    506         * @param value The value.
    507         *
    508         * @see #setEndValue(int, Comparable, Number)
    509         */
    510        public void setStartValue(int series, Comparable category, Number value) {
    511
    512            // does the series exist?
    513            if ((series < 0) || (series > getSeriesCount() - 1)) {
    514                throw new IllegalArgumentException(
    515                    "DefaultIntervalCategoryDataset.setValue: "
    516                    + "series outside valid range.");
    517            }
    518
    519            // is the category valid?
    520            int categoryIndex = getCategoryIndex(category);
    521            if (categoryIndex < 0) {
    522                throw new IllegalArgumentException(
    523                    "DefaultIntervalCategoryDataset.setValue: "
    524                    + "unrecognised category.");
    525            }
    526
    527            // update the data...
    528            this.startData[series][categoryIndex] = value;
    529            fireDatasetChanged();
    530
    531        }
    532
    533        /**
    534         * Sets the end data value for one category in a series.
    535         *
    536         * @param series  the series (zero-based index).
    537         * @param category  the category.
    538         *
    539         * @param value the value.
    540         *
    541         * @see #setStartValue(int, Comparable, Number)
    542         */
    543        public void setEndValue(int series, Comparable category, Number value) {
    544
    545            // does the series exist?
    546            if ((series < 0) || (series > getSeriesCount() - 1)) {
    547                throw new IllegalArgumentException(
    548                    "DefaultIntervalCategoryDataset.setValue: "
    549                    + "series outside valid range.");
    550            }
    551
    552            // is the category valid?
    553            int categoryIndex = getCategoryIndex(category);
    554            if (categoryIndex < 0) {
    555                throw new IllegalArgumentException(
    556                    "DefaultIntervalCategoryDataset.setValue: "
    557                    + "unrecognised category.");
    558            }
    559
    560            // update the data...
    561            this.endData[series][categoryIndex] = value;
    562            fireDatasetChanged();
    563
    564        }
    565
    566        /**
    567         * Returns the index for the given category.
    568         *
    569         * @param category  the category (<code>null</code> not permitted).
    570         *
    571         * @return The index.
    572         *
    573         * @see #getColumnIndex(Comparable)
    574         */
    575        public int getCategoryIndex(Comparable category) {
    576            int result = -1;
    577            for (int i = 0; i < this.categoryKeys.length; i++) {
    578                if (category.equals(this.categoryKeys[i])) {
    579                    result = i;
    580                    break;
    581                }
    582            }
    583            return result;
    584        }
    585
    586        /**
    587         * Generates an array of keys, by appending a space plus an integer
    588         * (starting with 1) to the supplied prefix string.
    589         *
    590         * @param count  the number of keys required.
    591         * @param prefix  the name prefix.
    592         *
    593         * @return An array of <i>prefixN</i> with N = { 1 .. count}.
    594         */
    595        private Comparable[] generateKeys(int count, String prefix) {
    596            Comparable[] result = new Comparable[count];
    597            String name;
    598            for (int i = 0; i < count; i++) {
    599                name = prefix + (i + 1);
    600                result[i] = name;
    601            }
    602            return result;
    603        }
    604
    605        /**
    606         * Returns a column key.
    607         *
    608         * @param column  the column index.
    609         *
    610         * @return The column key.
    611         *
    612         * @see #getRowKey(int)
    613         */
    614        public Comparable getColumnKey(int column) {
    615            return this.categoryKeys[column];
    616        }
    617
    618        /**
    619         * Returns a column index.
    620         *
    621         * @param columnKey  the column key (<code>null</code> not permitted).
    622         *
    623         * @return The column index.
    624         *
    625         * @see #getCategoryIndex(Comparable)
    626         */
    627        public int getColumnIndex(Comparable columnKey) {
    628            if (columnKey == null) {
    629                throw new IllegalArgumentException("Null 'columnKey' argument.");
    630            }
    631            return getCategoryIndex(columnKey);
    632        }
    633
    634        /**
    635         * Returns a row index.
    636         *
    637         * @param rowKey  the row key.
    638         *
    639         * @return The row index.
    640         *
    641         * @see #getSeriesIndex(Comparable)
    642         */
    643        public int getRowIndex(Comparable rowKey) {
    644            return getSeriesIndex(rowKey);
    645        }
    646
    647        /**
    648         * Returns a list of the series in the dataset.  This method supports the
    649         * {@link CategoryDataset} interface.
    650         *
    651         * @return A list of the series in the dataset.
    652         *
    653         * @see #getColumnKeys()
    654         */
    655        public List getRowKeys() {
    656            // the CategoryDataset interface expects a list of series, but
    657            // we've stored them in an array...
    658            if (this.seriesKeys == null) {
    659                return new java.util.ArrayList();
    660            }
    661            else {
    662                return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
    663            }
    664        }
    665
    666        /**
    667         * Returns the name of the specified series.
    668         *
    669         * @param row  the index of the required row/series (zero-based).
    670         *
    671         * @return The name of the specified series.
    672         *
    673         * @see #getColumnKey(int)
    674         */
    675        public Comparable getRowKey(int row) {
    676            if ((row >= getRowCount()) || (row < 0)) {
    677                throw new IllegalArgumentException(
    678                        "The 'row' argument is out of bounds.");
    679            }
    680            return this.seriesKeys[row];
    681        }
    682
    683        /**
    684         * Returns the number of categories in the dataset.  This method is part of
    685         * the {@link CategoryDataset} interface.
    686         *
    687         * @return The number of categories in the dataset.
    688         *
    689         * @see #getCategoryCount()
    690         * @see #getRowCount()
    691         */
    692        public int getColumnCount() {
    693            return this.categoryKeys.length;
    694        }
    695
    696        /**
    697         * Returns the number of series in the dataset (possibly zero).
    698         *
    699         * @return The number of series in the dataset.
    700         *
    701         * @see #getSeriesCount()
    702         * @see #getColumnCount()
    703         */
    704        public int getRowCount() {
    705            return this.seriesKeys.length;
    706        }
    707
    708        /**
    709         * Tests this dataset for equality with an arbitrary object.
    710         *
    711         * @param obj  the object (<code>null</code> permitted).
    712         *
    713         * @return A boolean.
    714         */
    715        public boolean equals(Object obj) {
    716            if (obj == this) {
    717                return true;
    718            }
    719            if (!(obj instanceof DefaultIntervalCategoryDataset)) {
    720                return false;
    721            }
    722            DefaultIntervalCategoryDataset that
    723                    = (DefaultIntervalCategoryDataset) obj;
    724            if (!Arrays.equals(this.seriesKeys, that.seriesKeys)) {
    725                return false;
    726            }
    727            if (!Arrays.equals(this.categoryKeys, that.categoryKeys)) {
    728                return false;
    729            }
    730            if (!equal(this.startData, that.startData)) {
    731                return false;
    732            }
    733            if (!equal(this.endData, that.endData)) {
    734                return false;
    735            }
    736            // seem to be the same...
    737            return true;
    738        }
    739
    740        /**
    741         * Returns a clone of this dataset.
    742         *
    743         * @return A clone.
    744         *
    745         * @throws CloneNotSupportedException if there is a problem cloning the
    746         *         dataset.
    747         */
    748        public Object clone() throws CloneNotSupportedException {
    749            DefaultIntervalCategoryDataset clone
    750                    = (DefaultIntervalCategoryDataset) super.clone();
    751            clone.categoryKeys = (Comparable[]) this.categoryKeys.clone();
    752            clone.seriesKeys = (Comparable[]) this.seriesKeys.clone();
    753            clone.startData = clone(this.startData);
    754            clone.endData = clone(this.endData);
    755            return clone;
    756        }
    757
    758        /**
    759         * Tests two double[][] arrays for equality.
    760         *
    761         * @param array1  the first array (<code>null</code> permitted).
    762         * @param array2  the second arrray (<code>null</code> permitted).
    763         *
    764         * @return A boolean.
    765         */
    766        private static boolean equal(Number[][] array1, Number[][] array2) {
    767            if (array1 == null) {
    768                return (array2 == null);
    769            }
    770            if (array2 == null) {
    771                return false;
    772            }
    773            if (array1.length != array2.length) {
    774                return false;
    775            }
    776            for (int i = 0; i < array1.length; i++) {
    777                if (!Arrays.equals(array1[i], array2[i])) {
    778                    return false;
    779                }
    780            }
    781            return true;
    782        }
    783
    784        /**
    785         * Clones a two dimensional array of <code>Number</code> objects.
    786         *
    787         * @param array  the array (<code>null</code> not permitted).
    788         *
    789         * @return A clone of the array.
    790         */
    791        private static Number[][] clone(Number[][] array) {
    792            if (array == null) {
    793                throw new IllegalArgumentException("Null 'array' argument.");
    794            }
    795            Number[][] result = new Number[array.length][];
    796            for (int i = 0; i < array.length; i++) {
    797                Number[] child = array[i];
    798                Number[] copychild = new Number[child.length];
    799                System.arraycopy(child, 0, copychild, 0, child.length);
    800                result[i] = copychild;
    801            }
    802            return result;
    803        }
    804
    805        /**
    806         * Returns a list of the series in the dataset.
    807         *
    808         * @return A list of the series in the dataset.
    809         *
    810         * @deprecated Use {@link #getRowKeys()} instead.
    811         */
    812        public List getSeries() {
    813            if (this.seriesKeys == null) {
    814                return new java.util.ArrayList();
    815            }
    816            else {
    817                return Collections.unmodifiableList(Arrays.asList(this.seriesKeys));
    818            }
    819        }
    820
    821        /**
    822         * Returns a list of the categories in the dataset.
    823         *
    824         * @return A list of the categories in the dataset.
    825         *
    826         * @deprecated Use {@link #getColumnKeys()} instead.
    827         */
    828        public List getCategories() {
    829            return getColumnKeys();
    830        }
    831
    832        /**
    833         * Returns the item count.
    834         *
    835         * @return The item count.
    836         *
    837         * @deprecated Use {@link #getCategoryCount()} instead.
    838         */
    839        public int getItemCount() {
    840            return this.categoryKeys.length;
    841        }
    842
    843    }