    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     * XYSeriesCollection.java
    029     * -----------------------
    030     * (C) Copyright 2001-2011, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Aaron Metzger;
    034     *
    035     * Changes
    036     * -------
    037     * 15-Nov-2001 : Version 1 (DG);
    038     * 03-Apr-2002 : Added change listener code (DG);
    039     * 29-Apr-2002 : Added removeSeries, removeAllSeries methods (ARM);
    040     * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    041     * 26-Mar-2003 : Implemented Serializable (DG);
    042     * 04-Aug-2003 : Added getSeries() method (DG);
    043     * 31-Mar-2004 : Modified to use an XYIntervalDelegate.
    044     * 05-May-2004 : Now extends AbstractIntervalXYDataset (DG);
    045     * 18-Aug-2004 : Moved from org.jfree.data --> org.jfree.data.xy (DG);
    046     * 17-Nov-2004 : Updated for changes to DomainInfo interface (DG);
    047     * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
    048     * 28-Mar-2005 : Fixed bug in getSeries(int) method (1170825) (DG);
    049     * 05-Oct-2005 : Made the interval delegate a dataset listener (DG);
    050     * ------------- JFREECHART 1.0.x ---------------------------------------------
    051     * 27-Nov-2006 : Added clone() override (DG);
    052     * 08-May-2007 : Added indexOf(XYSeries) method (DG);
    053     * 03-Dec-2007 : Added getSeries(Comparable) method (DG);
    054     * 22-Apr-2008 : Implemented PublicCloneable (DG);
    055     * 27-Feb-2009 : Overridden getDomainOrder() to detect when all series are
    056     *               sorted in ascending order (DG);
    057     * 06-Mar-2009 : Implemented RangeInfo (DG);
    058     * 06-Mar-2009 : Fixed equals() implementation (DG);
    059     * 10-Jun-2009 : Simplified code in getX() and getY() methods (DG);
    060     *
    061     */
    062
    063    package org.jfree.data.xy;
    064
    065    import java.beans.PropertyChangeEvent;
    066    import java.beans.PropertyVetoException;
    067    import java.beans.VetoableChangeListener;
    068    import java.io.Serializable;
    069    import java.util.Collections;
    070    import java.util.Iterator;
    071    import java.util.List;
    072
    073    import org.jfree.chart.HashUtilities;
    074    import org.jfree.chart.util.ParamChecks;
    075    import org.jfree.data.DomainInfo;
    076    import org.jfree.data.DomainOrder;
    077    import org.jfree.data.Range;
    078    import org.jfree.data.RangeInfo;
    079    import org.jfree.data.UnknownKeyException;
    080    import org.jfree.data.general.DatasetChangeEvent;
    081    import org.jfree.data.general.Series;
    082    import org.jfree.util.ObjectUtilities;
    083    import org.jfree.util.PublicCloneable;
    084
    085    /**
    086     * Represents a collection of {@link XYSeries} objects that can be used as a
    087     * dataset.
    088     */
    089    public class XYSeriesCollection extends AbstractIntervalXYDataset
    090            implements IntervalXYDataset, DomainInfo, RangeInfo,
    091            VetoableChangeListener, PublicCloneable, Serializable {
    092
    093        /** For serialization. */
    094        private static final long serialVersionUID = -7590013825931496766L;
    095
    096        /** The series that are included in the collection. */
    097        private List data;
    098
    099        /** The interval delegate (used to calculate the start and end x-values). */
    100        private IntervalXYDelegate intervalDelegate;
    101
    102        /**
    103         * Constructs an empty dataset.
    104         */
    105        public XYSeriesCollection() {
    106            this(null);
    107        }
    108
    109        /**
    110         * Constructs a dataset and populates it with a single series.
    111         *
    112         * @param series  the series (<code>null</code> ignored).
    113         */
    114        public XYSeriesCollection(XYSeries series) {
    115            this.data = new java.util.ArrayList();
    116            this.intervalDelegate = new IntervalXYDelegate(this, false);
    117            addChangeListener(this.intervalDelegate);
    118            if (series != null) {
    119                this.data.add(series);
    120                series.addChangeListener(this);
    121                series.addVetoableChangeListener(this);
    122            }
    123        }
    124
    125        /**
    126         * Returns the order of the domain (X) values, if this is known.
    127         *
    128         * @return The domain order.
    129         */
    130        public DomainOrder getDomainOrder() {
    131            int seriesCount = getSeriesCount();
    132            for (int i = 0; i < seriesCount; i++) {
    133                XYSeries s = getSeries(i);
    134                if (!s.getAutoSort()) {
    135                    return DomainOrder.NONE;  // we can't be sure of the order
    136                }
    137            }
    138            return DomainOrder.ASCENDING;
    139        }
    140
    141        /**
    142         * Adds a series to the collection and sends a {@link DatasetChangeEvent}
    143         * to all registered listeners.
    144         *
    145         * @param series  the series (<code>null</code> not permitted).
    146         *
    147         * @throws IllegalArgumentException if the key for the series is null or
    148         *     not unique within the dataset.
    149         */
    150        public void addSeries(XYSeries series) {
    151            ParamChecks.nullNotPermitted(series, "series");
    152            if (getSeriesIndex(series.getKey()) >= 0) {
    153                throw new IllegalArgumentException(
    154                    "This dataset already contains a series with the key "
    155                    + series.getKey());
    156            }
    157            this.data.add(series);
    158            series.addChangeListener(this);
    159            series.addVetoableChangeListener(this);
    160            fireDatasetChanged();
    161        }
    162
    163        /**
    164         * Removes a series from the collection and sends a
    165         * {@link DatasetChangeEvent} to all registered listeners.
    166         *
    167         * @param series  the series index (zero-based).
    168         */
    169        public void removeSeries(int series) {
    170            if ((series < 0) || (series >= getSeriesCount())) {
    171                throw new IllegalArgumentException("Series index out of bounds.");
    172            }
    173
    174            // fetch the series, remove the change listener, then remove the series.
    175            XYSeries ts = (XYSeries) this.data.get(series);
    176            ts.removeChangeListener(this);
    177            this.data.remove(series);
    178            fireDatasetChanged();
    179        }
    180
    181        /**
    182         * Removes a series from the collection and sends a
    183         * {@link DatasetChangeEvent} to all registered listeners.
    184         *
    185         * @param series  the series (<code>null</code> not permitted).
    186         */
    187        public void removeSeries(XYSeries series) {
    188            if (series == null) {
    189                throw new IllegalArgumentException("Null 'series' argument.");
    190            }
    191            if (this.data.contains(series)) {
    192                series.removeChangeListener(this);
    193                series.removeVetoableChangeListener(this);
    194                this.data.remove(series);
    195                fireDatasetChanged();
    196            }
    197        }
    198
    199        /**
    200         * Removes all the series from the collection and sends a
    201         * {@link DatasetChangeEvent} to all registered listeners.
    202         */
    203        public void removeAllSeries() {
    204            // Unregister the collection as a change listener to each series in
    205            // the collection.
    206            for (int i = 0; i < this.data.size(); i++) {
    207              XYSeries series = (XYSeries) this.data.get(i);
    208              series.removeChangeListener(this);
    209              series.removeVetoableChangeListener(this);
    210            }
    211
    212            // Remove all the series from the collection and notify listeners.
    213            this.data.clear();
    214            fireDatasetChanged();
    215        }
    216
    217        /**
    218         * Returns the number of series in the collection.
    219         *
    220         * @return The series count.
    221         */
    222        public int getSeriesCount() {
    223            return this.data.size();
    224        }
    225
    226        /**
    227         * Returns a list of all the series in the collection.
    228         *
    229         * @return The list (which is unmodifiable).
    230         */
    231        public List getSeries() {
    232            return Collections.unmodifiableList(this.data);
    233        }
    234
    235        /**
    236         * Returns the index of the specified series, or -1 if that series is not
    237         * present in the dataset.
    238         *
    239         * @param series  the series (<code>null</code> not permitted).
    240         *
    241         * @return The series index.
    242         *
    243         * @since 1.0.6
    244         */
    245        public int indexOf(XYSeries series) {
    246            if (series == null) {
    247                throw new IllegalArgumentException("Null 'series' argument.");
    248            }
    249            return this.data.indexOf(series);
    250        }
    251
    252        /**
    253         * Returns a series from the collection.
    254         *
    255         * @param series  the series index (zero-based).
    256         *
    257         * @return The series.
    258         *
    259         * @throws IllegalArgumentException if <code>series</code> is not in the
    260         *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    261         */
    262        public XYSeries getSeries(int series) {
    263            if ((series < 0) || (series >= getSeriesCount())) {
    264                throw new IllegalArgumentException("Series index out of bounds");
    265            }
    266            return (XYSeries) this.data.get(series);
    267        }
    268
    269        /**
    270         * Returns a series from the collection.
    271         *
    272         * @param key  the key (<code>null</code> not permitted).
    273         *
    274         * @return The series with the specified key.
    275         *
    276         * @throws UnknownKeyException if <code>key</code> is not found in the
    277         *         collection.
    278         *
    279         * @since 1.0.9
    280         */
    281        public XYSeries getSeries(Comparable key) {
    282            if (key == null) {
    283                throw new IllegalArgumentException("Null 'key' argument.");
    284            }
    285            Iterator iterator = this.data.iterator();
    286            while (iterator.hasNext()) {
    287                XYSeries series = (XYSeries) iterator.next();
    288                if (key.equals(series.getKey())) {
    289                    return series;
    290                }
    291            }
    292            throw new UnknownKeyException("Key not found: " + key);
    293        }
    294
    295        /**
    296         * Returns the key for a series.
    297         *
    298         * @param series  the series index (in the range <code>0</code> to
    299         *     <code>getSeriesCount() - 1</code>).
    300         *
    301         * @return The key for a series.
    302         *
    303         * @throws IllegalArgumentException if <code>series</code> is not in the
    304         *     specified range.
    305         */
    306        public Comparable getSeriesKey(int series) {
    307            // defer argument checking
    308            return getSeries(series).getKey();
    309        }
    310
    311        /**
    312         * Returns the index of the series with the specified key, or -1 if no
    313         * series has that key.
    314         *
    315         * @param key  the key (<code>null</code> not permitted).
    316         *
    317         * @return The index.
    318         *
    319         * @since 1.0.14
    320         */
    321        public int getSeriesIndex(Comparable key) {
    322            ParamChecks.nullNotPermitted(key, "key");
    323            int seriesCount = getSeriesCount();
    324            for (int i = 0; i < seriesCount; i++) {
    325                XYSeries series = (XYSeries) this.data.get(i);
    326                if (key.equals(series.getKey())) {
    327                    return i;
    328                }
    329            }
    330            return -1;
    331        }
    332
    333        /**
    334         * Returns the number of items in the specified series.
    335         *
    336         * @param series  the series (zero-based index).
    337         *
    338         * @return The item count.
    339         *
    340         * @throws IllegalArgumentException if <code>series</code> is not in the
    341         *     range <code>0</code> to <code>getSeriesCount() - 1</code>.
    342         */
    343        public int getItemCount(int series) {
    344            // defer argument checking
    345            return getSeries(series).getItemCount();
    346        }
    347
    348        /**
    349         * Returns the x-value for the specified series and item.
    350         *
    351         * @param series  the series (zero-based index).
    352         * @param item  the item (zero-based index).
    353         *
    354         * @return The value.
    355         */
    356        public Number getX(int series, int item) {
    357            XYSeries s = (XYSeries) this.data.get(series);
    358            return s.getX(item);
    359        }
    360
    361        /**
    362         * Returns the starting X value for the specified series and item.
    363         *
    364         * @param series  the series (zero-based index).
    365         * @param item  the item (zero-based index).
    366         *
    367         * @return The starting X value.
    368         */
    369        public Number getStartX(int series, int item) {
    370            return this.intervalDelegate.getStartX(series, item);
    371        }
    372
    373        /**
    374         * Returns the ending X value for the specified series and item.
    375         *
    376         * @param series  the series (zero-based index).
    377         * @param item  the item (zero-based index).
    378         *
    379         * @return The ending X value.
    380         */
    381        public Number getEndX(int series, int item) {
    382            return this.intervalDelegate.getEndX(series, item);
    383        }
    384
    385        /**
    386         * Returns the y-value for the specified series and item.
    387         *
    388         * @param series  the series (zero-based index).
    389         * @param index  the index of the item of interest (zero-based).
    390         *
    391         * @return The value (possibly <code>null</code>).
    392         */
    393        public Number getY(int series, int index) {
    394            XYSeries s = (XYSeries) this.data.get(series);
    395            return s.getY(index);
    396        }
    397
    398        /**
    399         * Returns the starting Y value for the specified series and item.
    400         *
    401         * @param series  the series (zero-based index).
    402         * @param item  the item (zero-based index).
    403         *
    404         * @return The starting Y value.
    405         */
    406        public Number getStartY(int series, int item) {
    407            return getY(series, item);
    408        }
    409
    410        /**
    411         * Returns the ending Y value for the specified series and item.
    412         *
    413         * @param series  the series (zero-based index).
    414         * @param item  the item (zero-based index).
    415         *
    416         * @return The ending Y value.
    417         */
    418        public Number getEndY(int series, int item) {
    419            return getY(series, item);
    420        }
    421
    422        /**
    423         * Tests this collection for equality with an arbitrary object.
    424         *
    425         * @param obj  the object (<code>null</code> permitted).
    426         *
    427         * @return A boolean.
    428         */
    429        public boolean equals(Object obj) {
    430            if (obj == this) {
    431                return true;
    432            }
    433            if (!(obj instanceof XYSeriesCollection)) {
    434                return false;
    435            }
    436            XYSeriesCollection that = (XYSeriesCollection) obj;
    437            if (!this.intervalDelegate.equals(that.intervalDelegate)) {
    438                return false;
    439            }
    440            return ObjectUtilities.equal(this.data, that.data);
    441        }
    442
    443        /**
    444         * Returns a clone of this instance.
    445         *
    446         * @return A clone.
    447         *
    448         * @throws CloneNotSupportedException if there is a problem.
    449         */
    450        public Object clone() throws CloneNotSupportedException {
    451            XYSeriesCollection clone = (XYSeriesCollection) super.clone();
    452            clone.data = (List) ObjectUtilities.deepClone(this.data);
    453            clone.intervalDelegate
    454                    = (IntervalXYDelegate) this.intervalDelegate.clone();
    455            return clone;
    456        }
    457
    458        /**
    459         * Returns a hash code.
    460         *
    461         * @return A hash code.
    462         */
    463        public int hashCode() {
    464            int hash = 5;
    465            hash = HashUtilities.hashCode(hash, this.intervalDelegate);
    466            hash = HashUtilities.hashCode(hash, this.data);
    467            return hash;
    468        }
    469
    470        /**
    471         * Returns the minimum x-value in the dataset.
    472         *
    473         * @param includeInterval  a flag that determines whether or not the
    474         *                         x-interval is taken into account.
    475         *
    476         * @return The minimum value.
    477         */
    478        public double getDomainLowerBound(boolean includeInterval) {
    479            if (includeInterval) {
    480                return this.intervalDelegate.getDomainLowerBound(includeInterval);
    481            }
    482            double result = Double.NaN;
    483            int seriesCount = getSeriesCount();
    484            for (int s = 0; s < seriesCount; s++) {
    485                XYSeries series = getSeries(s);
    486                double lowX = series.getMinX();
    487                if (Double.isNaN(result)) {
    488                    result = lowX;
    489                }
    490                else {
    491                    if (!Double.isNaN(lowX)) {
    492                        result = Math.min(result, lowX);
    493                    }
    494                }
    495            }
    496            return result;
    497        }
    498
    499        /**
    500         * Returns the maximum x-value in the dataset.
    501         *
    502         * @param includeInterval  a flag that determines whether or not the
    503         *                         x-interval is taken into account.
    504         *
    505         * @return The maximum value.
    506         */
    507        public double getDomainUpperBound(boolean includeInterval) {
    508            if (includeInterval) {
    509                return this.intervalDelegate.getDomainUpperBound(includeInterval);
    510            }
    511            else {
    512                double result = Double.NaN;
    513                int seriesCount = getSeriesCount();
    514                for (int s = 0; s < seriesCount; s++) {
    515                    XYSeries series = getSeries(s);
    516                    double hiX = series.getMaxX();
    517                    if (Double.isNaN(result)) {
    518                        result = hiX;
    519                    }
    520                    else {
    521                        if (!Double.isNaN(hiX)) {
    522                            result = Math.max(result, hiX);
    523                        }
    524                    }
    525                }
    526                return result;
    527            }
    528        }
    529
    530        /**
    531         * Returns the range of the values in this dataset's domain.
    532         *
    533         * @param includeInterval  a flag that determines whether or not the
    534         *                         x-interval is taken into account.
    535         *
    536         * @return The range (or <code>null</code> if the dataset contains no
    537         *     values).
    538         */
    539        public Range getDomainBounds(boolean includeInterval) {
    540            if (includeInterval) {
    541                return this.intervalDelegate.getDomainBounds(includeInterval);
    542            }
    543            else {
    544                double lower = Double.POSITIVE_INFINITY;
    545                double upper = Double.NEGATIVE_INFINITY;
    546                int seriesCount = getSeriesCount();
    547                for (int s = 0; s < seriesCount; s++) {
    548                    XYSeries series = getSeries(s);
    549                    double minX = series.getMinX();
    550                    if (!Double.isNaN(minX)) {
    551                        lower = Math.min(lower, minX);
    552                    }
    553                    double maxX = series.getMaxX();
    554                    if (!Double.isNaN(maxX)) {
    555                        upper = Math.max(upper, maxX);
    556                    }
    557                }
    558                if (lower > upper) {
    559                    return null;
    560                }
    561                else {
    562                    return new Range(lower, upper);
    563                }
    564            }
    565        }
    566
    567        /**
    568         * Returns the interval width. This is used to calculate the start and end
    569         * x-values, if/when the dataset is used as an {@link IntervalXYDataset}.
    570         *
    571         * @return The interval width.
    572         */
    573        public double getIntervalWidth() {
    574            return this.intervalDelegate.getIntervalWidth();
    575        }
    576
    577        /**
    578         * Sets the interval width and sends a {@link DatasetChangeEvent} to all
    579         * registered listeners.
    580         *
    581         * @param width  the width (negative values not permitted).
    582         */
    583        public void setIntervalWidth(double width) {
    584            if (width < 0.0) {
    585                throw new IllegalArgumentException("Negative 'width' argument.");
    586            }
    587            this.intervalDelegate.setFixedIntervalWidth(width);
    588            fireDatasetChanged();
    589        }
    590
    591        /**
    592         * Returns the interval position factor.
    593         *
    594         * @return The interval position factor.
    595         */
    596        public double getIntervalPositionFactor() {
    597            return this.intervalDelegate.getIntervalPositionFactor();
    598        }
    599
    600        /**
    601         * Sets the interval position factor. This controls where the x-value is in
    602         * relation to the interval surrounding the x-value (0.0 means the x-value
    603         * will be positioned at the start, 0.5 in the middle, and 1.0 at the end).
    604         *
    605         * @param factor  the factor.
    606         */
    607        public void setIntervalPositionFactor(double factor) {
    608            this.intervalDelegate.setIntervalPositionFactor(factor);
    609            fireDatasetChanged();
    610        }
    611
    612        /**
    613         * Returns whether the interval width is automatically calculated or not.
    614         *
    615         * @return Whether the width is automatically calculated or not.
    616         */
    617        public boolean isAutoWidth() {
    618            return this.intervalDelegate.isAutoWidth();
    619        }
    620
    621        /**
    622         * Sets the flag that indicates wether the interval width is automatically
    623         * calculated or not.
    624         *
    625         * @param b  a boolean.
    626         */
    627        public void setAutoWidth(boolean b) {
    628            this.intervalDelegate.setAutoWidth(b);
    629            fireDatasetChanged();
    630        }
    631
    632        /**
    633         * Returns the range of the values in this dataset's range.
    634         *
    635         * @param includeInterval  ignored.
    636         *
    637         * @return The range (or <code>null</code> if the dataset contains no
    638         *     values).
    639         */
    640        public Range getRangeBounds(boolean includeInterval) {
    641            double lower = Double.POSITIVE_INFINITY;
    642            double upper = Double.NEGATIVE_INFINITY;
    643            int seriesCount = getSeriesCount();
    644            for (int s = 0; s < seriesCount; s++) {
    645                XYSeries series = getSeries(s);
    646                double minY = series.getMinY();
    647                if (!Double.isNaN(minY)) {
    648                    lower = Math.min(lower, minY);
    649                }
    650                double maxY = series.getMaxY();
    651                if (!Double.isNaN(maxY)) {
    652                    upper = Math.max(upper, maxY);
    653                }
    654            }
    655            if (lower > upper) {
    656                return null;
    657            }
    658            else {
    659                return new Range(lower, upper);
    660            }
    661        }
    662
    663        /**
    664         * Returns the minimum y-value in the dataset.
    665         *
    666         * @param includeInterval  a flag that determines whether or not the
    667         *                         y-interval is taken into account.
    668         *
    669         * @return The minimum value.
    670         */
    671        public double getRangeLowerBound(boolean includeInterval) {
    672            double result = Double.NaN;
    673            int seriesCount = getSeriesCount();
    674            for (int s = 0; s < seriesCount; s++) {
    675                XYSeries series = getSeries(s);
    676                double lowY = series.getMinY();
    677                if (Double.isNaN(result)) {
    678                    result = lowY;
    679                }
    680                else {
    681                    if (!Double.isNaN(lowY)) {
    682                        result = Math.min(result, lowY);
    683                    }
    684                }
    685            }
    686            return result;
    687        }
    688
    689        /**
    690         * Returns the maximum y-value in the dataset.
    691         *
    692         * @param includeInterval  a flag that determines whether or not the
    693         *                         y-interval is taken into account.
    694         *
    695         * @return The maximum value.
    696         */
    697        public double getRangeUpperBound(boolean includeInterval) {
    698            double result = Double.NaN;
    699            int seriesCount = getSeriesCount();
    700            for (int s = 0; s < seriesCount; s++) {
    701                XYSeries series = getSeries(s);
    702                double hiY = series.getMaxY();
    703                if (Double.isNaN(result)) {
    704                    result = hiY;
    705                }
    706                else {
    707                    if (!Double.isNaN(hiY)) {
    708                        result = Math.max(result, hiY);
    709                    }
    710                }
    711            }
    712            return result;
    713        }
    714
    715        /**
    716         * Receives notification that the key for one of the series in the
    717         * collection has changed, and vetos it if the key is already present in
    718         * the collection.
    719         *
    720         * @param e  the event.
    721         *
    722         * @since 1.0.14
    723         */
    724        public void vetoableChange(PropertyChangeEvent e)
    725                throws PropertyVetoException {
    726            // if it is not the series name, then we have no interest
    727            if (!"Key".equals(e.getPropertyName())) {
    728                return;
    729            }
    730
    731            // to be defensive, let's check that the source series does in fact
    732            // belong to this collection
    733            Series s = (Series) e.getSource();
    734            if (getSeries(s.getKey()) == null) {
    735                throw new IllegalStateException("Receiving events from a series " +
    736                        "that does not belong to this collection.");
    737            }
    738            // check if the new series name already exists for another series
    739            Comparable key = (Comparable) e.getNewValue();
    740            if (this.getSeries(key) != null) {
    741                throw new PropertyVetoException("Duplicate key2", e);
    742            }
    743        }
    744
    745    }