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 }