blob: 2d4ed88fdb32cf45dda35e1d3584fabcefde2767 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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. You can use an instance of this class as a member field
38 * of your bean and delegate various work to it.
39 *
40 * This class is serializable. When it is serialized it will save
41 * (and restore) any listeners that are themselves serializable. Any
42 * non-serializable listeners will be skipped during serialization.
43 */
44public class PropertyChangeSupport implements Serializable {
45 private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
46
47 /**
48 * Constructs a <code>PropertyChangeSupport</code> object.
49 *
50 * @param sourceBean The bean to be given as the source for any events.
51 */
52 public PropertyChangeSupport(Object sourceBean) {
53 if (sourceBean == null) {
54 throw new NullPointerException();
55 }
56 source = sourceBean;
57 }
58
59 /**
60 * Add a PropertyChangeListener to the listener list.
61 * The listener is registered for all properties.
62 * The same listener object may be added more than once, and will be called
63 * as many times as it is added.
64 * If <code>listener</code> is null, no exception is thrown and no action
65 * is taken.
66 *
67 * @param listener The PropertyChangeListener to be added
68 */
69 public void addPropertyChangeListener(PropertyChangeListener listener) {
70 if (listener == null) {
71 return;
72 }
73 if (listener instanceof PropertyChangeListenerProxy) {
74 PropertyChangeListenerProxy proxy =
75 (PropertyChangeListenerProxy)listener;
76 // Call two argument add method.
77 addPropertyChangeListener(proxy.getPropertyName(),
78 proxy.getListener());
79 } else {
80 this.map.add(null, listener);
81 }
82 }
83
84 /**
85 * Remove a PropertyChangeListener from the listener list.
86 * This removes a PropertyChangeListener that was registered
87 * for all properties.
88 * If <code>listener</code> was added more than once to the same event
89 * source, it will be notified one less time after being removed.
90 * If <code>listener</code> is null, or was never added, no exception is
91 * thrown and no action is taken.
92 *
93 * @param listener The PropertyChangeListener to be removed
94 */
95 public void removePropertyChangeListener(PropertyChangeListener listener) {
96 if (listener == null) {
97 return;
98 }
99 if (listener instanceof PropertyChangeListenerProxy) {
100 PropertyChangeListenerProxy proxy =
101 (PropertyChangeListenerProxy)listener;
102 // Call two argument remove method.
103 removePropertyChangeListener(proxy.getPropertyName(),
104 proxy.getListener());
105 } else {
106 this.map.remove(null, listener);
107 }
108 }
109
110 /**
111 * Returns an array of all the listeners that were added to the
112 * PropertyChangeSupport object with addPropertyChangeListener().
113 * <p>
114 * If some listeners have been added with a named property, then
115 * the returned array will be a mixture of PropertyChangeListeners
116 * and <code>PropertyChangeListenerProxy</code>s. If the calling
117 * method is interested in distinguishing the listeners then it must
118 * test each element to see if it's a
119 * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
120 * the parameter.
121 *
122 * <pre>
123 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
124 * for (int i = 0; i < listeners.length; i++) {
125 * if (listeners[i] instanceof PropertyChangeListenerProxy) {
126 * PropertyChangeListenerProxy proxy =
127 * (PropertyChangeListenerProxy)listeners[i];
128 * if (proxy.getPropertyName().equals("foo")) {
129 * // proxy is a PropertyChangeListener which was associated
130 * // with the property named "foo"
131 * }
132 * }
133 * }
134 *</pre>
135 *
136 * @see PropertyChangeListenerProxy
137 * @return all of the <code>PropertyChangeListeners</code> added or an
138 * empty array if no listeners have been added
139 * @since 1.4
140 */
141 public PropertyChangeListener[] getPropertyChangeListeners() {
142 return this.map.getListeners();
143 }
144
145 /**
146 * Add a PropertyChangeListener for a specific property. The listener
147 * will be invoked only when a call on firePropertyChange names that
148 * specific property.
149 * The same listener object may be added more than once. For each
150 * property, the listener will be invoked the number of times it was added
151 * for that property.
152 * If <code>propertyName</code> or <code>listener</code> is null, no
153 * exception is thrown and no action is taken.
154 *
155 * @param propertyName The name of the property to listen on.
156 * @param listener The PropertyChangeListener to be added
157 */
158 public void addPropertyChangeListener(
159 String propertyName,
160 PropertyChangeListener listener) {
161 if (listener == null || propertyName == null) {
162 return;
163 }
164 listener = this.map.extract(listener);
165 if (listener != null) {
166 this.map.add(propertyName, listener);
167 }
168 }
169
170 /**
171 * Remove a PropertyChangeListener for a specific property.
172 * If <code>listener</code> was added more than once to the same event
173 * source for the specified property, it will be notified one less time
174 * after being removed.
175 * If <code>propertyName</code> is null, no exception is thrown and no
176 * action is taken.
177 * If <code>listener</code> is null, or was never added for the specified
178 * property, no exception is thrown and no action is taken.
179 *
180 * @param propertyName The name of the property that was listened on.
181 * @param listener The PropertyChangeListener to be removed
182 */
183 public void removePropertyChangeListener(
184 String propertyName,
185 PropertyChangeListener listener) {
186 if (listener == null || propertyName == null) {
187 return;
188 }
189 listener = this.map.extract(listener);
190 if (listener != null) {
191 this.map.remove(propertyName, listener);
192 }
193 }
194
195 /**
196 * Returns an array of all the listeners which have been associated
197 * with the named property.
198 *
199 * @param propertyName The name of the property being listened to
200 * @return all of the <code>PropertyChangeListeners</code> associated with
201 * the named property. If no such listeners have been added,
202 * or if <code>propertyName</code> is null, an empty array is
203 * returned.
204 * @since 1.4
205 */
206 public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
207 return this.map.getListeners(propertyName);
208 }
209
210 /**
211 * Report a bound property update to any registered listeners.
212 * No event is fired if old and new are equal and non-null.
213 *
214 * <p>
215 * This is merely a convenience wrapper around the more general
216 * firePropertyChange method that takes {@code
217 * PropertyChangeEvent} value.
218 *
219 * @param propertyName The programmatic name of the property
220 * that was changed.
221 * @param oldValue The old value of the property.
222 * @param newValue The new value of the property.
223 */
224 public void firePropertyChange(String propertyName,
225 Object oldValue, Object newValue) {
226 if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
227 return;
228 }
229 firePropertyChange(new PropertyChangeEvent(source, propertyName,
230 oldValue, newValue));
231 }
232
233 /**
234 * Report an int bound property update to any registered listeners.
235 * No event is fired if old and new are equal.
236 * <p>
237 * This is merely a convenience wrapper around the more general
238 * firePropertyChange method that takes Object values.
239 *
240 * @param propertyName The programmatic name of the property
241 * that was changed.
242 * @param oldValue The old value of the property.
243 * @param newValue The new value of the property.
244 */
245 public void firePropertyChange(String propertyName,
246 int oldValue, int newValue) {
247 if (oldValue == newValue) {
248 return;
249 }
250 firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
251 }
252
253 /**
254 * Report a boolean bound property update to any registered listeners.
255 * No event is fired if old and new are equal.
256 * <p>
257 * This is merely a convenience wrapper around the more general
258 * firePropertyChange method that takes Object values.
259 *
260 * @param propertyName The programmatic name of the property
261 * that was changed.
262 * @param oldValue The old value of the property.
263 * @param newValue The new value of the property.
264 */
265 public void firePropertyChange(String propertyName,
266 boolean oldValue, boolean newValue) {
267 if (oldValue == newValue) {
268 return;
269 }
270 firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
271 }
272
273 /**
274 * Fire an existing PropertyChangeEvent to any registered listeners.
275 * No event is fired if the given event's old and new values are
276 * equal and non-null.
277 * @param evt The PropertyChangeEvent object.
278 */
279 public void firePropertyChange(PropertyChangeEvent evt) {
280 Object oldValue = evt.getOldValue();
281 Object newValue = evt.getNewValue();
282 String propertyName = evt.getPropertyName();
283 if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
284 return;
285 }
286 PropertyChangeListener[] common = this.map.get(null);
287 PropertyChangeListener[] named = (propertyName != null)
288 ? this.map.get(propertyName)
289 : null;
290
291 fire(common, evt);
292 fire(named, evt);
293 }
294
295 private void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
296 if (listeners != null) {
297 for (PropertyChangeListener listener : listeners) {
298 listener.propertyChange(event);
299 }
300 }
301 }
302
303 /**
304 * Report a bound indexed property update to any registered
305 * listeners.
306 * <p>
307 * No event is fired if old and new values are equal
308 * and non-null.
309 *
310 * <p>
311 * This is merely a convenience wrapper around the more general
312 * firePropertyChange method that takes {@code PropertyChangeEvent} value.
313 *
314 * @param propertyName The programmatic name of the property that
315 * was changed.
316 * @param index index of the property element that was changed.
317 * @param oldValue The old value of the property.
318 * @param newValue The new value of the property.
319 * @since 1.5
320 */
321 public void fireIndexedPropertyChange(String propertyName, int index,
322 Object oldValue, Object newValue) {
323 firePropertyChange(new IndexedPropertyChangeEvent
324 (source, propertyName, oldValue, newValue, index));
325 }
326
327 /**
328 * Report an <code>int</code> bound indexed property update to any registered
329 * listeners.
330 * <p>
331 * No event is fired if old and new values are equal.
332 * <p>
333 * This is merely a convenience wrapper around the more general
334 * fireIndexedPropertyChange method which takes Object values.
335 *
336 * @param propertyName The programmatic name of the property that
337 * was changed.
338 * @param index index of the property element that was changed.
339 * @param oldValue The old value of the property.
340 * @param newValue The new value of the property.
341 * @since 1.5
342 */
343 public void fireIndexedPropertyChange(String propertyName, int index,
344 int oldValue, int newValue) {
345 if (oldValue == newValue) {
346 return;
347 }
348 fireIndexedPropertyChange(propertyName, index,
349 Integer.valueOf(oldValue),
350 Integer.valueOf(newValue));
351 }
352
353 /**
354 * Report a <code>boolean</code> bound indexed property update to any
355 * registered listeners.
356 * <p>
357 * No event is fired if old and new values are equal.
358 * <p>
359 * This is merely a convenience wrapper around the more general
360 * fireIndexedPropertyChange method which takes Object values.
361 *
362 * @param propertyName The programmatic name of the property that
363 * was changed.
364 * @param index index of the property element that was changed.
365 * @param oldValue The old value of the property.
366 * @param newValue The new value of the property.
367 * @since 1.5
368 */
369 public void fireIndexedPropertyChange(String propertyName, int index,
370 boolean oldValue, boolean newValue) {
371 if (oldValue == newValue) {
372 return;
373 }
374 fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue),
375 Boolean.valueOf(newValue));
376 }
377
378 /**
379 * Check if there are any listeners for a specific property, including
380 * those registered on all properties. If <code>propertyName</code>
381 * is null, only check for listeners registered on all properties.
382 *
383 * @param propertyName the property name.
384 * @return true if there are one or more listeners for the given property
385 */
386 public boolean hasListeners(String propertyName) {
387 return this.map.hasListeners(propertyName);
388 }
389
390 /**
391 * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
392 * <p>
393 * At serialization time we skip non-serializable listeners and
394 * only serialize the serializable listeners.
395 */
396 private void writeObject(ObjectOutputStream s) throws IOException {
397 Hashtable<String, PropertyChangeSupport> children = null;
398 PropertyChangeListener[] listeners = null;
399 synchronized (this.map) {
400 for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {
401 String property = entry.getKey();
402 if (property == null) {
403 listeners = entry.getValue();
404 } else {
405 if (children == null) {
406 children = new Hashtable<String, PropertyChangeSupport>();
407 }
408 PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);
409 pcs.map.set(null, entry.getValue());
410 children.put(property, pcs);
411 }
412 }
413 }
414 ObjectOutputStream.PutField fields = s.putFields();
415 fields.put("children", children);
416 fields.put("source", this.source);
417 fields.put("propertyChangeSupportSerializedDataVersion", 2);
418 s.writeFields();
419
420 if (listeners != null) {
421 for (PropertyChangeListener l : listeners) {
422 if (l instanceof Serializable) {
423 s.writeObject(l);
424 }
425 }
426 }
427 s.writeObject(null);
428 }
429
430 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
431 this.map = new PropertyChangeListenerMap();
432
433 ObjectInputStream.GetField fields = s.readFields();
434
435 Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
436 this.source = fields.get("source", null);
437 fields.get("propertyChangeSupportSerializedDataVersion", 2);
438
439 Object listenerOrNull;
440 while (null != (listenerOrNull = s.readObject())) {
441 this.map.add(null, (PropertyChangeListener)listenerOrNull);
442 }
443 if (children != null) {
444 for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
445 for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
446 this.map.add(entry.getKey(), listener);
447 }
448 }
449 }
450 }
451
452 /**
453 * The object to be provided as the "source" for any generated events.
454 */
455 private Object source;
456
457 /**
458 * @serialField children Hashtable
459 * @serialField source Object
460 * @serialField propertyChangeSupportSerializedDataVersion int
461 */
462 private static final ObjectStreamField[] serialPersistentFields = {
463 new ObjectStreamField("children", Hashtable.class),
464 new ObjectStreamField("source", Object.class),
465 new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
466 };
467
468 /**
469 * Serialization version ID, so we're compatible with JDK 1.1
470 */
471 static final long serialVersionUID = 6401253773779951803L;
472
473 /**
474 * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
475 * that works with {@link PropertyChangeListener PropertyChangeListener} objects.
476 */
477 private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
478 private static final PropertyChangeListener[] EMPTY = {};
479
480 /**
481 * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
482 * This method uses the same instance of the empty array
483 * when {@code length} equals {@code 0}.
484 *
485 * @param length the array length
486 * @return an array with specified length
487 */
488 @Override
489 protected PropertyChangeListener[] newArray(int length) {
490 return (0 < length)
491 ? new PropertyChangeListener[length]
492 : EMPTY;
493 }
494
495 /**
496 * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
497 * object for the specified property.
498 *
499 * @param name the name of the property to listen on
500 * @param listener the listener to process events
501 * @return a {@code PropertyChangeListenerProxy} object
502 */
503 @Override
504 protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
505 return new PropertyChangeListenerProxy(name, listener);
506 }
507 }
508}