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 }