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