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™ 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