001/* 002 * $Id: AbstractBean.java 4088 2011-11-17 19:53:49Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022package org.jdesktop.beans; 023 024import java.beans.PropertyChangeEvent; 025import java.beans.PropertyChangeListener; 026import java.beans.PropertyChangeSupport; 027import java.beans.PropertyVetoException; 028import java.beans.VetoableChangeListener; 029import java.beans.VetoableChangeSupport; 030 031/** 032 * <p> 033 * A convenience class from which to extend all non-visual AbstractBeans. It 034 * manages the PropertyChange notification system, making it relatively trivial 035 * to add support for property change events in getters/setters. 036 * </p> 037 * 038 * <p> 039 * A non-visual java bean is a Java class that conforms to the AbstractBean 040 * patterns to allow visual manipulation of the bean's properties and event 041 * handlers at design-time. 042 * </p> 043 * 044 * <p> 045 * Here is a simple example bean that contains one property, foo, and the proper 046 * pattern for implementing property change notification: 047 * 048 * <pre><code> 049 * public class ABean extends AbstractBean { 050 * private String foo; 051 * 052 * public void setFoo(String newFoo) { 053 * String old = getFoo(); 054 * this.foo = newFoo; 055 * firePropertyChange("foo", old, getFoo()); 056 * } 057 * 058 * public String getFoo() { 059 * return foo; 060 * } 061 * } 062 * </code></pre> 063 * 064 * </p> 065 * 066 * <p> 067 * You will notice that "getFoo()" is used in the setFoo method rather than 068 * accessing "foo" directly for the gets. This is done intentionally so that if 069 * a subclass overrides getFoo() to return, for instance, a constant value the 070 * property change notification system will continue to work properly. 071 * </p> 072 * 073 * <p> 074 * The firePropertyChange method takes into account the old value and the new 075 * value. Only if the two differ will it fire a property change event. So you 076 * can be assured from the above code fragment that a property change event will 077 * only occur if old is indeed different from getFoo() 078 * </p> 079 * 080 * <p> 081 * <code>AbstractBean</code> also supports vetoable 082 * {@link PropertyChangeEvent} events. These events are similar to 083 * <code>PropertyChange</code> events, except a special exception can be used 084 * to veto changing the property. For example, perhaps the property is changing 085 * from "fred" to "red", but a listener deems that "red" is unexceptable. In 086 * this case, the listener can fire a veto exception and the property must 087 * remain "fred". For example: 088 * 089 * <pre><code> 090 * public class ABean extends AbstractBean { 091 * private String foo; 092 * 093 * public void setFoo(String newFoo) throws PropertyVetoException { 094 * String old = getFoo(); 095 * this.foo = newFoo; 096 * fireVetoableChange("foo", old, getFoo()); 097 * } 098 * public String getFoo() { 099 * return foo; 100 * } 101 * } 102 * 103 * public class Tester { 104 * public static void main(String... args) { 105 * try { 106 * ABean a = new ABean(); 107 * a.setFoo("fred"); 108 * a.addVetoableChangeListener(new VetoableChangeListener() { 109 * public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { 110 * if ("red".equals(evt.getNewValue()) { 111 * throw new PropertyVetoException("Cannot be red!", evt); 112 * } 113 * } 114 * } 115 * a.setFoo("red"); 116 * } catch (Exception e) { 117 * e.printStackTrace(); // this will be executed 118 * } 119 * } 120 * } 121 * </code></pre> 122 * 123 * </p> 124 * <p> 125 * {@code AbstractBean} is not {@link java.io.Serializable}. Special care must 126 * be taken when creating {@code Serializable} subclasses, as the 127 * {@code Serializable} listeners will not be saved. Subclasses will need to 128 * manually save the serializable listeners. The {@link AbstractSerializableBean} 129 * is {@code Serializable} and already handles the listeners correctly. If 130 * possible, it is recommended that {@code Serializable} beans should extend 131 * {@code AbstractSerializableBean}. If it is not possible, the 132 * {@code AbstractSerializableBean} bean implementation provides details on 133 * how to correctly serialize an {@code AbstractBean} subclass. 134 * </p> 135 * 136 * @see AbstractSerializableBean 137 * @status REVIEWED 138 * @author rbair 139 */ 140@SuppressWarnings("nls") 141public abstract class AbstractBean { 142 /** 143 * Helper class that manages all the property change notification machinery. 144 * PropertyChangeSupport cannot be extended directly because it requires 145 * a bean in the constructor, and the "this" argument is not valid until 146 * after super construction. Hence, delegation instead of extension 147 */ 148 private transient PropertyChangeSupport pcs; 149 150 /** 151 * Helper class that manages all the veto property change notification machinery. 152 */ 153 private transient VetoableChangeSupport vcs; 154 155 /** Creates a new instance of AbstractBean */ 156 protected AbstractBean() { 157 pcs = new PropertyChangeSupport(this); 158 vcs = new VetoableChangeSupport(this); 159 } 160 161 /** 162 * Creates a new instance of AbstractBean, using the supplied PropertyChangeSupport and 163 * VetoableChangeSupport delegates. Neither of these may be null. 164 */ 165 protected AbstractBean(PropertyChangeSupport pcs, VetoableChangeSupport vcs) { 166 if (pcs == null) { 167 throw new NullPointerException("PropertyChangeSupport must not be null"); 168 } 169 if (vcs == null) { 170 throw new NullPointerException("VetoableChangeSupport must not be null"); 171 } 172 173 this.pcs = pcs; 174 this.vcs = vcs; 175 } 176 177 /** 178 * Add a PropertyChangeListener to the listener list. 179 * The listener is registered for all properties. 180 * The same listener object may be added more than once, and will be called 181 * as many times as it is added. 182 * If <code>listener</code> is null, no exception is thrown and no action 183 * is taken. 184 * 185 * @param listener The PropertyChangeListener to be added 186 */ 187 public final void addPropertyChangeListener(PropertyChangeListener listener) { 188 pcs.addPropertyChangeListener(listener); 189 } 190 191 /** 192 * Remove a PropertyChangeListener from the listener list. 193 * This removes a PropertyChangeListener that was registered 194 * for all properties. 195 * If <code>listener</code> was added more than once to the same event 196 * source, it will be notified one less time after being removed. 197 * If <code>listener</code> is null, or was never added, no exception is 198 * thrown and no action is taken. 199 * 200 * @param listener The PropertyChangeListener to be removed 201 */ 202 public final void removePropertyChangeListener(PropertyChangeListener listener) { 203 pcs.removePropertyChangeListener(listener); 204 } 205 206 /** 207 * Returns an array of all the listeners that were added to the 208 * PropertyChangeSupport object with addPropertyChangeListener(). 209 * <p> 210 * If some listeners have been added with a named property, then 211 * the returned array will be a mixture of PropertyChangeListeners 212 * and <code>PropertyChangeListenerProxy</code>s. If the calling 213 * method is interested in distinguishing the listeners then it must 214 * test each element to see if it's a 215 * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine 216 * the parameter. 217 * 218 * <pre> 219 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners(); 220 * for (int i = 0; i < listeners.length; i++) { 221 * if (listeners[i] instanceof PropertyChangeListenerProxy) { 222 * PropertyChangeListenerProxy proxy = 223 * (PropertyChangeListenerProxy)listeners[i]; 224 * if (proxy.getPropertyName().equals("foo")) { 225 * // proxy is a PropertyChangeListener which was associated 226 * // with the property named "foo" 227 * } 228 * } 229 * } 230 *</pre> 231 * 232 * @see java.beans.PropertyChangeListenerProxy 233 * @return all of the <code>PropertyChangeListeners</code> added or an 234 * empty array if no listeners have been added 235 */ 236 public final PropertyChangeListener[] getPropertyChangeListeners() { 237 return pcs.getPropertyChangeListeners(); 238 } 239 240 /** 241 * Add a PropertyChangeListener for a specific property. The listener 242 * will be invoked only when a call on firePropertyChange names that 243 * specific property. 244 * The same listener object may be added more than once. For each 245 * property, the listener will be invoked the number of times it was added 246 * for that property. 247 * If <code>propertyName</code> or <code>listener</code> is null, no 248 * exception is thrown and no action is taken. 249 * 250 * @param propertyName The name of the property to listen on. 251 * @param listener The PropertyChangeListener to be added 252 */ 253 public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 254 pcs.addPropertyChangeListener(propertyName, listener); 255 } 256 257 /** 258 * Remove a PropertyChangeListener for a specific property. 259 * If <code>listener</code> was added more than once to the same event 260 * source for the specified property, it will be notified one less time 261 * after being removed. 262 * If <code>propertyName</code> is null, no exception is thrown and no 263 * action is taken. 264 * If <code>listener</code> is null, or was never added for the specified 265 * property, no exception is thrown and no action is taken. 266 * 267 * @param propertyName The name of the property that was listened on. 268 * @param listener The PropertyChangeListener to be removed 269 */ 270 public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { 271 pcs.removePropertyChangeListener(propertyName, listener); 272 } 273 274 /** 275 * Returns an array of all the listeners which have been associated 276 * with the named property. 277 * 278 * @param propertyName The name of the property being listened to 279 * @return all of the <code>PropertyChangeListeners</code> associated with 280 * the named property. If no such listeners have been added, 281 * or if <code>propertyName</code> is null, an empty array is 282 * returned. 283 */ 284 public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 285 return pcs.getPropertyChangeListeners(propertyName); 286 } 287 288 /** 289 * Report a bound property update to any registered listeners. 290 * No event is fired if old and new are equal and non-null. 291 * 292 * <p> 293 * This is merely a convenience wrapper around the more general 294 * firePropertyChange method that takes {@code 295 * PropertyChangeEvent} value. 296 * 297 * @param propertyName The programmatic name of the property 298 * that was changed. 299 * @param oldValue The old value of the property. 300 * @param newValue The new value of the property. 301 */ 302 protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 303 pcs.firePropertyChange(propertyName, oldValue, newValue); 304 } 305 306 /** 307 * Fire an existing PropertyChangeEvent to any registered listeners. 308 * No event is fired if the given event's old and new values are 309 * equal and non-null. 310 * @param evt The PropertyChangeEvent object. 311 */ 312 protected final void firePropertyChange(PropertyChangeEvent evt) { 313 pcs.firePropertyChange(evt); 314 } 315 316 317 /** 318 * Report a bound indexed property update to any registered 319 * listeners. 320 * <p> 321 * No event is fired if old and new values are equal 322 * and non-null. 323 * 324 * <p> 325 * This is merely a convenience wrapper around the more general 326 * firePropertyChange method that takes {@code PropertyChangeEvent} value. 327 * 328 * @param propertyName The programmatic name of the property that 329 * was changed. 330 * @param index index of the property element that was changed. 331 * @param oldValue The old value of the property. 332 * @param newValue The new value of the property. 333 */ 334 protected final void fireIndexedPropertyChange(String propertyName, int index, 335 Object oldValue, Object newValue) { 336 pcs.fireIndexedPropertyChange(propertyName, index, oldValue, newValue); 337 } 338 339 /** 340 * Check if there are any listeners for a specific property, including 341 * those registered on all properties. If <code>propertyName</code> 342 * is null, only check for listeners registered on all properties. 343 * 344 * @param propertyName the property name. 345 * @return true if there are one or more listeners for the given property 346 */ 347 protected final boolean hasPropertyChangeListeners(String propertyName) { 348 return pcs.hasListeners(propertyName); 349 } 350 351 /** 352 * Check if there are any listeners for a specific property, including 353 * those registered on all properties. If <code>propertyName</code> 354 * is null, only check for listeners registered on all properties. 355 * 356 * @param propertyName the property name. 357 * @return true if there are one or more listeners for the given property 358 */ 359 protected final boolean hasVetoableChangeListeners(String propertyName) { 360 return vcs.hasListeners(propertyName); 361 } 362 363 /** 364 * Add a VetoableListener to the listener list. 365 * The listener is registered for all properties. 366 * The same listener object may be added more than once, and will be called 367 * as many times as it is added. 368 * If <code>listener</code> is null, no exception is thrown and no action 369 * is taken. 370 * 371 * @param listener The VetoableChangeListener to be added 372 */ 373 374 public final void addVetoableChangeListener(VetoableChangeListener listener) { 375 vcs.addVetoableChangeListener(listener); 376 } 377 378 /** 379 * Remove a VetoableChangeListener from the listener list. 380 * This removes a VetoableChangeListener that was registered 381 * for all properties. 382 * If <code>listener</code> was added more than once to the same event 383 * source, it will be notified one less time after being removed. 384 * If <code>listener</code> is null, or was never added, no exception is 385 * thrown and no action is taken. 386 * 387 * @param listener The VetoableChangeListener to be removed 388 */ 389 public final void removeVetoableChangeListener(VetoableChangeListener listener) { 390 vcs.removeVetoableChangeListener(listener); 391 } 392 393 /** 394 * Returns the list of VetoableChangeListeners. If named vetoable change listeners 395 * were added, then VetoableChangeListenerProxy wrappers will returned 396 * <p> 397 * @return List of VetoableChangeListeners and VetoableChangeListenerProxys 398 * if named property change listeners were added. 399 */ 400 public final VetoableChangeListener[] getVetoableChangeListeners(){ 401 return vcs.getVetoableChangeListeners(); 402 } 403 404 /** 405 * Add a VetoableChangeListener for a specific property. The listener 406 * will be invoked only when a call on fireVetoableChange names that 407 * specific property. 408 * The same listener object may be added more than once. For each 409 * property, the listener will be invoked the number of times it was added 410 * for that property. 411 * If <code>propertyName</code> or <code>listener</code> is null, no 412 * exception is thrown and no action is taken. 413 * 414 * @param propertyName The name of the property to listen on. 415 * @param listener The VetoableChangeListener to be added 416 */ 417 418 public final void addVetoableChangeListener(String propertyName, 419 VetoableChangeListener listener) { 420 vcs.addVetoableChangeListener(propertyName, listener); 421 } 422 423 /** 424 * Remove a VetoableChangeListener for a specific property. 425 * If <code>listener</code> was added more than once to the same event 426 * source for the specified property, it will be notified one less time 427 * after being removed. 428 * If <code>propertyName</code> is null, no exception is thrown and no 429 * action is taken. 430 * If <code>listener</code> is null, or was never added for the specified 431 * property, no exception is thrown and no action is taken. 432 * 433 * @param propertyName The name of the property that was listened on. 434 * @param listener The VetoableChangeListener to be removed 435 */ 436 437 public final void removeVetoableChangeListener(String propertyName, 438 VetoableChangeListener listener) { 439 vcs.removeVetoableChangeListener(propertyName, listener); 440 } 441 442 /** 443 * Returns an array of all the listeners which have been associated 444 * with the named property. 445 * 446 * @param propertyName The name of the property being listened to 447 * @return all the <code>VetoableChangeListeners</code> associated with 448 * the named property. If no such listeners have been added, 449 * or if <code>propertyName</code> is null, an empty array is 450 * returned. 451 */ 452 public final VetoableChangeListener[] getVetoableChangeListeners(String propertyName) { 453 return vcs.getVetoableChangeListeners(propertyName); 454 } 455 456 /** 457 * Report a vetoable property update to any registered listeners. If 458 * anyone vetos the change, then fire a new event reverting everyone to 459 * the old value and then rethrow the PropertyVetoException. 460 * <p> 461 * No event is fired if old and new are equal and non-null. 462 * 463 * @param propertyName The programmatic name of the property 464 * that is about to change.. 465 * @param oldValue The old value of the property. 466 * @param newValue The new value of the property. 467 * @exception PropertyVetoException if the recipient wishes the property 468 * change to be rolled back. 469 */ 470 protected final void fireVetoableChange(String propertyName, 471 Object oldValue, Object newValue) 472 throws PropertyVetoException { 473 vcs.fireVetoableChange(propertyName, oldValue, newValue); 474 } 475 476 /** 477 * Fire a vetoable property update to any registered listeners. If 478 * anyone vetos the change, then fire a new event reverting everyone to 479 * the old value and then rethrow the PropertyVetoException. 480 * <p> 481 * No event is fired if old and new are equal and non-null. 482 * 483 * @param evt The PropertyChangeEvent to be fired. 484 * @exception PropertyVetoException if the recipient wishes the property 485 * change to be rolled back. 486 */ 487 protected final void fireVetoableChange(PropertyChangeEvent evt) 488 throws PropertyVetoException { 489 vcs.fireVetoableChange(evt); 490 } 491 492 /** 493 * {@inheritDoc} 494 */ 495 @Override 496 public Object clone() throws CloneNotSupportedException { 497 AbstractBean result = (AbstractBean) super.clone(); 498 result.pcs = new PropertyChangeSupport(result); 499 result.vcs = new VetoableChangeSupport(result); 500 return result; 501 } 502}