emul/compact/src/main/java/java/beans/VetoableChangeSupport.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 28 Jan 2013 18:12:47 +0100
branchjdk7-b147
changeset 601 5198affdb915
child 604 3fcc279c921b
permissions -rw-r--r--
Adding ObjectInputStream and ObjectOutputStream (but without implementation). Adding PropertyChange related classes.
     1 /*
     2  * Copyright (c) 1996, 2011, 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>
    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>
   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<String, VetoableChangeSupport>();
   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         Hashtable<String, VetoableChangeSupport> children = (Hashtable<String, VetoableChangeSupport>) fields.get("children", null);
   453         this.source = fields.get("source", null);
   454         fields.get("vetoableChangeSupportSerializedDataVersion", 2);
   455 
   456         Object listenerOrNull;
   457         while (null != (listenerOrNull = s.readObject())) {
   458             this.map.add(null, (VetoableChangeListener)listenerOrNull);
   459         }
   460         if (children != null) {
   461             for (Entry<String, VetoableChangeSupport> entry : children.entrySet()) {
   462                 for (VetoableChangeListener listener : entry.getValue().getVetoableChangeListeners()) {
   463                     this.map.add(entry.getKey(), listener);
   464                 }
   465             }
   466         }
   467     }
   468 
   469     /**
   470      * The object to be provided as the "source" for any generated events.
   471      */
   472     private Object source;
   473 
   474     /**
   475      * @serialField children                                   Hashtable
   476      * @serialField source                                     Object
   477      * @serialField vetoableChangeSupportSerializedDataVersion int
   478      */
   479     private static final ObjectStreamField[] serialPersistentFields = {
   480             new ObjectStreamField("children", Hashtable.class),
   481             new ObjectStreamField("source", Object.class),
   482             new ObjectStreamField("vetoableChangeSupportSerializedDataVersion", Integer.TYPE)
   483     };
   484 
   485     /**
   486      * Serialization version ID, so we're compatible with JDK 1.1
   487      */
   488     static final long serialVersionUID = -5090210921595982017L;
   489 
   490     /**
   491      * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
   492      * that works with {@link VetoableChangeListener VetoableChangeListener} objects.
   493      */
   494     private static final class VetoableChangeListenerMap extends ChangeListenerMap<VetoableChangeListener> {
   495         private static final VetoableChangeListener[] EMPTY = {};
   496 
   497         /**
   498          * Creates an array of {@link VetoableChangeListener VetoableChangeListener} objects.
   499          * This method uses the same instance of the empty array
   500          * when {@code length} equals {@code 0}.
   501          *
   502          * @param length  the array length
   503          * @return        an array with specified length
   504          */
   505         @Override
   506         protected VetoableChangeListener[] newArray(int length) {
   507             return (0 < length)
   508                     ? new VetoableChangeListener[length]
   509                     : EMPTY;
   510         }
   511 
   512         /**
   513          * Creates a {@link VetoableChangeListenerProxy VetoableChangeListenerProxy}
   514          * object for the specified property.
   515          *
   516          * @param name      the name of the property to listen on
   517          * @param listener  the listener to process events
   518          * @return          a {@code VetoableChangeListenerProxy} object
   519          */
   520         @Override
   521         protected VetoableChangeListener newProxy(String name, VetoableChangeListener listener) {
   522             return new VetoableChangeListenerProxy(name, listener);
   523         }
   524     }
   525 }