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  */

25 package java.beans;
26
27 import java.io.Serializable;
28 import java.io.ObjectStreamField;
29 import java.io.ObjectOutputStream;
30 import java.io.ObjectInputStream;
31 import java.io.IOException;
32 import java.util.Hashtable;
33 import 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  */

82 public 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