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

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