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 constrained
  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 VetoableChangeListener} can be registered for all properties
  41  * or for a property specified by name.
  42  * <p>
  43  * Here is an example of {@code VetoableChangeSupport} usage that follows
  44  * the rules and recommendations laid out in the JavaBeans specification:
  45  * <pre>{@code
  46  * public class MyBean {
  47  *     private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);
  48  *
  49  *     public void addVetoableChangeListener(VetoableChangeListener listener) {
  50  *         this.vcs.addVetoableChangeListener(listener);
  51  *     }
  52  *
  53  *     public void removeVetoableChangeListener(VetoableChangeListener listener) {
  54  *         this.vcs.removeVetoableChangeListener(listener);
  55  *     }
  56  *
  57  *     private String value;
  58  *
  59  *     public String getValue() {
  60  *         return this.value;
  61  *     }
  62  *
  63  *     public void setValue(String newValue) throws PropertyVetoException {
  64  *         String oldValue = this.value;
  65  *         this.vcs.fireVetoableChange("value", oldValue, newValue);
  66  *         this.value = newValue;
  67  *     }
  68  *
  69  *     [...]
  70  * }
  71  * }</pre>
  72  * <p>
  73  * A {@code VetoableChangeSupport} 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 PropertyChangeSupport
  80  * @since 1.1
  81  */
  82 public class VetoableChangeSupport implements Serializable {
  83     private VetoableChangeListenerMap map = new VetoableChangeListenerMap();
  84 
  85     /**
  86      * Constructs a {@code VetoableChangeSupport} object.
  87      *
  88      * @param sourceBean  The bean to be given as the source for any events.
  89      */
  90     public VetoableChangeSupport(Object sourceBean) {
  91         if (sourceBean == null) {
  92             throw new NullPointerException();
  93         }
  94         source = sourceBean;
  95     }
  96 
  97     /**
  98      * Add a VetoableChangeListener 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} is null, no exception is thrown and no action
 103      * is taken.
 104      *
 105      * @param listener  The VetoableChangeListener to be added
 106      */
 107     public void addVetoableChangeListener(VetoableChangeListener listener) {
 108         if (listener == null) {
 109             return;
 110         }
 111         if (listener instanceof VetoableChangeListenerProxy) {
 112             VetoableChangeListenerProxy proxy =
 113                     (VetoableChangeListenerProxy)listener;
 114             // Call two argument add method.
 115             addVetoableChangeListener(proxy.getPropertyName(),
 116                                       proxy.getListener());
 117         } else {
 118             this.map.add(null, listener);
 119         }
 120     }
 121 
 122     /**
 123      * Remove a VetoableChangeListener from the listener list.
 124      * This removes a VetoableChangeListener that was registered
 125      * for all properties.
 126      * If {@code listener} 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} is null, or was never added, no exception is
 129      * thrown and no action is taken.
 130      *
 131      * @param listener  The VetoableChangeListener to be removed
 132      */
 133     public void removeVetoableChangeListener(VetoableChangeListener listener) {
 134         if (listener == null) {
 135             return;
 136         }
 137         if (listener instanceof VetoableChangeListenerProxy) {
 138             VetoableChangeListenerProxy proxy =
 139                     (VetoableChangeListenerProxy)listener;
 140             // Call two argument remove method.
 141             removeVetoableChangeListener(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      * VetoableChangeSupport object with addVetoableChangeListener().
 151      * <p>
 152      * If some listeners have been added with a named property, then
 153      * the returned array will be a mixture of VetoableChangeListeners
 154      * and {@code VetoableChangeListenerProxy}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 VetoableChangeListenerProxy}, perform the cast, and examine
 158      * the parameter.
 159      *
 160      * <pre>{@code
 161      * VetoableChangeListener[] listeners = bean.getVetoableChangeListeners();
 162      * for (int i = 0; i < listeners.length; i++) {
 163      *        if (listeners[i] instanceof VetoableChangeListenerProxy) {
 164      *     VetoableChangeListenerProxy proxy =
 165      *                    (VetoableChangeListenerProxy)listeners[i];
 166      *     if (proxy.getPropertyName().equals("foo")) {
 167      *       // proxy is a VetoableChangeListener which was associated
 168      *       // with the property named "foo"
 169      *     }
 170      *   }
 171      * }
 172      * }</pre>
 173      *
 174      * @see VetoableChangeListenerProxy
 175      * @return all of the {@code VetoableChangeListeners} added or an
 176      *         empty array if no listeners have been added
 177      * @since 1.4
 178      */
 179     public VetoableChangeListener[] getVetoableChangeListeners(){
 180         return this.map.getListeners();
 181     }
 182 
 183     /**
 184      * Add a VetoableChangeListener for a specific property.  The listener
 185      * will be invoked only when a call on fireVetoableChange 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} or {@code listener} 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 VetoableChangeListener to be added
 195      * @since 1.2
 196      */
 197     public void addVetoableChangeListener(
 198                                 String propertyName,
 199                 VetoableChangeListener 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 VetoableChangeListener for a specific property.
 211      * If {@code listener} 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} is null, no exception is thrown and no
 215      * action is taken.
 216      * If {@code listener} 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 VetoableChangeListener to be removed
 221      * @since 1.2
 222      */
 223     public void removeVetoableChangeListener(
 224                                 String propertyName,
 225                 VetoableChangeListener 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 the {@code VetoableChangeListeners} associated with
 241      *         the named property.  If no such listeners have been added,
 242      *         or if {@code propertyName} is null, an empty array is
 243      *         returned.
 244      * @since 1.4
 245      */
 246     public VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {
 247         return this.map.getListeners(propertyName);
 248     }
 249 
 250     /**
 251      * Reports a constrained 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      * Any listener can throw a {@code PropertyVetoException} to veto the update.
 256      * If one of the listeners vetoes the update, this method passes
 257      * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
 258      * to all listeners that already confirmed this update
 259      * and throws the {@code PropertyVetoException} again.
 260      * <p>
 261      * No event is fired if old and new values are equal and non-null.
 262      * <p>
 263      * This is merely a convenience wrapper around the more general
 264      * {@link #fireVetoableChange(PropertyChangeEvent)} method.
 265      *
 266      * @param propertyName  the programmatic name of the property that is about to change
 267      * @param oldValue      the old value of the property
 268      * @param newValue      the new value of the property
 269      * @throws PropertyVetoException if one of listeners vetoes the property update
 270      */
 271     public void fireVetoableChange(String propertyName, Object oldValue, Object newValue)
 272             throws PropertyVetoException {
 273         if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
 274             fireVetoableChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
 275         }
 276     }
 277 
 278     /**
 279      * Reports an integer constrained property update to listeners
 280      * that have been registered to track updates of
 281      * all properties or a property with the specified name.
 282      * <p>
 283      * Any listener can throw a {@code PropertyVetoException} to veto the update.
 284      * If one of the listeners vetoes the update, this method passes
 285      * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
 286      * to all listeners that already confirmed this update
 287      * and throws the {@code PropertyVetoException} again.
 288      * <p>
 289      * No event is fired if old and new values are equal.
 290      * <p>
 291      * This is merely a convenience wrapper around the more general
 292      * {@link #fireVetoableChange(String, Object, Object)} method.
 293      *
 294      * @param propertyName  the programmatic name of the property that is about to change
 295      * @param oldValue      the old value of the property
 296      * @param newValue      the new value of the property
 297      * @throws PropertyVetoException if one of listeners vetoes the property update
 298      * @since 1.2
 299      */
 300     public void fireVetoableChange(String propertyName, int oldValue, int newValue)
 301             throws PropertyVetoException {
 302         if (oldValue != newValue) {
 303             fireVetoableChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
 304         }
 305     }
 306 
 307     /**
 308      * Reports a boolean constrained property update to listeners
 309      * that have been registered to track updates of
 310      * all properties or a property with the specified name.
 311      * <p>
 312      * Any listener can throw a {@code PropertyVetoException} to veto the update.
 313      * If one of the listeners vetoes the update, this method passes
 314      * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
 315      * to all listeners that already confirmed this update
 316      * and throws the {@code PropertyVetoException} again.
 317      * <p>
 318      * No event is fired if old and new values are equal.
 319      * <p>
 320      * This is merely a convenience wrapper around the more general
 321      * {@link #fireVetoableChange(String, Object, Object)} method.
 322      *
 323      * @param propertyName  the programmatic name of the property that is about to change
 324      * @param oldValue      the old value of the property
 325      * @param newValue      the new value of the property
 326      * @throws PropertyVetoException if one of listeners vetoes the property update
 327      * @since 1.2
 328      */
 329     public void fireVetoableChange(String propertyName, boolean oldValue, boolean newValue)
 330             throws PropertyVetoException {
 331         if (oldValue != newValue) {
 332             fireVetoableChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
 333         }
 334     }
 335 
 336     /**
 337      * Fires a property change event to listeners
 338      * that have been registered to track updates of
 339      * all properties or a property with the specified name.
 340      * <p>
 341      * Any listener can throw a {@code PropertyVetoException} to veto the update.
 342      * If one of the listeners vetoes the update, this method passes
 343      * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
 344      * to all listeners that already confirmed this update
 345      * and throws the {@code PropertyVetoException} again.
 346      * <p>
 347      * No event is fired if the given event's old and new values are equal and non-null.
 348      *
 349      * @param event  the {@code PropertyChangeEvent} to be fired
 350      * @throws PropertyVetoException if one of listeners vetoes the property update
 351      * @since 1.2
 352      */
 353     public void fireVetoableChange(PropertyChangeEvent event)
 354             throws PropertyVetoException {
 355         Object oldValue = event.getOldValue();
 356         Object newValue = event.getNewValue();
 357         if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
 358             String name = event.getPropertyName();
 359 
 360             VetoableChangeListener[] common = this.map.get(null);
 361             VetoableChangeListener[] named = (name != null)
 362                         ? this.map.get(name)
 363                         : null;
 364 
 365             VetoableChangeListener[] listeners;
 366             if (common == null) {
 367                 listeners = named;
 368             }
 369             else if (named == null) {
 370                 listeners = common;
 371             }
 372             else {
 373                 listeners = new VetoableChangeListener[common.length + named.length];
 374                 System.arraycopy(common, 0, listeners, 0, common.length);
 375                 System.arraycopy(named, 0, listeners, common.length, named.length);
 376             }
 377             if (listeners != null) {
 378                 int current = 0;
 379                 try {
 380                     while (current < listeners.length) {
 381                         listeners[current].vetoableChange(event);
 382                         current++;
 383                     }
 384                 }
 385                 catch (PropertyVetoException veto) {
 386                     event = new PropertyChangeEvent(this.source, name, newValue, oldValue);
 387                     for (int i = 0; i < current; i++) {
 388                         try {
 389                             listeners[i].vetoableChange(event);
 390                         }
 391                         catch (PropertyVetoException exception) {
 392                             // ignore exceptions that occur during rolling back
 393                         }
 394                     }
 395                     throw veto; // rethrow the veto exception
 396                 }
 397             }
 398         }
 399     }
 400 
 401     /**
 402      * Check if there are any listeners for a specific property, including
 403      * those registered on all properties.  If {@code propertyName}
 404      * is null, only check for listeners registered on all properties.
 405      *
 406      * @param propertyName  the property name.
 407      * @return true if there are one or more listeners for the given property
 408      * @since 1.2
 409      */
 410     public boolean hasListeners(String propertyName) {
 411         return this.map.hasListeners(propertyName);
 412     }
 413 
 414     /**
 415      * @serialData Null terminated list of {@code VetoableChangeListeners}.
 416      * <p>
 417      * At serialization time we skip non-serializable listeners and
 418      * only serialize the serializable listeners.
 419      */
 420     private void writeObject(ObjectOutputStream s) throws IOException {
 421         Hashtable<String, VetoableChangeSupport> children = null;
 422         VetoableChangeListener[] listeners = null;
 423         synchronized (this.map) {
 424             for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) {
 425                 String property = entry.getKey();
 426                 if (property == null) {
 427                     listeners = entry.getValue();
 428                 } else {
 429                     if (children == null) {
 430                         children = new Hashtable<>();
 431                     }
 432                     VetoableChangeSupport vcs = new VetoableChangeSupport(this.source);
 433                     vcs.map.set(null, entry.getValue());
 434                     children.put(property, vcs);
 435                 }
 436             }
 437         }
 438         ObjectOutputStream.PutField fields = s.putFields();
 439         fields.put("children", children);
 440         fields.put("source", this.source);
 441         fields.put("vetoableChangeSupportSerializedDataVersion", 2);
 442         s.writeFields();
 443 
 444         if (listeners != null) {
 445             for (VetoableChangeListener l : listeners) {
 446                 if (l instanceof Serializable) {
 447                     s.writeObject(l);
 448                 }
 449             }
 450         }
 451         s.writeObject(null);
 452     }
 453 
 454     private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
 455         this.map = new VetoableChangeListenerMap();
 456 
 457         ObjectInputStream.GetField fields = s.readFields();
 458 
 459         @SuppressWarnings("unchecked")
 460         Hashtable<String, VetoableChangeSupport> children = (Hashtable<String, VetoableChangeSupport>)fields.get("children", null);
 461         this.source = fields.get("source", null);
 462         fields.get("vetoableChangeSupportSerializedDataVersion", 2);
 463 
 464         Object listenerOrNull;
 465         while (null != (listenerOrNull = s.readObject())) {
 466             this.map.add(null, (VetoableChangeListener)listenerOrNull);
 467         }
 468         if (children != null) {
 469             for (Entry<String, VetoableChangeSupport> entry : children.entrySet()) {
 470                 for (VetoableChangeListener listener : entry.getValue().getVetoableChangeListeners()) {
 471                     this.map.add(entry.getKey(), listener);
 472                 }
 473             }
 474         }
 475     }
 476 
 477     /**
 478      * The object to be provided as the "source" for any generated events.
 479      */
 480     private Object source;
 481 
 482     /**
 483      * @serialField children                                   Hashtable
 484      * @serialField source                                     Object
 485      * @serialField vetoableChangeSupportSerializedDataVersion int
 486      */
 487     private static final ObjectStreamField[] serialPersistentFields = {
 488             new ObjectStreamField("children", Hashtable.class),
 489             new ObjectStreamField("source", Object.class),
 490             new ObjectStreamField("vetoableChangeSupportSerializedDataVersion", Integer.TYPE)
 491     };
 492 
 493     /**
 494      * Serialization version ID, so we're compatible with JDK 1.1
 495      */
 496     static final long serialVersionUID = -5090210921595982017L;
 497 
 498     /**
 499      * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
 500      * that works with {@link VetoableChangeListener VetoableChangeListener} objects.
 501      */
 502     private static final class VetoableChangeListenerMap extends ChangeListenerMap<VetoableChangeListener> {
 503         private static final VetoableChangeListener[] EMPTY = {};
 504 
 505         /**
 506          * Creates an array of {@link VetoableChangeListener VetoableChangeListener} objects.
 507          * This method uses the same instance of the empty array
 508          * when {@code length} equals {@code 0}.
 509          *
 510          * @param length  the array length
 511          * @return        an array with specified length
 512          */
 513         @Override
 514         protected VetoableChangeListener[] newArray(int length) {
 515             return (0 < length)
 516                     ? new VetoableChangeListener[length]
 517                     : EMPTY;
 518         }
 519 
 520         /**
 521          * Creates a {@link VetoableChangeListenerProxy VetoableChangeListenerProxy}
 522          * object for the specified property.
 523          *
 524          * @param name      the name of the property to listen on
 525          * @param listener  the listener to process events
 526          * @return          a {@code VetoableChangeListenerProxy} object
 527          */
 528         @Override
 529         protected VetoableChangeListener newProxy(String name, VetoableChangeListener listener) {
 530             return new VetoableChangeListenerProxy(name, listener);
 531         }
 532 
 533         /**
 534          * {@inheritDoc}
 535          */
 536         public VetoableChangeListener extract(VetoableChangeListener listener) {
 537             while (listener instanceof VetoableChangeListenerProxy) {
 538                 listener = ((VetoableChangeListenerProxy) listener).getListener();
 539             }
 540             return listener;
 541         }
 542     }
 543 }