001 /* PropertyChangeSupport.java -- support to manage property change listeners
002 Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006
003 Free Software Foundation, Inc.
004
005 This file is part of GNU Classpath.
006
007 GNU Classpath is free software; you can redistribute it and/or modify
008 it under the terms of the GNU General Public License as published by
009 the Free Software Foundation; either version 2, or (at your option)
010 any later version.
011
012 GNU Classpath is distributed in the hope that it will be useful, but
013 WITHOUT ANY WARRANTY; without even the implied warranty of
014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 General Public License for more details.
016
017 You should have received a copy of the GNU General Public License
018 along with GNU Classpath; see the file COPYING. If not, write to the
019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020 02110-1301 USA.
021
022 Linking this library statically or dynamically with other modules is
023 making a combined work based on this library. Thus, the terms and
024 conditions of the GNU General Public License cover the whole
025 combination.
026
027 As a special exception, the copyright holders of this library give you
028 permission to link this library with independent modules to produce an
029 executable, regardless of the license terms of these independent
030 modules, and to copy and distribute the resulting executable under
031 terms of your choice, provided that you also meet, for each linked
032 independent module, the terms and conditions of the license of that
033 module. An independent module is a module which is not derived from
034 or based on this library. If you modify this library, you may extend
035 this exception to your version of the library, but you are not
036 obligated to do so. If you do not wish to do so, delete this
037 exception statement from your version. */
038
039
040 package java.beans;
041
042 import java.io.IOException;
043 import java.io.ObjectInputStream;
044 import java.io.ObjectOutputStream;
045 import java.io.Serializable;
046 import java.util.ArrayList;
047 import java.util.Arrays;
048 import java.util.Hashtable;
049 import java.util.Iterator;
050 import java.util.Map.Entry;
051 import java.util.Vector;
052
053 /**
054 * PropertyChangeSupport makes it easy to fire property change events and
055 * handle listeners. It allows chaining of listeners, as well as filtering
056 * by property name. In addition, it will serialize only those listeners
057 * which are serializable, ignoring the others without problem. This class
058 * is thread-safe.
059 *
060 * @author John Keiser
061 * @author Eric Blake (ebb9@email.byu.edu)
062 * @since 1.1
063 * @status updated to 1.4
064 */
065 public class PropertyChangeSupport implements Serializable
066 {
067 /**
068 * Compatible with JDK 1.1+.
069 */
070 private static final long serialVersionUID = 6401253773779951803L;
071
072 /**
073 * Maps property names (String) to named listeners (PropertyChangeSupport).
074 * If this is a child instance, this field will be null.
075 *
076 * @serial the map of property names to named listener managers
077 * @since 1.2
078 */
079 private Hashtable children;
080
081 /**
082 * The non-null source object for any generated events.
083 *
084 * @serial the event source
085 */
086 private final Object source;
087
088 /**
089 * A field to compare serialization versions - this class uses version 2.
090 *
091 * @serial the serialization format
092 */
093 private static final int propertyChangeSupportSerializedDataVersion = 2;
094
095 /**
096 * The list of all registered property listeners. If this instance was
097 * created by user code, this only holds the global listeners (ie. not tied
098 * to a name), and may be null. If it was created by this class, as a
099 * helper for named properties, then this vector will be non-null, and this
100 * instance appears as a value in the <code>children</code> hashtable of
101 * another instance, so that the listeners are tied to the key of that
102 * hashtable entry.
103 */
104 private transient Vector listeners;
105
106 /**
107 * Create a PropertyChangeSupport to work with a specific source bean.
108 *
109 * @param source the source bean to use
110 * @throws NullPointerException if source is null
111 */
112 public PropertyChangeSupport(Object source)
113 {
114 this.source = source;
115 if (source == null)
116 throw new NullPointerException();
117 }
118
119 /**
120 * Adds a PropertyChangeListener to the list of global listeners. All
121 * property change events will be sent to this listener. The listener add
122 * is not unique: that is, <em>n</em> adds with the same listener will
123 * result in <em>n</em> events being sent to that listener for every
124 * property change. Adding a null listener is silently ignored.
125 * This method will unwrap a PropertyChangeListenerProxy,
126 * registering the underlying delegate to the named property list.
127 *
128 * @param l the listener to add
129 */
130 public synchronized void addPropertyChangeListener(PropertyChangeListener l)
131 {
132 if (l == null)
133 return;
134
135 if (l instanceof PropertyChangeListenerProxy)
136 {
137 PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
138 addPropertyChangeListener(p.propertyName,
139 (PropertyChangeListener) p.getListener());
140 }
141 else
142 {
143 if (listeners == null)
144 listeners = new Vector();
145 listeners.add(l);
146 }
147 }
148
149 /**
150 * Removes a PropertyChangeListener from the list of global listeners. If
151 * any specific properties are being listened on, they must be deregistered
152 * by themselves; this will only remove the general listener to all
153 * properties. If <code>add()</code> has been called multiple times for a
154 * particular listener, <code>remove()</code> will have to be called the
155 * same number of times to deregister it. This method will unwrap a
156 * PropertyChangeListenerProxy, removing the underlying delegate from the
157 * named property list.
158 *
159 * @param l the listener to remove
160 */
161 public synchronized void
162 removePropertyChangeListener(PropertyChangeListener l)
163 {
164 if (l instanceof PropertyChangeListenerProxy)
165 {
166 PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
167 removePropertyChangeListener(p.propertyName,
168 (PropertyChangeListener) p.getListener());
169 }
170 else if (listeners != null)
171 {
172 listeners.remove(l);
173 if (listeners.isEmpty())
174 listeners = null;
175 }
176 }
177
178 /**
179 * Returns an array of all registered property change listeners. Those that
180 * were registered under a name will be wrapped in a
181 * <code>PropertyChangeListenerProxy</code>, so you must check whether the
182 * listener is an instance of the proxy class in order to see what name the
183 * real listener is registered under. If there are no registered listeners,
184 * this returns an empty array.
185 *
186 * @return the array of registered listeners
187 * @see PropertyChangeListenerProxy
188 * @since 1.4
189 */
190 public synchronized PropertyChangeListener[] getPropertyChangeListeners()
191 {
192 ArrayList list = new ArrayList();
193 if (listeners != null)
194 list.addAll(listeners);
195 if (children != null)
196 {
197 int i = children.size();
198 Iterator iter = children.entrySet().iterator();
199 while (--i >= 0)
200 {
201 Entry e = (Entry) iter.next();
202 String name = (String) e.getKey();
203 Vector v = ((PropertyChangeSupport) e.getValue()).listeners;
204 int j = v.size();
205 while (--j >= 0)
206 list.add(new PropertyChangeListenerProxy
207 (name, (PropertyChangeListener) v.get(j)));
208 }
209 }
210 return (PropertyChangeListener[])
211 list.toArray(new PropertyChangeListener[list.size()]);
212 }
213
214 /**
215 * Adds a PropertyChangeListener listening on the specified property. Events
216 * will be sent to the listener only if the property name matches. The
217 * listener add is not unique; that is, <em>n</em> adds on a particular
218 * property for a particular listener will result in <em>n</em> events
219 * being sent to that listener when that property is changed. The effect is
220 * cumulative, too; if you are registered to listen to receive events on
221 * all property changes, and then you register on a particular property,
222 * you will receive change events for that property twice. Adding a null
223 * listener is silently ignored. This method will unwrap a
224 * PropertyChangeListenerProxy, registering the underlying
225 * delegate to the named property list if the names match, and discarding
226 * it otherwise.
227 *
228 * @param propertyName the name of the property to listen on
229 * @param l the listener to add
230 * @throws NullPointerException if propertyName is null
231 */
232 public synchronized void addPropertyChangeListener(String propertyName,
233 PropertyChangeListener l)
234 {
235 if (l == null)
236 return;
237
238 while (l instanceof PropertyChangeListenerProxy)
239 {
240 PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
241 if (propertyName == null ? p.propertyName != null
242 : ! propertyName.equals(p.propertyName))
243 return;
244 l = (PropertyChangeListener) p.getListener();
245 }
246 PropertyChangeSupport s = null;
247 if (children == null)
248 children = new Hashtable();
249 else
250 s = (PropertyChangeSupport) children.get(propertyName);
251 if (s == null)
252 {
253 s = new PropertyChangeSupport(source);
254 s.listeners = new Vector();
255 children.put(propertyName, s);
256 }
257 s.listeners.add(l);
258 }
259
260 /**
261 * Removes a PropertyChangeListener from listening to a specific property.
262 * If <code>add()</code> has been called multiple times for a particular
263 * listener on a property, <code>remove()</code> will have to be called the
264 * same number of times to deregister it. This method will unwrap a
265 * PropertyChangeListenerProxy, removing the underlying delegate from the
266 * named property list if the names match.
267 *
268 * @param propertyName the property to stop listening on
269 * @param l the listener to remove
270 * @throws NullPointerException if propertyName is null
271 */
272 public synchronized void
273 removePropertyChangeListener(String propertyName, PropertyChangeListener l)
274 {
275 if (children == null)
276 return;
277 PropertyChangeSupport s
278 = (PropertyChangeSupport) children.get(propertyName);
279 if (s == null)
280 return;
281 while (l instanceof PropertyChangeListenerProxy)
282 {
283 PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
284 if (propertyName == null ? p.propertyName != null
285 : ! propertyName.equals(p.propertyName))
286 return;
287 l = (PropertyChangeListener) p.getListener();
288 }
289 s.listeners.remove(l);
290 if (s.listeners.isEmpty())
291 {
292 children.remove(propertyName);
293 if (children.isEmpty())
294 children = null;
295 }
296 }
297
298 /**
299 * Returns an array of all property change listeners registered under the
300 * given property name. If there are no registered listeners, or
301 * propertyName is null, this returns an empty array.
302 *
303 * @return the array of registered listeners
304 * @since 1.4
305 */
306 public synchronized PropertyChangeListener[]
307 getPropertyChangeListeners(String propertyName)
308 {
309 if (children == null || propertyName == null)
310 return new PropertyChangeListener[0];
311 PropertyChangeSupport s
312 = (PropertyChangeSupport) children.get(propertyName);
313 if (s == null)
314 return new PropertyChangeListener[0];
315 return (PropertyChangeListener[])
316 s.listeners.toArray(new PropertyChangeListener[s.listeners.size()]);
317 }
318
319 /**
320 * Fire a PropertyChangeEvent containing the old and new values of the
321 * property to all the global listeners, and to all the listeners for the
322 * specified property name. This does nothing if old and new are non-null
323 * and equal.
324 *
325 * @param propertyName the name of the property that changed
326 * @param oldVal the old value
327 * @param newVal the new value
328 */
329 public void firePropertyChange(String propertyName,
330 Object oldVal, Object newVal)
331 {
332 firePropertyChange(new PropertyChangeEvent(source, propertyName,
333 oldVal, newVal));
334 }
335
336 /**
337 * Fire a PropertyChangeEvent containing the old and new values of the
338 * property to all the global listeners, and to all the listeners for the
339 * specified property name. This does nothing if old and new are equal.
340 *
341 * @param propertyName the name of the property that changed
342 * @param oldVal the old value
343 * @param newVal the new value
344 */
345 public void firePropertyChange(String propertyName, int oldVal, int newVal)
346 {
347 if (oldVal != newVal)
348 firePropertyChange(new PropertyChangeEvent(source, propertyName,
349 Integer.valueOf(oldVal),
350 Integer.valueOf(newVal)));
351 }
352
353 /**
354 * Fire a PropertyChangeEvent containing the old and new values of the
355 * property to all the global listeners, and to all the listeners for the
356 * specified property name. This does nothing if old and new are equal.
357 *
358 * @param propertyName the name of the property that changed
359 * @param oldVal the old value
360 * @param newVal the new value
361 */
362 public void firePropertyChange(String propertyName,
363 boolean oldVal, boolean newVal)
364 {
365 if (oldVal != newVal)
366 firePropertyChange(new PropertyChangeEvent(source, propertyName,
367 Boolean.valueOf(oldVal),
368 Boolean.valueOf(newVal)));
369 }
370
371 /**
372 * Fire a PropertyChangeEvent to all the global listeners, and to all the
373 * listeners for the specified property name. This does nothing if old and
374 * new values of the event are equal.
375 *
376 * @param event the event to fire
377 * @throws NullPointerException if event is null
378 */
379 public void firePropertyChange(PropertyChangeEvent event)
380 {
381 if (event.oldValue != null && event.oldValue.equals(event.newValue))
382 return;
383 Vector v = listeners; // Be thread-safe.
384 if (v != null)
385 {
386 int i = v.size();
387 while (--i >= 0)
388 ((PropertyChangeListener) v.get(i)).propertyChange(event);
389 }
390 Hashtable h = children; // Be thread-safe.
391 if (h != null && event.propertyName != null)
392 {
393 PropertyChangeSupport s
394 = (PropertyChangeSupport) h.get(event.propertyName);
395 if (s != null)
396 {
397 v = s.listeners; // Be thread-safe.
398 int i = v == null ? 0 : v.size();
399 while (--i >= 0)
400 ((PropertyChangeListener) v.get(i)).propertyChange(event);
401 }
402 }
403 }
404
405 /**
406 * Fire an indexed property change event. This will only fire
407 * an event if the old and new values are not equal and not null.
408 * @param name the name of the property which changed
409 * @param index the index of the property which changed
410 * @param oldValue the old value of the property
411 * @param newValue the new value of the property
412 * @since 1.5
413 */
414 public void fireIndexedPropertyChange(String name, int index,
415 Object oldValue, Object newValue)
416 {
417 // Argument checking is done in firePropertyChange(PropertyChangeEvent) .
418 firePropertyChange(new IndexedPropertyChangeEvent(source, name,
419 oldValue, newValue,
420 index));
421 }
422
423 /**
424 * Fire an indexed property change event. This will only fire
425 * an event if the old and new values are not equal.
426 * @param name the name of the property which changed
427 * @param index the index of the property which changed
428 * @param oldValue the old value of the property
429 * @param newValue the new value of the property
430 * @since 1.5
431 */
432 public void fireIndexedPropertyChange(String name, int index,
433 int oldValue, int newValue)
434 {
435 if (oldValue != newValue)
436 fireIndexedPropertyChange(name, index, Integer.valueOf(oldValue),
437 Integer.valueOf(newValue));
438 }
439
440 /**
441 * Fire an indexed property change event. This will only fire
442 * an event if the old and new values are not equal.
443 * @param name the name of the property which changed
444 * @param index the index of the property which changed
445 * @param oldValue the old value of the property
446 * @param newValue the new value of the property
447 * @since 1.5
448 */
449 public void fireIndexedPropertyChange(String name, int index,
450 boolean oldValue, boolean newValue)
451 {
452 if (oldValue != newValue)
453 fireIndexedPropertyChange(name, index, Boolean.valueOf(oldValue),
454 Boolean.valueOf(newValue));
455 }
456
457 /**
458 * Tell whether the specified property is being listened on or not. This
459 * will only return <code>true</code> if there are listeners on all
460 * properties or if there is a listener specifically on this property.
461 *
462 * @param propertyName the property that may be listened on
463 * @return whether the property is being listened on
464 */
465 public synchronized boolean hasListeners(String propertyName)
466 {
467 return listeners != null || (children != null
468 && children.get(propertyName) != null);
469 }
470
471 /**
472 * Saves the state of the object to the stream.
473 *
474 * @param s the stream to write to
475 * @throws IOException if anything goes wrong
476 * @serialData this writes out a null-terminated list of serializable
477 * global property change listeners (the listeners for a named
478 * property are written out as the global listeners of the
479 * children, when the children hashtable is saved)
480 */
481 private synchronized void writeObject(ObjectOutputStream s)
482 throws IOException
483 {
484 s.defaultWriteObject();
485 if (listeners != null)
486 {
487 int i = listeners.size();
488 while (--i >= 0)
489 if (listeners.get(i) instanceof Serializable)
490 s.writeObject(listeners.get(i));
491 }
492 s.writeObject(null);
493 }
494
495 /**
496 * Reads the object back from stream (deserialization).
497 *
498 * XXX Since serialization for 1.1 streams was not documented, this may
499 * not work if propertyChangeSupportSerializedDataVersion is 1.
500 *
501 * @param s the stream to read from
502 * @throws IOException if reading the stream fails
503 * @throws ClassNotFoundException if deserialization fails
504 * @serialData this reads in a null-terminated list of serializable
505 * global property change listeners (the listeners for a named
506 * property are written out as the global listeners of the
507 * children, when the children hashtable is saved)
508 */
509 private void readObject(ObjectInputStream s)
510 throws IOException, ClassNotFoundException
511 {
512 s.defaultReadObject();
513 PropertyChangeListener l = (PropertyChangeListener) s.readObject();
514 while (l != null)
515 {
516 addPropertyChangeListener(l);
517 l = (PropertyChangeListener) s.readObject();
518 }
519 // Sun is not as careful with children as we are, and lets some proxys
520 // in that can never receive events. So, we clean up anything that got
521 // serialized, to make sure our invariants hold.
522 if (children != null)
523 {
524 int i = children.size();
525 Iterator iter = children.entrySet().iterator();
526 while (--i >= 0)
527 {
528 Entry e = (Entry) iter.next();
529 String name = (String) e.getKey();
530 PropertyChangeSupport pcs = (PropertyChangeSupport) e.getValue();
531 if (pcs.listeners == null)
532 pcs.listeners = new Vector();
533 if (pcs.children != null)
534 pcs.listeners.addAll
535 (Arrays.asList(pcs.getPropertyChangeListeners(name)));
536 if (pcs.listeners.size() == 0)
537 iter.remove();
538 else
539 pcs.children = null;
540 }
541 if (children.size() == 0)
542 children = null;
543 }
544 }
545 } // class PropertyChangeSupport