1/*
2 * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package java.beans;
26
27import java.io.Serializable;
28import java.io.ObjectStreamField;
29import java.io.ObjectOutputStream;
30import java.io.ObjectInputStream;
31import java.io.IOException;
32import java.util.Hashtable;
33import java.util.Map.Entry;
34
35/**
36 * This is a utility class that can be used by beans that support bound
37 * properties.  It manages a list of listeners and dispatches
38 * {@link PropertyChangeEvent}s to them.  You can use an instance of this class
39 * as a member field of your bean and delegate these types of work to it.
40 * The {@link PropertyChangeListener} can be registered for all properties
41 * or for a property specified by name.
42 * <p>
43 * Here is an example of {@code PropertyChangeSupport} usage that follows
44 * the rules and recommendations laid out in the JavaBeans&trade; specification:
45 * <pre>
46 * public class MyBean {
47 *     private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
48 *
49 *     public void addPropertyChangeListener(PropertyChangeListener listener) {
50 *         this.pcs.addPropertyChangeListener(listener);
51 *     }
52 *
53 *     public void removePropertyChangeListener(PropertyChangeListener listener) {
54 *         this.pcs.removePropertyChangeListener(listener);
55 *     }
56 *
57 *     private String value;
58 *
59 *     public String getValue() {
60 *         return this.value;
61 *     }
62 *
63 *     public void setValue(String newValue) {
64 *         String oldValue = this.value;
65 *         this.value = newValue;
66 *         this.pcs.firePropertyChange("value", oldValue, newValue);
67 *     }
68 *
69 *     [...]
70 * }
71 * </pre>
72 * <p>
73 * A {@code PropertyChangeSupport} instance is thread-safe.
74 * <p>
75 * This class is serializable.  When it is serialized it will save
76 * (and restore) any listeners that are themselves serializable.  Any
77 * non-serializable listeners will be skipped during serialization.
78 *
79 * @see VetoableChangeSupport
80 * @since 1.1
81 */
82public class PropertyChangeSupport implements Serializable {
83    private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
84
85    /**
86     * Constructs a {@code PropertyChangeSupport} object.
87     *
88     * @param sourceBean  The bean to be given as the source for any events.
89     */
90    public PropertyChangeSupport(Object sourceBean) {
91        if (sourceBean == null) {
92            throw new NullPointerException();
93        }
94        source = sourceBean;
95    }
96
97    /**
98     * Add a PropertyChangeListener to the listener list.
99     * The listener is registered for all properties.
100     * The same listener object may be added more than once, and will be called
101     * as many times as it is added.
102     * If {@code listener} is null, no exception is thrown and no action
103     * is taken.
104     *
105     * @param listener  The PropertyChangeListener to be added
106     */
107    public void addPropertyChangeListener(PropertyChangeListener listener) {
108        if (listener == null) {
109            return;
110        }
111        if (listener instanceof PropertyChangeListenerProxy) {
112            PropertyChangeListenerProxy proxy =
113                   (PropertyChangeListenerProxy)listener;
114            // Call two argument add method.
115            addPropertyChangeListener(proxy.getPropertyName(),
116                                      proxy.getListener());
117        } else {
118            this.map.add(null, listener);
119        }
120    }
121
122    /**
123     * Remove a PropertyChangeListener from the listener list.
124     * This removes a PropertyChangeListener that was registered
125     * for all properties.
126     * If {@code listener} was added more than once to the same event
127     * source, it will be notified one less time after being removed.
128     * If {@code listener} is null, or was never added, no exception is
129     * thrown and no action is taken.
130     *
131     * @param listener  The PropertyChangeListener to be removed
132     */
133    public void removePropertyChangeListener(PropertyChangeListener listener) {
134        if (listener == null) {
135            return;
136        }
137        if (listener instanceof PropertyChangeListenerProxy) {
138            PropertyChangeListenerProxy proxy =
139                    (PropertyChangeListenerProxy)listener;
140            // Call two argument remove method.
141            removePropertyChangeListener(proxy.getPropertyName(),
142                                         proxy.getListener());
143        } else {
144            this.map.remove(null, listener);
145        }
146    }
147
148    /**
149     * Returns an array of all the listeners that were added to the
150     * PropertyChangeSupport object with addPropertyChangeListener().
151     * <p>
152     * If some listeners have been added with a named property, then
153     * the returned array will be a mixture of PropertyChangeListeners
154     * and {@code PropertyChangeListenerProxy}s. If the calling
155     * method is interested in distinguishing the listeners then it must
156     * test each element to see if it's a
157     * {@code PropertyChangeListenerProxy}, perform the cast, and examine
158     * the parameter.
159     *
160     * <pre>{@code
161     * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
162     * for (int i = 0; i < listeners.length; i++) {
163     *   if (listeners[i] instanceof PropertyChangeListenerProxy) {
164     *     PropertyChangeListenerProxy proxy =
165     *                    (PropertyChangeListenerProxy)listeners[i];
166     *     if (proxy.getPropertyName().equals("foo")) {
167     *       // proxy is a PropertyChangeListener which was associated
168     *       // with the property named "foo"
169     *     }
170     *   }
171     * }
172     * }</pre>
173     *
174     * @see PropertyChangeListenerProxy
175     * @return all of the {@code PropertyChangeListeners} added or an
176     *         empty array if no listeners have been added
177     * @since 1.4
178     */
179    public PropertyChangeListener[] getPropertyChangeListeners() {
180        return this.map.getListeners();
181    }
182
183    /**
184     * Add a PropertyChangeListener for a specific property.  The listener
185     * will be invoked only when a call on firePropertyChange names that
186     * specific property.
187     * The same listener object may be added more than once.  For each
188     * property,  the listener will be invoked the number of times it was added
189     * for that property.
190     * If {@code propertyName} or {@code listener} is null, no
191     * exception is thrown and no action is taken.
192     *
193     * @param propertyName  The name of the property to listen on.
194     * @param listener  The PropertyChangeListener to be added
195     * @since 1.2
196     */
197    public void addPropertyChangeListener(
198                String propertyName,
199                PropertyChangeListener listener) {
200        if (listener == null || propertyName == null) {
201            return;
202        }
203        listener = this.map.extract(listener);
204        if (listener != null) {
205            this.map.add(propertyName, listener);
206        }
207    }
208
209    /**
210     * Remove a PropertyChangeListener for a specific property.
211     * If {@code listener} was added more than once to the same event
212     * source for the specified property, it will be notified one less time
213     * after being removed.
214     * If {@code propertyName} is null,  no exception is thrown and no
215     * action is taken.
216     * If {@code listener} is null, or was never added for the specified
217     * property, no exception is thrown and no action is taken.
218     *
219     * @param propertyName  The name of the property that was listened on.
220     * @param listener  The PropertyChangeListener to be removed
221     * @since 1.2
222     */
223    public void removePropertyChangeListener(
224                String propertyName,
225                PropertyChangeListener listener) {
226        if (listener == null || propertyName == null) {
227            return;
228        }
229        listener = this.map.extract(listener);
230        if (listener != null) {
231            this.map.remove(propertyName, listener);
232        }
233    }
234
235    /**
236     * Returns an array of all the listeners which have been associated
237     * with the named property.
238     *
239     * @param propertyName  The name of the property being listened to
240     * @return all of the {@code PropertyChangeListeners} associated with
241     *         the named property.  If no such listeners have been added,
242     *         or if {@code propertyName} is null, an empty array is
243     *         returned.
244     * @since 1.4
245     */
246    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
247        return this.map.getListeners(propertyName);
248    }
249
250    /**
251     * Reports a bound property update to listeners
252     * that have been registered to track updates of
253     * all properties or a property with the specified name.
254     * <p>
255     * No event is fired if old and new values are equal and non-null.
256     * <p>
257     * This is merely a convenience wrapper around the more general
258     * {@link #firePropertyChange(PropertyChangeEvent)} method.
259     *
260     * @param propertyName  the programmatic name of the property that was changed
261     * @param oldValue      the old value of the property
262     * @param newValue      the new value of the property
263     */
264    public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
265        if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
266            firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
267        }
268    }
269
270    /**
271     * Reports an integer bound property update to listeners
272     * that have been registered to track updates of
273     * all properties or a property with the specified name.
274     * <p>
275     * No event is fired if old and new values are equal.
276     * <p>
277     * This is merely a convenience wrapper around the more general
278     * {@link #firePropertyChange(String, Object, Object)}  method.
279     *
280     * @param propertyName  the programmatic name of the property that was changed
281     * @param oldValue      the old value of the property
282     * @param newValue      the new value of the property
283     * @since 1.2
284     */
285    public void firePropertyChange(String propertyName, int oldValue, int newValue) {
286        if (oldValue != newValue) {
287            firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
288        }
289    }
290
291    /**
292     * Reports a boolean bound property update to listeners
293     * that have been registered to track updates of
294     * all properties or a property with the specified name.
295     * <p>
296     * No event is fired if old and new values are equal.
297     * <p>
298     * This is merely a convenience wrapper around the more general
299     * {@link #firePropertyChange(String, Object, Object)}  method.
300     *
301     * @param propertyName  the programmatic name of the property that was changed
302     * @param oldValue      the old value of the property
303     * @param newValue      the new value of the property
304     * @since 1.2
305     */
306    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
307        if (oldValue != newValue) {
308            firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
309        }
310    }
311
312    /**
313     * Fires a property change event to listeners
314     * that have been registered to track updates of
315     * all properties or a property with the specified name.
316     * <p>
317     * No event is fired if the given event's old and new values are equal and non-null.
318     *
319     * @param event  the {@code PropertyChangeEvent} to be fired
320     * @since 1.2
321     */
322    public void firePropertyChange(PropertyChangeEvent event) {
323        Object oldValue = event.getOldValue();
324        Object newValue = event.getNewValue();
325        if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
326            String name = event.getPropertyName();
327
328            PropertyChangeListener[] common = this.map.get(null);
329            PropertyChangeListener[] named = (name != null)
330                        ? this.map.get(name)
331                        : null;
332
333            fire(common, event);
334            fire(named, event);
335        }
336    }
337
338    private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
339        if (listeners != null) {
340            for (PropertyChangeListener listener : listeners) {
341                listener.propertyChange(event);
342            }
343        }
344    }
345
346    /**
347     * Reports a bound indexed property update to listeners
348     * that have been registered to track updates of
349     * all properties or a property with the specified name.
350     * <p>
351     * No event is fired if old and new values are equal and non-null.
352     * <p>
353     * This is merely a convenience wrapper around the more general
354     * {@link #firePropertyChange(PropertyChangeEvent)} method.
355     *
356     * @param propertyName  the programmatic name of the property that was changed
357     * @param index         the index of the property element that was changed
358     * @param oldValue      the old value of the property
359     * @param newValue      the new value of the property
360     * @since 1.5
361     */
362    public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
363        if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
364            firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index));
365        }
366    }
367
368    /**
369     * Reports an integer bound indexed property update to listeners
370     * that have been registered to track updates of
371     * all properties or a property with the specified name.
372     * <p>
373     * No event is fired if old and new values are equal.
374     * <p>
375     * This is merely a convenience wrapper around the more general
376     * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
377     *
378     * @param propertyName  the programmatic name of the property that was changed
379     * @param index         the index of the property element that was changed
380     * @param oldValue      the old value of the property
381     * @param newValue      the new value of the property
382     * @since 1.5
383     */
384    public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) {
385        if (oldValue != newValue) {
386            fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue));
387        }
388    }
389
390    /**
391     * Reports a boolean bound indexed property update to listeners
392     * that have been registered to track updates of
393     * all properties or a property with the specified name.
394     * <p>
395     * No event is fired if old and new values are equal.
396     * <p>
397     * This is merely a convenience wrapper around the more general
398     * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
399     *
400     * @param propertyName  the programmatic name of the property that was changed
401     * @param index         the index of the property element that was changed
402     * @param oldValue      the old value of the property
403     * @param newValue      the new value of the property
404     * @since 1.5
405     */
406    public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) {
407        if (oldValue != newValue) {
408            fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
409        }
410    }
411
412    /**
413     * Check if there are any listeners for a specific property, including
414     * those registered on all properties.  If {@code propertyName}
415     * is null, only check for listeners registered on all properties.
416     *
417     * @param propertyName  the property name.
418     * @return true if there are one or more listeners for the given property
419     * @since 1.2
420     */
421    public boolean hasListeners(String propertyName) {
422        return this.map.hasListeners(propertyName);
423    }
424
425    /**
426     * @serialData Null terminated list of {@code PropertyChangeListeners}.
427     * <p>
428     * At serialization time we skip non-serializable listeners and
429     * only serialize the serializable listeners.
430     */
431    private void writeObject(ObjectOutputStream s) throws IOException {
432        Hashtable<String, PropertyChangeSupport> children = null;
433        PropertyChangeListener[] listeners = null;
434        synchronized (this.map) {
435            for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {
436                String property = entry.getKey();
437                if (property == null) {
438                    listeners = entry.getValue();
439                } else {
440                    if (children == null) {
441                        children = new Hashtable<>();
442                    }
443                    PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);
444                    pcs.map.set(null, entry.getValue());
445                    children.put(property, pcs);
446                }
447            }
448        }
449        ObjectOutputStream.PutField fields = s.putFields();
450        fields.put("children", children);
451        fields.put("source", this.source);
452        fields.put("propertyChangeSupportSerializedDataVersion", 2);
453        s.writeFields();
454
455        if (listeners != null) {
456            for (PropertyChangeListener l : listeners) {
457                if (l instanceof Serializable) {
458                    s.writeObject(l);
459                }
460            }
461        }
462        s.writeObject(null);
463    }
464
465    private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
466        this.map = new PropertyChangeListenerMap();
467
468        ObjectInputStream.GetField fields = s.readFields();
469
470        @SuppressWarnings("unchecked")
471        Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
472        this.source = fields.get("source", null);
473        fields.get("propertyChangeSupportSerializedDataVersion", 2);
474
475        Object listenerOrNull;
476        while (null != (listenerOrNull = s.readObject())) {
477            this.map.add(null, (PropertyChangeListener)listenerOrNull);
478        }
479        if (children != null) {
480            for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
481                for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
482                    this.map.add(entry.getKey(), listener);
483                }
484            }
485        }
486    }
487
488    /**
489     * The object to be provided as the "source" for any generated events.
490     */
491    private Object source;
492
493    /**
494     * @serialField children                                   Hashtable
495     * @serialField source                                     Object
496     * @serialField propertyChangeSupportSerializedDataVersion int
497     */
498    private static final ObjectStreamField[] serialPersistentFields = {
499            new ObjectStreamField("children", Hashtable.class),
500            new ObjectStreamField("source", Object.class),
501            new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
502    };
503
504    /**
505     * Serialization version ID, so we're compatible with JDK 1.1
506     */
507    static final long serialVersionUID = 6401253773779951803L;
508
509    /**
510     * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
511     * that works with {@link PropertyChangeListener PropertyChangeListener} objects.
512     */
513    private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
514        private static final PropertyChangeListener[] EMPTY = {};
515
516        /**
517         * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
518         * This method uses the same instance of the empty array
519         * when {@code length} equals {@code 0}.
520         *
521         * @param length  the array length
522         * @return        an array with specified length
523         */
524        @Override
525        protected PropertyChangeListener[] newArray(int length) {
526            return (0 < length)
527                    ? new PropertyChangeListener[length]
528                    : EMPTY;
529        }
530
531        /**
532         * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
533         * object for the specified property.
534         *
535         * @param name      the name of the property to listen on
536         * @param listener  the listener to process events
537         * @return          a {@code PropertyChangeListenerProxy} object
538         */
539        @Override
540        protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
541            return new PropertyChangeListenerProxy(name, listener);
542        }
543
544        /**
545         * {@inheritDoc}
546         */
547        public PropertyChangeListener extract(PropertyChangeListener listener) {
548            while (listener instanceof PropertyChangeListenerProxy) {
549                listener = ((PropertyChangeListenerProxy) listener).getListener();
550            }
551            return listener;
552        }
553    }
554}
555