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 }