Mercurial > hg > blitz_condensed
comparison src/EDU/oswego/cs/dl/util/concurrent/VetoableChangeMulticaster.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: ProperyChangeMulticaster.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.VetoableChangeListener; | |
21 import java.beans.PropertyChangeEvent; | |
22 import java.beans.PropertyVetoException; | |
23 import java.util.HashMap; | |
24 import java.io.Serializable; | |
25 import java.io.ObjectOutputStream; | |
26 import java.io.ObjectInputStream; | |
27 import java.io.IOException; | |
28 | |
29 /** | |
30 * This class is interoperable with java.beans.VetoableChangeSupport, | |
31 * but relies on a streamlined copy-on-write scheme similar to | |
32 * that used in CopyOnWriteArrayList. It also adheres to clarified | |
33 * semantics of add, remove, and fireVetoableChange operations. | |
34 * <p> | |
35 * <b>Sample usage.</b> | |
36 * | |
37 * <pre> | |
38 * class Thing { | |
39 * protected Color myColor = Color.red; // an example property | |
40 * protected boolean changePending; // track whether in midst of change | |
41 * | |
42 * // vetoable listeners: | |
43 * protected VetoableChangeMulticaster vetoers = | |
44 * new VetoableChangeMulticaster(this); | |
45 * | |
46 * // Possibly also some ordinary listeners: | |
47 * protected PropertyChangeMulticaster listeners = | |
48 * new PropertyChangeMulticaster(this); | |
49 * | |
50 * // registration methods, including: | |
51 * void addVetoer(VetoableChangeListener l) { | |
52 * // Use the `ifAbsent' version to avoid duplicate notifications | |
53 * vetoers.addVetoableChangeListenerIfAbsent(l); | |
54 * } | |
55 * | |
56 * public synchronized Color getColor() { // accessor | |
57 * return myColor; | |
58 * } | |
59 * | |
60 * // Simple transactional control for vetos | |
61 * | |
62 * public void setColor(int newColor) throws PropertyVetoException { | |
63 * Color oldColor = prepareSetColor(newColor); | |
64 * | |
65 * try { | |
66 * vetoers.fireVetoableChange("color", oldColor, newColor); | |
67 * commitColor(newColor); | |
68 * listeners.firePropertyChange("color", oldColor, newColor); | |
69 * } | |
70 * catch(PropertyVetoException ex) { | |
71 * abortSetColor(); | |
72 * throw ex; | |
73 * } | |
74 * } | |
75 * | |
76 * // Called on entry to proposed vetoable change from setColor. | |
77 * // Throws exception if there is already another change in progress. | |
78 * // Returns current color | |
79 * synchronized int prepareSetColor(Color c) throws PropertyVetoException { | |
80 * // only support one transaction at a time | |
81 * if (changePending) | |
82 * throw new PropertyVetoException("Concurrent modification"); | |
83 * // (Could alternatively wait out other transactions via | |
84 * // a wait/notify construction based on changePending.) | |
85 * | |
86 * // perhaps some other screenings, like: | |
87 * else if (c == null) | |
88 * throw new PropertyVetoException("Cannot change color to Null"); | |
89 * else { | |
90 * changePending = true; | |
91 * return myColor; | |
92 * } | |
93 * } | |
94 * | |
95 * synchronized void commitColor(Color newColor) { | |
96 * myColor = newColor; | |
97 * changePending = false; | |
98 * } | |
99 * | |
100 * synchronized void abortSetColor() { | |
101 * changePending = false; | |
102 * } | |
103 * | |
104 * } | |
105 * </pre> | |
106 * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>] | |
107 **/ | |
108 | |
109 public class VetoableChangeMulticaster implements Serializable { | |
110 | |
111 // This code is 90% identical with PropertyChangeMulticaster, | |
112 // but there is no good way to unify the code while maintaining | |
113 // interoperability with beans versions. | |
114 | |
115 /** | |
116 * The array of listeners. Copied on each update | |
117 **/ | |
118 | |
119 protected transient VetoableChangeListener[] listeners = new VetoableChangeListener[0]; | |
120 | |
121 | |
122 /** | |
123 * The object to be provided as the "source" for any generated events. | |
124 * @serial | |
125 */ | |
126 protected final Object source; | |
127 | |
128 /** | |
129 * HashMap for managing listeners for specific properties. | |
130 * Maps property names to VetoableChangeMulticaster objects. | |
131 * @serial | |
132 */ | |
133 protected HashMap children; | |
134 | |
135 /** | |
136 * Return the child associated with property, or null if no such | |
137 **/ | |
138 | |
139 protected synchronized VetoableChangeMulticaster getChild(String propertyName) { | |
140 return (children == null)? null : | |
141 ((VetoableChangeMulticaster)children.get(propertyName)); | |
142 } | |
143 | |
144 | |
145 /** | |
146 * Constructs a <code>VetoableChangeMulticaster</code> object. | |
147 * | |
148 * @param sourceBean The bean to be given as the source for any events. | |
149 * @exception NullPointerException if sourceBean is null | |
150 */ | |
151 | |
152 public VetoableChangeMulticaster(Object sourceBean) { | |
153 if (sourceBean == null) { | |
154 throw new NullPointerException(); | |
155 } | |
156 | |
157 source = sourceBean; | |
158 } | |
159 | |
160 /** | |
161 * Add a VetoableChangeListener to the listener list. | |
162 * The listener is registered for all properties. | |
163 * If the listener is added multiple times, it will | |
164 * receive multiple change notifications upon any fireVetoableChange. | |
165 * | |
166 * @param listener The VetoableChangeListener to be added | |
167 */ | |
168 | |
169 public synchronized void addVetoableChangeListener(VetoableChangeListener listener) { | |
170 | |
171 if (listener == null) throw new NullPointerException(); | |
172 | |
173 int len = listeners.length; | |
174 VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1]; | |
175 if (len > 0) | |
176 System.arraycopy(listeners, 0, newArray, 0, len); | |
177 newArray[len] = listener; | |
178 listeners = newArray; | |
179 } | |
180 | |
181 /** | |
182 * Add a PropertyChangeListener to the listener list if it is | |
183 * not already present. | |
184 * The listener is registered for all properties. | |
185 * The operation maintains Set semantics: If the listener is already | |
186 * registered, the operation has no effect. | |
187 * | |
188 * @param listener The PropertyChangeListener to be added | |
189 * @exception NullPointerException If listener is null | |
190 */ | |
191 | |
192 public synchronized void addVetoableChangeListenerIfAbsent(VetoableChangeListener listener) { | |
193 | |
194 if (listener == null) throw new NullPointerException(); | |
195 | |
196 // Copy while checking if already present. | |
197 int len = listeners.length; | |
198 VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1]; | |
199 for (int i = 0; i < len; ++i) { | |
200 newArray[i] = listeners[i]; | |
201 if (listener.equals(listeners[i])) | |
202 return; // already present -- throw away copy | |
203 } | |
204 newArray[len] = listener; | |
205 listeners = newArray; | |
206 } | |
207 | |
208 | |
209 /** | |
210 * Remove an occurrence of a VetoableChangeListener from the listener list. | |
211 * It removes at most one occurrence of the given listener. | |
212 * If the listener was added multiple times it must be removed | |
213 * mulitple times. | |
214 * This removes a VetoableChangeListener that was registered | |
215 * for all properties, and has no effect if registered for only | |
216 * one or more specified properties. | |
217 * | |
218 * @param listener The VetoableChangeListener to be removed | |
219 */ | |
220 | |
221 public synchronized void removeVetoableChangeListener(VetoableChangeListener listener) { | |
222 | |
223 int newlen = listeners.length-1; | |
224 if (newlen < 0 || listener == null) return; | |
225 | |
226 // Copy while searching for element to remove | |
227 | |
228 VetoableChangeListener[] newArray = new VetoableChangeListener[newlen]; | |
229 | |
230 for (int i = 0; i < newlen; ++i) { | |
231 if (listener.equals(listeners[i])) { | |
232 // copy remaining and exit | |
233 for (int k = i + 1; k <= newlen; ++k) newArray[k-1] = listeners[k]; | |
234 listeners = newArray; | |
235 return; | |
236 } | |
237 else | |
238 newArray[i] = listeners[i]; | |
239 } | |
240 | |
241 // special-case last cell | |
242 if (listener.equals(listeners[newlen])) | |
243 listeners = newArray; | |
244 | |
245 } | |
246 | |
247 /** | |
248 * Add a VetoableChangeListener for a specific property. The listener | |
249 * will be invoked only when a call on fireVetoableChange names that | |
250 * specific property. However, if a listener is registered both for all | |
251 * properties and a specific property, it will receive multiple | |
252 * notifications upon changes to that property. | |
253 * | |
254 * @param propertyName The name of the property to listen on. | |
255 * @param listener The VetoableChangeListener to be added | |
256 * @exception NullPointerException If listener is null | |
257 */ | |
258 | |
259 public void addVetoableChangeListener(String propertyName, | |
260 VetoableChangeListener listener) { | |
261 | |
262 if (listener == null) throw new NullPointerException(); | |
263 | |
264 VetoableChangeMulticaster child = null; | |
265 | |
266 synchronized(this) { | |
267 if (children == null) | |
268 children = new HashMap(); | |
269 else | |
270 child = (VetoableChangeMulticaster)children.get(propertyName); | |
271 | |
272 if (child == null) { | |
273 child = new VetoableChangeMulticaster(source); | |
274 children.put(propertyName, child); | |
275 } | |
276 } | |
277 | |
278 child.addVetoableChangeListener(listener); | |
279 } | |
280 | |
281 /** | |
282 * Add a VetoableChangeListener for a specific property, if it is not | |
283 * already registered. The listener | |
284 * will be invoked only when a call on fireVetoableChange names that | |
285 * specific property. | |
286 * | |
287 * @param propertyName The name of the property to listen on. | |
288 * @param listener The VetoableChangeListener to be added | |
289 * @exception NullPointerException If listener is null | |
290 */ | |
291 | |
292 public void addVetoableChangeListenerIfAbsent(String propertyName, | |
293 VetoableChangeListener listener) { | |
294 | |
295 if (listener == null) throw new NullPointerException(); | |
296 | |
297 VetoableChangeMulticaster child = null; | |
298 | |
299 synchronized(this) { | |
300 if (children == null) | |
301 children = new HashMap(); | |
302 else | |
303 child = (VetoableChangeMulticaster)children.get(propertyName); | |
304 | |
305 if (child == null) { | |
306 child = new VetoableChangeMulticaster(source); | |
307 children.put(propertyName, child); | |
308 } | |
309 } | |
310 | |
311 child.addVetoableChangeListenerIfAbsent(listener); | |
312 } | |
313 | |
314 | |
315 /** | |
316 * Remove a VetoableChangeListener for a specific property. | |
317 * Affects only the given property. | |
318 * If the listener is also registered for all properties, | |
319 * then it will continue to be registered for them. | |
320 * | |
321 * @param propertyName The name of the property that was listened on. | |
322 * @param listener The VetoableChangeListener to be removed | |
323 */ | |
324 | |
325 public void removeVetoableChangeListener(String propertyName, | |
326 VetoableChangeListener listener) { | |
327 | |
328 VetoableChangeMulticaster child = getChild(propertyName); | |
329 if (child != null) | |
330 child.removeVetoableChangeListener(listener); | |
331 } | |
332 | |
333 | |
334 /** | |
335 * Helper method to relay evt to all listeners. | |
336 * Called by all public fireVetoableChange methods. | |
337 **/ | |
338 | |
339 protected void multicast(PropertyChangeEvent evt) throws PropertyVetoException { | |
340 | |
341 VetoableChangeListener[] array; // bind in synch block below | |
342 VetoableChangeMulticaster child = null; | |
343 | |
344 synchronized (this) { | |
345 array = listeners; | |
346 | |
347 if (children != null && evt.getPropertyName() != null) | |
348 child = (VetoableChangeMulticaster)children.get(evt.getPropertyName()); | |
349 } | |
350 | |
351 // Loop through array, and then cascade to child. | |
352 | |
353 int i = 0; // make visible to catch clause | |
354 | |
355 try { | |
356 for (i = 0; i < array.length; ++i) | |
357 array[i].vetoableChange(evt); | |
358 | |
359 if (child != null) | |
360 child.multicast(evt); | |
361 } | |
362 | |
363 catch (PropertyVetoException veto) { | |
364 | |
365 // Revert all that have been notified | |
366 | |
367 PropertyChangeEvent revert = | |
368 new PropertyChangeEvent(evt.getSource(), | |
369 evt.getPropertyName(), | |
370 evt.getNewValue(), | |
371 evt.getOldValue()); | |
372 | |
373 int lastNotified = (i < array.length)? i : (array.length-1); | |
374 | |
375 for (int k = 0; k <= lastNotified; ++k) { | |
376 try { | |
377 array[k].vetoableChange(revert); | |
378 } | |
379 catch (PropertyVetoException ignore) { | |
380 // Cannot veto a reversion | |
381 } | |
382 } | |
383 | |
384 // Rethrow the PropertyVetoException. | |
385 throw veto; | |
386 } | |
387 } | |
388 | |
389 | |
390 /** | |
391 * Report a vetoable property update to any registered listeners. | |
392 * Notifications are sent serially (although in no particular order) | |
393 * to the list of listeners, | |
394 * aborting if one throws PropertyVetoException. Upon this exception, | |
395 * fire a new event reverting this | |
396 * change to all listeners that have already been notified | |
397 * (ignoring any further vetos), | |
398 * suppress notifications to all other listeners, and | |
399 * then rethrow the PropertyVetoException. | |
400 * <p> | |
401 * No event is fired if old and new are equal non-null. | |
402 * | |
403 * @param propertyName The programmatic name of the property | |
404 * that was changed. | |
405 * @param oldValue The old value of the property. | |
406 * @param newValue The new value of the property. | |
407 * @exception PropertyVetoException if a recipient wishes the property | |
408 * change to be rolled back. | |
409 */ | |
410 public void fireVetoableChange(String propertyName, | |
411 Object oldValue, Object newValue) throws PropertyVetoException { | |
412 | |
413 if (oldValue == null || newValue == null || !oldValue.equals(newValue)) { | |
414 multicast(new PropertyChangeEvent(source, | |
415 propertyName, | |
416 oldValue, | |
417 newValue)); | |
418 } | |
419 | |
420 } | |
421 | |
422 /** | |
423 * Report a vetoable property update to any registered listeners. | |
424 * Notifications are sent serially (although in no particular order) | |
425 * to the list of listeners, | |
426 * aborting if one throws PropertyVetoException. Upon this exception, | |
427 * fire a new event reverting this | |
428 * change to all listeners that have already been notified | |
429 * (ignoring any further vetos), | |
430 * suppress notifications to all other listeners, and | |
431 * then rethrow the PropertyVetoException. | |
432 * <p> | |
433 * No event is fired if old and new are equal. | |
434 * <p> | |
435 * This is merely a convenience wrapper around the more general | |
436 * fireVetoableChange method that takes Object values. | |
437 * | |
438 * @param propertyName The programmatic name of the property | |
439 * that was changed. | |
440 * @param oldValue The old value of the property. | |
441 * @param newValue The new value of the property. | |
442 * @exception PropertyVetoException if the recipient wishes the property | |
443 * change to be rolled back. | |
444 */ | |
445 public void fireVetoableChange(String propertyName, | |
446 int oldValue, int newValue) throws PropertyVetoException { | |
447 if (oldValue != newValue) { | |
448 multicast(new PropertyChangeEvent(source, | |
449 propertyName, | |
450 new Integer(oldValue), | |
451 new Integer(newValue))); | |
452 } | |
453 } | |
454 | |
455 | |
456 /** | |
457 * Report a vetoable property update to any registered listeners. | |
458 * Notifications are sent serially (although in no particular order) | |
459 * to the list of listeners, | |
460 * aborting if one throws PropertyVetoException. Upon this exception, | |
461 * fire a new event reverting this | |
462 * change to all listeners that have already been notified | |
463 * (ignoring any further vetos), | |
464 * suppress notifications to all other listeners, and | |
465 * then rethrow the PropertyVetoException. | |
466 * <p> | |
467 * No event is fired if old and new are equal. | |
468 * <p> | |
469 * This is merely a convenience wrapper around the more general | |
470 * fireVetoableChange method that takes Object values. | |
471 * | |
472 * @param propertyName The programmatic name of the property | |
473 * that was changed. | |
474 * @param oldValue The old value of the property. | |
475 * @param newValue The new value of the property. | |
476 * @exception PropertyVetoException if the recipient wishes the property | |
477 * change to be rolled back. | |
478 */ | |
479 public void fireVetoableChange(String propertyName, | |
480 boolean oldValue, boolean newValue) throws PropertyVetoException { | |
481 if (oldValue != newValue) { | |
482 multicast(new PropertyChangeEvent(source, | |
483 propertyName, | |
484 new Boolean(oldValue), | |
485 new Boolean(newValue))); | |
486 } | |
487 } | |
488 | |
489 /** | |
490 * Report a vetoable property update to any registered listeners. | |
491 * Notifications are sent serially (although in no particular order) | |
492 * to the list of listeners, | |
493 * aborting if one throws PropertyVetoException. Upon this exception, | |
494 * fire a new event reverting this | |
495 * change to all listeners that have already been notified | |
496 * (ignoring any further vetos), | |
497 * suppress notifications to all other listeners, and | |
498 * then rethrow the PropertyVetoException. | |
499 * <p> | |
500 * No event is fired if old and new are equal and non-null. | |
501 * | |
502 * equal and non-null. | |
503 * @param evt The PropertyChangeEvent object. | |
504 * @exception PropertyVetoException if the recipient wishes the property | |
505 * change to be rolled back. | |
506 */ | |
507 public void fireVetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { | |
508 Object oldValue = evt.getOldValue(); | |
509 Object newValue = evt.getNewValue(); | |
510 if (oldValue == null || newValue == null || !oldValue.equals(newValue)) | |
511 multicast(evt); | |
512 } | |
513 | |
514 /** | |
515 * Check if there are any listeners for a specific property. | |
516 * If propertyName is null, return whether there are any listeners at all. | |
517 * | |
518 * @param propertyName the property name. | |
519 * @return true if there are one or more listeners for the given property | |
520 * | |
521 */ | |
522 public boolean hasListeners(String propertyName) { | |
523 | |
524 VetoableChangeMulticaster child; | |
525 | |
526 synchronized (this) { | |
527 if (listeners.length > 0) | |
528 return true; | |
529 else if (propertyName == null || children == null) | |
530 return false; | |
531 else { | |
532 child = (VetoableChangeMulticaster)children.get(propertyName); | |
533 if (child == null) | |
534 return false; | |
535 } | |
536 } | |
537 | |
538 return child.hasListeners(null); | |
539 } | |
540 | |
541 | |
542 /** | |
543 * @serialData Null terminated list of <code>VetoableChangeListeners</code>. | |
544 * <p> | |
545 * At serialization time we skip non-serializable listeners and | |
546 * only serialize the serializable listeners. | |
547 * | |
548 */ | |
549 private synchronized void writeObject(ObjectOutputStream s) throws IOException { | |
550 s.defaultWriteObject(); | |
551 | |
552 for (int i = 0; i < listeners.length; i++) { | |
553 VetoableChangeListener l = listeners[i]; | |
554 if (listeners[i] instanceof Serializable) { | |
555 s.writeObject(listeners[i]); | |
556 } | |
557 } | |
558 s.writeObject(null); | |
559 } | |
560 | |
561 | |
562 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { | |
563 listeners = new VetoableChangeListener[0]; // paranoically reset | |
564 s.defaultReadObject(); | |
565 | |
566 Object listenerOrNull; | |
567 while (null != (listenerOrNull = s.readObject())) { | |
568 addVetoableChangeListener((VetoableChangeListener)listenerOrNull); | |
569 } | |
570 } | |
571 | |
572 } |