PropertyChangeMulticaster.java
- /*
- File: PropertyChangeMulticaster.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 org.dbunit.util.concurrent;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.beans.PropertyChangeEvent;
- import java.beans.PropertyChangeListener;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.util.HashMap;
- /**
- * This class is interoperable with java.beans.PropertyChangeSupport,
- * but relies on a streamlined copy-on-write scheme similar to
- * that used in CopyOnWriteArrayList. This leads to much better
- * performance in most event-intensive programs. It also adheres to clarified
- * semantics of add and remove operations.
- * <p>
- * <b>Sample usage.</b>
- *
- * <pre>
- * class Thing {
- * protected Color myColor = Color.red; // an example property
- *
- * protected PropertyChangeMulticaster listeners =
- * new PropertyChangeMulticaster(this);
- *
- * // registration methods, including:
- * void addListener(PropertyChangeListener l) {
- * // Use the `ifAbsent' version to avoid duplicate notifications
- * listeners.addPropertyChangeListenerIfAbsent(l);
- * }
- *
- * public synchronized Color getColor() { // accessor
- * return myColor;
- * }
- *
- * // internal synchronized state change method; returns old value
- * protected synchronized Color assignColor(Color newColor) {
- * Color oldColor = myColor;
- * myColor = newColor;
- * return oldColor;
- * }
- *
- * public void setColor(Color newColor) {
- * // atomically change state
- * Color oldColor = assignColor(newColor);
- * // broadcast change notification without holding synch lock
- * listeners.firePropertyChange("color", oldColor, newColor);
- * }
- * }
- * </pre>
- * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
- *
- * @author Doug Lea
- * @author Last changed by: $Author$
- * @version $Revision$ $Date$
- * @since ? (pre 2.1)
- */
- public class PropertyChangeMulticaster implements Serializable {
- /**
- * Logger for this class
- */
- private static final Logger logger = LoggerFactory.getLogger(PropertyChangeMulticaster.class);
- // In order to allow this class to be lifted out without using
- // the whole package, the basic mechanics of CopyOnWriteArrayList
- // are used here, but not the class itself.
- // This also makes it barely faster.
- /**
- * The array of listeners. Copied on each update
- **/
- protected transient PropertyChangeListener[] listeners = new PropertyChangeListener[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 PropertyChangeMulticaster objects.
- * @serial
- */
- protected HashMap children;
- /**
- * Return the child associated with property, or null if no such
- **/
- protected synchronized PropertyChangeMulticaster getChild(String propertyName) {
- return (children == null)? null :
- ((PropertyChangeMulticaster)children.get(propertyName));
- }
- /**
- * Constructs a <code>PropertyChangeMulticaster</code> object.
- *
- * @param sourceBean The bean to be given as the source for any events.
- * @exception NullPointerException if sourceBean is null
- */
-
- public PropertyChangeMulticaster(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 firePropertyChange
- *
- * @param listener The PropertyChangeListener to be added
- * @exception NullPointerException If listener is null
- */
-
- public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
- logger.debug("addPropertyChangeListener(listener={}) - start", listener);
- if (listener == null) throw new NullPointerException();
- int len = listeners.length;
- PropertyChangeListener[] newArray = new PropertyChangeListener[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 addPropertyChangeListenerIfAbsent(PropertyChangeListener listener) {
- logger.debug("addPropertyChangeListenerIfAbsent(listener={}) - start", listener);
- if (listener == null) throw new NullPointerException();
- // Copy while checking if already present.
- int len = listeners.length;
- PropertyChangeListener[] newArray = new PropertyChangeListener[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 a PropertyChangeListener 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 PropertyChangeListener that was registered
- * for all properties, and has no effect if registered for only
- * one or more specified properties.
- *
- * @param listener The PropertyChangeListener to be removed
- */
-
- public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
- logger.debug("removePropertyChangeListener(listener={}) - start", listener);
- int newlen = listeners.length-1;
- if (newlen < 0 || listener == null) return;
- // Copy while searching for element to remove
- PropertyChangeListener[] newArray = new PropertyChangeListener[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 PropertyChangeListener for a specific property. The listener
- * will be invoked only when a call on firePropertyChange 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 PropertyChangeListener to be added
- * @exception NullPointerException If listener is null
- */
-
- public void addPropertyChangeListener(String propertyName,
- PropertyChangeListener listener) {
- logger.debug("addPropertyChangeListener(propertyName={}, listener={}) - start", propertyName, listener);
- if (listener == null) throw new NullPointerException();
- PropertyChangeMulticaster child = null;
- synchronized(this) {
- if (children == null)
- children = new HashMap();
- else
- child = (PropertyChangeMulticaster)children.get(propertyName);
-
- if (child == null) {
- child = new PropertyChangeMulticaster(source);
- children.put(propertyName, child);
- }
- }
- child.addPropertyChangeListener(listener);
- }
- /**
- * Add a PropertyChangeListener for a specific property, if it is not
- * already registered. The listener
- * will be invoked only when a call on firePropertyChange names that
- * specific property.
- *
- * @param propertyName The name of the property to listen on.
- * @param listener The PropertyChangeListener to be added
- * @exception NullPointerException If listener is null
- */
-
- public void addPropertyChangeListenerIfAbsent(String propertyName,
- PropertyChangeListener listener) {
- logger.debug("addPropertyChangeListenerIfAbsent(propertyName={}, listener={}) - start", propertyName, listener);
- if (listener == null) throw new NullPointerException();
- PropertyChangeMulticaster child = null;
- synchronized(this) {
- if (children == null)
- children = new HashMap();
- else
- child = (PropertyChangeMulticaster)children.get(propertyName);
-
- if (child == null) {
- child = new PropertyChangeMulticaster(source);
- children.put(propertyName, child);
- }
- }
- child.addPropertyChangeListenerIfAbsent(listener);
- }
- /**
- * Remove a PropertyChangeListener 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 PropertyChangeListener to be removed
- */
-
- public void removePropertyChangeListener(String propertyName,
- PropertyChangeListener listener) {
- logger.debug("removePropertyChangeListener(propertyName={}, listener={}) - start", propertyName, listener);
- PropertyChangeMulticaster child = getChild(propertyName);
- if (child != null)
- child.removePropertyChangeListener(listener);
- }
- /**
- * Helper method to relay evt to all listeners.
- * Called by all public firePropertyChange methods.
- **/
- protected void multicast(PropertyChangeEvent evt) {
- logger.debug("multicast(evt={}) - start", evt);
- PropertyChangeListener[] array; // bind in synch block below
- PropertyChangeMulticaster child = null;
- synchronized (this) {
- array = listeners;
- if (children != null && evt.getPropertyName() != null)
- child = (PropertyChangeMulticaster)children.get(evt.getPropertyName());
- }
-
- for (int i = 0; i < array.length; ++i)
- array[i].propertyChange(evt);
-
- if (child != null)
- child.multicast(evt);
- }
-
- /**
- * Report a bound property update to any registered listeners.
- * No event is fired if old and new are equal and 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.
- */
- public void firePropertyChange(String propertyName,
- Object oldValue, Object newValue) {
- if (logger.isDebugEnabled())
- {
- logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
- new Object[]{ propertyName, oldValue, newValue });
- }
-
- if (oldValue == null || !oldValue.equals(newValue)) {
- multicast(new PropertyChangeEvent(source,
- propertyName,
- oldValue,
- newValue));
- }
-
- }
- /**
- * Report an int bound property update to any registered listeners.
- * No event is fired if old and new are equal and non-null.
- * <p>
- * This is merely a convenience wrapper around the more general
- * firePropertyChange 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.
- */
- public void firePropertyChange(String propertyName,
- int oldValue, int newValue) {
- if (logger.isDebugEnabled())
- {
- logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
- propertyName, oldValue, newValue);
- }
- if (oldValue != newValue) {
- multicast(new PropertyChangeEvent(source,
- propertyName, oldValue, newValue));
- }
- }
- /**
- * Report a boolean bound property update to any registered listeners.
- * No event is fired if old and new are equal and non-null.
- * <p>
- * This is merely a convenience wrapper around the more general
- * firePropertyChange 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.
- */
- public void firePropertyChange(String propertyName,
- boolean oldValue, boolean newValue) {
- if (logger.isDebugEnabled())
- {
- logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
- new Object[]{ propertyName, String.valueOf(oldValue), String.valueOf(newValue) });
- }
- if (oldValue != newValue) {
- multicast(new PropertyChangeEvent(source,
- propertyName,
- new Boolean(oldValue),
- new Boolean(newValue)));
- }
- }
- /**
- * Fire an existing PropertyChangeEvent to any registered listeners.
- * No event is fired if the given event's old and new values are
- * equal and non-null.
- * @param evt The PropertyChangeEvent object.
- */
- public void firePropertyChange(PropertyChangeEvent evt) {
- logger.debug("firePropertyChange(evt={}) - start", evt);
- Object oldValue = evt.getOldValue();
- Object newValue = evt.getNewValue();
- if (oldValue == 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) {
- logger.debug("hasListeners(propertyName={}) - start", propertyName);
- PropertyChangeMulticaster child;
- synchronized (this) {
- if (listeners.length > 0)
- return true;
- else if (propertyName == null || children == null)
- return false;
- else {
- child = (PropertyChangeMulticaster)children.get(propertyName);
- if (child == null)
- return false;
- }
- }
-
- return child.hasListeners(null);
- }
- /**
- * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
- * <p>
- * At serialization time we skip non-serializable listeners and
- * only serialize the serializable listeners.
- *
- */
- private synchronized void writeObject(ObjectOutputStream s) throws IOException {
- logger.debug("writeObject(s={}) - start", s);
- s.defaultWriteObject();
-
- for (int i = 0; i < listeners.length; i++) {
- if (listeners[i] instanceof Serializable) {
- s.writeObject(listeners[i]);
- }
- }
- s.writeObject(null);
- }
-
-
- private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
- logger.debug("readObject(s={}) - start", s);
- listeners = new PropertyChangeListener[0]; // paranoically reset
- s.defaultReadObject();
-
- Object listenerOrNull;
- while (null != (listenerOrNull = s.readObject())) {
- addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
- }
- }
- }