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}