1 /* 2 * Copyright (c) 1996, 2012, 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 bound 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 PropertyChangeListener} can be registered for all properties 41 * or for a property specified by name. 42 * <p> 43 * Here is an example of {@code PropertyChangeSupport} usage that follows 44 * the rules and recommendations laid out in the JavaBeans™ specification: 45 * <pre> 46 * public class MyBean { 47 * private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 48 * 49 * public void addPropertyChangeListener(PropertyChangeListener listener) { 50 * this.pcs.addPropertyChangeListener(listener); 51 * } 52 * 53 * public void removePropertyChangeListener(PropertyChangeListener listener) { 54 * this.pcs.removePropertyChangeListener(listener); 55 * } 56 * 57 * private String value; 58 * 59 * public String getValue() { 60 * return this.value; 61 * } 62 * 63 * public void setValue(String newValue) { 64 * String oldValue = this.value; 65 * this.value = newValue; 66 * this.pcs.firePropertyChange("value", oldValue, newValue); 67 * } 68 * 69 * [...] 70 * } 71 * </pre> 72 * <p> 73 * A {@code PropertyChangeSupport} 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 VetoableChangeSupport 80 */ 81 public class PropertyChangeSupport implements Serializable { 82 private PropertyChangeListenerMap map = new PropertyChangeListenerMap(); 83 84 /** 85 * Constructs a <code>PropertyChangeSupport</code> object. 86 * 87 * @param sourceBean The bean to be given as the source for any events. 88 */ PropertyChangeSupport(Object sourceBean)89 public PropertyChangeSupport(Object sourceBean) { 90 if (sourceBean == null) { 91 throw new NullPointerException(); 92 } 93 source = sourceBean; 94 } 95 96 /** 97 * Add a PropertyChangeListener 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 PropertyChangeListener to be added 105 */ addPropertyChangeListener(PropertyChangeListener listener)106 public void addPropertyChangeListener(PropertyChangeListener listener) { 107 if (listener == null) { 108 return; 109 } 110 if (listener instanceof PropertyChangeListenerProxy) { 111 PropertyChangeListenerProxy proxy = 112 (PropertyChangeListenerProxy)listener; 113 // Call two argument add method. 114 addPropertyChangeListener(proxy.getPropertyName(), 115 proxy.getListener()); 116 } else { 117 this.map.add(null, listener); 118 } 119 } 120 121 /** 122 * Remove a PropertyChangeListener from the listener list. 123 * This removes a PropertyChangeListener 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 PropertyChangeListener to be removed 131 */ removePropertyChangeListener(PropertyChangeListener listener)132 public void removePropertyChangeListener(PropertyChangeListener listener) { 133 if (listener == null) { 134 return; 135 } 136 if (listener instanceof PropertyChangeListenerProxy) { 137 PropertyChangeListenerProxy proxy = 138 (PropertyChangeListenerProxy)listener; 139 // Call two argument remove method. 140 removePropertyChangeListener(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 * PropertyChangeSupport object with addPropertyChangeListener(). 150 * <p> 151 * If some listeners have been added with a named property, then 152 * the returned array will be a mixture of PropertyChangeListeners 153 * and <code>PropertyChangeListenerProxy</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>PropertyChangeListenerProxy</code>, perform the cast, and examine 157 * the parameter. 158 * 159 * <pre> 160 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners(); 161 * for (int i = 0; i < listeners.length; i++) { 162 * if (listeners[i] instanceof PropertyChangeListenerProxy) { 163 * PropertyChangeListenerProxy proxy = 164 * (PropertyChangeListenerProxy)listeners[i]; 165 * if (proxy.getPropertyName().equals("foo")) { 166 * // proxy is a PropertyChangeListener which was associated 167 * // with the property named "foo" 168 * } 169 * } 170 * } 171 *</pre> 172 * 173 * @see PropertyChangeListenerProxy 174 * @return all of the <code>PropertyChangeListeners</code> added or an 175 * empty array if no listeners have been added 176 * @since 1.4 177 */ getPropertyChangeListeners()178 public PropertyChangeListener[] getPropertyChangeListeners() { 179 return this.map.getListeners(); 180 } 181 182 /** 183 * Add a PropertyChangeListener for a specific property. The listener 184 * will be invoked only when a call on firePropertyChange 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 PropertyChangeListener to be added 194 */ addPropertyChangeListener( String propertyName, PropertyChangeListener listener)195 public void addPropertyChangeListener( 196 String propertyName, 197 PropertyChangeListener 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 PropertyChangeListener 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 PropertyChangeListener to be removed 219 */ removePropertyChangeListener( String propertyName, PropertyChangeListener listener)220 public void removePropertyChangeListener( 221 String propertyName, 222 PropertyChangeListener 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 of the <code>PropertyChangeListeners</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 */ getPropertyChangeListeners(String propertyName)243 public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 244 return this.map.getListeners(propertyName); 245 } 246 247 /** 248 * Reports a bound 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 * No event is fired if old and new values are equal and non-null. 253 * <p> 254 * This is merely a convenience wrapper around the more general 255 * {@link #firePropertyChange(PropertyChangeEvent)} method. 256 * 257 * @param propertyName the programmatic name of the property that was changed 258 * @param oldValue the old value of the property 259 * @param newValue the new value of the property 260 */ firePropertyChange(String propertyName, Object oldValue, Object newValue)261 public void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 262 if (oldValue == null || newValue == null || !oldValue.equals(newValue)) { 263 firePropertyChange(new PropertyChangeEvent(this.source, propertyName, oldValue, newValue)); 264 } 265 } 266 267 /** 268 * Reports an integer bound property update to listeners 269 * that have been registered to track updates of 270 * all properties or a property with the specified name. 271 * <p> 272 * No event is fired if old and new values are equal. 273 * <p> 274 * This is merely a convenience wrapper around the more general 275 * {@link #firePropertyChange(String, Object, Object)} method. 276 * 277 * @param propertyName the programmatic name of the property that was changed 278 * @param oldValue the old value of the property 279 * @param newValue the new value of the property 280 */ firePropertyChange(String propertyName, int oldValue, int newValue)281 public void firePropertyChange(String propertyName, int oldValue, int newValue) { 282 if (oldValue != newValue) { 283 firePropertyChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue)); 284 } 285 } 286 287 /** 288 * Reports a boolean bound property update to listeners 289 * that have been registered to track updates of 290 * all properties or a property with the specified name. 291 * <p> 292 * No event is fired if old and new values are equal. 293 * <p> 294 * This is merely a convenience wrapper around the more general 295 * {@link #firePropertyChange(String, Object, Object)} method. 296 * 297 * @param propertyName the programmatic name of the property that was changed 298 * @param oldValue the old value of the property 299 * @param newValue the new value of the property 300 */ firePropertyChange(String propertyName, boolean oldValue, boolean newValue)301 public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { 302 if (oldValue != newValue) { 303 firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); 304 } 305 } 306 307 /** 308 * Fires a property change event to listeners 309 * that have been registered to track updates of 310 * all properties or a property with the specified name. 311 * <p> 312 * No event is fired if the given event's old and new values are equal and non-null. 313 * 314 * @param event the {@code PropertyChangeEvent} to be fired 315 */ firePropertyChange(PropertyChangeEvent event)316 public void firePropertyChange(PropertyChangeEvent event) { 317 Object oldValue = event.getOldValue(); 318 Object newValue = event.getNewValue(); 319 if (oldValue == null || newValue == null || !oldValue.equals(newValue)) { 320 String name = event.getPropertyName(); 321 322 PropertyChangeListener[] common = this.map.get(null); 323 PropertyChangeListener[] named = (name != null) 324 ? this.map.get(name) 325 : null; 326 327 fire(common, event); 328 fire(named, event); 329 } 330 } 331 fire(PropertyChangeListener[] listeners, PropertyChangeEvent event)332 private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) { 333 if (listeners != null) { 334 for (PropertyChangeListener listener : listeners) { 335 listener.propertyChange(event); 336 } 337 } 338 } 339 340 /** 341 * Reports a bound indexed property update to listeners 342 * that have been registered to track updates of 343 * all properties or a property with the specified name. 344 * <p> 345 * No event is fired if old and new values are equal and non-null. 346 * <p> 347 * This is merely a convenience wrapper around the more general 348 * {@link #firePropertyChange(PropertyChangeEvent)} method. 349 * 350 * @param propertyName the programmatic name of the property that was changed 351 * @param index the index of the property element that was changed 352 * @param oldValue the old value of the property 353 * @param newValue the new value of the property 354 * @since 1.5 355 */ fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue)356 public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) { 357 if (oldValue == null || newValue == null || !oldValue.equals(newValue)) { 358 firePropertyChange(new IndexedPropertyChangeEvent(source, propertyName, oldValue, newValue, index)); 359 } 360 } 361 362 /** 363 * Reports an integer bound indexed property update to listeners 364 * that have been registered to track updates of 365 * all properties or a property with the specified name. 366 * <p> 367 * No event is fired if old and new values are equal. 368 * <p> 369 * This is merely a convenience wrapper around the more general 370 * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method. 371 * 372 * @param propertyName the programmatic name of the property that was changed 373 * @param index the index of the property element that was changed 374 * @param oldValue the old value of the property 375 * @param newValue the new value of the property 376 * @since 1.5 377 */ fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue)378 public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue) { 379 if (oldValue != newValue) { 380 fireIndexedPropertyChange(propertyName, index, Integer.valueOf(oldValue), Integer.valueOf(newValue)); 381 } 382 } 383 384 /** 385 * Reports a boolean bound indexed property update to listeners 386 * that have been registered to track updates of 387 * all properties or a property with the specified name. 388 * <p> 389 * No event is fired if old and new values are equal. 390 * <p> 391 * This is merely a convenience wrapper around the more general 392 * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method. 393 * 394 * @param propertyName the programmatic name of the property that was changed 395 * @param index the index of the property element that was changed 396 * @param oldValue the old value of the property 397 * @param newValue the new value of the property 398 * @since 1.5 399 */ fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue)400 public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) { 401 if (oldValue != newValue) { 402 fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); 403 } 404 } 405 406 /** 407 * Check if there are any listeners for a specific property, including 408 * those registered on all properties. If <code>propertyName</code> 409 * is null, only check for listeners registered on all properties. 410 * 411 * @param propertyName the property name. 412 * @return true if there are one or more listeners for the given property 413 */ hasListeners(String propertyName)414 public boolean hasListeners(String propertyName) { 415 return this.map.hasListeners(propertyName); 416 } 417 418 /** 419 * @serialData Null terminated list of <code>PropertyChangeListeners</code>. 420 * <p> 421 * At serialization time we skip non-serializable listeners and 422 * only serialize the serializable listeners. 423 */ writeObject(ObjectOutputStream s)424 private void writeObject(ObjectOutputStream s) throws IOException { 425 Hashtable<String, PropertyChangeSupport> children = null; 426 PropertyChangeListener[] listeners = null; 427 synchronized (this.map) { 428 for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) { 429 String property = entry.getKey(); 430 if (property == null) { 431 listeners = entry.getValue(); 432 } else { 433 if (children == null) { 434 children = new Hashtable<String, PropertyChangeSupport>(); 435 } 436 PropertyChangeSupport pcs = new PropertyChangeSupport(this.source); 437 pcs.map.set(null, entry.getValue()); 438 children.put(property, pcs); 439 } 440 } 441 } 442 ObjectOutputStream.PutField fields = s.putFields(); 443 fields.put("children", children); 444 fields.put("source", this.source); 445 fields.put("propertyChangeSupportSerializedDataVersion", 2); 446 s.writeFields(); 447 448 if (listeners != null) { 449 for (PropertyChangeListener l : listeners) { 450 if (l instanceof Serializable) { 451 s.writeObject(l); 452 } 453 } 454 } 455 s.writeObject(null); 456 } 457 readObject(ObjectInputStream s)458 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { 459 this.map = new PropertyChangeListenerMap(); 460 461 ObjectInputStream.GetField fields = s.readFields(); 462 463 Hashtable<String, PropertyChangeSupport> children = (Hashtable<String, PropertyChangeSupport>) fields.get("children", null); 464 this.source = fields.get("source", null); 465 fields.get("propertyChangeSupportSerializedDataVersion", 2); 466 467 Object listenerOrNull; 468 while (null != (listenerOrNull = s.readObject())) { 469 this.map.add(null, (PropertyChangeListener)listenerOrNull); 470 } 471 if (children != null) { 472 for (Entry<String, PropertyChangeSupport> entry : children.entrySet()) { 473 for (PropertyChangeListener listener : entry.getValue().getPropertyChangeListeners()) { 474 this.map.add(entry.getKey(), listener); 475 } 476 } 477 } 478 } 479 480 /** 481 * The object to be provided as the "source" for any generated events. 482 */ 483 private Object source; 484 485 /** 486 * @serialField children Hashtable 487 * @serialField source Object 488 * @serialField propertyChangeSupportSerializedDataVersion int 489 */ 490 private static final ObjectStreamField[] serialPersistentFields = { 491 new ObjectStreamField("children", Hashtable.class), 492 new ObjectStreamField("source", Object.class), 493 new ObjectStreamField("propertyChangeSupportSerializedDataVersion", Integer.TYPE) 494 }; 495 496 /** 497 * Serialization version ID, so we're compatible with JDK 1.1 498 */ 499 static final long serialVersionUID = 6401253773779951803L; 500 501 /** 502 * This is a {@link ChangeListenerMap ChangeListenerMap} implementation 503 * that works with {@link PropertyChangeListener PropertyChangeListener} objects. 504 */ 505 private static final class PropertyChangeListenerMap extends ChangeListenerMap<PropertyChangeListener> { 506 private static final PropertyChangeListener[] EMPTY = {}; 507 508 /** 509 * Creates an array of {@link PropertyChangeListener PropertyChangeListener} objects. 510 * This method uses the same instance of the empty array 511 * when {@code length} equals {@code 0}. 512 * 513 * @param length the array length 514 * @return an array with specified length 515 */ 516 @Override newArray(int length)517 protected PropertyChangeListener[] newArray(int length) { 518 return (0 < length) 519 ? new PropertyChangeListener[length] 520 : EMPTY; 521 } 522 523 /** 524 * Creates a {@link PropertyChangeListenerProxy PropertyChangeListenerProxy} 525 * object for the specified property. 526 * 527 * @param name the name of the property to listen on 528 * @param listener the listener to process events 529 * @return a {@code PropertyChangeListenerProxy} object 530 */ 531 @Override newProxy(String name, PropertyChangeListener listener)532 protected PropertyChangeListener newProxy(String name, PropertyChangeListener listener) { 533 return new PropertyChangeListenerProxy(name, listener); 534 } 535 536 /** 537 * {@inheritDoc} 538 */ extract(PropertyChangeListener listener)539 public final PropertyChangeListener extract(PropertyChangeListener listener) { 540 while (listener instanceof PropertyChangeListenerProxy) { 541 listener = ((PropertyChangeListenerProxy) listener).getListener(); 542 } 543 return listener; 544 } 545 } 546 } 547