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 * AbstractDataset.java
029 * --------------------
030 * (C)opyright 2000-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Nicolas Brodu (for Astrium and EADS Corporate Research
034 *                   Center);
035                     Tracy Hiltbrand (added equals/canEqual/hashCode);
036 *
037 */
038
039package org.jfree.data.general;
040
041import java.io.IOException;
042import java.io.InvalidObjectException;
043import java.io.ObjectInputStream;
044import java.io.ObjectInputValidation;
045import java.io.ObjectOutputStream;
046import java.io.Serializable;
047import java.util.Arrays;
048import java.util.EventListener;
049import java.util.List;
050import java.util.Objects;
051
052import javax.swing.event.EventListenerList;
053import org.jfree.chart.util.Args;
054
055/**
056 * An abstract implementation of the {@link Dataset} interface, containing a
057 * mechanism for registering change listeners.
058 */
059public abstract class AbstractDataset implements Dataset, Cloneable,
060        Serializable, ObjectInputValidation {
061
062    /** For serialization. */
063    private static final long serialVersionUID = 1918768939869230744L;
064
065    /** The group that the dataset belongs to. */
066    private DatasetGroup group;
067
068    /** Storage for registered change listeners. */
069    private transient EventListenerList listenerList;
070    
071    /** 
072     * A flag that can be used to temporarily suppress dataset change event
073     * notifications.
074     */
075    private boolean notify;
076
077    @Override
078    public int hashCode() {
079        int hash = 3;
080        hash = 29 * hash + Objects.hashCode(this.group);
081        hash = 29 * hash + (this.notify ? 1 : 0);
082        return hash;
083    }
084
085    /**
086     * Ensures symmetry between super/subclass implementations of equals. For
087     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
088     *
089     * @param other Object
090     * 
091     * @return true ONLY if the parameter is THIS class type
092     */
093    public boolean canEqual(Object other) {
094        // fix the "equals not symmetric" problem
095        return (other instanceof AbstractDataset);
096    }
097
098    @Override
099    public boolean equals(Object obj) {
100        if (this == obj) {
101            return true;
102        }
103        if (!(obj instanceof AbstractDataset)) {
104            return false;
105        }
106        
107        AbstractDataset that = (AbstractDataset) obj;
108        if (this.notify != that.notify) {
109            return false;
110        }
111        if (!Objects.equals(this.group, that.group)) {
112            return false;
113        }
114        if (!that.canEqual(this)) {
115            return false;
116        }
117        return true;
118    }
119
120    /**
121     * Constructs a dataset. By default, the dataset is assigned to its own
122     * group.
123     */
124    protected AbstractDataset() {
125        this.group = new DatasetGroup();
126        this.listenerList = new EventListenerList();
127        this.notify = true;
128    }
129
130    /**
131     * Returns the dataset group for the dataset.
132     *
133     * @return The group (never {@code null}).
134     *
135     * @see #setGroup(DatasetGroup)
136     */
137    @Override
138    public DatasetGroup getGroup() {
139        return this.group;
140    }
141
142    /**
143     * Sets the dataset group for the dataset.
144     *
145     * @param group  the group ({@code null} not permitted).
146     *
147     * @see #getGroup()
148     */
149    @Override
150    public void setGroup(DatasetGroup group) {
151        Args.nullNotPermitted(group, "group");
152        this.group = group;
153    }
154
155    /**
156     * Returns the value of the notify flag.  The default value is 
157     * {@code true}.  If this is {@code false}, calls to the 
158     * {@link #fireDatasetChanged()} method will NOT trigger a dataset
159     * change event.
160     * 
161     * @return A boolean.
162     */
163    public boolean getNotify() {
164        return this.notify;
165    }
166    
167    /**
168     * Sets the notify flag, which controls whether or not the {@link #fireDatasetChanged()}
169     * method notifies listeners.  Setting this flag to {@code true} will
170     * trigger a {@code DatasetChangeEvent} because there may be 
171     * queued up changes.
172     * 
173     * @param notify  the new flag value.
174     */
175    public void setNotify(boolean notify) {
176        this.notify = notify;
177        if (notify) {
178            fireDatasetChanged();
179        }    
180    }
181    
182    /**
183     * Registers an object to receive notification of changes to the dataset.
184     *
185     * @param listener  the object to register.
186     *
187     * @see #removeChangeListener(DatasetChangeListener)
188     */
189    @Override
190    public void addChangeListener(DatasetChangeListener listener) {
191        this.listenerList.add(DatasetChangeListener.class, listener);
192    }
193
194    /**
195     * Deregisters an object so that it no longer receives notification of
196     * changes to the dataset.
197     *
198     * @param listener  the object to deregister.
199     *
200     * @see #addChangeListener(DatasetChangeListener)
201     */
202    @Override
203    public void removeChangeListener(DatasetChangeListener listener) {
204        this.listenerList.remove(DatasetChangeListener.class, listener);
205    }
206
207    /**
208     * Returns {@code true} if the specified object is registered with
209     * the dataset as a listener.  Most applications won't need to call this
210     * method, it exists mainly for use by unit testing code.
211     *
212     * @param listener  the listener.
213     *
214     * @return A boolean.
215     *
216     * @see #addChangeListener(DatasetChangeListener)
217     * @see #removeChangeListener(DatasetChangeListener)
218     */
219    public boolean hasListener(EventListener listener) {
220        List list = Arrays.asList(this.listenerList.getListenerList());
221        return list.contains(listener);
222    }
223
224    /**
225     * Notifies all registered listeners that the dataset has changed, 
226     * provided that the {@code notify} flag has not been set to 
227     * {@code false}.
228     *
229     * @see #addChangeListener(DatasetChangeListener)
230     */
231    protected void fireDatasetChanged() {
232        if (this.notify) {
233            notifyListeners(new DatasetChangeEvent(this, this));
234        }
235    }
236
237    /**
238     * Notifies all registered listeners that the dataset has changed.
239     *
240     * @param event  contains information about the event that triggered the
241     *               notification.
242     *
243     * @see #addChangeListener(DatasetChangeListener)
244     * @see #removeChangeListener(DatasetChangeListener)
245     */
246    protected void notifyListeners(DatasetChangeEvent event) {
247        Object[] listeners = this.listenerList.getListenerList();
248        for (int i = listeners.length - 2; i >= 0; i -= 2) {
249            if (listeners[i] == DatasetChangeListener.class) {
250                ((DatasetChangeListener) listeners[i + 1]).datasetChanged(
251                        event);
252            }
253        }
254    }
255
256    /**
257     * Returns a clone of the dataset. The cloned dataset will NOT include the
258     * {@link DatasetChangeListener} references that have been registered with
259     * this dataset.
260     *
261     * @return A clone.
262     *
263     * @throws CloneNotSupportedException  if the dataset does not support
264     *                                     cloning.
265     */
266    @Override
267    public Object clone() throws CloneNotSupportedException {
268        AbstractDataset clone = (AbstractDataset) super.clone();
269        clone.listenerList = new EventListenerList();
270        return clone;
271    }
272
273    /**
274     * Handles serialization.
275     *
276     * @param stream  the output stream.
277     *
278     * @throws IOException if there is an I/O problem.
279     */
280    private void writeObject(ObjectOutputStream stream) throws IOException {
281        stream.defaultWriteObject();
282    }
283
284    /**
285     * Restores a serialized object.
286     *
287     * @param stream  the input stream.
288     *
289     * @throws IOException if there is an I/O problem.
290     * @throws ClassNotFoundException if there is a problem loading a class.
291     */
292    private void readObject(ObjectInputStream stream)
293        throws IOException, ClassNotFoundException {
294        stream.defaultReadObject();
295        this.listenerList = new EventListenerList();
296        stream.registerValidation(this, 10);  // see comments about priority of
297                                              // 10 in validateObject()
298    }
299
300    /**
301     * Validates the object. We use this opportunity to call listeners who have
302     * registered during the deserialization process, as listeners are not
303     * serialized. This method is called by the serialization system after the
304     * entire graph is read.
305     *
306     * This object has registered itself to the system with a priority of 10.
307     * Other callbacks may register with a higher priority number to be called
308     * before this object, or with a lower priority number to be called after
309     * the listeners were notified.
310     *
311     * All listeners are supposed to have register by now, either in their
312     * readObject or validateObject methods. Notify them that this dataset has
313     * changed.
314     *
315     * @exception InvalidObjectException If the object cannot validate itself.
316     */
317    @Override
318    public void validateObject() throws InvalidObjectException {
319        fireDatasetChanged();
320    }
321
322}