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