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  */
  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>{@code
 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<>();
 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         @SuppressWarnings("unchecked")
 464         Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null);
 465         this.source = fields.get("source", null);
 466         fields.get("propertyChangeSupportSerializedDataVersion", 2);
 467 
 468         Object listenerOrNull;
 469         while (null != (listenerOrNull = s.readObject())) {
 470             this.map.add(null, (PropertyChangeListener)listenerOrNull);
 471         }
 472         if (children != null) {
 473             for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) {
 474                 for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) {
 475                     this.map.add(entry.getKey(), listener);
 476                 }
 477             }
 478         }
 479     }
 480 
 481     /**
 482      * The object to be provided as the "source" for any generated events.
 483      */
 484     private Object source;
 485 
 486     /**
 487      * @serialField children                                   Hashtable
 488      * @serialField source                                     Object
 489      * @serialField propertyChangeSupportSerializedDataVersion int
 490      */
 491     private static final ObjectStreamField[] serialPersistentFields = {
 492             new ObjectStreamField("children", Hashtable.class),
 493             new ObjectStreamField("source", Object.class),
 494             new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE)
 495     };
 496 
 497     /**
 498      * Serialization version ID, so we're compatible with JDK 1.1
 499      */
 500     static final long serialVersionUID = 6401253773779951803L;
 501 
 502     /**
 503      * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
 504      * that works with {@link PropertyChangeListener PropertyChangeListener} objects.
 505      */
 506     private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> {
 507         private static final PropertyChangeListener[] EMPTY = {};
 508 
 509         /**
 510          * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects.
 511          * This method uses the same instance of the empty array
 512          * when {@code length} equals {@code 0}.
 513          *
 514          * @param length  the array length
 515          * @return        an array with specified length
 516          */
 517         @Override
 518         protected PropertyChangeListener[] newArray(int length) {
 519             return (0 < length)
 520                     ? new PropertyChangeListener[length]
 521                     : EMPTY;
 522         }
 523 
 524         /**
 525          * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy}
 526          * object for the specified property.
 527          *
 528          * @param name      the name of the property to listen on
 529          * @param listener  the listener to process events
 530          * @return          a {@code PropertyChangeListenerProxy} object
 531          */
 532         @Override
 533         protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) {
 534             return new PropertyChangeListenerProxy(name, listener);
 535         }
 536 
 537         /**
 538          * {@inheritDoc}
 539          */
 540         public final PropertyChangeListener extract(PropertyChangeListener listener) {
 541             while (listener instanceof PropertyChangeListenerProxy) {
 542                 listener = ((PropertyChangeListenerProxy) listener).getListener();
 543             }
 544             return listener;
 545         }
 546     }
 547 }