|
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</code> 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</code> 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</code> 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</code> 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</code>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</code>, 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</code> 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</code> or <code>listener</code> 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</code> 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</code> is null, no exception is thrown and no |
|
215 * action is taken. |
|
216 * If <code>listener</code> 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</code> associated with |
|
241 * the named property. If no such listeners have been added, |
|
242 * or if <code>propertyName</code> 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</code> |
|
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</code>. |
|
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 final PropertyChangeListener extract(PropertyChangeListener listener) { |
|
548 while (listener instanceof PropertyChangeListenerProxy) { |
|
549 listener = ((PropertyChangeListenerProxy) listener).getListener(); |
|
550 } |
|
551 return listener; |
|
552 } |
|
553 } |
|
554 } |