comparison src/EDU/oswego/cs/dl/util/concurrent/PropertyChangeMulticaster.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
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 }