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