    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 * Series.java
    029 * -----------
    030 * (C) Copyright 2001-2011, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 15-Nov-2001 : Version 1 (DG);
    038 * 29-Nov-2001 : Added cloning and property change support (DG);
    039 * 30-Jan-2002 : Added a description attribute and changed the constructors to
    040 *               protected (DG);
    041 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    042 * 13-Mar-2003 : Implemented Serializable (DG);
    043 * 01-May-2003 : Added equals() method (DG);
    044 * 26-Jun-2003 : Changed listener list to use EventListenerList - see bug
    045 *               757027 (DG);
    046 * 15-Oct-2003 : Added a flag to control whether or not change events are sent
    047 *               to registered listeners (DG);
    048 * 19-May-2005 : Made abstract (DG);
    049 * ------------- JFREECHART 1.0.x ---------------------------------------------
    050 * 04-May-2006 : Updated API docs (DG);
    051 * 26-Sep-2007 : Added isEmpty() and getItemCount() methods (DG);
    052 * 16-Oct-2011 : Added vetoable property change support for series name (DG);
    053 */
    054
    055package org.jfree.data.general;
    056
    057import java.beans.PropertyChangeListener;
    058import java.beans.PropertyChangeSupport;
    059import java.beans.PropertyVetoException;
    060import java.beans.VetoableChangeListener;
    061import java.beans.VetoableChangeSupport;
    062import java.io.Serializable;
    063
    064import javax.swing.event.EventListenerList;
    065
    066import org.jfree.chart.util.ParamChecks;
    067import org.jfree.util.ObjectUtilities;
    068
    069/**
    070 * Base class representing a data series.  Subclasses are left to implement the
    071 * actual data structures.
    072 * <P>
    073 * The series has two properties ("Key" and "Description") for which you can
    074 * register a <code>PropertyChangeListener</code>.
    075 * <P>
    076 * You can also register a {@link SeriesChangeListener} to receive notification
    077 * of changes to the series data.
    078 */
    079public abstract class Series implements Cloneable, Serializable {
    080
    081    /** For serialization. */
    082    private static final long serialVersionUID = -6906561437538683581L;
    083
    084    /** The key for the series. */
    085    private Comparable key;
    086
    087    /** A description of the series. */
    088    private String description;
    089
    090    /** Storage for registered change listeners. */
    091    private EventListenerList listeners;
    092
    093    /** Object to support property change notification. */
    094    private PropertyChangeSupport propertyChangeSupport;
    095
    096    /** Object to support property change notification. */
    097    private VetoableChangeSupport vetoableChangeSupport;
    098
    099    /** A flag that controls whether or not changes are notified. */
    100    private boolean notify;
    101
    102    /**
    103     * Creates a new series with the specified key.
    104     *
    105     * @param key  the series key (<code>null</code> not permitted).
    106     */
    107    protected Series(Comparable key) {
    108        this(key, null);
    109    }
    110
    111    /**
    112     * Creates a new series with the specified key and description.
    113     *
    114     * @param key  the series key (<code>null</code> NOT permitted).
    115     * @param description  the series description (<code>null</code> permitted).
    116     */
    117    protected Series(Comparable key, String description) {
    118        if (key == null) {
    119            throw new IllegalArgumentException("Null 'key' argument.");
    120        }
    121        this.key = key;
    122        this.description = description;
    123        this.listeners = new EventListenerList();
    124        this.propertyChangeSupport = new PropertyChangeSupport(this);
    125        this.vetoableChangeSupport = new VetoableChangeSupport(this);
    126        this.notify = true;
    127    }
    128
    129    /**
    130     * Returns the key for the series.
    131     *
    132     * @return The series key (never <code>null</code>).
    133     *
    134     * @see #setKey(Comparable)
    135     */
    136    public Comparable getKey() {
    137        return this.key;
    138    }
    139
    140    /**
    141     * Sets the key for the series and sends a <code>VetoableChangeEvent</code>
    142     * (with the property name "Key") to all registered listeners.  For
    143     * backwards compatibility, this method also fires a regular
    144     * <code>PropertyChangeEvent</code>.
    145     *
    146     * @param key  the key (<code>null</code> not permitted).
    147     *
    148     * @see #getKey()
    149     */
    150    public void setKey(Comparable key) {
    151        ParamChecks.nullNotPermitted(key, "key");
    152        Comparable old = this.key;
    153        try {
    154            // if this series belongs to a dataset, the dataset might veto the
    155            // change if it results in two series within the dataset having the
    156            // same key
    157            this.vetoableChangeSupport.fireVetoableChange("Key", old, key);
    158            this.key = key;
    159            // prior to 1.0.14, we just fired a PropertyChange - so we need to
    160            // keep doing this
    161            this.propertyChangeSupport.firePropertyChange("Key", old, key);
    162        } catch (PropertyVetoException e) {
    163            throw new IllegalArgumentException(e.getMessage());
    164        }
    165    }
    166
    167    /**
    168     * Returns a description of the series.
    169     *
    170     * @return The series description (possibly <code>null</code>).
    171     *
    172     * @see #setDescription(String)
    173     */
    174    public String getDescription() {
    175        return this.description;
    176    }
    177
    178    /**
    179     * Sets the description of the series and sends a
    180     * <code>PropertyChangeEvent</code> to all registered listeners.
    181     *
    182     * @param description  the description (<code>null</code> permitted).
    183     *
    184     * @see #getDescription()
    185     */
    186    public void setDescription(String description) {
    187        String old = this.description;
    188        this.description = description;
    189        this.propertyChangeSupport.firePropertyChange("Description", old,
    190                description);
    191    }
    192
    193    /**
    194     * Returns the flag that controls whether or not change events are sent to
    195     * registered listeners.
    196     *
    197     * @return A boolean.
    198     *
    199     * @see #setNotify(boolean)
    200     */
    201    public boolean getNotify() {
    202        return this.notify;
    203    }
    204
    205    /**
    206     * Sets the flag that controls whether or not change events are sent to
    207     * registered listeners.
    208     *
    209     * @param notify  the new value of the flag.
    210     *
    211     * @see #getNotify()
    212     */
    213    public void setNotify(boolean notify) {
    214        if (this.notify != notify) {
    215            this.notify = notify;
    216            fireSeriesChanged();
    217        }
    218    }
    219
    220    /**
    221     * Returns <code>true</code> if the series contains no data items, and
    222     * <code>false</code> otherwise.
    223     *
    224     * @return A boolean.
    225     *
    226     * @since 1.0.7
    227     */
    228    public boolean isEmpty() {
    229        return (getItemCount() == 0);
    230    }
    231
    232    /**
    233     * Returns the number of data items in the series.
    234     *
    235     * @return The number of data items in the series.
    236     */
    237    public abstract int getItemCount();
    238
    239    /**
    240     * Returns a clone of the series.
    241     * <P>
    242     * Notes:
    243     * <ul>
    244     * <li>No need to clone the name or description, since String object is
    245     * immutable.</li>
    246     * <li>We set the listener list to empty, since the listeners did not
    247     * register with the clone.</li>
    248     * <li>Same applies to the PropertyChangeSupport instance.</li>
    249     * </ul>
    250     *
    251     * @return A clone of the series.
    252     *
    253     * @throws CloneNotSupportedException  not thrown by this class, but
    254     *         subclasses may differ.
    255     */
    256    public Object clone() throws CloneNotSupportedException {
    257        Series clone = (Series) super.clone();
    258        clone.listeners = new EventListenerList();
    259        clone.propertyChangeSupport = new PropertyChangeSupport(clone);
    260        clone.vetoableChangeSupport = new VetoableChangeSupport(clone);
    261        return clone;
    262    }
    263
    264    /**
    265     * Tests the series for equality with another object.
    266     *
    267     * @param obj  the object (<code>null</code> permitted).
    268     *
    269     * @return <code>true</code> or <code>false</code>.
    270     */
    271    public boolean equals(Object obj) {
    272        if (obj == this) {
    273            return true;
    274        }
    275        if (!(obj instanceof Series)) {
    276            return false;
    277        }
    278        Series that = (Series) obj;
    279        if (!getKey().equals(that.getKey())) {
    280            return false;
    281        }
    282        if (!ObjectUtilities.equal(getDescription(), that.getDescription())) {
    283            return false;
    284        }
    285        return true;
    286    }
    287
    288    /**
    289     * Returns a hash code.
    290     *
    291     * @return A hash code.
    292     */
    293    public int hashCode() {
    294        int result;
    295        result = this.key.hashCode();
    296        result = 29 * result + (this.description != null
    297                ? this.description.hashCode() : 0);
    298        return result;
    299    }
    300
    301    /**
    302     * Registers an object with this series, to receive notification whenever
    303     * the series changes.
    304     * <P>
    305     * Objects being registered must implement the {@link SeriesChangeListener}
    306     * interface.
    307     *
    308     * @param listener  the listener to register.
    309     */
    310    public void addChangeListener(SeriesChangeListener listener) {
    311        this.listeners.add(SeriesChangeListener.class, listener);
    312    }
    313
    314    /**
    315     * Deregisters an object, so that it not longer receives notification
    316     * whenever the series changes.
    317     *
    318     * @param listener  the listener to deregister.
    319     */
    320    public void removeChangeListener(SeriesChangeListener listener) {
    321        this.listeners.remove(SeriesChangeListener.class, listener);
    322    }
    323
    324    /**
    325     * General method for signalling to registered listeners that the series
    326     * has been changed.
    327     */
    328    public void fireSeriesChanged() {
    329        if (this.notify) {
    330            notifyListeners(new SeriesChangeEvent(this));
    331        }
    332    }
    333
    334    /**
    335     * Sends a change event to all registered listeners.
    336     *
    337     * @param event  contains information about the event that triggered the
    338     *               notification.
    339     */
    340    protected void notifyListeners(SeriesChangeEvent event) {
    341
    342        Object[] listenerList = this.listeners.getListenerList();
    343        for (int i = listenerList.length - 2; i >= 0; i -= 2) {
    344            if (listenerList[i] == SeriesChangeListener.class) {
    345                ((SeriesChangeListener) listenerList[i + 1]).seriesChanged(
    346                        event);
    347            }
    348        }
    349
    350    }
    351
    352    /**
    353     * Adds a property change listener to the series.
    354     *
    355     * @param listener  the listener.
    356     */
    357    public void addPropertyChangeListener(PropertyChangeListener listener) {
    358        this.propertyChangeSupport.addPropertyChangeListener(listener);
    359    }
    360
    361    /**
    362     * Removes a property change listener from the series.
    363     *
    364     * @param listener  the listener.
    365     */
    366    public void removePropertyChangeListener(PropertyChangeListener listener) {
    367        this.propertyChangeSupport.removePropertyChangeListener(listener);
    368    }
    369
    370    /**
    371     * Fires a property change event.
    372     *
    373     * @param property  the property key.
    374     * @param oldValue  the old value.
    375     * @param newValue  the new value.
    376     */
    377    protected void firePropertyChange(String property, Object oldValue,
    378            Object newValue) {
    379        this.propertyChangeSupport.firePropertyChange(property, oldValue,
    380                newValue);
    381    }
    382
    383    /**
    384     * Adds a vetoable property change listener to the series.
    385     *
    386     * @param listener  the listener.
    387     *
    388     * @since 1.0.14
    389     */
    390    public void addVetoableChangeListener(VetoableChangeListener listener) {
    391        this.vetoableChangeSupport.addVetoableChangeListener(listener);
    392    }
    393
    394    /**
    395     * Removes a vetoable property change listener from the series.
    396     *
    397     * @param listener  the listener.
    398     *
    399     * @since 1.0.14
    400     */
    401    public void removeVetoableChangeListener(VetoableChangeListener listener) {
    402        this.vetoableChangeSupport.removeVetoableChangeListener(listener);
    403    }
    404
    405    /**
    406     * Fires a vetoable property change event.
    407     *
    408     * @param property  the property key.
    409     * @param oldValue  the old value.
    410     * @param newValue  the new value.
    411     */
    412    protected void fireVetoableChange(String property, Object oldValue,
    413            Object newValue) throws PropertyVetoException {
    414        this.vetoableChangeSupport.fireVetoableChange(property, oldValue,
    415                newValue);
    416    }
    417
    418}