001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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) Copyright 2000-2022, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Nicolas Brodu (for Astrium and EADS Corporate Research
034 *                   Center);
035 *
036 */
037
038package org.jfree.data.general;
039
040import javax.swing.event.EventListenerList;
041import java.io.*;
042import java.util.Arrays;
043import java.util.EventListener;
044import java.util.List;
045
046/**
047 * An abstract implementation of the {@link Dataset} interface, containing a
048 * mechanism for registering change listeners.
049 */
050public abstract class AbstractDataset implements Dataset, Cloneable,
051        Serializable, ObjectInputValidation {
052
053    /** For serialization. */
054    private static final long serialVersionUID = 1918768939869230744L;
055
056    /** Storage for registered change listeners. */
057    private transient EventListenerList listenerList;
058    
059    /** 
060     * A flag that can be used to temporarily suppress dataset change event
061     * notifications.
062     */
063    private boolean notify;
064
065    /**
066     * Constructs a dataset.
067     */
068    protected AbstractDataset() {
069        this.listenerList = new EventListenerList();
070        this.notify = true;
071    }
072
073    /**
074     * Returns the value of the notify flag.  The default value is 
075     * {@code true}.  If this is {@code false}, calls to the 
076     * {@link #fireDatasetChanged()} method will NOT trigger a dataset
077     * change event.
078     * 
079     * @return A boolean.
080     */
081    public boolean getNotify() {
082        return this.notify;
083    }
084    
085    /**
086     * Sets the notify flag, which controls whether or not the {@link #fireDatasetChanged()}
087     * method notifies listeners.  Setting this flag to {@code true} will
088     * trigger a {@code DatasetChangeEvent} because there may be 
089     * queued up changes.
090     * 
091     * @param notify  the new flag value.
092     */
093    public void setNotify(boolean notify) {
094        this.notify = notify;
095        if (notify) {
096            fireDatasetChanged();
097        }    
098    }
099    
100    /**
101     * Registers an object to receive notification of changes to the dataset.
102     *
103     * @param listener  the object to register.
104     *
105     * @see #removeChangeListener(DatasetChangeListener)
106     */
107    @Override
108    public void addChangeListener(DatasetChangeListener listener) {
109        this.listenerList.add(DatasetChangeListener.class, listener);
110    }
111
112    /**
113     * Deregisters an object so that it no longer receives notification of
114     * changes to the dataset.
115     *
116     * @param listener  the object to deregister.
117     *
118     * @see #addChangeListener(DatasetChangeListener)
119     */
120    @Override
121    public void removeChangeListener(DatasetChangeListener listener) {
122        this.listenerList.remove(DatasetChangeListener.class, listener);
123    }
124
125    /**
126     * Returns {@code true} if the specified object is registered with
127     * the dataset as a listener.  Most applications won't need to call this
128     * method, it exists mainly for use by unit testing code.
129     *
130     * @param listener  the listener.
131     *
132     * @return A boolean.
133     *
134     * @see #addChangeListener(DatasetChangeListener)
135     * @see #removeChangeListener(DatasetChangeListener)
136     */
137    public boolean hasListener(EventListener listener) {
138        List list = Arrays.asList(this.listenerList.getListenerList());
139        return list.contains(listener);
140    }
141
142    /**
143     * Notifies all registered listeners that the dataset has changed, 
144     * provided that the {@code notify} flag has not been set to 
145     * {@code false}.
146     *
147     * @see #addChangeListener(DatasetChangeListener)
148     */
149    protected void fireDatasetChanged() {
150        if (this.notify) {
151            notifyListeners(new DatasetChangeEvent(this, this));
152        }
153    }
154
155    /**
156     * Notifies all registered listeners that the dataset has changed.
157     *
158     * @param event  contains information about the event that triggered the
159     *               notification.
160     *
161     * @see #addChangeListener(DatasetChangeListener)
162     * @see #removeChangeListener(DatasetChangeListener)
163     */
164    protected void notifyListeners(DatasetChangeEvent event) {
165        Object[] listeners = this.listenerList.getListenerList();
166        for (int i = listeners.length - 2; i >= 0; i -= 2) {
167            if (listeners[i] == DatasetChangeListener.class) {
168                ((DatasetChangeListener) listeners[i + 1]).datasetChanged(
169                        event);
170            }
171        }
172    }
173
174    /**
175     * Returns a clone of the dataset. The cloned dataset will NOT include the
176     * {@link DatasetChangeListener} references that have been registered with
177     * this dataset.
178     *
179     * @return A clone.
180     *
181     * @throws CloneNotSupportedException  if the dataset does not support
182     *                                     cloning.
183     */
184    @Override
185    public Object clone() throws CloneNotSupportedException {
186        AbstractDataset clone = (AbstractDataset) super.clone();
187        clone.listenerList = new EventListenerList();
188        return clone;
189    }
190
191    /**
192     * Handles serialization.
193     *
194     * @param stream  the output stream.
195     *
196     * @throws IOException if there is an I/O problem.
197     */
198    private void writeObject(ObjectOutputStream stream) throws IOException {
199        stream.defaultWriteObject();
200    }
201
202    /**
203     * Restores a serialized object.
204     *
205     * @param stream  the input stream.
206     *
207     * @throws IOException if there is an I/O problem.
208     * @throws ClassNotFoundException if there is a problem loading a class.
209     */
210    private void readObject(ObjectInputStream stream)
211        throws IOException, ClassNotFoundException {
212        stream.defaultReadObject();
213        this.listenerList = new EventListenerList();
214        stream.registerValidation(this, 10);  // see comments about priority of
215                                              // 10 in validateObject()
216    }
217
218    /**
219     * Validates the object. We use this opportunity to call listeners who have
220     * registered during the deserialization process, as listeners are not
221     * serialized. This method is called by the serialization system after the
222     * entire graph is read.
223     *
224     * This object has registered itself to the system with a priority of 10.
225     * Other callbacks may register with a higher priority number to be called
226     * before this object, or with a lower priority number to be called after
227     * the listeners were notified.
228     *
229     * All listeners are supposed to have register by now, either in their
230     * readObject or validateObject methods. Notify them that this dataset has
231     * changed.
232     *
233     * @exception InvalidObjectException If the object cannot validate itself.
234     */
235    @Override
236    public void validateObject() throws InvalidObjectException {
237        fireDatasetChanged();
238    }
239
240}