001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
034 * 
035 */
036
037package org.jfree.data.general;
038
039import org.jfree.chart.util.Args;
040
041import javax.swing.event.EventListenerList;
042import java.beans.*;
043import java.io.IOException;
044import java.io.ObjectInputStream;
045import java.io.ObjectOutputStream;
046import java.io.Serializable;
047import java.util.Objects;
048
049/**
050 * Base class representing a data series.  Subclasses are left to implement the
051 * actual data structures.
052 * <P>
053 * The series has two properties ("Key" and "Description") for which you can
054 * register a {@code PropertyChangeListener}.
055 * <P>
056 * You can also register a {@link SeriesChangeListener} to receive notification
057 * of changes to the series data.
058 */
059public abstract class Series implements Cloneable, Serializable {
060
061    /** For serialization. */
062    private static final long serialVersionUID = -6906561437538683581L;
063
064    /** The key for the series. */
065    private Comparable key;
066
067    /** A description of the series. */
068    private String description;
069
070    /** Storage for registered change listeners. */
071    private transient EventListenerList listeners;
072
073    /** Object to support property change notification. */
074    private transient PropertyChangeSupport propertyChangeSupport;
075
076    /** Object to support property change notification. */
077    private transient VetoableChangeSupport vetoableChangeSupport;
078
079    /** A flag that controls whether changes are notified. */
080    private boolean notify;
081
082    /**
083     * Creates a new series with the specified key.
084     *
085     * @param key  the series key ({@code null} not permitted).
086     */
087    protected Series(Comparable key) {
088        this(key, null);
089    }
090
091    /**
092     * Creates a new series with the specified key and description.
093     *
094     * @param key  the series key ({@code null} NOT permitted).
095     * @param description  the series description ({@code null} permitted).
096     */
097    protected Series(Comparable key, String description) {
098        Args.nullNotPermitted(key, "key");
099        this.key = key;
100        this.description = description;
101        this.listeners = new EventListenerList();
102        this.propertyChangeSupport = new PropertyChangeSupport(this);
103        this.vetoableChangeSupport = new VetoableChangeSupport(this);
104        this.notify = true;
105    }
106
107    /**
108     * Returns the key for the series.
109     *
110     * @return The series key (never {@code null}).
111     *
112     * @see #setKey(Comparable)
113     */
114    public Comparable getKey() {
115        return this.key;
116    }
117
118    /**
119     * Sets the key for the series and sends a {@code VetoableChangeEvent}
120     * (with the property name "Key") to all registered listeners.  For 
121     * backwards compatibility, this method also fires a regular 
122     * {@code PropertyChangeEvent}.  If the key change is vetoed this 
123     * method will throw an IllegalArgumentException.
124     *
125     * This implementation is not very robust when cloning or deserialising
126     * series collections, so you should not rely upon it for that purpose.
127     * In future releases, the series key will be made immutable.
128     *
129     * @param key  the key ({@code null} not permitted).
130     *
131     * @see #getKey()
132     * @deprecated In future releases the series key will be immutable.
133     */
134    @Deprecated
135    public void setKey(Comparable key) {
136        Args.nullNotPermitted(key, "key");
137        Comparable old = this.key;
138        try {
139            // if this series belongs to a dataset, the dataset might veto the
140            // change if it results in two series within the dataset having the
141            // same key
142            this.vetoableChangeSupport.fireVetoableChange("Key", old, key);
143            this.key = key;
144            // prior to 1.0.14, we just fired a PropertyChange - so we need to
145            // keep doing this
146            this.propertyChangeSupport.firePropertyChange("Key", old, key);
147        } catch (PropertyVetoException e) {
148            throw new IllegalArgumentException(e.getMessage());
149        }
150    }
151
152    /**
153     * Returns a description of the series.
154     *
155     * @return The series description (possibly {@code null}).
156     *
157     * @see #setDescription(String)
158     */
159    public String getDescription() {
160        return this.description;
161    }
162
163    /**
164     * Sets the description of the series and sends a
165     * {@code PropertyChangeEvent} to all registered listeners.
166     *
167     * @param description  the description ({@code null} permitted).
168     *
169     * @see #getDescription()
170     */
171    public void setDescription(String description) {
172        String old = this.description;
173        this.description = description;
174        this.propertyChangeSupport.firePropertyChange("Description", old,
175                description);
176    }
177
178    /**
179     * Returns the flag that controls whether or not change events are sent to
180     * registered listeners.
181     *
182     * @return A boolean.
183     *
184     * @see #setNotify(boolean)
185     */
186    public boolean getNotify() {
187        return this.notify;
188    }
189
190    /**
191     * Sets the flag that controls whether or not change events are sent to
192     * registered listeners.
193     *
194     * @param notify  the new value of the flag.
195     *
196     * @see #getNotify()
197     */
198    public void setNotify(boolean notify) {
199        if (this.notify != notify) {
200            this.notify = notify;
201            fireSeriesChanged();
202        }
203    }
204
205    /**
206     * Returns {@code true} if the series contains no data items, and
207     * {@code false} otherwise.
208     *
209     * @return A boolean.
210     */
211    public boolean isEmpty() {
212        return (getItemCount() == 0);
213    }
214
215    /**
216     * Returns the number of data items in the series.
217     *
218     * @return The number of data items in the series.
219     */
220    public abstract int getItemCount();
221
222    /**
223     * Returns a clone of the series.
224     * <P>
225     * Notes:
226     * <ul>
227     * <li>No need to clone the name or description, since String object is
228     * immutable.</li>
229     * <li>We set the listener list to empty, since the listeners did not
230     * register with the clone.</li>
231     * <li>Same applies to the PropertyChangeSupport instance.</li>
232     * </ul>
233     *
234     * @return A clone of the series.
235     *
236     * @throws CloneNotSupportedException  not thrown by this class, but
237     *         subclasses may differ.
238     */
239    @Override
240    public Object clone() throws CloneNotSupportedException {
241        Series clone = (Series) super.clone();
242        clone.listeners = new EventListenerList();
243        clone.propertyChangeSupport = new PropertyChangeSupport(clone);
244        clone.vetoableChangeSupport = new VetoableChangeSupport(clone);
245        return clone;
246    }
247
248    /**
249     * Tests the series for equality with another object.
250     *
251     * @param obj  the object ({@code null} permitted).
252     *
253     * @return {@code true} or {@code false}.
254     */
255    @Override
256    public boolean equals(Object obj) {
257        if (obj == this) {
258            return true;
259        }
260        if (!(obj instanceof Series)) {
261            return false;
262        }
263        Series that = (Series) obj;
264        if (!Objects.equals(this.key, that.key)) {
265            return false;
266        }
267        if (!Objects.equals(this.description, that.description)) {
268            return false;
269        }
270        if (!that.canEqual(this)) {
271            return false;
272        }
273        return true;
274    }
275
276    /**
277     * Ensures symmetry between super/subclass implementations of equals. For
278     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
279     *
280     * @param other Object
281     * 
282     * @return true ONLY if the parameter is THIS class type
283     */
284    public boolean canEqual(Object other) {
285        // fix the "equals not symmetric" problem
286        return (other instanceof Series);
287    }
288
289    /**
290     * Returns a hash code.
291     *
292     * @return A hash code.
293     */
294    @Override
295    public int hashCode() {
296        int hash = 5;
297        hash = 53 * hash + Objects.hashCode(this.key);
298        hash = 53 * hash + Objects.hashCode(this.description);
299        return hash;
300    }
301
302    /**
303     * Registers an object with this series, to receive notification whenever
304     * the series changes.
305     * <P>
306     * Objects being registered must implement the {@link SeriesChangeListener}
307     * interface.
308     *
309     * @param listener  the listener to register.
310     */
311    public void addChangeListener(SeriesChangeListener listener) {
312        this.listeners.add(SeriesChangeListener.class, listener);
313    }
314
315    /**
316     * Deregisters an object, so that it not longer receives notification
317     * whenever the series changes.
318     *
319     * @param listener  the listener to deregister.
320     */
321    public void removeChangeListener(SeriesChangeListener listener) {
322        this.listeners.remove(SeriesChangeListener.class, listener);
323    }
324
325    /**
326     * General method for signalling to registered listeners that the series
327     * has been changed.
328     */
329    public void fireSeriesChanged() {
330        if (this.notify) {
331            notifyListeners(new SeriesChangeEvent(this));
332        }
333    }
334
335    /**
336     * Sends a change event to all registered listeners.
337     *
338     * @param event  contains information about the event that triggered the
339     *               notification.
340     */
341    protected void notifyListeners(SeriesChangeEvent event) {
342
343        Object[] listenerList = this.listeners.getListenerList();
344        for (int i = listenerList.length - 2; i >= 0; i -= 2) {
345            if (listenerList[i] == SeriesChangeListener.class) {
346                ((SeriesChangeListener) listenerList[i + 1]).seriesChanged(
347                        event);
348            }
349        }
350
351    }
352
353    /**
354     * Adds a property change listener to the series.
355     *
356     * @param listener  the listener.
357     */
358    public void addPropertyChangeListener(PropertyChangeListener listener) {
359        this.propertyChangeSupport.addPropertyChangeListener(listener);
360    }
361
362    /**
363     * Removes a property change listener from the series.
364     *
365     * @param listener  the listener.
366     */
367    public void removePropertyChangeListener(PropertyChangeListener listener) {
368        this.propertyChangeSupport.removePropertyChangeListener(listener);
369    }
370
371    /**
372     * Fires a property change event.
373     *
374     * @param property  the property key.
375     * @param oldValue  the old value.
376     * @param newValue  the new value.
377     */
378    protected void firePropertyChange(String property, Object oldValue,
379            Object newValue) {
380        this.propertyChangeSupport.firePropertyChange(property, oldValue,
381                newValue);
382    }
383    
384    /**
385     * Adds a vetoable property change listener to the series.
386     *
387     * @param listener  the listener.
388     */
389    public void addVetoableChangeListener(VetoableChangeListener listener) {
390        this.vetoableChangeSupport.addVetoableChangeListener(listener);
391    }
392
393    /**
394     * Removes a vetoable property change listener from the series.
395     *
396     * @param listener  the listener.
397     */
398    public void removeVetoableChangeListener(VetoableChangeListener listener) {
399        this.vetoableChangeSupport.removeVetoableChangeListener(listener);
400    }    
401
402    /**
403     * Fires a vetoable property change event.
404     *
405     * @param property  the property key.
406     * @param oldValue  the old value.
407     * @param newValue  the new value.
408     * 
409     * @throws PropertyVetoException if the change was vetoed.
410     */
411    protected void fireVetoableChange(String property, Object oldValue,
412            Object newValue) throws PropertyVetoException {
413        this.vetoableChangeSupport.fireVetoableChange(property, oldValue,
414                newValue);
415    }
416    /**
417     * Provides serialization support.
418     *
419     * @param stream  the output stream ({@code null} not permitted).
420     *
421     * @throws IOException  if there is an I/O error.
422     */
423    private void writeObject(ObjectOutputStream stream) throws IOException {
424        stream.defaultWriteObject();
425    }
426
427    /**
428     * Provides serialization support.
429     *
430     * @param stream  the input stream ({@code null} not permitted).
431     *
432     * @throws IOException  if there is an I/O error.
433     * @throws ClassNotFoundException  if there is a classpath problem.
434     */
435    private void readObject(ObjectInputStream stream)
436            throws IOException, ClassNotFoundException {
437        stream.defaultReadObject();
438        this.listeners = new EventListenerList();
439        this.propertyChangeSupport = new PropertyChangeSupport(this);
440        this.vetoableChangeSupport = new VetoableChangeSupport(this);
441    }
442
443}