Mercurial > hg > blitz_condensed
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 } |