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    
055    package org.jfree.data.general;
056    
057    import java.beans.PropertyChangeListener;
058    import java.beans.PropertyChangeSupport;
059    import java.io.Serializable;
060    
061    import javax.swing.event.EventListenerList;
062    
063    import 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     */
075    public 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    }