diff src/EDU/oswego/cs/dl/util/concurrent/PropertyChangeMulticaster.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/PropertyChangeMulticaster.java	Sat Mar 21 11:00:06 2009 +0000
@@ -0,0 +1,461 @@
+/*
+  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 EDU.oswego.cs.dl.util.concurrent;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+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.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>]
+ **/
+
+public class PropertyChangeMulticaster implements Serializable {
+
+  // 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) {
+
+    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) {
+
+    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) {
+
+    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) {
+
+    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) {
+
+    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) {
+
+    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) {
+
+    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 (oldValue == null || newValue == 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 (oldValue != newValue) {
+      multicast(new PropertyChangeEvent(source,
+                                        propertyName, 
+                                        new Integer(oldValue), 
+                                        new Integer(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 (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) {
+    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) {
+
+    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 {
+    s.defaultWriteObject();
+    
+    for (int i = 0; i < listeners.length; i++) {
+      PropertyChangeListener 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 PropertyChangeListener[0];     // paranoically reset
+    s.defaultReadObject();
+    
+    Object listenerOrNull;
+    while (null != (listenerOrNull = s.readObject())) {
+      addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
+    }
+  }
+
+}