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