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