Code - Class EDU.oswego.cs.dl.util.concurrent.PropertyChangeMulticaster


1 /*
2   File: PropertyChangeMulticaster.java
3
4   Originally written by Doug Lea and released into the public domain.
5   This may be used for any purposes whatsoever without acknowledgment.
6   Thanks for the assistance and support of Sun Microsystems Labs,
7   and everyone contributing, testing, and using this code.
8
9   This class is based on Sun JDK java.beans.VetoableChangeSupport,
10   which is copyrighted by Sun. (It shares practically no code, but for
11   consistency, the documentation was lifted and adapted here.)
12
13   History:
14   Date Who What
15   14Mar1999 dl first release
16 */

17
18 package EDU.oswego.cs.dl.util.concurrent;
19
20 import java.beans.PropertyChangeListener;
21 import java.beans.PropertyChangeEvent;
22 import java.util.HashMap;
23 import java.io.Serializable;
24 import java.io.ObjectOutputStream;
25 import java.io.ObjectInputStream;
26 import java.io.IOException;
27
28 /**
29  * This class is interoperable with java.beans.PropertyChangeSupport,
30  * but relies on a streamlined copy-on-write scheme similar to
31  * that used in CopyOnWriteArrayList. This leads to much better
32  * performance in most event-intensive programs. It also adheres to clarified
33  * semantics of add and remove operations.
34  * <p>
35  * <b>Sample usage.</b>
36  *
37  * <pre>
38  * class Thing {
39  * protected Color myColor = Color.red; // an example property
40  *
41  * protected PropertyChangeMulticaster listeners =
42  * new PropertyChangeMulticaster(this);
43  *
44  * // registration methods, including:
45  * void addListener(PropertyChangeListener l) {
46  * // Use the `ifAbsent' version to avoid duplicate notifications
47  * listeners.addPropertyChangeListenerIfAbsent(l);
48  * }
49  *
50  * public synchronized Color getColor() { // accessor
51  * return myColor;
52  * }
53  *
54  * // internal synchronized state change method; returns old value
55  * protected synchronized Color assignColor(Color newColor) {
56  * Color oldColor = myColor;
57  * myColor = newColor;
58  * return oldColor;
59  * }
60  *
61  * public void setColor(Color newColor) {
62  * // atomically change state
63  * Color oldColor = assignColor(newColor);
64  * // broadcast change notification without holding synch lock
65  * listeners.firePropertyChange("color", oldColor, newColor);
66  * }
67  * }
68  * </pre>
69  * <p>[<a HREF="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
70  **/

71
72 public class PropertyChangeMulticaster implements Serializable {
73
74   // In order to allow this class to be lifted out without using
75
// the whole package, the basic mechanics of CopyOnWriteArrayList
76
// are used here, but not the class itself.
77
// This also makes it barely faster.
78

79   /**
80    * The array of listeners. Copied on each update
81    **/

82
83   protected transient PropertyChangeListener[] listeners = new PropertyChangeListener[0];
84
85
86   /**
87    * The object to be provided as the "source" for any generated events.
88    * @serial
89    */

90   protected final Object source;
91
92   /**
93    * HashMap for managing listeners for specific properties.
94    * Maps property names to PropertyChangeMulticaster objects.
95    * @serial
96    */

97   protected HashMap children;
98
99   /**
100    * Return the child associated with property, or null if no such
101    **/

102
103   protected synchronized PropertyChangeMulticaster getChild(String propertyName) {
104     return (children == null)? null :
105       ((PropertyChangeMulticaster)children.get(propertyName));
106   }
107
108
109   /**
110    * Constructs a <code>PropertyChangeMulticaster</code> object.
111    *
112    * @param sourceBean The bean to be given as the source for any events.
113    * @exception NullPointerException if sourceBean is null
114    */

115   
116   public PropertyChangeMulticaster(Object sourceBean) {
117     if (sourceBean == null) {
118       throw new NullPointerException();
119     }
120
121     source = sourceBean;
122   }
123
124   /**
125    * Add a VetoableChangeListener to the listener list.
126    * The listener is registered for all properties.
127    * If the listener is added multiple times, it will
128    * receive multiple change notifications upon any firePropertyChange
129    *
130    * @param listener The PropertyChangeListener to be added
131    * @exception NullPointerException If listener is null
132    */

133   
134   public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
135
136     if (listener == null) throw new NullPointerException();
137
138     int len = listeners.length;
139     PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
140     if (len > 0)
141       System.arraycopy(listeners, 0, newArray, 0, len);
142     newArray[len] = listener;
143     listeners = newArray;
144   }
145
146
147   /**
148    * Add a PropertyChangeListener to the listener list if it is
149    * not already present.
150    * The listener is registered for all properties.
151    * The operation maintains Set semantics: If the listener is already
152    * registered, the operation has no effect.
153    *
154    * @param listener The PropertyChangeListener to be added
155    * @exception NullPointerException If listener is null
156    */

