jdk/src/java.desktop/share/classes/java/beans/PropertyChangeSupport.java
changeset 25859 3317bb8137f4
parent 25130 adfaa02ea516
child 26037 508779ce6619
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     1 /*
       
     2  * Copyright (c) 1996, 2013, 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  * @since 1.1
       
    81  */
       
    82 public class PropertyChangeSupport implements Serializable {
       
    83     private PropertyChangeListenerMap map = new PropertyChangeListenerMap();
       
    84 
       
    85     /**
       
    86      * Constructs a <code>PropertyChangeSupport</code> object.
       
    87      *
       
    88      * @param sourceBean  The bean to be given as the source for any events.
       
    89      */
       
    90     public PropertyChangeSupport(Object sourceBean) {
       
    91         if (sourceBean == null) {
       
    92             throw new NullPointerException();
       
    93         }
       
    94         source = sourceBean;
       
    95     }
       
    96 
       
    97     /**
       
    98      * Add a PropertyChangeListener to the listener list.
       
    99      * The listener is registered for all properties.
       
   100      * The same listener object may be added more than once, and will be called
       
   101      * as many times as it is added.
       
   102      * If <code>listener</code> is null, no exception is thrown and no action
       
   103      * is taken.
       
   104      *
       
   105      * @param listener  The PropertyChangeListener to be added
       
   106      */
       
   107     public void addPropertyChangeListener(PropertyChangeListener listener) {
       
   108         if (listener == null) {
       
   109             return;
       
   110         }
       
   111         if (listener instanceof PropertyChangeListenerProxy) {
       
   112             PropertyChangeListenerProxy proxy =
       
   113                    (PropertyChangeListenerProxy)listener;
       
   114             // Call two argument add method.
       
   115             addPropertyChangeListener(proxy.getPropertyName(),
       
   116                                       proxy.getListener());
       
   117         } else {
       
   118             this.map.add(null, listener);
       
   119         }
       
   120     }
       
   121 
       
   122     /**
       
   123      * Remove a PropertyChangeListener from the listener list.
       
   124      * This removes a PropertyChangeListener that was registered
       
   125      * for all properties.
       
   126      * If <code>listener</code> was added more than once to the same event
       
   127      * source, it will be notified one less time after being removed.
       
   128      * If <code>listener</code> is null, or was never added, no exception is
       
   129      * thrown and no action is taken.
       
   130      *
       
   131      * @param listener  The PropertyChangeListener to be removed
       
   132      */
       
   133     public void removePropertyChangeListener(PropertyChangeListener listener) {
       
   134         if (listener == null) {
       
   135             return;
       
   136         }
       
   137         if (listener instanceof PropertyChangeListenerProxy) {
       
   138             PropertyChangeListenerProxy proxy =
       
   139                     (PropertyChangeListenerProxy)listener;
       
   140             // Call two argument remove method.
       
   141             removePropertyChangeListener(proxy.getPropertyName(),
       
   142                                          proxy.getListener());
       
   143         } else {
       
   144             this.map.remove(null, listener);
       
   145         }
       
   146     }
       
   147 
       
   148     /**
       
   149      * Returns an array of all the listeners that were added to the
       
   150      * PropertyChangeSupport object with addPropertyChangeListener().
       
   151      * <p>
       
   152      * If some listeners have been added with a named property, then
       
   153      * the returned array will be a mixture of PropertyChangeListeners
       
   154      * and <code>PropertyChangeListenerProxy</code>s. If the calling
       
   155      * method is interested in distinguishing the listeners then it must
       
   156      * test each element to see if it's a
       
   157      * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
       
   158      * the parameter.
       
   159      *
       
   160      * <pre>{@code
       
   161      * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
       
   162      * for (int i = 0; i < listeners.length; i++) {
       
   163      *   if (listeners[i] instanceof PropertyChangeListenerProxy) {
       
   164      *     PropertyChangeListenerProxy proxy =
       
   165      *                    (PropertyChangeListenerProxy)listeners[i];
       
   166      *     if (proxy.getPropertyName().equals("foo")) {
       
   167      *       // proxy is a PropertyChangeListener which was associated
       
   168      *       // with the property named "foo"
       
   169      *     }
       
   170      *   }
       
   171      * }
       
   172      * }</pre>
       
   173      *
       
   174      * @see PropertyChangeListenerProxy
       
   175      * @return all of the <code>PropertyChangeListeners</code> added or an
       
   176      *         empty array if no listeners have been added
       
   177      * @since 1.4
       
   178      */
       
   179     public PropertyChangeListener[] getPropertyChangeListeners() {
       
   180         return this.map.getListeners();
       
   181     }
       
   182 
       
   183     /**
       
   184      * Add a PropertyChangeListener for a specific property.  The listener
       
   185      * will be invoked only when a call on firePropertyChange names that
       
   186      * specific property.
       
   187      * The same listener object may be added more than once.  For each
       
   188      * property,  the listener will be invoked the number of times it was added
       
   189      * for that property.
       
   190      * If <code>propertyName</code> or <code>listener</code> is null, no
       
   191      * exception is thrown and no action is taken.
       
   192      *
       
   193      * @param propertyName  The name of the property to listen on.
       
   194      * @param listener  The PropertyChangeListener to be added
       
   195      * @since 1.2
       
   196      */
       
   197     public void addPropertyChangeListener(
       
   198                 String propertyName,
       
   199                 PropertyChangeListener listener) {
       
   200         if (listener == null || propertyName == null) {
       
   201             return;
       
   202         }
       
   203         listener = this.map.extract(listener);
       
   204         if (listener != null) {
       
   205             this.map.add(propertyName, listener);
       
   206         }
       
   207     }
       
   208 
       
   209     /**
       
   210      * Remove a PropertyChangeListener for a specific property.
       
   211      * If <code>listener</code> was added more than once to the same event
       
   212      * source for the specified property, it will be notified one less time
       
   213      * after being removed.
       
   214      * If <code>propertyName</code> is null,  no exception is thrown and no
       
   215      * action is taken.
       
   216      * If <code>listener</code> is null, or was never added for the specified
       
   217      * property, no exception is thrown and no action is taken.
       
   218      *
       
   219      * @param propertyName  The name of the property that was listened on.
       
   220      * @param listener  The PropertyChangeListener to be removed
       
   221      * @since 1.2
       
   222      */
       
   223     public void removePropertyChangeListener(
       
   224                 String propertyName,
       
   225                 PropertyChangeListener listener) {
       
   226         if (listener == null || propertyName == null) {
       
   227             return;
       
   228         }
       
   229         listener = this.map.extract(listener);
       
   230         if (listener != null) {
       
   231             this.map.remove(propertyName, listener);
       
   232         }
       
   233     }
       
   234 
       
   235     /**
       
   236      * Returns an array of all the listeners which have been associated
       
   237      * with the named property.
       
   238      *
       
   239      * @param propertyName  The name of the property being listened to
       
   240      * @return all of the <code>PropertyChangeListeners</code> associated with
       
   241      *         the named property.  If no such listeners have been added,
       
   242      *         or if <code>propertyName</code> is null, an empty array is
       
   243      *         returned.
       
   244      * @since 1.4
       
   245      */
       
   246     public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
       
   247         return this.map.getListeners(propertyName);
       
   248     }
       
   249 
       
   250     /**
       
   251      * Reports a bound property update to listeners
       
   252      * that have been registered to track updates of
       
   253      * all properties or a property with the specified name.
       
   254      * <p>
       
   255      * No event is fired if old and new values are equal and non-null.
       
   256      * <p>
       
   257      * This is merely a convenience wrapper around the more general
       
   258      * {@link #firePropertyChange(PropertyChangeEvent)} method.
       
   259      *
       
   260      * @param propertyName  the programmatic name of the property that was changed
       
   261      * @param oldValue      the old value of the property
       
   262      * @param newValue      the new value of the property
       
   263      */
       
   264     public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
       
   265         if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
       
   266             firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
       
   267         }
       
   268     }
       
   269 
       
   270     /**
       
   271      * Reports an integer bound property update to listeners
       
   272      * that have been registered to track updates of
       
   273      * all properties or a property with the specified name.
       
   274      * <p>
       
   275      * No event is fired if old and new values are equal.
       
   276      * <p>
       
   277      * This is merely a convenience wrapper around the more general
       
   278      * {@link #firePropertyChange(String, Object, Object)}  method.
       
   279      *
       
   280      * @param propertyName  the programmatic name of the property that was changed
       
   281      * @param oldValue      the old value of the property
       
   282      * @param newValue      the new value of the property
       
   283      * @since 1.2
       
   284      */
       
   285     public void firePropertyChange(String propertyName, int oldValue, int newValue) {
       
   286         if (oldValue != newValue) {
       
   287             firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
       
   288         }
       
   289     }
       
   290 
       
   291     /**
       
   292      * Reports a boolean bound property update to listeners
       
   293      * that have been registered to track updates of
       
   294      * all properties or a property with the specified name.
       
   295      * <p>
       
   296      * No event is fired if old and new values are equal.
       
   297      * <p>
       
   298      * This is merely a convenience wrapper around the more general
       
   299      * {@link #firePropertyChange(String, Object, Object)}  method.
       
   300      *
       
   301      * @param propertyName  the programmatic name of the property that was changed
       
   302      * @param oldValue      the old value of the property
       
   303      * @param newValue      the new value of the property
       
   304      * @since 1.2
       
   305      */
       
   306     public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
       
   307         if (oldValue != newValue) {
       
   308             firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
       
   309         }
       
   310     }
       
   311 
       
   312     /**
       
   313      * Fires a property change event to listeners
       
   314      * that have been registered to track updates of
       
   315      * all properties or a property with the specified name.
       
   316      * <p>
       
   317      * No event is fired if the given event's old and new values are equal and non-null.
       
   318      *
       
   319      * @param event  the {@code PropertyChangeEvent} to be fired
       
   320      * @since 1.2
       
   321      */
       
   322     public void firePropertyChange(PropertyChangeEvent event) {
       
   323         Object oldValue = event.getOldValue();
       
   324         Object newValue = event.getNewValue();
       
   325         if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
       
   326             String name = event.getPropertyName();
       
   327 
       
   328             PropertyChangeListener[] common = this.map.get(null);
       
   329             PropertyChangeListener[] named = (name != null)
       
   330                         ? this.map.get(name)
       
   331                         : null;
       
   332 
       
   333             fire(common, event);
       
   334             fire(named, event);
       
   335         }
       
   336     }
       
   337 
       
   338     private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
       
   339         if (listeners != null) {
       
   340             for (PropertyChangeListener listener : listeners) {
       
   341                 listener.propertyChange(event);
       
   342             }
       
   343         }
       
   344     }
       
   345 
       
   346     /**
       
   347      * Reports a bound indexed property update to listeners
       
   348      * that have been registered to track updates of
       
   349      * all properties or a property with the specified name.
       
   350      * <p>
       
   351      * No event is fired if old and new values are equal and non-null.
       
   352      * <p>
       
   353      * This is merely a convenience wrapper around the more general
       
   354      * {@link #firePropertyChange(PropertyChangeEvent)} method.
       
   355      *
       
   356      * @param propertyName  the programmatic name of the property that was changed
       
   357      * @param index         the index of the property element that was changed
       
   358      * @param oldValue      the old value of the property
       
   359      * @param newValue      the new value of the property
       
   360      * @since 1.5
       
   361      */
       
   362     public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
       
   363         if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
       
   364             firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index));
       
   365         }
       
   366     }
       
   367 
       
   368     /**
       
   369      * Reports an integer bound indexed property update to listeners
       
   370      * that have been registered to track updates of
       
   371      * all properties or a property with the specified name.
       
   372      * <p>
       
   373      * No event is fired if old and new values are equal.
       
   374      * <p>
       
   375      * This is merely a convenience wrapper around the more general
       
   376      * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
       
   377      *
       
   378      * @param propertyName  the programmatic name of the property that was changed
       
   379      * @param index         the index of the property element that was changed
       
   380      * @param oldValue      the old value of the property
       
   381      * @param newValue      the new value of the property
       
   382      * @since 1.5
       
   383      */
       
   384     public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) {
       
   385         if (oldValue != newValue) {
       
   386             fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue));
       
   387         }
       
   388     }
       
   389 
       
   390     /**
       
   391      * Reports a boolean bound indexed property update to listeners
       
   392      * that have been registered to track updates of
       
   393      * all properties or a property with the specified name.
       
   394      * <p>
       
   395      * No event is fired if old and new values are equal.
       
   396      * <p>
       
   397      * This is merely a convenience wrapper around the more general
       
   398      * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method.
       
   399      *
       
   400      * @param propertyName  the programmatic name of the property that was changed
       
   401      * @param index         the index of the property element that was changed
       
   402      * @param oldValue      the old value of the property
       
   403      * @param newValue      the new value of the property
       
   404      * @since 1.5
       
   405      */
       
   406     public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) {
       
   407         if (oldValue != newValue) {
       
   408             fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
       
   409         }
       
   410     }
       
   411 
       
   412     /**
       
   413      * Check if there are any listeners for a specific property, including
       
   414      * those registered on all properties.  If <code>propertyName</code>
       
   415      * is null, only check for listeners registered on all properties.
       
   416      *
       
   417      * @param propertyName  the property name.
       
   418      * @return true if there are one or more listeners for the given property
       
   419      * @since 1.2
       
   420      */
       
   421     public boolean hasListeners(String propertyName) {
       
   422         return this.map.hasListeners(propertyName);
       
   423     }
       
   424 
       
   425     /**
       
   426      * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
       
   427      * <p>
       
   428      * At serialization time we skip non-serializable listeners and
       
   429      * only serialize the serializable listeners.
       
   430      */
       
   431     private void writeObject(ObjectOutputStream s) throws IOException {
       
   432         Hashtable<String, PropertyChangeSupport> children = null;
       
   433         PropertyChangeListener[] listeners = null;
       
   434         synchronized (this.map) {
       
   435             for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) {
       
   436                 String property = entry.getKey();
       
   437                 if (property == null) {
       
   438                     listeners = entry.getValue();
       
   439                 } else {
       
   440                     if (children == null) {
       
   441                         children = new Hashtable<>();
       
   442                     }
       
   443                     PropertyChangeSupport pcs = new PropertyChangeSupport(this.source);
       
   444                     pcs.map.set(null, entry.getValue());
       
   445                     children.put(property, pcs);
       
   446                 }
       
   447             }
       
   448         }
       
   449         ObjectOutputStream.PutField fields = s.putFields();
       
   450         fields.put("children", children);
       
   451         fields.put("source", this.source);
       
   452         fields.put("propertyChangeSupportSerializedDataVersion", 2);
       
   453         s.writeFields();
       
   454 
       
   455         if (listeners != null) {
       
   456             for (PropertyChangeListener l : listeners) {
       
   457                 if (l instanceof Serializable) {
       
   458                     s.writeObject(l);
       
   459                 }
       
   460             }
       
   461         }
       
   462         s.writeObject(null);
       
   463     }
       
   464 
       
   465     private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
       
   466         this.map = new PropertyChangeListenerMap();
       
   467 
       
   468         ObjectInputStream.GetField fields = s.readFields();
       
   469 
       
   470         @SuppressWarnings("unchecked")
       
   471         Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
       
   472         this.source = fields.get("source", null);
       
   473         fields.get("propertyChangeSupportSerializedDataVersion", 2);
       
   474 
       
   475         Object listenerOrNull;
       
   476         while (null != (listenerOrNull = s.readObject())) {
       
   477             this.map.add(null, (PropertyChangeListener)listenerOrNull);
       
   478         }
       
   479         if (children != null) {
       
   480             for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
       
   481                 for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
       
   482                     this.map.add(entry.getKey(), listener);
       
   483                 }
       
   484             }
       
   485         }
       
   486     }
       
   487 
       
   488     /**
       
   489      * The object to be provided as the "source" for any generated events.
       
   490      */
       
   491     private Object source;
       
   492 
       
   493     /**
       
   494      * @serialField children                                   Hashtable
       
   495      * @serialField source                                     Object
       
   496      * @serialField propertyChangeSupportSerializedDataVersion int
       
   497      */
       
   498     private static final ObjectStreamField[] serialPersistentFields = {
       
   499             new ObjectStreamField("children", Hashtable.class),
       
   500             new ObjectStreamField("source", Object.class),
       
   501             new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
       
   502     };
       
   503 
       
   504     /**
       
   505      * Serialization version ID, so we're compatible with JDK 1.1
       
   506      */
       
   507     static final long serialVersionUID = 6401253773779951803L;
       
   508 
       
   509     /**
       
   510      * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
       
   511      * that works with {@link PropertyChangeListener PropertyChangeListener} objects.
       
   512      */
       
   513     private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
       
   514         private static final PropertyChangeListener[] EMPTY = {};
       
   515 
       
   516         /**
       
   517          * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
       
   518          * This method uses the same instance of the empty array
       
   519          * when {@code length} equals {@code 0}.
       
   520          *
       
   521          * @param length  the array length
       
   522          * @return        an array with specified length
       
   523          */
       
   524         @Override
       
   525         protected PropertyChangeListener[] newArray(int length) {
       
   526             return (0 < length)
       
   527                     ? new PropertyChangeListener[length]
       
   528                     : EMPTY;
       
   529         }
       
   530 
       
   531         /**
       
   532          * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
       
   533          * object for the specified property.
       
   534          *
       
   535          * @param name      the name of the property to listen on
       
   536          * @param listener  the listener to process events
       
   537          * @return          a {@code PropertyChangeListenerProxy} object
       
   538          */
       
   539         @Override
       
   540         protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
       
   541             return new PropertyChangeListenerProxy(name, listener);
       
   542         }
       
   543 
       
   544         /**
       
   545          * {@inheritDoc}
       
   546          */
       
   547         public final PropertyChangeListener extract(PropertyChangeListener listener) {
       
   548             while (listener instanceof PropertyChangeListenerProxy) {
       
   549                 listener = ((PropertyChangeListenerProxy) listener).getListener();
       
   550             }
       
   551             return listener;
       
   552         }
       
   553     }
       
   554 }