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