001 /*
002 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.beans;
027
028 import java.io.Serializable;
029 import java.io.ObjectOutputStream;
030 import java.io.ObjectInputStream;
031 import java.io.IOException;
032 import java.util.Arrays;
033 import java.util.ArrayList;
034 import java.util.Iterator;
035 import java.util.List;
036
037 import sun.awt.EventListenerAggregate;
038
039 /**
040 * This is a utility class that can be used by beans that support bound
041 * properties. You can use an instance of this class as a member field
042 * of your bean and delegate various work to it.
043 *
044 * This class is serializable. When it is serialized it will save
045 * (and restore) any listeners that are themselves serializable. Any
046 * non-serializable listeners will be skipped during serialization.
047 *
048 */
049 public class PropertyChangeSupport implements java.io.Serializable {
050
051 // Manages the listener list.
052 private transient EventListenerAggregate listeners;
053
054 /**
055 * Constructs a <code>PropertyChangeSupport</code> object.
056 *
057 * @param sourceBean The bean to be given as the source for any events.
058 */
059
060 public PropertyChangeSupport(Object sourceBean) {
061 if (sourceBean == null) {
062 throw new NullPointerException();
063 }
064 source = sourceBean;
065 }
066
067 /**
068 * Add a PropertyChangeListener to the listener list.
069 * The listener is registered for all properties.
070 * The same listener object may be added more than once, and will be called
071 * as many times as it is added.
072 * If <code>listener</code> is null, no exception is thrown and no action
073 * is taken.
074 *
075 * @param listener The PropertyChangeListener to be added
076 */
077 public synchronized void addPropertyChangeListener(
078 PropertyChangeListener listener) {
079 if (listener == null) {
080 return;
081 }
082
083 if (listener instanceof PropertyChangeListenerProxy) {
084 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
085 // Call two argument add method.
086 addPropertyChangeListener(proxy.getPropertyName(),
087 (PropertyChangeListener) proxy.getListener());
088 } else {
089 if (listeners == null) {
090 listeners = new EventListenerAggregate(
091 PropertyChangeListener.class);
092 }
093 listeners.add(listener);
094 }
095 }
096
097 /**
098 * Remove a PropertyChangeListener from the listener list.
099 * This removes a PropertyChangeListener that was registered
100 * for all properties.
101 * If <code>listener</code> was added more than once to the same event
102 * source, it will be notified one less time after being removed.
103 * If <code>listener</code> is null, or was never added, no exception is
104 * thrown and no action is taken.
105 *
106 * @param listener The PropertyChangeListener to be removed
107 */
108 public synchronized void removePropertyChangeListener(
109 PropertyChangeListener listener) {
110 if (listener == null) {
111 return;
112 }
113
114 if (listener instanceof PropertyChangeListenerProxy) {
115 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
116 // Call two argument remove method.
117 removePropertyChangeListener(proxy.getPropertyName(),
118 (PropertyChangeListener) proxy.getListener());
119 } else {
120 if (listeners == null) {
121 return;
122 }
123 listeners.remove(listener);
124 }
125 }
126
127 /**
128 * Returns an array of all the listeners that were added to the
129 * PropertyChangeSupport object with addPropertyChangeListener().
130 * <p>
131 * If some listeners have been added with a named property, then
132 * the returned array will be a mixture of PropertyChangeListeners
133 * and <code>PropertyChangeListenerProxy</code>s. If the calling
134 * method is interested in distinguishing the listeners then it must
135 * test each element to see if it's a
136 * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
137 * the parameter.
138 *
139 * <pre>
140 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
141 * for (int i = 0; i < listeners.length; i++) {
142 * if (listeners[i] instanceof PropertyChangeListenerProxy) {
143 * PropertyChangeListenerProxy proxy =
144 * (PropertyChangeListenerProxy)listeners[i];
145 * if (proxy.getPropertyName().equals("foo")) {
146 * // proxy is a PropertyChangeListener which was associated
147 * // with the property named "foo"
148 * }
149 * }
150 * }
151 *</pre>
152 *
153 * @see PropertyChangeListenerProxy
154 * @return all of the <code>PropertyChangeListeners</code> added or an
155 * empty array if no listeners have been added
156 * @since 1.4
157 */
158 public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
159 List returnList = new ArrayList();
160
161 // Add all the PropertyChangeListeners
162 if (listeners != null) {
163 returnList.addAll(Arrays.asList(listeners
164 .getListenersInternal()));
165 }
166
167 // Add all the PropertyChangeListenerProxys
168 if (children != null) {
169 Iterator iterator = children.keySet().iterator();
170 while (iterator.hasNext()) {
171 String key = (String) iterator.next();
172 PropertyChangeSupport child = (PropertyChangeSupport) children
173 .get(key);
174 PropertyChangeListener[] childListeners = child
175 .getPropertyChangeListeners();
176 for (int index = childListeners.length - 1; index >= 0; index--) {
177 returnList.add(new PropertyChangeListenerProxy(key,
178 childListeners[index]));
179 }
180 }
181 }
182 return (PropertyChangeListener[]) (returnList
183 .toArray(new PropertyChangeListener[0]));
184 }
185
186 /**
187 * Add a PropertyChangeListener for a specific property. The listener
188 * will be invoked only when a call on firePropertyChange names that
189 * specific property.
190 * The same listener object may be added more than once. For each
191 * property, the listener will be invoked the number of times it was added
192 * for that property.
193 * If <code>propertyName</code> or <code>listener</code> is null, no
194 * exception is thrown and no action is taken.
195 *
196 * @param propertyName The name of the property to listen on.
197 * @param listener The PropertyChangeListener to be added
198 */
199
200 public synchronized void addPropertyChangeListener(
201 String propertyName, PropertyChangeListener listener) {
202 if (listener == null || propertyName == null) {
203 return;
204 }
205 if (children == null) {
206 children = new java.util.Hashtable();
207 }
208 PropertyChangeSupport child = (PropertyChangeSupport) children
209 .get(propertyName);
210 if (child == null) {
211 child = new PropertyChangeSupport(source);
212 children.put(propertyName, child);
213 }
214 child.addPropertyChangeListener(listener);
215 }
216
217 /**
218 * Remove a PropertyChangeListener for a specific property.
219 * If <code>listener</code> was added more than once to the same event
220 * source for the specified property, it will be notified one less time
221 * after being removed.
222 * If <code>propertyName</code> is null, no exception is thrown and no
223 * action is taken.
224 * If <code>listener</code> is null, or was never added for the specified
225 * property, no exception is thrown and no action is taken.
226 *
227 * @param propertyName The name of the property that was listened on.
228 * @param listener The PropertyChangeListener to be removed
229 */
230
231 public synchronized void removePropertyChangeListener(
232 String propertyName, PropertyChangeListener listener) {
233 if (listener == null || propertyName == null) {
234 return;
235 }
236 if (children == null) {
237 return;
238 }
239 PropertyChangeSupport child = (PropertyChangeSupport) children
240 .get(propertyName);
241 if (child == null) {
242 return;
243 }
244 child.removePropertyChangeListener(listener);
245 }
246
247 /**
248 * Returns an array of all the listeners which have been associated
249 * with the named property.
250 *
251 * @param propertyName The name of the property being listened to
252 * @return all of the <code>PropertyChangeListeners</code> associated with
253 * the named property. If no such listeners have been added,
254 * or if <code>propertyName</code> is null, an empty array is
255 * returned.
256 * @since 1.4
257 */
258 public synchronized PropertyChangeListener[] getPropertyChangeListeners(
259 String propertyName) {
260 ArrayList returnList = new ArrayList();
261
262 if (children != null && propertyName != null) {
263 PropertyChangeSupport support = (PropertyChangeSupport) children
264 .get(propertyName);
265 if (support != null) {
266 returnList.addAll(Arrays.asList(support
267 .getPropertyChangeListeners()));
268 }
269 }
270 return (PropertyChangeListener[]) (returnList
271 .toArray(new PropertyChangeListener[0]));
272 }
273
274 /**
275 * Report a bound property update to any registered listeners.
276 * No event is fired if old and new are equal and non-null.
277 *
278 * <p>
279 * This is merely a convenience wrapper around the more general
280 * firePropertyChange method that takes {@code
281 * PropertyChangeEvent} value.
282 *
283 * @param propertyName The programmatic name of the property
284 * that was changed.
285 * @param oldValue The old value of the property.
286 * @param newValue The new value of the property.
287 */
288 public void firePropertyChange(String propertyName,
289 Object oldValue, Object newValue) {
290 if (oldValue != null && newValue != null
291 && oldValue.equals(newValue)) {
292 return;
293 }
294 firePropertyChange(new PropertyChangeEvent(source,
295 propertyName, oldValue, newValue));
296 }
297
298 /**
299 * Report an int bound property update to any registered listeners.
300 * No event is fired if old and new are equal.
301 * <p>
302 * This is merely a convenience wrapper around the more general
303 * firePropertyChange method that takes Object values.
304 *
305 * @param propertyName The programmatic name of the property
306 * that was changed.
307 * @param oldValue The old value of the property.
308 * @param newValue The new value of the property.
309 */
310 public void firePropertyChange(String propertyName, int oldValue,
311 int newValue) {
312 if (oldValue == newValue) {
313 return;
314 }
315 firePropertyChange(propertyName, new Integer(oldValue),
316 new Integer(newValue));
317 }
318
319 /**
320 * Report a boolean bound property update to any registered listeners.
321 * No event is fired if old and new are equal.
322 * <p>
323 * This is merely a convenience wrapper around the more general
324 * firePropertyChange method that takes Object values.
325 *
326 * @param propertyName The programmatic name of the property
327 * that was changed.
328 * @param oldValue The old value of the property.
329 * @param newValue The new value of the property.
330 */
331 public void firePropertyChange(String propertyName,
332 boolean oldValue, boolean newValue) {
333 if (oldValue == newValue) {
334 return;
335 }
336 firePropertyChange(propertyName, Boolean.valueOf(oldValue),
337 Boolean.valueOf(newValue));
338 }
339
340 /**
341 * Fire an existing PropertyChangeEvent to any registered listeners.
342 * No event is fired if the given event's old and new values are
343 * equal and non-null.
344 * @param evt The PropertyChangeEvent object.
345 */
346 public void firePropertyChange(PropertyChangeEvent evt) {
347 Object oldValue = evt.getOldValue();
348 Object newValue = evt.getNewValue();
349 String propertyName = evt.getPropertyName();
350 if (oldValue != null && newValue != null
351 && oldValue.equals(newValue)) {
352 return;
353 }
354
355 if (listeners != null) {
356 Object[] list = listeners.getListenersInternal();
357 for (int i = 0; i < list.length; i++) {
358 PropertyChangeListener target = (PropertyChangeListener) list[i];
359 target.propertyChange(evt);
360 }
361 }
362
363 if (children != null && propertyName != null) {
364 PropertyChangeSupport child = null;
365 child = (PropertyChangeSupport) children.get(propertyName);
366 if (child != null) {
367 child.firePropertyChange(evt);
368 }
369 }
370 }
371
372 /**
373 * Report a bound indexed property update to any registered
374 * listeners.
375 * <p>
376 * No event is fired if old and new values are equal
377 * and non-null.
378 *
379 * <p>
380 * This is merely a convenience wrapper around the more general
381 * firePropertyChange method that takes {@code PropertyChangeEvent} value.
382 *
383 * @param propertyName The programmatic name of the property that
384 * was changed.
385 * @param index index of the property element that was changed.
386 * @param oldValue The old value of the property.
387 * @param newValue The new value of the property.
388 * @since 1.5
389 */
390 public void fireIndexedPropertyChange(String propertyName,
391 int index, Object oldValue, Object newValue) {
392 firePropertyChange(new IndexedPropertyChangeEvent(source,
393 propertyName, oldValue, newValue, index));
394 }
395
396 /**
397 * Report an <code>int</code> bound indexed property update to any registered
398 * listeners.
399 * <p>
400 * No event is fired if old and new values are equal.
401 * <p>
402 * This is merely a convenience wrapper around the more general
403 * fireIndexedPropertyChange method which takes Object values.
404 *
405 * @param propertyName The programmatic name of the property that
406 * was changed.
407 * @param index index of the property element that was changed.
408 * @param oldValue The old value of the property.
409 * @param newValue The new value of the property.
410 * @since 1.5
411 */
412 public void fireIndexedPropertyChange(String propertyName,
413 int index, int oldValue, int newValue) {
414 if (oldValue == newValue) {
415 return;
416 }
417 fireIndexedPropertyChange(propertyName, index, new Integer(
418 oldValue), new Integer(newValue));
419 }
420
421 /**
422 * Report a <code>boolean</code> bound indexed property update to any
423 * registered listeners.
424 * <p>
425 * No event is fired if old and new values are equal.
426 * <p>
427 * This is merely a convenience wrapper around the more general
428 * fireIndexedPropertyChange method which takes Object values.
429 *
430 * @param propertyName The programmatic name of the property that
431 * was changed.
432 * @param index index of the property element that was changed.
433 * @param oldValue The old value of the property.
434 * @param newValue The new value of the property.
435 * @since 1.5
436 */
437 public void fireIndexedPropertyChange(String propertyName,
438 int index, boolean oldValue, boolean newValue) {
439 if (oldValue == newValue) {
440 return;
441 }
442 fireIndexedPropertyChange(propertyName, index, Boolean
443 .valueOf(oldValue), Boolean.valueOf(newValue));
444 }
445
446 /**
447 * Check if there are any listeners for a specific property, including
448 * those registered on all properties. If <code>propertyName</code>
449 * is null, only check for listeners registered on all properties.
450 *
451 * @param propertyName the property name.
452 * @return true if there are one or more listeners for the given property
453 */
454 public synchronized boolean hasListeners(String propertyName) {
455 if (listeners != null && !listeners.isEmpty()) {
456 // there is a generic listener
457 return true;
458 }
459 if (children != null && propertyName != null) {
460 PropertyChangeSupport child = (PropertyChangeSupport) children
461 .get(propertyName);
462 if (child != null && child.listeners != null) {
463 return !child.listeners.isEmpty();
464 }
465 }
466 return false;
467 }
468
469 /**
470 * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
471 * <p>
472 * At serialization time we skip non-serializable listeners and
473 * only serialize the serializable listeners.
474 *
475 */
476 private void writeObject(ObjectOutputStream s) throws IOException {
477 s.defaultWriteObject();
478
479 if (listeners != null) {
480 Object[] list = listeners.getListenersCopy();
481
482 for (int i = 0; i < list.length; i++) {
483 PropertyChangeListener l = (PropertyChangeListener) list[i];
484 if (l instanceof Serializable) {
485 s.writeObject(l);
486 }
487 }
488 }
489 s.writeObject(null);
490 }
491
492 private void readObject(ObjectInputStream s)
493 throws ClassNotFoundException, IOException {
494 s.defaultReadObject();
495
496 Object listenerOrNull;
497 while (null != (listenerOrNull = s.readObject())) {
498 addPropertyChangeListener((PropertyChangeListener) listenerOrNull);
499 }
500 }
501
502 /**
503 * Hashtable for managing listeners for specific properties.
504 * Maps property names to PropertyChangeSupport objects.
505 * @serial
506 * @since 1.2
507 */
508 private java.util.Hashtable children;
509
510 /**
511 * The object to be provided as the "source" for any generated events.
512 * @serial
513 */
514 private Object source;
515
516 /**
517 * Internal version number
518 * @serial
519 * @since
520 */
521 private int propertyChangeSupportSerializedDataVersion = 2;
522
523 /**
524 * Serialization version ID, so we're compatible with JDK 1.1
525 */
526 static final long serialVersionUID = 6401253773779951803L;
527 }
|