Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/beans/VetoableChangeSupport.java
38829 views
/*1* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.beans;2526import java.io.Serializable;27import java.io.ObjectStreamField;28import java.io.ObjectOutputStream;29import java.io.ObjectInputStream;30import java.io.IOException;31import java.util.Hashtable;32import java.util.Map.Entry;3334/**35* This is a utility class that can be used by beans that support constrained36* properties. It manages a list of listeners and dispatches37* {@link PropertyChangeEvent}s to them. You can use an instance of this class38* as a member field of your bean and delegate these types of work to it.39* The {@link VetoableChangeListener} can be registered for all properties40* or for a property specified by name.41* <p>42* Here is an example of {@code VetoableChangeSupport} usage that follows43* the rules and recommendations laid out in the JavaBeans™ specification:44* <pre>{@code45* public class MyBean {46* private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);47*48* public void addVetoableChangeListener(VetoableChangeListener listener) {49* this.vcs.addVetoableChangeListener(listener);50* }51*52* public void removeVetoableChangeListener(VetoableChangeListener listener) {53* this.vcs.removeVetoableChangeListener(listener);54* }55*56* private String value;57*58* public String getValue() {59* return this.value;60* }61*62* public void setValue(String newValue) throws PropertyVetoException {63* String oldValue = this.value;64* this.vcs.fireVetoableChange("value", oldValue, newValue);65* this.value = newValue;66* }67*68* [...]69* }70* }</pre>71* <p>72* A {@code VetoableChangeSupport} instance is thread-safe.73* <p>74* This class is serializable. When it is serialized it will save75* (and restore) any listeners that are themselves serializable. Any76* non-serializable listeners will be skipped during serialization.77*78* @see PropertyChangeSupport79*/80public class VetoableChangeSupport implements Serializable {81private VetoableChangeListenerMap map = new VetoableChangeListenerMap();8283/**84* Constructs a <code>VetoableChangeSupport</code> object.85*86* @param sourceBean The bean to be given as the source for any events.87*/88public VetoableChangeSupport(Object sourceBean) {89if (sourceBean == null) {90throw new NullPointerException();91}92source = sourceBean;93}9495/**96* Add a VetoableChangeListener to the listener list.97* The listener is registered for all properties.98* The same listener object may be added more than once, and will be called99* as many times as it is added.100* If <code>listener</code> is null, no exception is thrown and no action101* is taken.102*103* @param listener The VetoableChangeListener to be added104*/105public void addVetoableChangeListener(VetoableChangeListener listener) {106if (listener == null) {107return;108}109if (listener instanceof VetoableChangeListenerProxy) {110VetoableChangeListenerProxy proxy =111(VetoableChangeListenerProxy)listener;112// Call two argument add method.113addVetoableChangeListener(proxy.getPropertyName(),114proxy.getListener());115} else {116this.map.add(null, listener);117}118}119120/**121* Remove a VetoableChangeListener from the listener list.122* This removes a VetoableChangeListener that was registered123* for all properties.124* If <code>listener</code> was added more than once to the same event125* source, it will be notified one less time after being removed.126* If <code>listener</code> is null, or was never added, no exception is127* thrown and no action is taken.128*129* @param listener The VetoableChangeListener to be removed130*/131public void removeVetoableChangeListener(VetoableChangeListener listener) {132if (listener == null) {133return;134}135if (listener instanceof VetoableChangeListenerProxy) {136VetoableChangeListenerProxy proxy =137(VetoableChangeListenerProxy)listener;138// Call two argument remove method.139removeVetoableChangeListener(proxy.getPropertyName(),140proxy.getListener());141} else {142this.map.remove(null, listener);143}144}145146/**147* Returns an array of all the listeners that were added to the148* VetoableChangeSupport object with addVetoableChangeListener().149* <p>150* If some listeners have been added with a named property, then151* the returned array will be a mixture of VetoableChangeListeners152* and <code>VetoableChangeListenerProxy</code>s. If the calling153* method is interested in distinguishing the listeners then it must154* test each element to see if it's a155* <code>VetoableChangeListenerProxy</code>, perform the cast, and examine156* the parameter.157*158* <pre>{@code159* VetoableChangeListener[] listeners = bean.getVetoableChangeListeners();160* for (int i = 0; i < listeners.length; i++) {161* if (listeners[i] instanceof VetoableChangeListenerProxy) {162* VetoableChangeListenerProxy proxy =163* (VetoableChangeListenerProxy)listeners[i];164* if (proxy.getPropertyName().equals("foo")) {165* // proxy is a VetoableChangeListener which was associated166* // with the property named "foo"167* }168* }169* }170* }</pre>171*172* @see VetoableChangeListenerProxy173* @return all of the <code>VetoableChangeListeners</code> added or an174* empty array if no listeners have been added175* @since 1.4176*/177public VetoableChangeListener[] getVetoableChangeListeners(){178return this.map.getListeners();179}180181/**182* Add a VetoableChangeListener for a specific property. The listener183* will be invoked only when a call on fireVetoableChange names that184* specific property.185* The same listener object may be added more than once. For each186* property, the listener will be invoked the number of times it was added187* for that property.188* If <code>propertyName</code> or <code>listener</code> is null, no189* exception is thrown and no action is taken.190*191* @param propertyName The name of the property to listen on.192* @param listener The VetoableChangeListener to be added193*/194public void addVetoableChangeListener(195String propertyName,196VetoableChangeListener listener) {197if (listener == null || propertyName == null) {198return;199}200listener = this.map.extract(listener);201if (listener != null) {202this.map.add(propertyName, listener);203}204}205206/**207* Remove a VetoableChangeListener for a specific property.208* If <code>listener</code> was added more than once to the same event209* source for the specified property, it will be notified one less time210* after being removed.211* If <code>propertyName</code> is null, no exception is thrown and no212* action is taken.213* If <code>listener</code> is null, or was never added for the specified214* property, no exception is thrown and no action is taken.215*216* @param propertyName The name of the property that was listened on.217* @param listener The VetoableChangeListener to be removed218*/219public void removeVetoableChangeListener(220String propertyName,221VetoableChangeListener listener) {222if (listener == null || propertyName == null) {223return;224}225listener = this.map.extract(listener);226if (listener != null) {227this.map.remove(propertyName, listener);228}229}230231/**232* Returns an array of all the listeners which have been associated233* with the named property.234*235* @param propertyName The name of the property being listened to236* @return all the <code>VetoableChangeListeners</code> associated with237* the named property. If no such listeners have been added,238* or if <code>propertyName</code> is null, an empty array is239* returned.240* @since 1.4241*/242public VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {243return this.map.getListeners(propertyName);244}245246/**247* Reports a constrained property update to listeners248* that have been registered to track updates of249* all properties or a property with the specified name.250* <p>251* Any listener can throw a {@code PropertyVetoException} to veto the update.252* If one of the listeners vetoes the update, this method passes253* a new "undo" {@code PropertyChangeEvent} that reverts to the old value254* to all listeners that already confirmed this update255* and throws the {@code PropertyVetoException} again.256* <p>257* No event is fired if old and new values are equal and non-null.258* <p>259* This is merely a convenience wrapper around the more general260* {@link #fireVetoableChange(PropertyChangeEvent)} method.261*262* @param propertyName the programmatic name of the property that is about to change263* @param oldValue the old value of the property264* @param newValue the new value of the property265* @throws PropertyVetoException if one of listeners vetoes the property update266*/267public void fireVetoableChange(String propertyName, Object oldValue, Object newValue)268throws PropertyVetoException {269if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {270fireVetoableChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));271}272}273274/**275* Reports an integer constrained property update to listeners276* that have been registered to track updates of277* all properties or a property with the specified name.278* <p>279* Any listener can throw a {@code PropertyVetoException} to veto the update.280* If one of the listeners vetoes the update, this method passes281* a new "undo" {@code PropertyChangeEvent} that reverts to the old value282* to all listeners that already confirmed this update283* and throws the {@code PropertyVetoException} again.284* <p>285* No event is fired if old and new values are equal.286* <p>287* This is merely a convenience wrapper around the more general288* {@link #fireVetoableChange(String, Object, Object)} method.289*290* @param propertyName the programmatic name of the property that is about to change291* @param oldValue the old value of the property292* @param newValue the new value of the property293* @throws PropertyVetoException if one of listeners vetoes the property update294*/295public void fireVetoableChange(String propertyName, int oldValue, int newValue)296throws PropertyVetoException {297if (oldValue != newValue) {298fireVetoableChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));299}300}301302/**303* Reports a boolean constrained property update to listeners304* that have been registered to track updates of305* all properties or a property with the specified name.306* <p>307* Any listener can throw a {@code PropertyVetoException} to veto the update.308* If one of the listeners vetoes the update, this method passes309* a new "undo" {@code PropertyChangeEvent} that reverts to the old value310* to all listeners that already confirmed this update311* and throws the {@code PropertyVetoException} again.312* <p>313* No event is fired if old and new values are equal.314* <p>315* This is merely a convenience wrapper around the more general316* {@link #fireVetoableChange(String, Object, Object)} method.317*318* @param propertyName the programmatic name of the property that is about to change319* @param oldValue the old value of the property320* @param newValue the new value of the property321* @throws PropertyVetoException if one of listeners vetoes the property update322*/323public void fireVetoableChange(String propertyName, boolean oldValue, boolean newValue)324throws PropertyVetoException {325if (oldValue != newValue) {326fireVetoableChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));327}328}329330/**331* Fires a property change event to listeners332* that have been registered to track updates of333* all properties or a property with the specified name.334* <p>335* Any listener can throw a {@code PropertyVetoException} to veto the update.336* If one of the listeners vetoes the update, this method passes337* a new "undo" {@code PropertyChangeEvent} that reverts to the old value338* to all listeners that already confirmed this update339* and throws the {@code PropertyVetoException} again.340* <p>341* No event is fired if the given event's old and new values are equal and non-null.342*343* @param event the {@code PropertyChangeEvent} to be fired344* @throws PropertyVetoException if one of listeners vetoes the property update345*/346public void fireVetoableChange(PropertyChangeEvent event)347throws PropertyVetoException {348Object oldValue = event.getOldValue();349Object newValue = event.getNewValue();350if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {351String name = event.getPropertyName();352353VetoableChangeListener[] common = this.map.get(null);354VetoableChangeListener[] named = (name != null)355? this.map.get(name)356: null;357358VetoableChangeListener[] listeners;359if (common == null) {360listeners = named;361}362else if (named == null) {363listeners = common;364}365else {366listeners = new VetoableChangeListener[common.length + named.length];367System.arraycopy(common, 0, listeners, 0, common.length);368System.arraycopy(named, 0, listeners, common.length, named.length);369}370if (listeners != null) {371int current = 0;372try {373while (current < listeners.length) {374listeners[current].vetoableChange(event);375current++;376}377}378catch (PropertyVetoException veto) {379event = new PropertyChangeEvent(this.source, name, newValue, oldValue);380for (int i = 0; i < current; i++) {381try {382listeners[i].vetoableChange(event);383}384catch (PropertyVetoException exception) {385// ignore exceptions that occur during rolling back386}387}388throw veto; // rethrow the veto exception389}390}391}392}393394/**395* Check if there are any listeners for a specific property, including396* those registered on all properties. If <code>propertyName</code>397* is null, only check for listeners registered on all properties.398*399* @param propertyName the property name.400* @return true if there are one or more listeners for the given property401*/402public boolean hasListeners(String propertyName) {403return this.map.hasListeners(propertyName);404}405406/**407* @serialData Null terminated list of <code>VetoableChangeListeners</code>.408* <p>409* At serialization time we skip non-serializable listeners and410* only serialize the serializable listeners.411*/412private void writeObject(ObjectOutputStream s) throws IOException {413Hashtable<String, VetoableChangeSupport> children = null;414VetoableChangeListener[] listeners = null;415synchronized (this.map) {416for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) {417String property = entry.getKey();418if (property == null) {419listeners = entry.getValue();420} else {421if (children == null) {422children = new Hashtable<>();423}424VetoableChangeSupport vcs = new VetoableChangeSupport(this.source);425vcs.map.set(null, entry.getValue());426children.put(property, vcs);427}428}429}430ObjectOutputStream.PutField fields = s.putFields();431fields.put("children", children);432fields.put("source", this.source);433fields.put("vetoableChangeSupportSerializedDataVersion", 2);434s.writeFields();435436if (listeners != null) {437for (VetoableChangeListener l : listeners) {438if (l instanceof Serializable) {439s.writeObject(l);440}441}442}443s.writeObject(null);444}445446private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {447this.map = new VetoableChangeListenerMap();448449ObjectInputStream.GetField fields = s.readFields();450451@SuppressWarnings("unchecked")452Hashtable<String, VetoableChangeSupport> children = (Hashtable<String, VetoableChangeSupport>)fields.get("children", null);453this.source = fields.get("source", null);454fields.get("vetoableChangeSupportSerializedDataVersion", 2);455456Object listenerOrNull;457while (null != (listenerOrNull = s.readObject())) {458this.map.add(null, (VetoableChangeListener)listenerOrNull);459}460if (children != null) {461for (Entry<String, VetoableChangeSupport> entry : children.entrySet()) {462for (VetoableChangeListener listener : entry.getValue().getVetoableChangeListeners()) {463this.map.add(entry.getKey(), listener);464}465}466}467}468469/**470* The object to be provided as the "source" for any generated events.471*/472private Object source;473474/**475* @serialField children Hashtable476* @serialField source Object477* @serialField vetoableChangeSupportSerializedDataVersion int478*/479private static final ObjectStreamField[] serialPersistentFields = {480new ObjectStreamField("children", Hashtable.class),481new ObjectStreamField("source", Object.class),482new ObjectStreamField("vetoableChangeSupportSerializedDataVersion", Integer.TYPE)483};484485/**486* Serialization version ID, so we're compatible with JDK 1.1487*/488static final long serialVersionUID = -5090210921595982017L;489490/**491* This is a {@link ChangeListenerMap ChangeListenerMap} implementation492* that works with {@link VetoableChangeListener VetoableChangeListener} objects.493*/494private static final class VetoableChangeListenerMap extends ChangeListenerMap<VetoableChangeListener> {495private static final VetoableChangeListener[] EMPTY = {};496497/**498* Creates an array of {@link VetoableChangeListener VetoableChangeListener} objects.499* This method uses the same instance of the empty array500* when {@code length} equals {@code 0}.501*502* @param length the array length503* @return an array with specified length504*/505@Override506protected VetoableChangeListener[] newArray(int length) {507return (0 < length)508? new VetoableChangeListener[length]509: EMPTY;510}511512/**513* Creates a {@link VetoableChangeListenerProxy VetoableChangeListenerProxy}514* object for the specified property.515*516* @param name the name of the property to listen on517* @param listener the listener to process events518* @return a {@code VetoableChangeListenerProxy} object519*/520@Override521protected VetoableChangeListener newProxy(String name, VetoableChangeListener listener) {522return new VetoableChangeListenerProxy(name, listener);523}524525/**526* {@inheritDoc}527*/528public final VetoableChangeListener extract(VetoableChangeListener listener) {529while (listener instanceof VetoableChangeListenerProxy) {530listener = ((VetoableChangeListenerProxy) listener).getListener();531}532return listener;533}534}535}536537538