157   
158   public synchronized void addPropertyChangeListenerIfAbsent(PropertyChangeListener listener) {
159
160     if (listener == null) throw new NullPointerException();
161
162     // Copy while checking if already present.
163
int len = listeners.length;
164     PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
165     for (int i = 0; i < len; ++i) {
166       newArray[i] = listeners[i];
167       if (listener.equals(listeners[i]))
168     return; // already present -- throw away copy
169
}
170     newArray[len] = listener;
171     listeners = newArray;
172   }
173
174
175   /**
176    * Remove a PropertyChangeListener from the listener list.
177    * It removes at most one occurrence of the given listener.
178    * If the listener was added multiple times it must be removed
179    * mulitple times.
180    * This removes a PropertyChangeListener that was registered
181    * for all properties, and has no effect if registered for only
182    * one or more specified properties.
183    *
184    * @param listener The PropertyChangeListener to be removed
185    */

186   
187   public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
188
189     int newlen = listeners.length-1;
190     if (newlen < 0 || listener == null) return;
191
192     // Copy while searching for element to remove
193

194     PropertyChangeListener[] newArray = new PropertyChangeListener[newlen];
195
196     for (int i = 0; i < newlen; ++i) {
197       if (listener.equals(listeners[i])) {
198         // copy remaining and exit
199
for (int k = i + 1; k <= newlen; ++k) newArray[k-1] = listeners[k];
200         listeners = newArray;
201         return;
202       }
203       else
204         newArray[i] = listeners[i];
205     }
206
207     // special-case last cell
208
if (listener.equals(listeners[newlen]))
209       listeners = newArray;
210   }
211
212   /**
213    * Add a PropertyChangeListener for a specific property. The listener
214    * will be invoked only when a call on firePropertyChange names that
215    * specific property. However, if a listener is registered both for all
216    * properties and a specific property, it will receive multiple
217    * notifications upon changes to that property.
218    *
219    * @param propertyName The name of the property to listen on.
220    * @param listener The PropertyChangeListener to be added
221    * @exception NullPointerException If listener is null
222    */

223   
224   public void addPropertyChangeListener(String propertyName,
225                                         PropertyChangeListener listener) {
226
227     if (listener == null) throw new NullPointerException();
228
229     PropertyChangeMulticaster child = null;
230
231     synchronized(this) {
232       if (children == null)
233         children = new HashMap();
234       else
235         child = (PropertyChangeMulticaster)children.get(propertyName);
236       
237       if (child == null) {
238         child = new PropertyChangeMulticaster(source);
239         children.put(propertyName, child);
240       }
241     }
242
243     child.addPropertyChangeListener(listener);
244   }
245
246   /**
247    * Add a PropertyChangeListener for a specific property, if it is not
248    * already registered. The listener
249    * will be invoked only when a call on firePropertyChange names that
250    * specific property.
251    *
252    * @param propertyName The name of the property to listen on.
253    * @param listener The PropertyChangeListener to be added
254    * @exception NullPointerException If listener is null
255    */

256   
257   public void addPropertyChangeListenerIfAbsent(String propertyName,
258                                         PropertyChangeListener listener) {
259
260     if (listener == null) throw new NullPointerException();
261
262     PropertyChangeMulticaster child = null;
263
264     synchronized(this) {
265       if (children == null)
266         children = new HashMap();
267       else
268         child = (PropertyChangeMulticaster)children.get(propertyName);
269       
270       if (child == null) {
271         child = new PropertyChangeMulticaster(source);
272         children.put(propertyName, child);
273       }
274     }
275
276     child.addPropertyChangeListenerIfAbsent(listener);
277   }
278
279   /**
280    * Remove a PropertyChangeListener for a specific property.
281    * Affects only the given property.
282    * If the listener is also registered for all properties,
283    * then it will continue to be registered for them.
284    *
285    * @param propertyName The name of the property that was listened on.
286    * @param listener The PropertyChangeListener to be removed
287    */

288   
289   public void removePropertyChangeListener(String propertyName,
290                                            PropertyChangeListener listener) {
291
292     PropertyChangeMulticaster child = getChild(propertyName);
293     if (child != null)
294       child.removePropertyChangeListener(listener);
295   }
296
297
298   /**
299    * Helper method to relay evt to all listeners.
300    * Called by all public firePropertyChange methods.
301    **/

