Home » openjdk-7 » java » beans » [javadoc | source]

    1   /*
    2    * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved.
    3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
    4    *
    5    * This code is free software; you can redistribute it and/or modify it
    6    * under the terms of the GNU General Public License version 2 only, as
    7    * published by the Free Software Foundation.  Oracle designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Oracle in the LICENSE file that accompanied this code.
   10    *
   11    * This code is distributed in the hope that it will be useful, but WITHOUT
   12    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   13    * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   14    * version 2 for more details (a copy is included in the LICENSE file that
   15    * accompanied this code).
   16    *
   17    * You should have received a copy of the GNU General Public License version
   18    * 2 along with this work; if not, write to the Free Software Foundation,
   19    * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   20    *
   21    * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   22    * or visit www.oracle.com if you need additional information or have any
   23    * questions.
   24    */
   25   package java.beans;
   26   
   27   import java.io.Serializable;
   28   import java.io.ObjectStreamField;
   29   import java.io.ObjectOutputStream;
   30   import java.io.ObjectInputStream;
   31   import java.io.IOException;
   32   import java.util.Hashtable;
   33   import java.util.Map.Entry;
   34   
   35   /**
   36    * This is a utility class that can be used by beans that support bound
   37    * properties.  It manages a list of listeners and dispatches
   38    * {@link PropertyChangeEvent}s to them.  You can use an instance of this class
   39    * as a member field of your bean and delegate these types of work to it.
   40    * The {@link PropertyChangeListener} can be registered for all properties
   41    * or for a property specified by name.
   42    * <p>
   43    * Here is an example of {@code PropertyChangeSupport} usage that follows
   44    * the rules and recommendations laid out in the JavaBeans&trade; specification:
   45    * <pre>
   46    * public class MyBean {
   47    *     private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
   48    *
   49    *     public void addPropertyChangeListener(PropertyChangeListener listener) {
   50    *         this.pcs.addPropertyChangeListener(listener);
   51    *     }
   52    *
   53    *     public void removePropertyChangeListener(PropertyChangeListener listener) {
   54    *         this.pcs.removePropertyChangeListener(listener);
   55    *     }
   56    *
   57    *     private String value;
   58    *
   59    *     public String getValue() {
   60    *         return this.value;
   61    *     }
   62    *
   63    *     public void setValue(String newValue) {
   64    *         String oldValue = this.value;
   65    *         this.value = newValue;
   66    *         this.pcs.firePropertyChange("value", oldValue, newValue);
   67    *     }
   68    *
   69    *     [...]
   70    * }
   71    * </pre>
   72    * <p>
   73    * A {@code PropertyChangeSupport} instance is thread-safe.
   74    * <p>
   75    * This class is serializable.  When it is serialized it will save
   76    * (and restore) any listeners that are themselves serializable.  Any
   77    * non-serializable listeners will be skipped during serialization.
   78    *
   79    * @see VetoableChangeSupport
   80    */
   81   public class PropertyChangeSupport implements Serializable {
   82       private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
   83   
   84       /**
   85        * Constructs a <code>PropertyChangeSupport</code> object.
   86        *
   87        * @param sourceBean  The bean to be given as the source for any events.
   88        */
   89       public PropertyChangeSupport(Object sourceBean) {
   90           if (sourceBean == null) {
   91               throw new NullPointerException();
   92           }
   93           source = sourceBean;
   94       }
   95   
   96       /**
   97        * Add a PropertyChangeListener to the listener list.
   98        * The listener is registered for all properties.
   99        * The same listener object may be added more than once, and will be called
  100        * as many times as it is added.
  101        * If <code>listener</code> is null, no exception is thrown and no action
  102        * is taken.
  103        *
  104        * @param listener  The PropertyChangeListener to be added
  105        */
  106       public void addPropertyChangeListener(PropertyChangeListener listener) {
  107           if (listener == null) {
  108               return;
  109           }
  110           if (listener instanceof PropertyChangeListenerProxy) {
  111               PropertyChangeListenerProxy proxy =
  112                      (PropertyChangeListenerProxy)listener;
  113               // Call two argument add method.
  114               addPropertyChangeListener(proxy.getPropertyName(),
  115                                         proxy.getListener());
  116           } else {
  117               this.map.add(null, listener);
  118           }
  119       }
  120   
  121       /**
  122        * Remove a PropertyChangeListener from the listener list.
  123        * This removes a PropertyChangeListener that was registered
  124        * for all properties.
  125        * If <code>listener</code> was added more than once to the same event
  126        * source, it will be notified one less time after being removed.
  127        * If <code>listener</code> is null, or was never added, no exception is
  128        * thrown and no action is taken.
  129        *
  130        * @param listener  The PropertyChangeListener to be removed
  131        */
  132       public void removePropertyChangeListener(PropertyChangeListener listener) {
  133           if (listener == null) {
  134               return;
  135           }
  136           if (listener instanceof PropertyChangeListenerProxy) {
  137               PropertyChangeListenerProxy proxy =
  138                       (PropertyChangeListenerProxy)listener;
  139               // Call two argument remove method.
  140               removePropertyChangeListener(proxy.getPropertyName(),
  141                                            proxy.getListener());
  142           } else {
  143               this.map.remove(null, listener);
  144           }
  145       }
  146   
  147       /**
  148        * Returns an array of all the listeners that were added to the
  149        * PropertyChangeSupport object with addPropertyChangeListener().
  150        * <p>
  151        * If some listeners have been added with a named property, then
  152        * the returned array will be a mixture of PropertyChangeListeners
  153        * and <code>PropertyChangeListenerProxy</code>s. If the calling
  154        * method is interested in distinguishing the listeners then it must
  155        * test each element to see if it's a
  156        * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
  157        * the parameter.
  158        *
  159        * <pre>
  160        * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
  161        * for (int i = 0; i < listeners.length; i++) {
  162        *   if (listeners[i] instanceof PropertyChangeListenerProxy) {
  163        *     PropertyChangeListenerProxy proxy =
  164        *                    (PropertyChangeListenerProxy)listeners[i];
  165        *     if (proxy.getPropertyName().equals("foo")) {
  166        *       // proxy is a PropertyChangeListener which was associated
  167        *       // with the property named "foo"
  168        *     }
  169        *   }
  170        * }
  171        *</pre>
  172        *
  173        * @see PropertyChangeListenerProxy
  174        * @return all of the <code>PropertyChangeListeners</code> added or an
  175        *         empty array if no listeners have been added
  176        * @since 1.4
  177        */
  178       public PropertyChangeListener[] getPropertyChangeListeners() {
  179           return this.map.getListeners();
  180       }
  181   
  182       /**
  183        * Add a PropertyChangeListener for a specific property.  The listener
  184        * will be invoked only when a call on firePropertyChange names that
  185        * specific property.
  186        * The same listener object may be added more than once.  For each
  187        * property,  the listener will be invoked the number of times it was added
  188        * for that property.
  189        * If <code>propertyName</code> or <code>listener</code> is null, no
  190        * exception is thrown and no action is taken.
  191        *
  192        * @param propertyName  The name of the property to listen on.
  193        * @param listener  The PropertyChangeListener to be added
  194        */
  195       public void addPropertyChangeListener(
  196                   String propertyName,
  197                   PropertyChangeListener listener) {
  198           if (listener == null || propertyName == null) {
  199               return;
  200           }
  201           listener = this.map.extract(listener);
  202           if (listener != null) {
  203               this.map.add(propertyName, listener);
  204           }
  205       }
  206   
  207       /**
  208        * Remove a PropertyChangeListener for a specific property.
  209        * If <code>listener</code> was added more than once to the same event
  210        * source for the specified property, it will be notified one less time
  211        * after being removed.
  212        * If <code>propertyName</code> is null,  no exception is thrown and no
  213        * action is taken.
  214        * If <code>listener</code> is null, or was never added for the specified
  215        * property, no exception is thrown and no action is taken.
  216        *
  217        * @param propertyName  The name of the property that was listened on.
  218        * @param listener  The PropertyChangeListener to be removed
  219        */
  220       public void removePropertyChangeListener(
  221                   String propertyName,
  222                   PropertyChangeListener listener) {
  223           if (listener == null || propertyName == null) {
  224               return;
  225           }
  226           listener = this.map.extract(listener);
  227           if (listener != null) {
  228               this.map.remove(propertyName, listener);
  229           }
  230       }
  231   
  232       /**
  233        * Returns an array of all the listeners which have been associated
  234        * with the named property.
  235        *
  236        * @param propertyName  The name of the property being listened to
  237        * @return all of the <code>PropertyChangeListeners</code> associated with
  238        *         the named property.  If no such listeners have been added,
  239        *         or if <code>propertyName</code> is null, an empty array is
  240        *         returned.
  241        * @since 1.4
  242        */
  243       public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
  244           return this.map.getListeners(propertyName);
  245       }
  246   
  247       /**
  248        * Reports a bound property update to listeners
  249        * that have been registered to track updates of
  250        * all properties or a property with the specified name.
  251        * <p>
  252        * No event is fired if old and new values are equal and non-null.
  253        * <p>
  254        * This is merely a convenience wrapper around the more general
  255        * {@link #firePropertyChange(PropertyChangeEvent)} method.
  256        *
  257        * @param propertyName  the programmatic name of the property that was changed
  258        * @param oldValue      the old value of the property
  259        * @param newValue      the new value of the property
  260        */
  261       public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
  262           if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
  263               firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
  264           }
  265       }
  266   
  267       /**
  268        * Reports an integer bound property update to listeners
  269        * that have been registered to track updates of
  270        * all properties or a property with the specified name.
  271        * <p>
  272        * No event is fired if old and new values are equal.
  273        * <p>
  274        * This is merely a convenience wrapper around the more general
  275        * {@link #firePropertyChange(String, Object, Object)}  method.
  276        *
  277        * @param propertyName  the programmatic name of the property that was changed
  278        * @param oldValue      the old value of the property
  279        * @param newValue      the new value of the property
  280        */
  281       public void firePropertyChange(String propertyName, int oldValue, int newValue) {
  282           if (oldValue != newValue) {
  283               firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
  284           }
  285       }
  286   
  287       /**
  288        * Reports a boolean bound property update to listeners
  289        * that have been registered to track updates of
  290        * all properties or a property with the specified name.
  291        * <p>
  292        * No event is fired if old and new values are equal.
  293        * <p>
  294        * This is merely a convenience wrapper around the more general
  295        * {@link #firePropertyChange(String, Object, Object)}  method.
  296        *
  297        * @param propertyName  the programmatic name of the property that was changed
  298        * @param oldValue      the old value of the property
  299        * @param newValue      the new value of the property
  300        */
  301       public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
  302           if (oldValue != newValue) {
  303               firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  304           }
  305       }
  306   
  307       /**
  308        * Fires a property change event to listeners
  309        * that have been registered to track updates of
  310        * all properties or a property with the specified name.
  311        * <p>
  312        * No event is fired if the given event's old and new values are equal and non-null.
  313        *
  314        * @param event  the {@code PropertyChangeEvent} to be fired
  315        */
  316       public void firePropertyChange(PropertyChangeEvent event) {
  317           Object oldValue = event.getOldValue();
  318           Object newValue = event.getNewValue();
  319           if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
  320               String name = event.getPropertyName();
  321   
  322               PropertyChangeListener[] common = this.map.get(null);
  323               PropertyChangeListener[] named = (name != null)
  324                           ? this.map.get(name)
  325                           : null;
  326   
  327               fire(common, event);
  328               fire(named, event);
  329           }
  330       }
  331   
  332       private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
  333           if (listeners != null) {
  334               for (PropertyChangeListener listener : listeners) {
  335                   listener.propertyChange(event);
  336               }
  337           }
  338       }
  339   
  340       /**
  341        * Reports a bound indexed property update to listeners
  342        * that have been registered to track updates of
  343        * all properties or a property with the specified name.
  344        * <p>
  345        * No event is fired if old and new values are equal and non-null.
  346        * <p>
  347        * This is merely a convenience wrapper around the more general
  348        * {@link #firePropertyChange(PropertyChangeEvent)} method.
  349        *
  350        * @param propertyName  the programmatic name of the property that was changed
  351        * @param index         the index of the property element that was changed
  352        * @param oldValue      the old value of the property
  353        * @param newValue      the new value of the property
  354        * @since 1.5
  355        */
  356       public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
  357           if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
  358               firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index));
  359           }
  360       }
  361   
  362       /**
  363        * Reports an integer bound indexed property update to listeners
  364        * that have been registered to track updates of
  365        * all properties or a property with the specified name.
  366        * <p>
  367        * No event is fired if old and new values are equal.
  368        * <p>
  369        * This is merely a convenience wrapper around the more general
  370        * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
  371        *
  372        * @param propertyName  the programmatic name of the property that was changed
  373        * @param index         the index of the property element that was changed
  374        * @param oldValue      the old value of the property
  375        * @param newValue      the new value of the property
  376        * @since 1.5
  377        */
  378       public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) {
  379           if (oldValue != newValue) {
  380               fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue));
  381           }
  382       }
  383   
  384       /**
  385        * Reports a boolean bound indexed property update to listeners
  386        * that have been registered to track updates of
  387        * all properties or a property with the specified name.
  388        * <p>
  389        * No event is fired if old and new values are equal.
  390        * <p>
  391        * This is merely a convenience wrapper around the more general
  392        * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
  393        *
  394        * @param propertyName  the programmatic name of the property that was changed
  395        * @param index         the index of the property element that was changed
  396        * @param oldValue      the old value of the property
  397        * @param newValue      the new value of the property
  398        * @since 1.5
  399        */
  400       public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) {
  401           if (oldValue != newValue) {
  402               fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  403           }
  404       }
  405   
  406       /**
  407        * Check if there are any listeners for a specific property, including
  408        * those registered on all properties.  If <code>propertyName</code>
  409        * is null, only check for listeners registered on all properties.
  410        *
  411        * @param propertyName  the property name.
  412        * @return true if there are one or more listeners for the given property
  413        */
  414       public boolean hasListeners(String propertyName) {
  415           return this.map.hasListeners(propertyName);
  416       }
  417   
  418       /**
  419        * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
  420        * <p>
  421        * At serialization time we skip non-serializable listeners and
  422        * only serialize the serializable listeners.
  423        */
  424       private void writeObject(ObjectOutputStream s) throws IOException {
  425           Hashtable<String, PropertyChangeSupport> children = null;
  426           PropertyChangeListener[] listeners = null;
  427           synchronized (this.map) {
  428               for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {
  429                   String property = entry.getKey();
  430                   if (property == null) {
  431                       listeners = entry.getValue();
  432                   } else {
  433                       if (children == null) {
  434                           children = new Hashtable<String, PropertyChangeSupport>();
  435                       }
  436                       PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);
  437                       pcs.map.set(null, entry.getValue());
  438                       children.put(property, pcs);
  439                   }
  440               }
  441           }
  442           ObjectOutputStream.PutField fields = s.putFields();
  443           fields.put("children", children);
  444           fields.put("source", this.source);
  445           fields.put("propertyChangeSupportSerializedDataVersion", 2);
  446           s.writeFields();
  447   
  448           if (listeners != null) {
  449               for (PropertyChangeListener l : listeners) {
  450                   if (l instanceof Serializable) {
  451                       s.writeObject(l);
  452                   }
  453               }
  454           }
  455           s.writeObject(null);
  456       }
  457   
  458       private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
  459           this.map = new PropertyChangeListenerMap();
  460   
  461           ObjectInputStream.GetField fields = s.readFields();
  462   
  463           Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
  464           this.source = fields.get("source", null);
  465           fields.get("propertyChangeSupportSerializedDataVersion", 2);
  466   
  467           Object listenerOrNull;
  468           while (null != (listenerOrNull = s.readObject())) {
  469               this.map.add(null, (PropertyChangeListener)listenerOrNull);
  470           }
  471           if (children != null) {
  472               for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
  473                   for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
  474                       this.map.add(entry.getKey(), listener);
  475                   }
  476               }
  477           }
  478       }
  479   
  480       /**
  481        * The object to be provided as the "source" for any generated events.
  482        */
  483       private Object source;
  484   
  485       /**
  486        * @serialField children                                   Hashtable
  487        * @serialField source                                     Object
  488        * @serialField propertyChangeSupportSerializedDataVersion int
  489        */
  490       private static final ObjectStreamField[] serialPersistentFields = {
  491               new ObjectStreamField("children", Hashtable.class),
  492               new ObjectStreamField("source", Object.class),
  493               new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
  494       };
  495   
  496       /**
  497        * Serialization version ID, so we're compatible with JDK 1.1
  498        */
  499       static final long serialVersionUID = 6401253773779951803L;
  500   
  501       /**
  502        * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
  503        * that works with {@link PropertyChangeListener PropertyChangeListener} objects.
  504        */
  505       private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
  506           private static final PropertyChangeListener[] EMPTY = {};
  507   
  508           /**
  509            * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
  510            * This method uses the same instance of the empty array
  511            * when {@code length} equals {@code 0}.
  512            *
  513            * @param length  the array length
  514            * @return        an array with specified length
  515            */
  516           @Override
  517           protected PropertyChangeListener[] newArray(int length) {
  518               return (0 < length)
  519                       ? new PropertyChangeListener[length]
  520                       : EMPTY;
  521           }
  522   
  523           /**
  524            * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
  525            * object for the specified property.
  526            *
  527            * @param name      the name of the property to listen on
  528            * @param listener  the listener to process events
  529            * @return          a {@code PropertyChangeListenerProxy} object
  530            */
  531           @Override
  532           protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
  533               return new PropertyChangeListenerProxy(name, listener);
  534           }
  535       }
  536   }

Home » openjdk-7 » java » beans » [javadoc | source]