001: /*
002: File: ProperyChangeMulticaster.java
003:
004: Originally written by Doug Lea and released into the public domain.
005: This may be used for any purposes whatsoever without acknowledgment.
006: Thanks for the assistance and support of Sun Microsystems Labs,
007: and everyone contributing, testing, and using this code.
008:
009: This class is based on Sun JDK java.beans.VetoableChangeSupport,
010: which is copyrighted by Sun. (It shares practically no code, but for
011: consistency, the documentation was lifted and adapted here.)
012:
013: History:
014: Date Who What
015: 14Mar1999 dl first release
016: */
017:
018: package EDU.oswego.cs.dl.util.concurrent;
019:
020: import java.beans.VetoableChangeListener;
021: import java.beans.PropertyChangeEvent;
022: import java.beans.PropertyVetoException;
023: import java.util.HashMap;
024: import java.io.Serializable;
025: import java.io.ObjectOutputStream;
026: import java.io.ObjectInputStream;
027: import java.io.IOException;
028:
029: /**
030: * This class is interoperable with java.beans.VetoableChangeSupport,
031: * but relies on a streamlined copy-on-write scheme similar to
032: * that used in CopyOnWriteArrayList. It also adheres to clarified
033: * semantics of add, remove, and fireVetoableChange operations.
034: * <p>
035: * <b>Sample usage.</b>
036: *
037: * <pre>
038: * class Thing {
039: * protected Color myColor = Color.red; // an example property
040: * protected boolean changePending; // track whether in midst of change
041: *
042: * // vetoable listeners:
043: * protected VetoableChangeMulticaster vetoers =
044: * new VetoableChangeMulticaster(this);
045: *
046: * // Possibly also some ordinary listeners:
047: * protected PropertyChangeMulticaster listeners =
048: * new PropertyChangeMulticaster(this);
049: *
050: * // registration methods, including:
051: * void addVetoer(VetoableChangeListener l) {
052: * // Use the `ifAbsent' version to avoid duplicate notifications
053: * vetoers.addVetoableChangeListenerIfAbsent(l);
054: * }
055: *
056: * public synchronized Color getColor() { // accessor
057: * return myColor;
058: * }
059: *
060: * // Simple transactional control for vetos
061: *
062: * public void setColor(int newColor) throws PropertyVetoException {
063: * Color oldColor = prepareSetColor(newColor);
064: *
065: * try {
066: * vetoers.fireVetoableChange("color", oldColor, newColor);
067: * commitColor(newColor);
068: * listeners.firePropertyChange("color", oldColor, newColor);
069: * }
070: * catch(PropertyVetoException ex) {
071: * abortSetColor();
072: * throw ex;
073: * }
074: * }
075: *
076: * // Called on entry to proposed vetoable change from setColor.
077: * // Throws exception if there is already another change in progress.
078: * // Returns current color
079: * synchronized int prepareSetColor(Color c) throws PropertyVetoException {
080: * // only support one transaction at a time
081: * if (changePending)
082: * throw new PropertyVetoException("Concurrent modification");
083: * // (Could alternatively wait out other transactions via
084: * // a wait/notify construction based on changePending.)
085: *
086: * // perhaps some other screenings, like:
087: * else if (c == null)
088: * throw new PropertyVetoException("Cannot change color to Null");
089: * else {
090: * changePending = true;
091: * return myColor;
092: * }
093: * }
094: *
095: * synchronized void commitColor(Color newColor) {
096: * myColor = newColor;
097: * changePending = false;
098: * }
099: *
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: * The object to be provided as the "source" for any generated events.
123: * @serial
124: */
125: protected final Object source;
126:
127: /**
128: * HashMap for managing listeners for specific properties.
129: * Maps property names to VetoableChangeMulticaster objects.
130: * @serial
131: */
132: protected HashMap children;
133:
134: /**
135: * Return the child associated with property, or null if no such
136: **/
137:
138: protected synchronized VetoableChangeMulticaster getChild(
139: String propertyName) {
140: return (children == null) ? null
141: : ((VetoableChangeMulticaster) children
142: .get(propertyName));
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(
170: VetoableChangeListener listener) {
171:
172: if (listener == null)
173: throw new NullPointerException();
174:
175: int len = listeners.length;
176: VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1];
177: if (len > 0)
178: System.arraycopy(listeners, 0, newArray, 0, len);
179: newArray[len] = listener;
180: listeners = newArray;
181: }
182:
183: /**
184: * Add a PropertyChangeListener to the listener list if it is
185: * not already present.
186: * The listener is registered for all properties.
187: * The operation maintains Set semantics: If the listener is already
188: * registered, the operation has no effect.
189: *
190: * @param listener The PropertyChangeListener to be added
191: * @exception NullPointerException If listener is null
192: */
193:
194: public synchronized void addVetoableChangeListenerIfAbsent(
195: VetoableChangeListener listener) {
196:
197: if (listener == null)
198: throw new NullPointerException();
199:
200: // Copy while checking if already present.
201: int len = listeners.length;
202: VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1];
203: for (int i = 0; i < len; ++i) {
204: newArray[i] = listeners[i];
205: if (listener.equals(listeners[i]))
206: return; // already present -- throw away copy
207: }
208: newArray[len] = listener;
209: listeners = newArray;
210: }
211:
212: /**
213: * Remove an occurrence of a VetoableChangeListener from the listener list.
214: * It removes at most one occurrence of the given listener.
215: * If the listener was added multiple times it must be removed
216: * mulitple times.
217: * This removes a VetoableChangeListener that was registered
218: * for all properties, and has no effect if registered for only
219: * one or more specified properties.
220: *
221: * @param listener The VetoableChangeListener to be removed
222: */
223:
224: public synchronized void removeVetoableChangeListener(
225: VetoableChangeListener listener) {
226:
227: int newlen = listeners.length - 1;
228: if (newlen < 0 || listener == null)
229: return;
230:
231: // Copy while searching for element to remove
232:
233: VetoableChangeListener[] newArray = new VetoableChangeListener[newlen];
234:
235: for (int i = 0; i < newlen; ++i) {
236: if (listener.equals(listeners[i])) {
237: // copy remaining and exit
238: for (int k = i + 1; k <= newlen; ++k)
239: newArray[k - 1] = listeners[k];
240: listeners = newArray;
241: return;
242: } else
243: newArray[i] = listeners[i];
244: }
245:
246: // special-case last cell
247: if (listener.equals(listeners[newlen]))
248: listeners = newArray;
249:
250: }
251:
252: /**
253: * Add a VetoableChangeListener for a specific property. The listener
254: * will be invoked only when a call on fireVetoableChange names that
255: * specific property. However, if a listener is registered both for all
256: * properties and a specific property, it will receive multiple
257: * notifications upon changes to that property.
258: *
259: * @param propertyName The name of the property to listen on.
260: * @param listener The VetoableChangeListener to be added
261: * @exception NullPointerException If listener is null
262: */
263:
264: public void addVetoableChangeListener(String propertyName,
265: VetoableChangeListener listener) {
266:
267: if (listener == null)
268: throw new NullPointerException();
269:
270: VetoableChangeMulticaster child = null;
271:
272: synchronized (this ) {
273: if (children == null)
274: children = new HashMap();
275: else
276: child = (VetoableChangeMulticaster) children
277: .get(propertyName);
278:
279: if (child == null) {
280: child = new VetoableChangeMulticaster(source);
281: children.put(propertyName, child);
282: }
283: }
284:
285: child.addVetoableChangeListener(listener);
286: }
287:
288: /**
289: * Add a VetoableChangeListener for a specific property, if it is not
290: * already registered. The listener
291: * will be invoked only when a call on fireVetoableChange names that
292: * specific property.
293: *
294: * @param propertyName The name of the property to listen on.
295: * @param listener The VetoableChangeListener to be added
296: * @exception NullPointerException If listener is null
297: */
298:
299: public void addVetoableChangeListenerIfAbsent(String propertyName,
300: VetoableChangeListener listener) {
301:
302: if (listener == null)
303: throw new NullPointerException();
304:
305: VetoableChangeMulticaster child = null;
306:
307: synchronized (this ) {
308: if (children == null)
309: children = new HashMap();
310: else
311: child = (VetoableChangeMulticaster) children
312: .get(propertyName);
313:
314: if (child == null) {
315: child = new VetoableChangeMulticaster(source);
316: children.put(propertyName, child);
317: }
318: }
319:
320: child.addVetoableChangeListenerIfAbsent(listener);
321: }
322:
323: /**
324: * Remove a VetoableChangeListener for a specific property.
325: * Affects only the given property.
326: * If the listener is also registered for all properties,
327: * then it will continue to be registered for them.
328: *
329: * @param propertyName The name of the property that was listened on.
330: * @param listener The VetoableChangeListener to be removed
331: */
332:
333: public void removeVetoableChangeListener(String propertyName,
334: VetoableChangeListener listener) {
335:
336: VetoableChangeMulticaster child = getChild(propertyName);
337: if (child != null)
338: child.removeVetoableChangeListener(listener);
339: }
340:
341: /**
342: * Helper method to relay evt to all listeners.
343: * Called by all public fireVetoableChange methods.
344: **/
345:
346: protected void multicast(PropertyChangeEvent evt)
347: throws PropertyVetoException {
348:
349: VetoableChangeListener[] array; // bind in synch block below
350: VetoableChangeMulticaster child = null;
351:
352: synchronized (this ) {
353: array = listeners;
354:
355: if (children != null && evt.getPropertyName() != null)
356: child = (VetoableChangeMulticaster) children.get(evt
357: .getPropertyName());
358: }
359:
360: // Loop through array, and then cascade to child.
361:
362: int i = 0; // make visible to catch clause
363:
364: try {
365: for (i = 0; i < array.length; ++i)
366: array[i].vetoableChange(evt);
367:
368: if (child != null)
369: child.multicast(evt);
370: }
371:
372: catch (PropertyVetoException veto) {
373:
374: // Revert all that have been notified
375:
376: PropertyChangeEvent revert = new PropertyChangeEvent(evt
377: .getSource(), evt.getPropertyName(), evt
378: .getNewValue(), evt.getOldValue());
379:
380: int lastNotified = (i < array.length) ? i
381: : (array.length - 1);
382:
383: for (int k = 0; k <= lastNotified; ++k) {
384: try {
385: array[k].vetoableChange(revert);
386: } catch (PropertyVetoException ignore) {
387: // Cannot veto a reversion
388: }
389: }
390:
391: // Rethrow the PropertyVetoException.
392: throw veto;
393: }
394: }
395:
396: /**
397: * Report a vetoable property update to any registered listeners.
398: * Notifications are sent serially (although in no particular order)
399: * to the list of listeners,
400: * aborting if one throws PropertyVetoException. Upon this exception,
401: * fire a new event reverting this
402: * change to all listeners that have already been notified
403: * (ignoring any further vetos),
404: * suppress notifications to all other listeners, and
405: * then rethrow the PropertyVetoException.
406: * <p>
407: * No event is fired if old and new are equal non-null.
408: *
409: * @param propertyName The programmatic name of the property
410: * that was changed.
411: * @param oldValue The old value of the property.
412: * @param newValue The new value of the property.
413: * @exception PropertyVetoException if a recipient wishes the property
414: * change to be rolled back.
415: */
416: public void fireVetoableChange(String propertyName,
417: Object oldValue, Object newValue)
418: throws PropertyVetoException {
419:
420: if (oldValue == null || newValue == null
421: || !oldValue.equals(newValue)) {
422: multicast(new PropertyChangeEvent(source, propertyName,
423: oldValue, newValue));
424: }
425:
426: }
427:
428: /**
429: * Report a vetoable property update to any registered listeners.
430: * Notifications are sent serially (although in no particular order)
431: * to the list of listeners,
432: * aborting if one throws PropertyVetoException. Upon this exception,
433: * fire a new event reverting this
434: * change to all listeners that have already been notified
435: * (ignoring any further vetos),
436: * suppress notifications to all other listeners, and
437: * then rethrow the PropertyVetoException.
438: * <p>
439: * No event is fired if old and new are equal.
440: * <p>
441: * This is merely a convenience wrapper around the more general
442: * fireVetoableChange method that takes Object values.
443: *
444: * @param propertyName The programmatic name of the property
445: * that was changed.
446: * @param oldValue The old value of the property.
447: * @param newValue The new value of the property.
448: * @exception PropertyVetoException if the recipient wishes the property
449: * change to be rolled back.
450: */
451: public void fireVetoableChange(String propertyName, int oldValue,
452: int newValue) throws PropertyVetoException {
453: if (oldValue != newValue) {
454: multicast(new PropertyChangeEvent(source, propertyName,
455: new Integer(oldValue), new Integer(newValue)));
456: }
457: }
458:
459: /**
460: * Report a vetoable property update to any registered listeners.
461: * Notifications are sent serially (although in no particular order)
462: * to the list of listeners,
463: * aborting if one throws PropertyVetoException. Upon this exception,
464: * fire a new event reverting this
465: * change to all listeners that have already been notified
466: * (ignoring any further vetos),
467: * suppress notifications to all other listeners, and
468: * then rethrow the PropertyVetoException.
469: * <p>
470: * No event is fired if old and new are equal.
471: * <p>
472: * This is merely a convenience wrapper around the more general
473: * fireVetoableChange method that takes Object values.
474: *
475: * @param propertyName The programmatic name of the property
476: * that was changed.
477: * @param oldValue The old value of the property.
478: * @param newValue The new value of the property.
479: * @exception PropertyVetoException if the recipient wishes the property
480: * change to be rolled back.
481: */
482: public void fireVetoableChange(String propertyName,
483: boolean oldValue, boolean newValue)
484: throws PropertyVetoException {
485: if (oldValue != newValue) {
486: multicast(new PropertyChangeEvent(source, propertyName,
487: new Boolean(oldValue), new Boolean(newValue)));
488: }
489: }
490:
491: /**
492: * Report a vetoable property update to any registered listeners.
493: * Notifications are sent serially (although in no particular order)
494: * to the list of listeners,
495: * aborting if one throws PropertyVetoException. Upon this exception,
496: * fire a new event reverting this
497: * change to all listeners that have already been notified
498: * (ignoring any further vetos),
499: * suppress notifications to all other listeners, and
500: * then rethrow the PropertyVetoException.
501: * <p>
502: * No event is fired if old and new are equal and non-null.
503: *
504: * equal and non-null.
505: * @param evt The PropertyChangeEvent object.
506: * @exception PropertyVetoException if the recipient wishes the property
507: * change to be rolled back.
508: */
509: public void fireVetoableChange(PropertyChangeEvent evt)
510: throws PropertyVetoException {
511: Object oldValue = evt.getOldValue();
512: Object newValue = evt.getNewValue();
513: if (oldValue == null || newValue == null
514: || !oldValue.equals(newValue))
515: multicast(evt);
516: }
517:
518: /**
519: * Check if there are any listeners for a specific property.
520: * If propertyName is null, return whether there are any listeners at all.
521: *
522: * @param propertyName the property name.
523: * @return true if there are one or more listeners for the given property
524: *
525: */
526: public boolean hasListeners(String propertyName) {
527:
528: VetoableChangeMulticaster child;
529:
530: synchronized (this ) {
531: if (listeners.length > 0)
532: return true;
533: else if (propertyName == null || children == null)
534: return false;
535: else {
536: child = (VetoableChangeMulticaster) children
537: .get(propertyName);
538: if (child == null)
539: return false;
540: }
541: }
542:
543: return child.hasListeners(null);
544: }
545:
546: /**
547: * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
548: * <p>
549: * At serialization time we skip non-serializable listeners and
550: * only serialize the serializable listeners.
551: *
552: */
553: private synchronized void writeObject(ObjectOutputStream s)
554: throws IOException {
555: s.defaultWriteObject();
556:
557: for (int i = 0; i < listeners.length; i++) {
558: VetoableChangeListener l = listeners[i];
559: if (listeners[i] instanceof Serializable) {
560: s.writeObject(listeners[i]);
561: }
562: }
563: s.writeObject(null);
564: }
565:
566: private void readObject(ObjectInputStream s)
567: throws ClassNotFoundException, IOException {
568: listeners = new VetoableChangeListener[0]; // paranoically reset
569: s.defaultReadObject();
570:
571: Object listenerOrNull;
572: while (null != (listenerOrNull = s.readObject())) {
573: addVetoableChangeListener((VetoableChangeListener) listenerOrNull);
574: }
575: }
576:
577: }
|