302
303   protected void multicast(PropertyChangeEvent evt) {
304
305     PropertyChangeListener[] array; // bind in synch block below
306
PropertyChangeMulticaster child = null;
307
308     synchronized (this) {
309       array = listeners;
310
311       if (children != null && evt.getPropertyName() != null)
312         child = (PropertyChangeMulticaster)children.get(evt.getPropertyName());
313     }
314     
315     for (int i = 0; i < array.length; ++i)
316       array[i].propertyChange(evt);
317     
318     if (child != null)
319       child.multicast(evt);
320
321   }
322
323   
324   /**
325    * Report a bound property update to any registered listeners.
326    * No event is fired if old and new are equal and non-null.
327    *
328    * @param propertyName The programmatic name of the property
329    * that was changed.
330    * @param oldValue The old value of the property.
331    * @param newValue The new value of the property.
332    */

333   public void firePropertyChange(String propertyName,
334                                  Object oldValue, Object newValue) {
335    
336     if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
337       multicast(new PropertyChangeEvent(source,
338                                         propertyName,
339                                         oldValue,
340                                         newValue));
341     }
342     
343   }
344
345   /**
346    * Report an int bound property update to any registered listeners.
347    * No event is fired if old and new are equal and non-null.
348    * <p>
349    * This is merely a convenience wrapper around the more general
350    * firePropertyChange method that takes Object values.
351    *
352    * @param propertyName The programmatic name of the property
353    * that was changed.
354    * @param oldValue The old value of the property.
355    * @param newValue The new value of the property.
356    */

357   public void firePropertyChange(String propertyName,
358                                  int oldValue, int newValue) {
359     if (oldValue != newValue) {
360       multicast(new PropertyChangeEvent(source,
361                                         propertyName,
362                                         new Integer(oldValue),
363                                         new Integer(newValue)));
364     }
365   }
366
367
368   /**
369    * Report a boolean bound property update to any registered listeners.
370    * No event is fired if old and new are equal and non-null.
371    * <p>
372    * This is merely a convenience wrapper around the more general
373    * firePropertyChange method that takes Object values.
374    *
375    * @param propertyName The programmatic name of the property
376    * that was changed.
377    * @param oldValue The old value of the property.
378    * @param newValue The new value of the property.
379    */

380   public void firePropertyChange(String propertyName,
381                                  boolean oldValue, boolean newValue) {
382     if (oldValue != newValue) {
383       multicast(new PropertyChangeEvent(source,
384                                         propertyName,
385                                         new Boolean(oldValue),
386                                         new Boolean(newValue)));
387     }
388   }
389
390   /**
391    * Fire an existing PropertyChangeEvent to any registered listeners.
392    * No event is fired if the given event's old and new values are
393    * equal and non-null.
394    * @param evt The PropertyChangeEvent object.
395    */

396   public void firePropertyChange(PropertyChangeEvent evt) {
397     Object oldValue = evt.getOldValue();
398     Object newValue = evt.getNewValue();
399     if (oldValue == null || newValue == null || !oldValue.equals(newValue))
400       multicast(evt);
401   }
402
403   /**
404    * Check if there are any listeners for a specific property.
405    * If propertyName is null, return whether there are any listeners at all.
406    *
407    * @param propertyName the property name.
408    * @return true if there are one or more listeners for the given property
409    *
410    */

411   public boolean hasListeners(String propertyName) {
412
413     PropertyChangeMulticaster child;
414
415     synchronized (this) {
416       if (listeners.length > 0)
417         return true;
418       else if (propertyName == null || children == null)
419         return false;
420       else {
421         child = (PropertyChangeMulticaster)children.get(propertyName);
422         if (child == null)
423           return false;
424       }
425     }
426     
427     return child.hasListeners(null);
428   }
429
430
431   /**
432    * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
433    * <p>
434    * At serialization time we skip non-serializable listeners and
435    * only serialize the serializable listeners.
436    *
437    */

438   private synchronized void writeObject(ObjectOutputStream s) throws IOException {
439     s.defaultWriteObject();
440     
441     for (int i = 0; i < listeners.length; i++) {
442       PropertyChangeListener l = listeners[i];
443       if (listeners[i] instanceof Serializable) {
444         s.writeObject(listeners[i]);
445       }
446     }
447     s.writeObject(null);
448   }
449   
450   
451   private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
452     listeners = new PropertyChangeListener[0]; // paranoically reset
453
s.defaultReadObject();
454     
455     Object listenerOrNull;
456     while (null != (listenerOrNull = s.readObject())) {
457       addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
458     }
459   }
460
461 }
462

Java API By Example, From Geeks To Geeks. | Conditions of Use | About Us © 2002 - 2005, KickJava.com, or its affiliates