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