Mercurial > hg > blitz_condensed
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/EDU/oswego/cs/dl/util/concurrent/VetoableChangeMulticaster.java Sat Mar 21 11:00:06 2009 +0000 @@ -0,0 +1,572 @@ +/* + File: ProperyChangeMulticaster.java + + Originally written by Doug Lea and released into the public domain. + This may be used for any purposes whatsoever without acknowledgment. + Thanks for the assistance and support of Sun Microsystems Labs, + and everyone contributing, testing, and using this code. + + This class is based on Sun JDK java.beans.VetoableChangeSupport, + which is copyrighted by Sun. (It shares practically no code, but for + consistency, the documentation was lifted and adapted here.) + + History: + Date Who What + 14Mar1999 dl first release +*/ + +package EDU.oswego.cs.dl.util.concurrent; + +import java.beans.VetoableChangeListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyVetoException; +import java.util.HashMap; +import java.io.Serializable; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.IOException; + +/** + * This class is interoperable with java.beans.VetoableChangeSupport, + * but relies on a streamlined copy-on-write scheme similar to + * that used in CopyOnWriteArrayList. It also adheres to clarified + * semantics of add, remove, and fireVetoableChange operations. + * <p> + * <b>Sample usage.</b> + * + * <pre> + * class Thing { + * protected Color myColor = Color.red; // an example property + * protected boolean changePending; // track whether in midst of change + * + * // vetoable listeners: + * protected VetoableChangeMulticaster vetoers = + * new VetoableChangeMulticaster(this); + * + * // Possibly also some ordinary listeners: + * protected PropertyChangeMulticaster listeners = + * new PropertyChangeMulticaster(this); + * + * // registration methods, including: + * void addVetoer(VetoableChangeListener l) { + * // Use the `ifAbsent' version to avoid duplicate notifications + * vetoers.addVetoableChangeListenerIfAbsent(l); + * } + * + * public synchronized Color getColor() { // accessor + * return myColor; + * } + * + * // Simple transactional control for vetos + * + * public void setColor(int newColor) throws PropertyVetoException { + * Color oldColor = prepareSetColor(newColor); + * + * try { + * vetoers.fireVetoableChange("color", oldColor, newColor); + * commitColor(newColor); + * listeners.firePropertyChange("color", oldColor, newColor); + * } + * catch(PropertyVetoException ex) { + * abortSetColor(); + * throw ex; + * } + * } + * + * // Called on entry to proposed vetoable change from setColor. + * // Throws exception if there is already another change in progress. + * // Returns current color + * synchronized int prepareSetColor(Color c) throws PropertyVetoException { + * // only support one transaction at a time + * if (changePending) + * throw new PropertyVetoException("Concurrent modification"); + * // (Could alternatively wait out other transactions via + * // a wait/notify construction based on changePending.) + * + * // perhaps some other screenings, like: + * else if (c == null) + * throw new PropertyVetoException("Cannot change color to Null"); + * else { + * changePending = true; + * return myColor; + * } + * } + * + * synchronized void commitColor(Color newColor) { + * myColor = newColor; + * changePending = false; + * } + * + * synchronized void abortSetColor() { + * changePending = false; + * } + * + * } + * </pre> + * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>] + **/ + +public class VetoableChangeMulticaster implements Serializable { + + // This code is 90% identical with PropertyChangeMulticaster, + // but there is no good way to unify the code while maintaining + // interoperability with beans versions. + + /** + * The array of listeners. Copied on each update + **/ + + protected transient VetoableChangeListener[] listeners = new VetoableChangeListener[0]; + + + /** + * The object to be provided as the "source" for any generated events. + * @serial + */ + protected final Object source; + + /** + * HashMap for managing listeners for specific properties. + * Maps property names to VetoableChangeMulticaster objects. + * @serial + */ + protected HashMap children; + + /** + * Return the child associated with property, or null if no such + **/ + + protected synchronized VetoableChangeMulticaster getChild(String propertyName) { + return (children == null)? null : + ((VetoableChangeMulticaster)children.get(propertyName)); + } + + + /** + * Constructs a <code>VetoableChangeMulticaster</code> object. + * + * @param sourceBean The bean to be given as the source for any events. + * @exception NullPointerException if sourceBean is null + */ + + public VetoableChangeMulticaster(Object sourceBean) { + if (sourceBean == null) { + throw new NullPointerException(); + } + + source = sourceBean; + } + + /** + * Add a VetoableChangeListener to the listener list. + * The listener is registered for all properties. + * If the listener is added multiple times, it will + * receive multiple change notifications upon any fireVetoableChange. + * + * @param listener The VetoableChangeListener to be added + */ + + public synchronized void addVetoableChangeListener(VetoableChangeListener listener) { + + if (listener == null) throw new NullPointerException(); + + int len = listeners.length; + VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1]; + if (len > 0) + System.arraycopy(listeners, 0, newArray, 0, len); + newArray[len] = listener; + listeners = newArray; + } + + /** + * Add a PropertyChangeListener to the listener list if it is + * not already present. + * The listener is registered for all properties. + * The operation maintains Set semantics: If the listener is already + * registered, the operation has no effect. + * + * @param listener The PropertyChangeListener to be added + * @exception NullPointerException If listener is null + */ + + public synchronized void addVetoableChangeListenerIfAbsent(VetoableChangeListener listener) { + + if (listener == null) throw new NullPointerException(); + + // Copy while checking if already present. + int len = listeners.length; + VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1]; + for (int i = 0; i < len; ++i) { + newArray[i] = listeners[i]; + if (listener.equals(listeners[i])) + return; // already present -- throw away copy + } + newArray[len] = listener; + listeners = newArray; + } + + + /** + * Remove an occurrence of a VetoableChangeListener from the listener list. + * It removes at most one occurrence of the given listener. + * If the listener was added multiple times it must be removed + * mulitple times. + * This removes a VetoableChangeListener that was registered + * for all properties, and has no effect if registered for only + * one or more specified properties. + * + * @param listener The VetoableChangeListener to be removed + */ + + public synchronized void removeVetoableChangeListener(VetoableChangeListener listener) { + + int newlen = listeners.length-1; + if (newlen < 0 || listener == null) return; + + // Copy while searching for element to remove + + VetoableChangeListener[] newArray = new VetoableChangeListener[newlen]; + + for (int i = 0; i < newlen; ++i) { + if (listener.equals(listeners[i])) { + // copy remaining and exit + for (int k = i + 1; k <= newlen; ++k) newArray[k-1] = listeners[k]; + listeners = newArray; + return; + } + else + newArray[i] = listeners[i]; + } + + // special-case last cell + if (listener.equals(listeners[newlen])) + listeners = newArray; + + } + + /** + * Add a VetoableChangeListener for a specific property. The listener + * will be invoked only when a call on fireVetoableChange names that + * specific property. However, if a listener is registered both for all + * properties and a specific property, it will receive multiple + * notifications upon changes to that property. + * + * @param propertyName The name of the property to listen on. + * @param listener The VetoableChangeListener to be added + * @exception NullPointerException If listener is null + */ + + public void addVetoableChangeListener(String propertyName, + VetoableChangeListener listener) { + + if (listener == null) throw new NullPointerException(); + + VetoableChangeMulticaster child = null; + + synchronized(this) { + if (children == null) + children = new HashMap(); + else + child = (VetoableChangeMulticaster)children.get(propertyName); + + if (child == null) { + child = new VetoableChangeMulticaster(source); + children.put(propertyName, child); + } + } + + child.addVetoableChangeListener(listener); + } + + /** + * Add a VetoableChangeListener for a specific property, if it is not + * already registered. The listener + * will be invoked only when a call on fireVetoableChange names that + * specific property. + * + * @param propertyName The name of the property to listen on. + * @param listener The VetoableChangeListener to be added + * @exception NullPointerException If listener is null + */ + + public void addVetoableChangeListenerIfAbsent(String propertyName, + VetoableChangeListener listener) { + + if (listener == null) throw new NullPointerException(); + + VetoableChangeMulticaster child = null; + + synchronized(this) { + if (children == null) + children = new HashMap(); + else + child = (VetoableChangeMulticaster)children.get(propertyName); + + if (child == null) { + child = new VetoableChangeMulticaster(source); + children.put(propertyName, child); + } + } + + child.addVetoableChangeListenerIfAbsent(listener); + } + + + /** + * Remove a VetoableChangeListener for a specific property. + * Affects only the given property. + * If the listener is also registered for all properties, + * then it will continue to be registered for them. + * + * @param propertyName The name of the property that was listened on. + * @param listener The VetoableChangeListener to be removed + */ + + public void removeVetoableChangeListener(String propertyName, + VetoableChangeListener listener) { + + VetoableChangeMulticaster child = getChild(propertyName); + if (child != null) + child.removeVetoableChangeListener(listener); + } + + + /** + * Helper method to relay evt to all listeners. + * Called by all public fireVetoableChange methods. + **/ + + protected void multicast(PropertyChangeEvent evt) throws PropertyVetoException { + + VetoableChangeListener[] array; // bind in synch block below + VetoableChangeMulticaster child = null; + + synchronized (this) { + array = listeners; + + if (children != null && evt.getPropertyName() != null) + child = (VetoableChangeMulticaster)children.get(evt.getPropertyName()); + } + + // Loop through array, and then cascade to child. + + int i = 0; // make visible to catch clause + + try { + for (i = 0; i < array.length; ++i) + array[i].vetoableChange(evt); + + if (child != null) + child.multicast(evt); + } + + catch (PropertyVetoException veto) { + + // Revert all that have been notified + + PropertyChangeEvent revert = + new PropertyChangeEvent(evt.getSource(), + evt.getPropertyName(), + evt.getNewValue(), + evt.getOldValue()); + + int lastNotified = (i < array.length)? i : (array.length-1); + + for (int k = 0; k <= lastNotified; ++k) { + try { + array[k].vetoableChange(revert); + } + catch (PropertyVetoException ignore) { + // Cannot veto a reversion + } + } + + // Rethrow the PropertyVetoException. + throw veto; + } + } + + + /** + * Report a vetoable property update to any registered listeners. + * Notifications are sent serially (although in no particular order) + * to the list of listeners, + * aborting if one throws PropertyVetoException. Upon this exception, + * fire a new event reverting this + * change to all listeners that have already been notified + * (ignoring any further vetos), + * suppress notifications to all other listeners, and + * then rethrow the PropertyVetoException. + * <p> + * No event is fired if old and new are equal non-null. + * + * @param propertyName The programmatic name of the property + * that was changed. + * @param oldValue The old value of the property. + * @param newValue The new value of the property. + * @exception PropertyVetoException if a recipient wishes the property + * change to be rolled back. + */ + public void fireVetoableChange(String propertyName, + Object oldValue, Object newValue) throws PropertyVetoException { + + if (oldValue == null || newValue == null || !oldValue.equals(newValue)) { + multicast(new PropertyChangeEvent(source, + propertyName, + oldValue, + newValue)); + } + + } + + /** + * Report a vetoable property update to any registered listeners. + * Notifications are sent serially (although in no particular order) + * to the list of listeners, + * aborting if one throws PropertyVetoException. Upon this exception, + * fire a new event reverting this + * change to all listeners that have already been notified + * (ignoring any further vetos), + * suppress notifications to all other listeners, and + * then rethrow the PropertyVetoException. + * <p> + * No event is fired if old and new are equal. + * <p> + * This is merely a convenience wrapper around the more general + * fireVetoableChange method that takes Object values. + * + * @param propertyName The programmatic name of the property + * that was changed. + * @param oldValue The old value of the property. + * @param newValue The new value of the property. + * @exception PropertyVetoException if the recipient wishes the property + * change to be rolled back. + */ + public void fireVetoableChange(String propertyName, + int oldValue, int newValue) throws PropertyVetoException { + if (oldValue != newValue) { + multicast(new PropertyChangeEvent(source, + propertyName, + new Integer(oldValue), + new Integer(newValue))); + } + } + + + /** + * Report a vetoable property update to any registered listeners. + * Notifications are sent serially (although in no particular order) + * to the list of listeners, + * aborting if one throws PropertyVetoException. Upon this exception, + * fire a new event reverting this + * change to all listeners that have already been notified + * (ignoring any further vetos), + * suppress notifications to all other listeners, and + * then rethrow the PropertyVetoException. + * <p> + * No event is fired if old and new are equal. + * <p> + * This is merely a convenience wrapper around the more general + * fireVetoableChange method that takes Object values. + * + * @param propertyName The programmatic name of the property + * that was changed. + * @param oldValue The old value of the property. + * @param newValue The new value of the property. + * @exception PropertyVetoException if the recipient wishes the property + * change to be rolled back. + */ + public void fireVetoableChange(String propertyName, + boolean oldValue, boolean newValue) throws PropertyVetoException { + if (oldValue != newValue) { + multicast(new PropertyChangeEvent(source, + propertyName, + new Boolean(oldValue), + new Boolean(newValue))); + } + } + + /** + * Report a vetoable property update to any registered listeners. + * Notifications are sent serially (although in no particular order) + * to the list of listeners, + * aborting if one throws PropertyVetoException. Upon this exception, + * fire a new event reverting this + * change to all listeners that have already been notified + * (ignoring any further vetos), + * suppress notifications to all other listeners, and + * then rethrow the PropertyVetoException. + * <p> + * No event is fired if old and new are equal and non-null. + * + * equal and non-null. + * @param evt The PropertyChangeEvent object. + * @exception PropertyVetoException if the recipient wishes the property + * change to be rolled back. + */ + public void fireVetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { + Object oldValue = evt.getOldValue(); + Object newValue = evt.getNewValue(); + if (oldValue == null || newValue == null || !oldValue.equals(newValue)) + multicast(evt); + } + + /** + * Check if there are any listeners for a specific property. + * If propertyName is null, return whether there are any listeners at all. + * + * @param propertyName the property name. + * @return true if there are one or more listeners for the given property + * + */ + public boolean hasListeners(String propertyName) { + + VetoableChangeMulticaster child; + + synchronized (this) { + if (listeners.length > 0) + return true; + else if (propertyName == null || children == null) + return false; + else { + child = (VetoableChangeMulticaster)children.get(propertyName); + if (child == null) + return false; + } + } + + return child.hasListeners(null); + } + + + /** + * @serialData Null terminated list of <code>VetoableChangeListeners</code>. + * <p> + * At serialization time we skip non-serializable listeners and + * only serialize the serializable listeners. + * + */ + private synchronized void writeObject(ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + + for (int i = 0; i < listeners.length; i++) { + VetoableChangeListener l = listeners[i]; + if (listeners[i] instanceof Serializable) { + s.writeObject(listeners[i]); + } + } + s.writeObject(null); + } + + + private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { + listeners = new VetoableChangeListener[0]; // paranoically reset + s.defaultReadObject(); + + Object listenerOrNull; + while (null != (listenerOrNull = s.readObject())) { + addVetoableChangeListener((VetoableChangeListener)listenerOrNull); + } + } + +}