    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2006, 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     * ComparableObjectSeries.java
    029     * ---------------------------
    030     * (C) Copyright 2006, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: ComparableObjectSeries.java,v 1.1.2.2 2006/10/23 09:18:54 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 19-Oct-2006 : New class, based on XYDataItem (DG);
    040     *
    041     */
    042
    043    package org.jfree.data;
    044
    045    import java.io.Serializable;
    046    import java.util.Collections;
    047    import java.util.List;
    048
    049    import org.jfree.data.general.Series;
    050    import org.jfree.data.general.SeriesChangeEvent;
    051    import org.jfree.data.general.SeriesException;
    052    import org.jfree.util.ObjectUtilities;
    053
    054    /**
    055     * A (possibly ordered) list of (Comparable, Object) data items.
    056     *
    057     * @since 1.0.3
    058     */
    059    public class ComparableObjectSeries extends Series
    060            implements Cloneable, Serializable {
    061
    062        /** Storage for the data items in the series. */
    063        protected List data;
    064
    065        /** The maximum number of items for the series. */
    066        private int maximumItemCount = Integer.MAX_VALUE;
    067
    068        /** A flag that controls whether the items are automatically sorted. */
    069        private boolean autoSort;
    070
    071        /** A flag that controls whether or not duplicate x-values are allowed. */
    072        private boolean allowDuplicateXValues;
    073
    074        /**
    075         * Creates a new empty series.  By default, items added to the series will
    076         * be sorted into ascending order by x-value, and duplicate x-values will
    077         * be allowed (these defaults can be modified with another constructor.
    078         *
    079         * @param key  the series key (<code>null</code> not permitted).
    080         */
    081        public ComparableObjectSeries(Comparable key) {
    082            this(key, true, true);
    083        }
    084
    085        /**
    086         * Constructs a new series that contains no data.  You can specify
    087         * whether or not duplicate x-values are allowed for the series.
    088         *
    089         * @param key  the series key (<code>null</code> not permitted).
    090         * @param autoSort  a flag that controls whether or not the items in the
    091         *                  series are sorted.
    092         * @param allowDuplicateXValues  a flag that controls whether duplicate
    093         *                               x-values are allowed.
    094         */
    095        public ComparableObjectSeries(Comparable key, boolean autoSort,
    096                boolean allowDuplicateXValues) {
    097            super(key);
    098            this.data = new java.util.ArrayList();
    099            this.autoSort = autoSort;
    100            this.allowDuplicateXValues = allowDuplicateXValues;
    101        }
    102
    103        /**
    104         * Returns the flag that controls whether the items in the series are
    105         * automatically sorted.  There is no setter for this flag, it must be
    106         * defined in the series constructor.
    107         *
    108         * @return A boolean.
    109         */
    110        public boolean getAutoSort() {
    111            return this.autoSort;
    112        }
    113
    114        /**
    115         * Returns a flag that controls whether duplicate x-values are allowed.
    116         * This flag can only be set in the constructor.
    117         *
    118         * @return A boolean.
    119         */
    120        public boolean getAllowDuplicateXValues() {
    121            return this.allowDuplicateXValues;
    122        }
    123
    124        /**
    125         * Returns the number of items in the series.
    126         *
    127         * @return The item count.
    128         */
    129        public int getItemCount() {
    130            return this.data.size();
    131        }
    132
    133        /**
    134         * Returns the maximum number of items that will be retained in the series.
    135         * The default value is <code>Integer.MAX_VALUE</code>.
    136         *
    137         * @return The maximum item count.
    138         * @see #setMaximumItemCount(int)
    139         */
    140        public int getMaximumItemCount() {
    141            return this.maximumItemCount;
    142        }
    143
    144        /**
    145         * Sets the maximum number of items that will be retained in the series.
    146         * If you add a new item to the series such that the number of items will
    147         * exceed the maximum item count, then the first element in the series is
    148         * automatically removed, ensuring that the maximum item count is not
    149         * exceeded.
    150         * <p>
    151         * Typically this value is set before the series is populated with data,
    152         * but if it is applied later, it may cause some items to be removed from
    153         * the series (in which case a {@link SeriesChangeEvent} will be sent to
    154         * all registered listeners.
    155         *
    156         * @param maximum  the maximum number of items for the series.
    157         */
    158        public void setMaximumItemCount(int maximum) {
    159            this.maximumItemCount = maximum;
    160            boolean dataRemoved = false;
    161            while (this.data.size() > maximum) {
    162                this.data.remove(0);
    163                dataRemoved = true;
    164            }
    165            if (dataRemoved) {
    166                fireSeriesChanged();
    167            }
    168        }
    169
    170        /**
    171         * Adds new data to the series and sends a {@link SeriesChangeEvent} to
    172         * all registered listeners.
    173         * <P>
    174         * Throws an exception if the x-value is a duplicate AND the
    175         * allowDuplicateXValues flag is false.
    176         *
    177         * @param x  the x-value (<code>null</code> not permitted).
    178         * @param y  the y-value (<code>null</code> permitted).
    179         */
    180        protected void add(Comparable x, Object y) {
    181            // argument checking delegated...
    182            add(x, y, true);
    183        }
    184
    185        /**
    186         * Adds new data to the series and, if requested, sends a
    187         * {@link SeriesChangeEvent} to all registered listeners.
    188         * <P>
    189         * Throws an exception if the x-value is a duplicate AND the
    190         * allowDuplicateXValues flag is false.
    191         *
    192         * @param x  the x-value (<code>null</code> not permitted).
    193         * @param y  the y-value (<code>null</code> permitted).
    194         * @param notify  a flag the controls whether or not a
    195         *                {@link SeriesChangeEvent} is sent to all registered
    196         *                listeners.
    197         */
    198        protected void add(Comparable x, Object y, boolean notify) {
    199            // delegate argument checking to XYDataItem...
    200            ComparableObjectItem item = new ComparableObjectItem(x, y);
    201            add(item, notify);
    202        }
    203
    204        /**
    205         * Adds a data item to the series and, if requested, sends a
    206         * {@link SeriesChangeEvent} to all registered listeners.
    207         *
    208         * @param item  the (x, y) item (<code>null</code> not permitted).
    209         * @param notify  a flag that controls whether or not a
    210         *                {@link SeriesChangeEvent} is sent to all registered
    211         *                listeners.
    212         */
    213        protected void add(ComparableObjectItem item, boolean notify) {
    214
    215            if (item == null) {
    216                throw new IllegalArgumentException("Null 'item' argument.");
    217            }
    218
    219            if (this.autoSort) {
    220                int index = Collections.binarySearch(this.data, item);
    221                if (index < 0) {
    222                    this.data.add(-index - 1, item);
    223                }
    224                else {
    225                    if (this.allowDuplicateXValues) {
    226                        // need to make sure we are adding *after* any duplicates
    227                        int size = this.data.size();
    228                        while (index < size
    229                               && item.compareTo(this.data.get(index)) == 0) {
    230                            index++;
    231                        }
    232                        if (index < this.data.size()) {
    233                            this.data.add(index, item);
    234                        }
    235                        else {
    236                            this.data.add(item);
    237                        }
    238                    }
    239                    else {
    240                        throw new SeriesException("X-value already exists.");
    241                    }
    242                }
    243            }
    244            else {
    245                if (!this.allowDuplicateXValues) {
    246                    // can't allow duplicate values, so we need to check whether
    247                    // there is an item with the given x-value already
    248                    int index = indexOf(item.getComparable());
    249                    if (index >= 0) {
    250                        throw new SeriesException("X-value already exists.");
    251                    }
    252                }
    253                this.data.add(item);
    254            }
    255            if (getItemCount() > this.maximumItemCount) {
    256                this.data.remove(0);
    257            }
    258            if (notify) {
    259                fireSeriesChanged();
    260            }
    261        }
    262
    263        /**
    264         * Returns the index of the item with the specified x-value, or a negative
    265         * index if the series does not contain an item with that x-value.  Be
    266         * aware that for an unsorted series, the index is found by iterating
    267         * through all items in the series.
    268         *
    269         * @param x  the x-value (<code>null</code> not permitted).
    270         *
    271         * @return The index.
    272         */
    273        public int indexOf(Comparable x) {
    274            if (this.autoSort) {
    275                return Collections.binarySearch(this.data, new ComparableObjectItem(x, null));
    276            }
    277            else {
    278                for (int i = 0; i < this.data.size(); i++) {
    279                    ComparableObjectItem item = (ComparableObjectItem) this.data.get(i);
    280                    if (item.getComparable().equals(x)) {
    281                        return i;
    282                    }
    283                }
    284                return -1;
    285            }
    286        }
    287
    288        /**
    289         * Updates an item in the series.
    290         *
    291         * @param x  the x-value (<code>null</code> not permitted).
    292         * @param y  the y-value (<code>null</code> permitted).
    293         *
    294         * @throws SeriesException if there is no existing item with the specified
    295         *         x-value.
    296         */
    297        protected void update(Comparable x, Object y) {
    298            int index = indexOf(x);
    299            if (index < 0) {
    300                throw new SeriesException("No observation for x = " + x);
    301            }
    302            else {
    303                ComparableObjectItem item = getDataItem(index);
    304                item.setObject(y);
    305                fireSeriesChanged();
    306            }
    307        }
    308
    309        /**
    310         * Updates the value of an item in the series and sends a
    311         * {@link SeriesChangeEvent} to all registered listeners.
    312         *
    313         * @param index  the item (zero based index).
    314         * @param y  the new value (<code>null</code> permitted).
    315         */
    316        protected void updateByIndex(int index, Object y) {
    317            ComparableObjectItem item = getDataItem(index);
    318            item.setObject(y);
    319            fireSeriesChanged();
    320        }
    321
    322        /**
    323         * Return the data item with the specified index.
    324         *
    325         * @param index  the index.
    326         *
    327         * @return The data item with the specified index.
    328         */
    329        protected ComparableObjectItem getDataItem(int index) {
    330            return (ComparableObjectItem) this.data.get(index);
    331        }
    332
    333        /**
    334         * Deletes a range of items from the series and sends a
    335         * {@link SeriesChangeEvent} to all registered listeners.
    336         *
    337         * @param start  the start index (zero-based).
    338         * @param end  the end index (zero-based).
    339         */
    340        protected void delete(int start, int end) {
    341            for (int i = start; i <= end; i++) {
    342                this.data.remove(start);
    343            }
    344            fireSeriesChanged();
    345        }
    346
    347        /**
    348         * Removes all data items from the series.
    349         */
    350        protected void clear() {
    351            if (this.data.size() > 0) {
    352                this.data.clear();
    353                fireSeriesChanged();
    354            }
    355        }
    356
    357        /**
    358         * Removes the item at the specified index and sends a
    359         * {@link SeriesChangeEvent} to all registered listeners.
    360         *
    361         * @param index  the index.
    362         *
    363         * @return The item removed.
    364         */
    365        protected ComparableObjectItem remove(int index) {
    366            ComparableObjectItem result = (ComparableObjectItem) this.data.remove(index);
    367            fireSeriesChanged();
    368            return result;
    369        }
    370
    371        /**
    372         * Removes the item with the specified x-value and sends a
    373         * {@link SeriesChangeEvent} to all registered listeners.
    374         *
    375         * @param x  the x-value.
    376
    377         * @return The item removed.
    378         */
    379        public ComparableObjectItem remove(Comparable x) {
    380            return remove(indexOf(x));
    381        }
    382
    383        /**
    384         * Tests this series for equality with an arbitrary object.
    385         *
    386         * @param obj  the object to test against for equality
    387         *             (<code>null</code> permitted).
    388         *
    389         * @return A boolean.
    390         */
    391        public boolean equals(Object obj) {
    392            if (obj == this) {
    393                return true;
    394            }
    395            if (!(obj instanceof ComparableObjectSeries)) {
    396                return false;
    397            }
    398            if (!super.equals(obj)) {
    399                return false;
    400            }
    401            ComparableObjectSeries that = (ComparableObjectSeries) obj;
    402            if (this.maximumItemCount != that.maximumItemCount) {
    403                return false;
    404            }
    405            if (this.autoSort != that.autoSort) {
    406                return false;
    407            }
    408            if (this.allowDuplicateXValues != that.allowDuplicateXValues) {
    409                return false;
    410            }
    411            if (!ObjectUtilities.equal(this.data, that.data)) {
    412                return false;
    413            }
    414            return true;
    415        }
    416
    417        /**
    418         * Returns a hash code.
    419         *
    420         * @return A hash code.
    421         */
    422        public int hashCode() {
    423            int result = super.hashCode();
    424            result = 29 * result + (this.data != null ? this.data.hashCode() : 0);
    425            result = 29 * result + this.maximumItemCount;
    426            result = 29 * result + (this.autoSort ? 1 : 0);
    427            result = 29 * result + (this.allowDuplicateXValues ? 1 : 0);
    428            return result;
    429        }
    430
    431    }