001 /* 002 * $Id: AbstractBean.java 3100 2008-10-14 22:33:10Z rah003 $ 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 022 package org.jdesktop.beans; 023 024 import java.beans.PropertyChangeEvent; 025 import java.beans.PropertyChangeListener; 026 import java.beans.PropertyChangeSupport; 027 import java.beans.PropertyVetoException; 028 import java.beans.VetoableChangeListener; 029 import 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 public abstract class AbstractBean { 141 /** 142 * Helper class that manages all the property change notification machinery. 143 * PropertyChangeSupport cannot be extended directly because it requires 144 * a bean in the constructor, and the "this" argument is not valid until 145 * after super construction. Hence, delegation instead of extension 146 */ 147 private transient PropertyChangeSupport pcs; 148 149 /** 150 * Helper class that manages all the veto property change notification machinery. 151 */ 152 private transient VetoableChangeSupport vcs; 153 154 /** Creates a new instance of AbstractBean */ 155 protected AbstractBean() { 156 pcs = new PropertyChangeSupport(this); 157 vcs = new VetoableChangeSupport(this); 158 } 159 160 /** 161 * Creates a new instance of AbstractBean, using the supplied PropertyChangeSupport and 162 * VetoableChangeSupport delegates. Neither of these may be null. 163 */ 164 protected AbstractBean(PropertyChangeSupport pcs, VetoableChangeSupport vcs) { 165 if (pcs == null) { 166 throw new NullPointerException("PropertyChangeSupport must not be null"); 167 } 168 if (vcs == null) { 169 throw new NullPointerException("VetoableChangeSupport must not be null"); 170 } 171 172 this.pcs = pcs; 173 this.vcs = vcs; 174 } 175 176 /** 177 * Add a PropertyChangeListener to the listener list. 178 * The listener is registered for all properties. 179 * The same listener object may be added more than once, and will be called 180 * as many times as it is added. 181 * If <code>listener</code> is null, no exception is thrown and no action 182 * is taken. 183 * 184 * @param listener The PropertyChangeListener to be added 185 */ 186 public final void addPropertyChangeListener(PropertyChangeListener listener) { 187 pcs.addPropertyChangeListener(listener); 188 } 189 190 /** 191 * Remove a PropertyChangeListener from the listener list. 192 * This removes a PropertyChangeListener that was registered 193 * for all properties. 194 * If <code>listener</code> was added more than once to the same event 195 * source, it will be notified one less time after being removed. 196 * If <code>listener</code> is null, or was never added, no exception is 197 * thrown and no action is taken. 198 * 199 * @param listener The PropertyChangeListener to be removed 200 */ 201 public final void removePropertyChangeListener(PropertyChangeListener listener) { 202 pcs.removePropertyChangeListener(listener); 203 } 204 205 /** 206 * Returns an array of all the listeners that were added to the 207 * PropertyChangeSupport object with addPropertyChangeListener(). 208 * <p> 209 * If some listeners have been added with a named property, then 210 * the returned array will be a mixture of PropertyChangeListeners 211 * and <code>PropertyChangeListenerProxy</code>s. If the calling 212 * method is interested in distinguishing the listeners then it must 213 * test each element to see if it's a 214 * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine 215 * the parameter. 216 * 217 * <pre> 218 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners(); 219 * for (int i = 0; i < listeners.length; i++) { 220 * if (listeners[i] instanceof PropertyChangeListenerProxy) { 221 * PropertyChangeListenerProxy proxy = 222 * (PropertyChangeListenerProxy)listeners[i]; 223 * if (proxy.getPropertyName().equals("foo")) { 224 * // proxy is a PropertyChangeListener which was associated 225 * // with the property named "foo" 226 * } 227 * } 228 * } 229 *</pre> 230 * 231 * @see java.beans.PropertyChangeListenerProxy 232 * @return all of the <code>PropertyChangeListeners</code> added or an 233 * empty array if no listeners have been added 234 */ 235 public final PropertyChangeListener[] getPropertyChangeListeners() { 236 return pcs.getPropertyChangeListeners(); 237 } 238 239 /** 240 * Add a PropertyChangeListener for a specific property. The listener 241 * will be invoked only when a call on firePropertyChange names that 242 * specific property. 243 * The same listener object may be added more than once. For each 244 * property, the listener will be invoked the number of times it was added 245 * for that property. 246 * If <code>propertyName</code> or <code>listener</code> is null, no 247 * exception is thrown and no action is taken. 248 * 249 * @param propertyName The name of the property to listen on. 250 * @param listener The PropertyChangeListener to be added 251 */ 252 public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 253 pcs.addPropertyChangeListener(propertyName, listener); 254 } 255 256 /** 257 * Remove a PropertyChangeListener for a specific property. 258 * If <code>listener</code> was added more than once to the same event 259 * source for the specified property, it will be notified one less time 260 * after being removed. 261 * If <code>propertyName</code> is null, no exception is thrown and no 262 * action is taken. 263 * If <code>listener</code> is null, or was never added for the specified 264 * property, no exception is thrown and no action is taken. 265 * 266 * @param propertyName The name of the property that was listened on. 267 * @param listener The PropertyChangeListener to be removed 268 */ 269 public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { 270 pcs.removePropertyChangeListener(propertyName, listener); 271 } 272 273 /** 274 * Returns an array of all the listeners which have been associated 275 * with the named property. 276 * 277 * @param propertyName The name of the property being listened to 278 * @return all of the <code>PropertyChangeListeners</code> associated with 279 * the named property. If no such listeners have been added, 280 * or if <code>propertyName</code> is null, an empty array is 281 * returned. 282 */ 283 public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 284 return pcs.getPropertyChangeListeners(propertyName); 285 } 286 287 /** 288 * Report a bound property update to any registered listeners. 289 * No event is fired if old and new are equal and non-null. 290 * 291 * <p> 292 * This is merely a convenience wrapper around the more general 293 * firePropertyChange method that takes {@code 294 * PropertyChangeEvent} value. 295 * 296 * @param propertyName The programmatic name of the property 297 * that was changed. 298 * @param oldValue The old value of the property. 299 * @param newValue The new value of the property. 300 */ 301 protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 302 pcs.firePropertyChange(propertyName, oldValue, newValue); 303 } 304 305 /** 306 * Fire an existing PropertyChangeEvent to any registered listeners. 307 * No event is fired if the given event's old and new values are 308 * equal and non-null. 309 * @param evt The PropertyChangeEvent object. 310 */ 311 protected final void firePropertyChange(PropertyChangeEvent evt) { 312 pcs.firePropertyChange(evt); 313 } 314 315 316 /** 317 * Report a bound indexed property update to any registered 318 * listeners. 319 * <p> 320 * No event is fired if old and new values are equal 321 * and non-null. 322 * 323 * <p> 324 * This is merely a convenience wrapper around the more general 325 * firePropertyChange method that takes {@code PropertyChangeEvent} value. 326 * 327 * @param propertyName The programmatic name of the property that 328 * was changed. 329 * @param index index of the property element that was changed. 330 * @param oldValue The old value of the property. 331 * @param newValue The new value of the property. 332 */ 333 protected final void fireIndexedPropertyChange(String propertyName, int index, 334 Object oldValue, Object newValue) { 335 pcs.fireIndexedPropertyChange(propertyName, index, oldValue, newValue); 336 } 337 338 /** 339 * Check if there are any listeners for a specific property, including 340 * those registered on all properties. If <code>propertyName</code> 341 * is null, only check for listeners registered on all properties. 342 * 343 * @param propertyName the property name. 344 * @return true if there are one or more listeners for the given property 345 */ 346 protected final boolean hasPropertyChangeListeners(String propertyName) { 347 return pcs.hasListeners(propertyName); 348 } 349 350 /** 351 * Check if there are any listeners for a specific property, including 352 * those registered on all properties. If <code>propertyName</code> 353 * is null, only check for listeners registered on all properties. 354 * 355 * @param propertyName the property name. 356 * @return true if there are one or more listeners for the given property 357 */ 358 protected final boolean hasVetoableChangeListeners(String propertyName) { 359 return vcs.hasListeners(propertyName); 360 } 361 362 /** 363 * Add a VetoableListener to the listener list. 364 * The listener is registered for all properties. 365 * The same listener object may be added more than once, and will be called 366 * as many times as it is added. 367 * If <code>listener</code> is null, no exception is thrown and no action 368 * is taken. 369 * 370 * @param listener The VetoableChangeListener to be added 371 */ 372 373 public final void addVetoableChangeListener(VetoableChangeListener listener) { 374 vcs.addVetoableChangeListener(listener); 375 } 376 377 /** 378 * Remove a VetoableChangeListener from the listener list. 379 * This removes a VetoableChangeListener that was registered 380 * for all properties. 381 * If <code>listener</code> was added more than once to the same event 382 * source, it will be notified one less time after being removed. 383 * If <code>listener</code> is null, or was never added, no exception is 384 * thrown and no action is taken. 385 * 386 * @param listener The VetoableChangeListener to be removed 387 */ 388 public final void removeVetoableChangeListener(VetoableChangeListener listener) { 389 vcs.removeVetoableChangeListener(listener); 390 } 391 392 /** 393 * Returns the list of VetoableChangeListeners. If named vetoable change listeners 394 * were added, then VetoableChangeListenerProxy wrappers will returned 395 * <p> 396 * @return List of VetoableChangeListeners and VetoableChangeListenerProxys 397 * if named property change listeners were added. 398 */ 399 public final VetoableChangeListener[] getVetoableChangeListeners(){ 400 return vcs.getVetoableChangeListeners(); 401 } 402 403 /** 404 * Add a VetoableChangeListener for a specific property. The listener 405 * will be invoked only when a call on fireVetoableChange names that 406 * specific property. 407 * The same listener object may be added more than once. For each 408 * property, the listener will be invoked the number of times it was added 409 * for that property. 410 * If <code>propertyName</code> or <code>listener</code> is null, no 411 * exception is thrown and no action is taken. 412 * 413 * @param propertyName The name of the property to listen on. 414 * @param listener The VetoableChangeListener to be added 415 */ 416 417 public final void addVetoableChangeListener(String propertyName, 418 VetoableChangeListener listener) { 419 vcs.addVetoableChangeListener(propertyName, listener); 420 } 421 422 /** 423 * Remove a VetoableChangeListener for a specific property. 424 * If <code>listener</code> was added more than once to the same event 425 * source for the specified property, it will be notified one less time 426 * after being removed. 427 * If <code>propertyName</code> is null, no exception is thrown and no 428 * action is taken. 429 * If <code>listener</code> is null, or was never added for the specified 430 * property, no exception is thrown and no action is taken. 431 * 432 * @param propertyName The name of the property that was listened on. 433 * @param listener The VetoableChangeListener to be removed 434 */ 435 436 public final void removeVetoableChangeListener(String propertyName, 437 VetoableChangeListener listener) { 438 vcs.removeVetoableChangeListener(propertyName, listener); 439 } 440 441 /** 442 * Returns an array of all the listeners which have been associated 443 * with the named property. 444 * 445 * @param propertyName The name of the property being listened to 446 * @return all the <code>VetoableChangeListeners</code> associated with 447 * the named property. If no such listeners have been added, 448 * or if <code>propertyName</code> is null, an empty array is 449 * returned. 450 */ 451 public final VetoableChangeListener[] getVetoableChangeListeners(String propertyName) { 452 return vcs.getVetoableChangeListeners(propertyName); 453 } 454 455 /** 456 * Report a vetoable property update to any registered listeners. If 457 * anyone vetos the change, then fire a new event reverting everyone to 458 * the old value and then rethrow the PropertyVetoException. 459 * <p> 460 * No event is fired if old and new are equal and non-null. 461 * 462 * @param propertyName The programmatic name of the property 463 * that is about to change.. 464 * @param oldValue The old value of the property. 465 * @param newValue The new value of the property. 466 * @exception PropertyVetoException if the recipient wishes the property 467 * change to be rolled back. 468 */ 469 protected final void fireVetoableChange(String propertyName, 470 Object oldValue, Object newValue) 471 throws PropertyVetoException { 472 vcs.fireVetoableChange(propertyName, oldValue, newValue); 473 } 474 475 /** 476 * Fire a vetoable property update to any registered listeners. If 477 * anyone vetos the change, then fire a new event reverting everyone to 478 * the old value and then rethrow the PropertyVetoException. 479 * <p> 480 * No event is fired if old and new are equal and non-null. 481 * 482 * @param evt The PropertyChangeEvent to be fired. 483 * @exception PropertyVetoException if the recipient wishes the property 484 * change to be rolled back. 485 */ 486 protected final void fireVetoableChange(PropertyChangeEvent evt) 487 throws PropertyVetoException { 488 vcs.fireVetoableChange(evt); 489 } 490 491 /** 492 * {@inheritDoc} 493 */ 494 @Override 495 public Object clone() throws CloneNotSupportedException { 496 AbstractBean result = (AbstractBean) super.clone(); 497 result.pcs = new PropertyChangeSupport(result); 498 result.vcs = new VetoableChangeSupport(result); 499 return result; 500 } 501 }