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 */
25package java.beans;
26
27import java.io.Serializable;
28import java.io.ObjectStreamField;
29import java.io.ObjectOutputStream;
30import java.io.ObjectInputStream;
31import java.io.IOException;
32import java.util.Hashtable;
33import 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 * @since 1.1
81 */
82public 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}
544