rt/emul/compact/src/main/java/java/beans/PropertyChangeSupport.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 26 Feb 2013 16:54:16 +0100
changeset 772 d382dacfd73f
parent 601 emul/compact/src/main/java/java/beans/PropertyChangeSupport.java@5198affdb915
permissions -rw-r--r--
Moving modules around so the runtime is under one master pom and can be built without building other modules that are in the repository
     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 }