PropertyChangeMulticaster.java

  1. /*
  2.   File: PropertyChangeMulticaster.java

  3.   Originally written by Doug Lea and released into the public domain.
  4.   This may be used for any purposes whatsoever without acknowledgment.
  5.   Thanks for the assistance and support of Sun Microsystems Labs,
  6.   and everyone contributing, testing, and using this code.

  7.   This class is based on Sun JDK java.beans.VetoableChangeSupport,
  8.   which is copyrighted by Sun. (It shares practically no code, but for
  9.   consistency, the documentation was lifted and adapted here.)

  10.   History:
  11.   Date       Who                What
  12.   14Mar1999   dl                 first release
  13. */

  14. package org.dbunit.util.concurrent;

  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;

  17. import java.beans.PropertyChangeEvent;
  18. import java.beans.PropertyChangeListener;
  19. import java.io.IOException;
  20. import java.io.ObjectInputStream;
  21. import java.io.ObjectOutputStream;
  22. import java.io.Serializable;
  23. import java.util.HashMap;

  24. /**
  25.  * This class is interoperable with java.beans.PropertyChangeSupport,
  26.  * but relies on a streamlined copy-on-write scheme similar to
  27.  * that used in CopyOnWriteArrayList. This leads to much better
  28.  * performance in most event-intensive programs. It also adheres to clarified
  29.  * semantics of add and remove  operations.
  30.  * <p>
  31.  * <b>Sample usage.</b>
  32.  *
  33.  * <pre>
  34.  * class Thing {
  35.  *   protected Color myColor = Color.red; // an example property
  36.  *
  37.  *   protected PropertyChangeMulticaster listeners =
  38.  *     new PropertyChangeMulticaster(this);
  39.  *
  40.  *   // registration methods, including:
  41.  *   void addListener(PropertyChangeListener l) {
  42.  *     // Use the `ifAbsent' version to avoid duplicate notifications
  43.  *     listeners.addPropertyChangeListenerIfAbsent(l);
  44.  *   }
  45.  *  
  46.  *  public synchronized Color getColor() { // accessor
  47.  *    return myColor;
  48.  *  }
  49.  *
  50.  *   // internal synchronized state change method; returns old value
  51.  *   protected synchronized Color assignColor(Color newColor) {
  52.  *     Color oldColor = myColor;
  53.  *     myColor = newColor;
  54.  *     return oldColor;
  55.  *   }
  56.  *
  57.  *   public void setColor(Color newColor) {
  58.  *     // atomically change state
  59.  *     Color oldColor = assignColor(newColor);
  60.  *     // broadcast change notification without holding synch lock
  61.  *     listeners.firePropertyChange("color", oldColor, newColor);
  62.  *   }
  63.  * }
  64.  * </pre>  
  65.  * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
  66.  *
  67.  * @author Doug Lea
  68.  * @author Last changed by: $Author$
  69.  * @version $Revision$ $Date$
  70.  * @since ? (pre 2.1)
  71.  */
  72. public class PropertyChangeMulticaster implements Serializable {

  73.     /**
  74.      * Logger for this class
  75.      */
  76.     private static final Logger logger = LoggerFactory.getLogger(PropertyChangeMulticaster.class);

  77.   // In order to allow this class to be lifted out without using
  78.   // the whole package, the basic mechanics of CopyOnWriteArrayList
  79.   // are used here, but not the class itself.
  80.   // This also makes it barely faster.

  81.   /**
  82.    * The array of listeners. Copied on each update
  83.    **/

  84.   protected transient PropertyChangeListener[] listeners = new PropertyChangeListener[0];


  85.   /**
  86.    * The object to be provided as the "source" for any generated events.
  87.    * @serial
  88.    */
  89.   protected final Object source;

  90.   /**
  91.    * HashMap for managing listeners for specific properties.
  92.    * Maps property names to PropertyChangeMulticaster objects.
  93.    * @serial
  94.    */
  95.   protected HashMap children;

  96.   /**
  97.    * Return the child associated with property, or null if no such
  98.    **/

  99.   protected synchronized PropertyChangeMulticaster getChild(String propertyName) {
  100.     return (children == null)? null :
  101.       ((PropertyChangeMulticaster)children.get(propertyName));
  102.   }


  103.   /**
  104.    * Constructs a <code>PropertyChangeMulticaster</code> object.
  105.    *
  106.    * @param sourceBean  The bean to be given as the source for any events.
  107.    * @exception NullPointerException if sourceBean is null
  108.    */
  109.  
  110.   public PropertyChangeMulticaster(Object sourceBean) {
  111.     if (sourceBean == null) {
  112.       throw new NullPointerException();
  113.     }

  114.     source = sourceBean;
  115.   }

  116.   /**
  117.    * Add a VetoableChangeListener to the listener list.
  118.    * The listener is registered for all properties.
  119.    * If the listener is added multiple times, it will
  120.    * receive multiple change notifications upon any firePropertyChange
  121.    *
  122.    * @param listener  The PropertyChangeListener to be added
  123.    * @exception NullPointerException If listener is null
  124.    */
  125.  
  126.   public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
  127.         logger.debug("addPropertyChangeListener(listener={}) - start", listener);

  128.     if (listener == null) throw new NullPointerException();

  129.     int len = listeners.length;
  130.     PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
  131.     if (len > 0)
  132.       System.arraycopy(listeners, 0, newArray, 0, len);
  133.     newArray[len] = listener;
  134.     listeners = newArray;
  135.   }


  136.   /**
  137.    * Add a PropertyChangeListener to the listener list if it is
  138.    * not already present.
  139.    * The listener is registered for all properties.
  140.    * The operation maintains Set semantics: If the listener is already
  141.    * registered, the operation has no effect.
  142.    *
  143.    * @param listener  The PropertyChangeListener to be added
  144.    * @exception NullPointerException If listener is null
  145.    */
  146.  
  147.   public synchronized void addPropertyChangeListenerIfAbsent(PropertyChangeListener listener) {
  148.         logger.debug("addPropertyChangeListenerIfAbsent(listener={}) - start", listener);

  149.     if (listener == null) throw new NullPointerException();

  150.     // Copy while checking if already present.
  151.     int len = listeners.length;
  152.     PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
  153.     for (int i = 0; i < len; ++i) {
  154.       newArray[i] = listeners[i];
  155.       if (listener.equals(listeners[i]))
  156.     return; // already present -- throw away copy
  157.     }
  158.     newArray[len] = listener;
  159.     listeners = newArray;
  160.   }


  161.   /**
  162.    * Remove a PropertyChangeListener from the listener list.
  163.    * It removes at most one occurrence of the given listener.
  164.    * If the listener was added multiple times it must be removed
  165.    * mulitple times.
  166.    * This removes a PropertyChangeListener that was registered
  167.    * for all properties, and has no effect if registered for only
  168.    * one or more specified properties.
  169.    *
  170.    * @param listener  The PropertyChangeListener to be removed
  171.    */
  172.  
  173.   public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
  174.         logger.debug("removePropertyChangeListener(listener={}) - start", listener);

  175.     int newlen = listeners.length-1;
  176.     if (newlen < 0 || listener == null) return;

  177.     // Copy while searching for element to remove

  178.     PropertyChangeListener[] newArray = new PropertyChangeListener[newlen];

  179.     for (int i = 0; i < newlen; ++i) {
  180.       if (listener.equals(listeners[i])) {
  181.         //  copy remaining and exit
  182.         for (int k = i + 1; k <= newlen; ++k) newArray[k-1] = listeners[k];
  183.         listeners = newArray;
  184.         return;
  185.       }
  186.       else
  187.         newArray[i] = listeners[i];
  188.     }

  189.     // special-case last cell
  190.     if (listener.equals(listeners[newlen]))
  191.       listeners = newArray;
  192.   }

  193.   /**
  194.    * Add a PropertyChangeListener for a specific property.  The listener
  195.    * will be invoked only when a call on firePropertyChange names that
  196.    * specific property. However, if a listener is registered both for all
  197.    * properties and a specific property, it will receive multiple
  198.    * notifications upon changes to that property.
  199.    *
  200.    * @param propertyName  The name of the property to listen on.
  201.    * @param listener  The PropertyChangeListener to be added
  202.    * @exception NullPointerException If listener is null
  203.    */
  204.  
  205.   public void addPropertyChangeListener(String propertyName,
  206.                                         PropertyChangeListener listener) {
  207.         logger.debug("addPropertyChangeListener(propertyName={}, listener={}) - start", propertyName, listener);

  208.     if (listener == null) throw new NullPointerException();

  209.     PropertyChangeMulticaster child = null;

  210.     synchronized(this) {
  211.       if (children == null)
  212.         children = new HashMap();
  213.       else
  214.         child = (PropertyChangeMulticaster)children.get(propertyName);
  215.      
  216.       if (child == null) {
  217.         child = new PropertyChangeMulticaster(source);
  218.         children.put(propertyName, child);
  219.       }
  220.     }

  221.     child.addPropertyChangeListener(listener);
  222.   }

  223.   /**
  224.    * Add a PropertyChangeListener for a specific property, if it is not
  225.    * already registered.  The listener
  226.    * will be invoked only when a call on firePropertyChange names that
  227.    * specific property.
  228.    *
  229.    * @param propertyName  The name of the property to listen on.
  230.    * @param listener  The PropertyChangeListener to be added
  231.    * @exception NullPointerException If listener is null
  232.    */
  233.  
  234.   public void addPropertyChangeListenerIfAbsent(String propertyName,
  235.                                         PropertyChangeListener listener) {
  236.         logger.debug("addPropertyChangeListenerIfAbsent(propertyName={}, listener={}) - start", propertyName, listener);

  237.     if (listener == null) throw new NullPointerException();

  238.     PropertyChangeMulticaster child = null;

  239.     synchronized(this) {
  240.       if (children == null)
  241.         children = new HashMap();
  242.       else
  243.         child = (PropertyChangeMulticaster)children.get(propertyName);
  244.      
  245.       if (child == null) {
  246.         child = new PropertyChangeMulticaster(source);
  247.         children.put(propertyName, child);
  248.       }
  249.     }

  250.     child.addPropertyChangeListenerIfAbsent(listener);
  251.   }

  252.   /**
  253.    * Remove a PropertyChangeListener for a specific property.
  254.    * Affects only the given property.
  255.    * If the listener is also registered for all properties,
  256.    * then it will continue to be registered for them.
  257.    *
  258.    * @param propertyName  The name of the property that was listened on.
  259.    * @param listener  The PropertyChangeListener to be removed
  260.    */
  261.  
  262.   public void removePropertyChangeListener(String propertyName,
  263.                                            PropertyChangeListener listener) {
  264.         logger.debug("removePropertyChangeListener(propertyName={}, listener={}) - start", propertyName, listener);

  265.     PropertyChangeMulticaster child = getChild(propertyName);
  266.     if (child != null)
  267.       child.removePropertyChangeListener(listener);
  268.   }


  269.   /**
  270.    * Helper method to relay evt to all listeners.
  271.    * Called by all public firePropertyChange methods.
  272.    **/

  273.   protected void multicast(PropertyChangeEvent evt) {
  274.         logger.debug("multicast(evt={}) - start", evt);

  275.     PropertyChangeListener[] array;  // bind in synch block below
  276.     PropertyChangeMulticaster child = null;

  277.     synchronized (this) {
  278.       array = listeners;

  279.       if (children != null && evt.getPropertyName() != null)
  280.         child = (PropertyChangeMulticaster)children.get(evt.getPropertyName());
  281.     }
  282.    
  283.     for (int i = 0; i < array.length; ++i)
  284.       array[i].propertyChange(evt);
  285.    
  286.     if (child != null)
  287.       child.multicast(evt);

  288.   }

  289.  
  290.   /**
  291.    * Report a bound property update to any registered listeners.
  292.    * No event is fired if old and new are equal and non-null.
  293.    *
  294.    * @param propertyName  The programmatic name of the property
  295.    *        that was changed.
  296.    * @param oldValue  The old value of the property.
  297.    * @param newValue  The new value of the property.
  298.    */
  299.   public void firePropertyChange(String propertyName,
  300.                                  Object oldValue, Object newValue) {
  301.       if (logger.isDebugEnabled())
  302.       {
  303.           logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
  304.                   new Object[]{ propertyName, oldValue, newValue });
  305.       }
  306.    
  307.     if (oldValue == null || !oldValue.equals(newValue)) {
  308.       multicast(new PropertyChangeEvent(source,
  309.                                         propertyName,
  310.                                         oldValue,
  311.                                         newValue));
  312.     }
  313.    
  314.   }

  315.   /**
  316.    * Report an int bound property update to any registered listeners.
  317.    * No event is fired if old and new are equal and non-null.
  318.    * <p>
  319.    * This is merely a convenience wrapper around the more general
  320.    * firePropertyChange method that takes Object values.
  321.    *
  322.    * @param propertyName  The programmatic name of the property
  323.    *        that was changed.
  324.    * @param oldValue  The old value of the property.
  325.    * @param newValue  The new value of the property.
  326.    */
  327.   public void firePropertyChange(String propertyName,
  328.                                  int oldValue, int newValue) {
  329.       if (logger.isDebugEnabled())
  330.       {
  331.           logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
  332.                   propertyName, oldValue, newValue);
  333.       }

  334.     if (oldValue != newValue) {
  335.       multicast(new PropertyChangeEvent(source,
  336.                                         propertyName, oldValue, newValue));
  337.     }
  338.   }


  339.   /**
  340.    * Report a boolean bound property update to any registered listeners.
  341.    * No event is fired if old and new are equal and non-null.
  342.    * <p>
  343.    * This is merely a convenience wrapper around the more general
  344.    * firePropertyChange method that takes Object values.
  345.    *
  346.    * @param propertyName  The programmatic name of the property
  347.    *        that was changed.
  348.    * @param oldValue  The old value of the property.
  349.    * @param newValue  The new value of the property.
  350.    */
  351.   public void firePropertyChange(String propertyName,
  352.                                  boolean oldValue, boolean newValue) {
  353.       if (logger.isDebugEnabled())
  354.       {
  355.           logger.debug("firePropertyChange(propertyName={}, oldValue={}, newValue={}) - start",
  356.                   new Object[]{ propertyName, String.valueOf(oldValue), String.valueOf(newValue) });
  357.       }

  358.     if (oldValue != newValue) {
  359.       multicast(new PropertyChangeEvent(source,
  360.                                         propertyName,
  361.                                         new Boolean(oldValue),
  362.                                         new Boolean(newValue)));
  363.     }
  364.   }

  365.   /**
  366.    * Fire an existing PropertyChangeEvent to any registered listeners.
  367.    * No event is fired if the given event's old and new values are
  368.    * equal and non-null.
  369.    * @param evt  The PropertyChangeEvent object.
  370.    */
  371.   public void firePropertyChange(PropertyChangeEvent evt) {
  372.         logger.debug("firePropertyChange(evt={}) - start", evt);

  373.     Object oldValue = evt.getOldValue();
  374.     Object newValue = evt.getNewValue();
  375.     if (oldValue == null || !oldValue.equals(newValue))
  376.       multicast(evt);
  377.   }

  378.   /**
  379.    * Check if there are any listeners for a specific property.
  380.    * If propertyName is null, return whether there are any listeners at all.
  381.    *
  382.    * @param propertyName  the property name.
  383.    * @return true if there are one or more listeners for the given property
  384.    *
  385.    */
  386.   public boolean hasListeners(String propertyName) {
  387.         logger.debug("hasListeners(propertyName={}) - start", propertyName);

  388.     PropertyChangeMulticaster child;

  389.     synchronized (this) {
  390.       if (listeners.length > 0)
  391.         return true;
  392.       else if (propertyName == null || children == null)
  393.         return false;
  394.       else {
  395.         child = (PropertyChangeMulticaster)children.get(propertyName);
  396.         if (child == null)
  397.           return false;
  398.       }
  399.     }
  400.    
  401.     return child.hasListeners(null);
  402.   }


  403.   /**
  404.    * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
  405.    * <p>
  406.    * At serialization time we skip non-serializable listeners and
  407.    * only serialize the serializable listeners.
  408.    *
  409.    */
  410.   private synchronized void writeObject(ObjectOutputStream s) throws IOException {
  411.         logger.debug("writeObject(s={}) - start", s);

  412.     s.defaultWriteObject();
  413.    
  414.     for (int i = 0; i < listeners.length; i++) {      
  415.       if (listeners[i] instanceof Serializable) {
  416.         s.writeObject(listeners[i]);
  417.       }
  418.     }
  419.     s.writeObject(null);
  420.   }
  421.  
  422.  
  423.   private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
  424.         logger.debug("readObject(s={}) - start", s);

  425.     listeners = new PropertyChangeListener[0];     // paranoically reset
  426.     s.defaultReadObject();
  427.    
  428.     Object listenerOrNull;
  429.     while (null != (listenerOrNull = s.readObject())) {
  430.       addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
  431.     }
  432.   }

  433. }