1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/rt/emul/compact/src/main/java/java/beans/VetoableChangeSupport.java Tue Feb 26 16:54:16 2013 +0100
1.3 @@ -0,0 +1,526 @@
1.4 +/*
1.5 + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
1.7 + *
1.8 + * This code is free software; you can redistribute it and/or modify it
1.9 + * under the terms of the GNU General Public License version 2 only, as
1.10 + * published by the Free Software Foundation. Oracle designates this
1.11 + * particular file as subject to the "Classpath" exception as provided
1.12 + * by Oracle in the LICENSE file that accompanied this code.
1.13 + *
1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1.17 + * version 2 for more details (a copy is included in the LICENSE file that
1.18 + * accompanied this code).
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License version
1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1.23 + *
1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
1.25 + * or visit www.oracle.com if you need additional information or have any
1.26 + * questions.
1.27 + */
1.28 +package java.beans;
1.29 +
1.30 +import java.io.Serializable;
1.31 +import java.io.ObjectStreamField;
1.32 +import java.io.ObjectOutputStream;
1.33 +import java.io.ObjectInputStream;
1.34 +import java.io.IOException;
1.35 +import java.util.Hashtable;
1.36 +import java.util.Map.Entry;
1.37 +import org.apidesign.bck2brwsr.emul.lang.System;
1.38 +
1.39 +/**
1.40 + * This is a utility class that can be used by beans that support constrained
1.41 + * properties. It manages a list of listeners and dispatches
1.42 + * {@link PropertyChangeEvent}s to them. You can use an instance of this class
1.43 + * as a member field of your bean and delegate these types of work to it.
1.44 + * The {@link VetoableChangeListener} can be registered for all properties
1.45 + * or for a property specified by name.
1.46 + * <p>
1.47 + * Here is an example of {@code VetoableChangeSupport} usage that follows
1.48 + * the rules and recommendations laid out in the JavaBeans™ specification:
1.49 + * <pre>
1.50 + * public class MyBean {
1.51 + * private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);
1.52 + *
1.53 + * public void addVetoableChangeListener(VetoableChangeListener listener) {
1.54 + * this.vcs.addVetoableChangeListener(listener);
1.55 + * }
1.56 + *
1.57 + * public void removeVetoableChangeListener(VetoableChangeListener listener) {
1.58 + * this.vcs.removeVetoableChangeListener(listener);
1.59 + * }
1.60 + *
1.61 + * private String value;
1.62 + *
1.63 + * public String getValue() {
1.64 + * return this.value;
1.65 + * }
1.66 + *
1.67 + * public void setValue(String newValue) throws PropertyVetoException {
1.68 + * String oldValue = this.value;
1.69 + * this.vcs.fireVetoableChange("value", oldValue, newValue);
1.70 + * this.value = newValue;
1.71 + * }
1.72 + *
1.73 + * [...]
1.74 + * }
1.75 + * </pre>
1.76 + * <p>
1.77 + * A {@code VetoableChangeSupport} instance is thread-safe.
1.78 + * <p>
1.79 + * This class is serializable. When it is serialized it will save
1.80 + * (and restore) any listeners that are themselves serializable. Any
1.81 + * non-serializable listeners will be skipped during serialization.
1.82 + *
1.83 + * @see PropertyChangeSupport
1.84 + */
1.85 +public class VetoableChangeSupport implements Serializable {
1.86 + private VetoableChangeListenerMap map = new VetoableChangeListenerMap();
1.87 +
1.88 + /**
1.89 + * Constructs a <code>VetoableChangeSupport</code> object.
1.90 + *
1.91 + * @param sourceBean The bean to be given as the source for any events.
1.92 + */
1.93 + public VetoableChangeSupport(Object sourceBean) {
1.94 + if (sourceBean == null) {
1.95 + throw new NullPointerException();
1.96 + }
1.97 + source = sourceBean;
1.98 + }
1.99 +
1.100 + /**
1.101 + * Add a VetoableChangeListener to the listener list.
1.102 + * The listener is registered for all properties.
1.103 + * The same listener object may be added more than once, and will be called
1.104 + * as many times as it is added.
1.105 + * If <code>listener</code> is null, no exception is thrown and no action
1.106 + * is taken.
1.107 + *
1.108 + * @param listener The VetoableChangeListener to be added
1.109 + */
1.110 + public void addVetoableChangeListener(VetoableChangeListener listener) {
1.111 + if (listener == null) {
1.112 + return;
1.113 + }
1.114 + if (listener instanceof VetoableChangeListenerProxy) {
1.115 + VetoableChangeListenerProxy proxy =
1.116 + (VetoableChangeListenerProxy)listener;
1.117 + // Call two argument add method.
1.118 + addVetoableChangeListener(proxy.getPropertyName(),
1.119 + proxy.getListener());
1.120 + } else {
1.121 + this.map.add(null, listener);
1.122 + }
1.123 + }
1.124 +
1.125 + /**
1.126 + * Remove a VetoableChangeListener from the listener list.
1.127 + * This removes a VetoableChangeListener that was registered
1.128 + * for all properties.
1.129 + * If <code>listener</code> was added more than once to the same event
1.130 + * source, it will be notified one less time after being removed.
1.131 + * If <code>listener</code> is null, or was never added, no exception is
1.132 + * thrown and no action is taken.
1.133 + *
1.134 + * @param listener The VetoableChangeListener to be removed
1.135 + */
1.136 + public void removeVetoableChangeListener(VetoableChangeListener listener) {
1.137 + if (listener == null) {
1.138 + return;
1.139 + }
1.140 + if (listener instanceof VetoableChangeListenerProxy) {
1.141 + VetoableChangeListenerProxy proxy =
1.142 + (VetoableChangeListenerProxy)listener;
1.143 + // Call two argument remove method.
1.144 + removeVetoableChangeListener(proxy.getPropertyName(),
1.145 + proxy.getListener());
1.146 + } else {
1.147 + this.map.remove(null, listener);
1.148 + }
1.149 + }
1.150 +
1.151 + /**
1.152 + * Returns an array of all the listeners that were added to the
1.153 + * VetoableChangeSupport object with addVetoableChangeListener().
1.154 + * <p>
1.155 + * If some listeners have been added with a named property, then
1.156 + * the returned array will be a mixture of VetoableChangeListeners
1.157 + * and <code>VetoableChangeListenerProxy</code>s. If the calling
1.158 + * method is interested in distinguishing the listeners then it must
1.159 + * test each element to see if it's a
1.160 + * <code>VetoableChangeListenerProxy</code>, perform the cast, and examine
1.161 + * the parameter.
1.162 + *
1.163 + * <pre>
1.164 + * VetoableChangeListener[] listeners = bean.getVetoableChangeListeners();
1.165 + * for (int i = 0; i < listeners.length; i++) {
1.166 + * if (listeners[i] instanceof VetoableChangeListenerProxy) {
1.167 + * VetoableChangeListenerProxy proxy =
1.168 + * (VetoableChangeListenerProxy)listeners[i];
1.169 + * if (proxy.getPropertyName().equals("foo")) {
1.170 + * // proxy is a VetoableChangeListener which was associated
1.171 + * // with the property named "foo"
1.172 + * }
1.173 + * }
1.174 + * }
1.175 + *</pre>
1.176 + *
1.177 + * @see VetoableChangeListenerProxy
1.178 + * @return all of the <code>VetoableChangeListeners</code> added or an
1.179 + * empty array if no listeners have been added
1.180 + * @since 1.4
1.181 + */
1.182 + public VetoableChangeListener[] getVetoableChangeListeners(){
1.183 + return this.map.getListeners();
1.184 + }
1.185 +
1.186 + /**
1.187 + * Add a VetoableChangeListener for a specific property. The listener
1.188 + * will be invoked only when a call on fireVetoableChange names that
1.189 + * specific property.
1.190 + * The same listener object may be added more than once. For each
1.191 + * property, the listener will be invoked the number of times it was added
1.192 + * for that property.
1.193 + * If <code>propertyName</code> or <code>listener</code> is null, no
1.194 + * exception is thrown and no action is taken.
1.195 + *
1.196 + * @param propertyName The name of the property to listen on.
1.197 + * @param listener The VetoableChangeListener to be added
1.198 + */
1.199 + public void addVetoableChangeListener(
1.200 + String propertyName,
1.201 + VetoableChangeListener listener) {
1.202 + if (listener == null || propertyName == null) {
1.203 + return;
1.204 + }
1.205 + listener = this.map.extract(listener);
1.206 + if (listener != null) {
1.207 + this.map.add(propertyName, listener);
1.208 + }
1.209 + }
1.210 +
1.211 + /**
1.212 + * Remove a VetoableChangeListener for a specific property.
1.213 + * If <code>listener</code> was added more than once to the same event
1.214 + * source for the specified property, it will be notified one less time
1.215 + * after being removed.
1.216 + * If <code>propertyName</code> is null, no exception is thrown and no
1.217 + * action is taken.
1.218 + * If <code>listener</code> is null, or was never added for the specified
1.219 + * property, no exception is thrown and no action is taken.
1.220 + *
1.221 + * @param propertyName The name of the property that was listened on.
1.222 + * @param listener The VetoableChangeListener to be removed
1.223 + */
1.224 + public void removeVetoableChangeListener(
1.225 + String propertyName,
1.226 + VetoableChangeListener listener) {
1.227 + if (listener == null || propertyName == null) {
1.228 + return;
1.229 + }
1.230 + listener = this.map.extract(listener);
1.231 + if (listener != null) {
1.232 + this.map.remove(propertyName, listener);
1.233 + }
1.234 + }
1.235 +
1.236 + /**
1.237 + * Returns an array of all the listeners which have been associated
1.238 + * with the named property.
1.239 + *
1.240 + * @param propertyName The name of the property being listened to
1.241 + * @return all the <code>VetoableChangeListeners</code> associated with
1.242 + * the named property. If no such listeners have been added,
1.243 + * or if <code>propertyName</code> is null, an empty array is
1.244 + * returned.
1.245 + * @since 1.4
1.246 + */
1.247 + public VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {
1.248 + return this.map.getListeners(propertyName);
1.249 + }
1.250 +
1.251 + /**
1.252 + * Reports a constrained property update to listeners
1.253 + * that have been registered to track updates of
1.254 + * all properties or a property with the specified name.
1.255 + * <p>
1.256 + * Any listener can throw a {@code PropertyVetoException} to veto the update.
1.257 + * If one of the listeners vetoes the update, this method passes
1.258 + * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
1.259 + * to all listeners that already confirmed this update
1.260 + * and throws the {@code PropertyVetoException} again.
1.261 + * <p>
1.262 + * No event is fired if old and new values are equal and non-null.
1.263 + * <p>
1.264 + * This is merely a convenience wrapper around the more general
1.265 + * {@link #fireVetoableChange(PropertyChangeEvent)} method.
1.266 + *
1.267 + * @param propertyName the programmatic name of the property that is about to change
1.268 + * @param oldValue the old value of the property
1.269 + * @param newValue the new value of the property
1.270 + * @throws PropertyVetoException if one of listeners vetoes the property update
1.271 + */
1.272 + public void fireVetoableChange(String propertyName, Object oldValue, Object newValue)
1.273 + throws PropertyVetoException {
1.274 + if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
1.275 + fireVetoableChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue));
1.276 + }
1.277 + }
1.278 +
1.279 + /**
1.280 + * Reports an integer constrained property update to listeners
1.281 + * that have been registered to track updates of
1.282 + * all properties or a property with the specified name.
1.283 + * <p>
1.284 + * Any listener can throw a {@code PropertyVetoException} to veto the update.
1.285 + * If one of the listeners vetoes the update, this method passes
1.286 + * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
1.287 + * to all listeners that already confirmed this update
1.288 + * and throws the {@code PropertyVetoException} again.
1.289 + * <p>
1.290 + * No event is fired if old and new values are equal.
1.291 + * <p>
1.292 + * This is merely a convenience wrapper around the more general
1.293 + * {@link #fireVetoableChange(String, Object, Object)} method.
1.294 + *
1.295 + * @param propertyName the programmatic name of the property that is about to change
1.296 + * @param oldValue the old value of the property
1.297 + * @param newValue the new value of the property
1.298 + * @throws PropertyVetoException if one of listeners vetoes the property update
1.299 + */
1.300 + public void fireVetoableChange(String propertyName, int oldValue, int newValue)
1.301 + throws PropertyVetoException {
1.302 + if (oldValue != newValue) {
1.303 + fireVetoableChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
1.304 + }
1.305 + }
1.306 +
1.307 + /**
1.308 + * Reports a boolean constrained property update to listeners
1.309 + * that have been registered to track updates of
1.310 + * all properties or a property with the specified name.
1.311 + * <p>
1.312 + * Any listener can throw a {@code PropertyVetoException} to veto the update.
1.313 + * If one of the listeners vetoes the update, this method passes
1.314 + * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
1.315 + * to all listeners that already confirmed this update
1.316 + * and throws the {@code PropertyVetoException} again.
1.317 + * <p>
1.318 + * No event is fired if old and new values are equal.
1.319 + * <p>
1.320 + * This is merely a convenience wrapper around the more general
1.321 + * {@link #fireVetoableChange(String, Object, Object)} method.
1.322 + *
1.323 + * @param propertyName the programmatic name of the property that is about to change
1.324 + * @param oldValue the old value of the property
1.325 + * @param newValue the new value of the property
1.326 + * @throws PropertyVetoException if one of listeners vetoes the property update
1.327 + */
1.328 + public void fireVetoableChange(String propertyName, boolean oldValue, boolean newValue)
1.329 + throws PropertyVetoException {
1.330 + if (oldValue != newValue) {
1.331 + fireVetoableChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
1.332 + }
1.333 + }
1.334 +
1.335 + /**
1.336 + * Fires a property change event to listeners
1.337 + * that have been registered to track updates of
1.338 + * all properties or a property with the specified name.
1.339 + * <p>
1.340 + * Any listener can throw a {@code PropertyVetoException} to veto the update.
1.341 + * If one of the listeners vetoes the update, this method passes
1.342 + * a new "undo" {@code PropertyChangeEvent} that reverts to the old value
1.343 + * to all listeners that already confirmed this update
1.344 + * and throws the {@code PropertyVetoException} again.
1.345 + * <p>
1.346 + * No event is fired if the given event's old and new values are equal and non-null.
1.347 + *
1.348 + * @param event the {@code PropertyChangeEvent} to be fired
1.349 + * @throws PropertyVetoException if one of listeners vetoes the property update
1.350 + */
1.351 + public void fireVetoableChange(PropertyChangeEvent event)
1.352 + throws PropertyVetoException {
1.353 + Object oldValue = event.getOldValue();
1.354 + Object newValue = event.getNewValue();
1.355 + if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
1.356 + String name = event.getPropertyName();
1.357 +
1.358 + VetoableChangeListener[] common = this.map.get(null);
1.359 + VetoableChangeListener[] named = (name != null)
1.360 + ? this.map.get(name)
1.361 + : null;
1.362 +
1.363 + VetoableChangeListener[] listeners;
1.364 + if (common == null) {
1.365 + listeners = named;
1.366 + }
1.367 + else if (named == null) {
1.368 + listeners = common;
1.369 + }
1.370 + else {
1.371 + listeners = new VetoableChangeListener[common.length + named.length];
1.372 + System.arraycopy(common, 0, listeners, 0, common.length);
1.373 + System.arraycopy(named, 0, listeners, common.length, named.length);
1.374 + }
1.375 + if (listeners != null) {
1.376 + int current = 0;
1.377 + try {
1.378 + while (current < listeners.length) {
1.379 + listeners[current].vetoableChange(event);
1.380 + current++;
1.381 + }
1.382 + }
1.383 + catch (PropertyVetoException veto) {
1.384 + event = new PropertyChangeEvent(this.source, name, newValue, oldValue);
1.385 + for (int i = 0; i < current; i++) {
1.386 + try {
1.387 + listeners[i].vetoableChange(event);
1.388 + }
1.389 + catch (PropertyVetoException exception) {
1.390 + // ignore exceptions that occur during rolling back
1.391 + }
1.392 + }
1.393 + throw veto; // rethrow the veto exception
1.394 + }
1.395 + }
1.396 + }
1.397 + }
1.398 +
1.399 + /**
1.400 + * Check if there are any listeners for a specific property, including
1.401 + * those registered on all properties. If <code>propertyName</code>
1.402 + * is null, only check for listeners registered on all properties.
1.403 + *
1.404 + * @param propertyName the property name.
1.405 + * @return true if there are one or more listeners for the given property
1.406 + */
1.407 + public boolean hasListeners(String propertyName) {
1.408 + return this.map.hasListeners(propertyName);
1.409 + }
1.410 +
1.411 + /**
1.412 + * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
1.413 + * <p>
1.414 + * At serialization time we skip non-serializable listeners and
1.415 + * only serialize the serializable listeners.
1.416 + */
1.417 + private void writeObject(ObjectOutputStream s) throws IOException {
1.418 + Hashtable<String, VetoableChangeSupport> children = null;
1.419 + VetoableChangeListener[] listeners = null;
1.420 + synchronized (this.map) {
1.421 + for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) {
1.422 + String property = entry.getKey();
1.423 + if (property == null) {
1.424 + listeners = entry.getValue();
1.425 + } else {
1.426 + if (children == null) {
1.427 + children = new Hashtable<String, VetoableChangeSupport>();
1.428 + }
1.429 + VetoableChangeSupport vcs = new VetoableChangeSupport(this.source);
1.430 + vcs.map.set(null, entry.getValue());
1.431 + children.put(property, vcs);
1.432 + }
1.433 + }
1.434 + }
1.435 + ObjectOutputStream.PutField fields = s.putFields();
1.436 + fields.put("children", children);
1.437 + fields.put("source", this.source);
1.438 + fields.put("vetoableChangeSupportSerializedDataVersion", 2);
1.439 + s.writeFields();
1.440 +
1.441 + if (listeners != null) {
1.442 + for (VetoableChangeListener l : listeners) {
1.443 + if (l instanceof Serializable) {
1.444 + s.writeObject(l);
1.445 + }
1.446 + }
1.447 + }
1.448 + s.writeObject(null);
1.449 + }
1.450 +
1.451 + private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
1.452 + this.map = new VetoableChangeListenerMap();
1.453 +
1.454 + ObjectInputStream.GetField fields = s.readFields();
1.455 +
1.456 + Hashtable<String, VetoableChangeSupport> children = (Hashtable<String, VetoableChangeSupport>) fields.get("children", null);
1.457 + this.source = fields.get("source", null);
1.458 + fields.get("vetoableChangeSupportSerializedDataVersion", 2);
1.459 +
1.460 + Object listenerOrNull;
1.461 + while (null != (listenerOrNull = s.readObject())) {
1.462 + this.map.add(null, (VetoableChangeListener)listenerOrNull);
1.463 + }
1.464 + if (children != null) {
1.465 + for (Entry<String, VetoableChangeSupport> entry : children.entrySet()) {
1.466 + for (VetoableChangeListener listener : entry.getValue().getVetoableChangeListeners()) {
1.467 + this.map.add(entry.getKey(), listener);
1.468 + }
1.469 + }
1.470 + }
1.471 + }
1.472 +
1.473 + /**
1.474 + * The object to be provided as the "source" for any generated events.
1.475 + */
1.476 + private Object source;
1.477 +
1.478 + /**
1.479 + * @serialField children Hashtable
1.480 + * @serialField source Object
1.481 + * @serialField vetoableChangeSupportSerializedDataVersion int
1.482 + */
1.483 + private static final ObjectStreamField[] serialPersistentFields = {
1.484 + new ObjectStreamField("children", Hashtable.class),
1.485 + new ObjectStreamField("source", Object.class),
1.486 + new ObjectStreamField("vetoableChangeSupportSerializedDataVersion", Integer.TYPE)
1.487 + };
1.488 +
1.489 + /**
1.490 + * Serialization version ID, so we're compatible with JDK 1.1
1.491 + */
1.492 + static final long serialVersionUID = -5090210921595982017L;
1.493 +
1.494 + /**
1.495 + * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
1.496 + * that works with {@link VetoableChangeListener VetoableChangeListener} objects.
1.497 + */
1.498 + private static final class VetoableChangeListenerMap extends ChangeListenerMap<VetoableChangeListener> {
1.499 + private static final VetoableChangeListener[] EMPTY = {};
1.500 +
1.501 + /**
1.502 + * Creates an array of {@link VetoableChangeListener VetoableChangeListener} objects.
1.503 + * This method uses the same instance of the empty array
1.504 + * when {@code length} equals {@code 0}.
1.505 + *
1.506 + * @param length the array length
1.507 + * @return an array with specified length
1.508 + */
1.509 + @Override
1.510 + protected VetoableChangeListener[] newArray(int length) {
1.511 + return (0 < length)
1.512 + ? new VetoableChangeListener[length]
1.513 + : EMPTY;
1.514 + }
1.515 +
1.516 + /**
1.517 + * Creates a {@link VetoableChangeListenerProxy VetoableChangeListenerProxy}
1.518 + * object for the specified property.
1.519 + *
1.520 + * @param name the name of the property to listen on
1.521 + * @param listener the listener to process events
1.522 + * @return a {@code VetoableChangeListenerProxy} object
1.523 + */
1.524 + @Override
1.525 + protected VetoableChangeListener newProxy(String name, VetoableChangeListener listener) {
1.526 + return new VetoableChangeListenerProxy(name, listener);
1.527 + }
1.528 + }
1.529 +}