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 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 */ 82 public 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 }