001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.beans;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035: import java.beans.PropertyDescriptor;
036: import java.beans.PropertyVetoException;
037:
038: import com.jgoodies.binding.value.AbstractValueModel;
039: import com.jgoodies.binding.value.ValueHolder;
040: import com.jgoodies.binding.value.ValueModel;
041:
042: /**
043: * Converts a single Java Bean property into the generic ValueModel interface.
044: * The bean property must be a single value as described by the
045: * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java
046: * Bean Specification</a>. See below for a comparison with the more frequently
047: * used BeanAdapter and PresentationModel classes.<p>
048: *
049: * The constructors accept either a property name or a triple of
050: * (property name, getter name, setter name). If you just specify the
051: * property name, the adapter uses the standard Java Bean introspection
052: * to lookup the available properties and how to read and write the
053: * property value. In case of custom readers and writers you may
054: * specify a custom BeanInfo class, or as a shortcut use the constructors
055: * that accept the optional getter and setter name. If these are specified,
056: * introspection will be bypassed and a PropertyDescriptor will be
057: * created for the given property name, getter and setter name.<p>
058: *
059: * Optionally the PropertyAdapter can observe changes in <em>bound
060: * properties</em> as described in section 7.4 of the Bean specification.
061: * You can enable this feature by setting the constructor parameter
062: * <code>observeChanges</code> to <code>true</code>.
063: * If the adapter observes changes, it will fire value change events,
064: * i.e. PropertyChangeEvents for the property <code>"value"</code>.
065: * Even if you ignore property changes, you can access the adapted
066: * property value via <code>#getValue()</code>.
067: * It's just that you won't be notified about changes.<p>
068: *
069: * The PropertyAdapter provides two access styles to the target bean
070: * that holds the adapted property: you can specify a bean directly,
071: * or you can use a <em>bean channel</em> to access the bean indirectly.
072: * In the latter case you specify a <code>ValueModel</code>
073: * that holds the bean that in turn holds the adapted property.<p>
074: *
075: * If the adapted bean is <code>null</code> the PropertyAdapter can
076: * neither read nor set a value. In this case <code>#getValue</code>
077: * returns <code>null</code> and <code>#setValue</code> will silently
078: * ignore the new value.<p>
079: *
080: * This adapter throws three PropertyChangeEvents if the bean changes:
081: * <em>beforeBean</em>, <em>bean</em> and <em>afterBean</em>. This is useful
082: * when sharing a bean channel and you must perform an operation before
083: * or after other listeners handle a bean change. Since you cannot rely
084: * on the order listeners will be notified, only the <em>beforeBean</em>
085: * and <em>afterBean</em> events are guaranteed to be fired before and
086: * after the bean change is fired.
087: * Note that <code>#getBean()</code> returns the new bean before
088: * any of these three PropertyChangeEvents is fired. Therefore listeners
089: * that handle these events must use the event's old and new value
090: * to determine the old and new bean.
091: * The order of events fired during a bean change is:<ol>
092: * <li>this adapter's bean channel fires a <em>value</em> change,
093: * <li>this adapter fires a <em>beforeBean</em> change,
094: * <li>this adapter fires the <em>bean</em> change,
095: * <li>this adapter fires an <em>afterBean</em> change.
096: * </ol><p>
097: *
098: * <strong>Note:</strong>
099: * PropertyAdapters that observe changes have a PropertyChangeListener
100: * registered with the target bean. Hence, a bean has a reference
101: * to any PropertyAdapter that observes it. To avoid memory leaks
102: * it is recommended to remove this listener if the bean lives much longer than
103: * the PropertyAdapter, enabling the garbage collector to remove the adapter.
104: * To do so, you can call <code>setBean(null)</code> or set the
105: * bean channel's value to null.
106: * As an alternative you can use event listener lists in your beans
107: * that implement references with <code>WeakReference</code>.<p>
108: *
109: * Setting the bean to null has side-effects, for example the adapter fires
110: * a change event for the bound property <em>bean</em> and other properties.
111: * And the adpter's value may change.
112: * However, typically this is fine and setting the bean to null
113: * is the first choice for removing the reference from the bean to the adapter.
114: * Another way to clear the reference from the target bean is
115: * to call <code>#release</code>. It has no side-effects, but the adapter
116: * must not be used anymore once #release has been called.<p>
117: *
118: * <strong>Constraints:</strong> If property changes shall be observed,
119: * the bean class must support bound properties, i. e. it must provide
120: * the following pair of methods for registration of multicast property
121: * change event listeners:
122: * <pre>
123: * public void addPropertyChangeListener(PropertyChangeListener x);
124: * public void removePropertyChangeListener(PropertyChangeListener x);
125: * </pre>
126: *
127: * <strong>PropertyAdapter vs. BeanAdapter vs. PresentationModel</strong><br>
128: * If you adapt multiple properties of the same bean, you better use
129: * a {@link com.jgoodies.binding.beans.BeanAdapter}. The BeanAdapter
130: * registers only a single PropertyChangeListener with the bean,
131: * where multiple PropertyAdapters would register multiple listeners.
132: * If you adapt bean properties for an editor, you will typically use the
133: * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is
134: * more powerful than the BeanAdapter. It adds support for buffered models,
135: * and provides an extensible mechanism for observing the change state
136: * of the bean and related objects.<p>
137: *
138: * <strong>Basic Examples:</strong>
139: * <pre>
140: * // Direct access, ignores changes
141: * Address address = new Address()
142: * PropertyAdapter adapter = new PropertyAdapter(address, "street");
143: * adapter.setValue("Broadway");
144: * System.out.println(address.getStreet()); // Prints "Broadway"
145: * address.setStreet("Franz-Josef-Strasse");
146: * System.out.println(adapter.getValue()); // Prints "Franz-Josef-Strasse"
147: *
148: *
149: * //Direct access, observes changes
150: * PropertyAdapter adapter = new PropertyAdapter(address, "street", true);
151: *
152: *
153: * // Indirect access, ignores changes
154: * ValueHolder addressHolder = new ValueHolder(address1);
155: * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street");
156: * adapter.setValue("Broadway"); // Sets the street in address1
157: * System.out.println(address1.getValue()); // Prints "Broadway"
158: * adapter.setBean(address2);
159: * adapter.setValue("Robert-Koch-Strasse"); // Sets the street in address2
160: * System.out.println(address2.getValue()); // Prints "Robert-Koch-Strasse"
161: *
162: *
163: * // Indirect access, observes changes
164: * ValueHolder addressHolder = new ValueHolder();
165: * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street", true);
166: * addressHolder.setValue(address1);
167: * address1.setStreet("Broadway");
168: * System.out.println(adapter.getValue()); // Prints "Broadway"
169: * </pre>
170: *
171: * <strong>Adapter Chain Example:</strong>
172: * <br>Builds an adapter chain from a domain model to the presentation layer.
173: * <pre>
174: * Country country = new Country();
175: * country.setName("Germany");
176: * country.setEuMember(true);
177: *
178: * JTextField nameField = new JTextField();
179: * nameField.setDocument(new DocumentAdapter(
180: * new PropertyAdapter(country, "name", true)));
181: *
182: * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
183: * euMemberBox.setModel(new ToggleButtonAdapter(
184: * new PropertyAdapter(country, "euMember", true)));
185: *
186: * // Using factory methods
187: * JTextField nameField = Factory.createTextField(country, "name");
188: * JCheckBox euMemberBox = Factory.createCheckBox (country, "euMember");
189: * euMemberBox.setText("Is EU Member");
190: * </pre><p>
191: *
192: * TODO: Consider adding a feature to ensure that update notifications
193: * are performed in the event dispatch thread. In case the adapted bean
194: * is changed in a thread other than the event dispatch thread, such
195: * a feature would help complying with Swing's single thread rule.
196: * The feature could be implemented by an extended PropertyChangeSupport.<p>
197: *
198: * TODO: I plan to improve the support for adapting beans that do not fire
199: * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter,
200: * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's
201: * internal SimplePropertyAdapter's shall be able to optionally self-fire
202: * a PropertyChangeEvent in case the bean does not. There are several
203: * downsides with self-firing events compared to bound bean properties.
204: * See <a href="https://binding.dev.java.net/issues/show_bug.cgi?id=49">Issue
205: * 49</a> for more information about the downsides.<p>
206: *
207: * The observeChanges constructor parameter shall be replaced by a more
208: * fine-grained choice to not observe (former observeChanges=false),
209: * to observe bound properties (former observeChanges=true), and a new
210: * setting for self-firing PropertyChangeEvents if a value is set.
211: * The latter case may be further splitted up to specify how the
212: * self-fired PropertyChangeEvent is created:
213: * <ol>
214: * <li>oldValue=null, newValue=null
215: * <li>oldValue=null, newValue=the value set
216: * <li>oldValue=value read before the set, newValue=the value set
217: * <li>oldValue=value read before the set, newValue=value read after the set
218: * </ol>
219: *
220: * @author Karsten Lentzsch
221: * @version $Revision: 1.13 $
222: *
223: * @see com.jgoodies.binding.beans.BeanAdapter
224: * @see ValueModel
225: * @see ValueModel#getValue()
226: * @see ValueModel#setValue(Object)
227: * @see PropertyChangeEvent
228: * @see PropertyChangeListener
229: * @see java.beans.Introspector
230: * @see java.beans.BeanInfo
231: * @see PropertyDescriptor
232: *
233: * @param <B> the type of the adapted bean
234: */
235: public final class PropertyAdapter<B> extends AbstractValueModel {
236:
237: /**
238: * The property name used in the PropertyChangeEvent that is fired
239: * before the <em>bean</em> property fires its PropertyChangeEvent.
240: * Useful to perform an operation before listeners that handle the
241: * bean change are notified. See also the class comment.
242: */
243: public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean";
244:
245: /**
246: * The name of the read-write bound property that holds the target bean.
247: *
248: * @see #getBean()
249: * @see #setBean(Object)
250: */
251: public static final String PROPERTYNAME_BEAN = "bean";
252:
253: /**
254: * The property name used in the PropertyChangeEvent that is fired
255: * after the <em>bean</em> property fires its PropertyChangeEvent.
256: * Useful to perform an operation after listeners that handle the
257: * bean change are notified. See also the class comment.
258: */
259: public static final String PROPERTYNAME_AFTER_BEAN = "afterBean";
260:
261: /**
262: * The name of the read-only bound bean property that
263: * indicates whether one of the observed properties has changed.
264: *
265: * @see #isChanged()
266: */
267: public static final String PROPERTYNAME_CHANGED = "changed";
268:
269: // Fields *****************************************************************
270:
271: /**
272: * Holds a <code>ValueModel</code> that holds the bean, that in turn
273: * holds the adapted property.
274: *
275: * @see #getBean()
276: * @see #setBean(Object)
277: */
278: private final ValueModel beanChannel;
279:
280: /**
281: * Holds the name of the adapted property.
282: *
283: * @see #getPropertyName()
284: */
285: private final String propertyName;
286:
287: /**
288: * Holds the optional name of the property's getter.
289: */
290: private final String getterName;
291:
292: /**
293: * Holds the optional name of the property's setter.
294: */
295: private final String setterName;
296:
297: /**
298: * Specifies whether we observe property changes and in turn
299: * fire state changes.
300: *
301: * @see #getObserveChanges()
302: */
303: private final boolean observeChanges;
304:
305: /**
306: * Refers to the old bean. Used as old value if the bean changes.
307: * Updated after a bean change in the BeanChangeHandler.
308: */
309: private B storedOldBean;
310:
311: /**
312: * Indicates whether a property in the current target been has changed.
313: * Will be reset to <code>false</code> every time the target bean changes.
314: *
315: * @see #isChanged()
316: * @see #setBean(Object)
317: */
318: private boolean changed = false;
319:
320: /**
321: * The <code>PropertyChangeListener</code> used to handle changes
322: * in the adapted bean property. A new instance is created every time
323: * the target bean changes.
324: */
325: private PropertyChangeListener propertyChangeHandler;
326:
327: /**
328: * Describes the property accessor; basically a getter and setter.
329: */
330: private PropertyDescriptor cachedPropertyDescriptor;
331:
332: /**
333: * Holds the bean class associated with the cached property descriptor.
334: */
335: private Class<?> cachedBeanClass;
336:
337: // Instance creation ****************************************************
338:
339: /**
340: * Constructs a <code>PropertyAdapter</code> for the given
341: * bean and property name; does not observe changes.
342: *
343: * @param bean the bean that owns the property
344: * @param propertyName the name of the adapted property
345: * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
346: * @throws IllegalArgumentException if <code>propertyName</code> is empty
347: */
348: public PropertyAdapter(B bean, String propertyName) {
349: this (bean, propertyName, false);
350: }
351:
352: /**
353: * Constructs a <code>PropertyAdapter</code> for the given
354: * bean and property name; observes changes if specified.
355: *
356: * @param bean the bean that owns the property
357: * @param propertyName the name of the adapted property
358: * @param observeChanges <code>true</code> to observe changes of bound
359: * or constrained properties, <code>false</code> to ignore changes
360: * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
361: * @throws IllegalArgumentException if <code>propertyName</code> is empty
362: * @throws PropertyUnboundException if <code>observeChanges</code>
363: * is true but the property is unbound, i. e. the <code>bean</code>
364: * does not provide a pair of methods to register a multicast
365: * PropertyChangeListener
366: */
367: public PropertyAdapter(B bean, String propertyName,
368: boolean observeChanges) {
369: this (bean, propertyName, null, null, observeChanges);
370: }
371:
372: /**
373: * Constructs a <code>PropertyAdapter</code> for the given bean,
374: * property name, getter and setter name; does not observe changes.
375: *
376: * @param bean the bean that owns the property
377: * @param propertyName the name of the adapted property
378: * @param getterName the optional name of the property reader
379: * @param setterName the optional name of the property writer
380: * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
381: * @throws IllegalArgumentException if <code>propertyName</code> is empty
382: */
383: public PropertyAdapter(B bean, String propertyName,
384: String getterName, String setterName) {
385: this (bean, propertyName, getterName, setterName, false);
386: }
387:
388: /**
389: * Constructs a <code>PropertyAdapter</code> for the given bean,
390: * property name, getter and setter name; observes changes if specified.
391: *
392: * @param bean the bean that owns the property
393: * @param propertyName the name of the adapted property
394: * @param getterName the optional name of the property reader
395: * @param setterName the optional name of the property writer
396: * @param observeChanges <code>true</code> to observe changes of bound
397: * or constrained properties, <code>false</code> to ignore changes
398: * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
399: * @throws IllegalArgumentException if <code>propertyName</code> is empty
400: * @throws PropertyUnboundException if <code>observeChanges</code>
401: * is true but the property is unbound, i. e. the <code>bean</code>
402: * does not provide a pair of methods to register a multicast
403: * PropertyChangeListener
404: */
405: public PropertyAdapter(B bean, String propertyName,
406: String getterName, String setterName, boolean observeChanges) {
407: this (new ValueHolder(bean, true), propertyName, getterName,
408: setterName, observeChanges);
409: }
410:
411: /**
412: * Constructs a <code>PropertyAdapter</code> for the given
413: * bean channel and property name; does not observe changes.
414: *
415: * @param beanChannel the <code>ValueModel</code> that holds the bean
416: * @param propertyName the name of the adapted property
417: * @throws NullPointerException if <code>beanChannel</code> or
418: * <code>propertyName</code> is <code>null</code>
419: * @throws IllegalArgumentException if <code>propertyName</code> is empty
420: */
421: public PropertyAdapter(ValueModel beanChannel, String propertyName) {
422: this (beanChannel, propertyName, false);
423: }
424:
425: /**
426: * Constructs a <code>PropertyAdapter</code> for the given
427: * bean channel and property name; observes changes if specified.
428: *
429: * @param beanChannel the <code>ValueModel</code> that holds the bean
430: * @param propertyName the name of the adapted property
431: * @param observeChanges <code>true</code> to observe changes of bound
432: * or constrained properties, <code>false</code> to ignore changes
433: * @throws NullPointerException if <code>beanChannel</code> or
434: * <code>propertyName</code> is <code>null</code>
435: * @throws IllegalArgumentException if <code>propertyName</code> is empty
436: * @throws PropertyUnboundException if <code>observeChanges</code>
437: * is true but the property is unbound, i. e. the <code>bean</code>
438: * does not provide a pair of methods to register a multicast
439: * PropertyChangeListener
440: * @throws PropertyAccessException if the <code>beanChannel</code>'s value
441: * does not provide a property descriptor for <code>propertyName</code>
442: */
443: public PropertyAdapter(ValueModel beanChannel, String propertyName,
444: boolean observeChanges) {
445: this (beanChannel, propertyName, null, null, observeChanges);
446: }
447:
448: /**
449: * Constructs a <code>PropertyAdapter</code> for the given bean channel,
450: * property name, getter and setter name; does not observe changes.
451: *
452: * @param beanChannel the <code>ValueModel</code> that holds the bean
453: * @param propertyName the name of the adapted property
454: * @param getterName the optional name of the property reader
455: * @param setterName the optional name of the property writer
456: * @throws NullPointerException if <code>beanChannel</code> or
457: * <code>propertyName</code> is <code>null</code>
458: * @throws IllegalArgumentException if <code>propertyName</code> is empty
459: */
460: public PropertyAdapter(ValueModel beanChannel, String propertyName,
461: String getterName, String setterName) {
462: this (beanChannel, propertyName, getterName, setterName, false);
463: }
464:
465: /**
466: * Constructs a <code>PropertyAdapter</code> for the given bean channel,
467: * property name, getter and setter name; observes changes if specified.
468: *
469: * @param beanChannel the <code>ValueModel</code> that holds the bean
470: * @param propertyName the name of the adapted property
471: * @param getterName the optional name of the property reader
472: * @param setterName the optional name of the property writer
473: * @param observeChanges <code>true</code> to observe changes of bound
474: * or constrained properties, <code>false</code> to ignore changes
475: *
476: * @throws NullPointerException if <code>propertyName</code> is <code>null</code>
477: * @throws IllegalArgumentException if <code>propertyName</code> is empty
478: * @throws IllegalArgumentException if the bean channel is a ValueHolder
479: * that has the identityCheck feature disabled
480: * @throws PropertyUnboundException if <code>observeChanges</code>
481: * is true but the property is unbound, i. e. the <code>bean</code>
482: * does not provide a pair of methods to register a multicast
483: * PropertyChangeListener
484: * @throws PropertyAccessException if the <code>beanChannel</code>'s value
485: * does not provide a property descriptor for <code>propertyName</code>
486: */
487: public PropertyAdapter(ValueModel beanChannel, String propertyName,
488: String getterName, String setterName, boolean observeChanges) {
489: this .beanChannel = beanChannel != null ? beanChannel
490: : new ValueHolder(null, true);
491: this .propertyName = propertyName;
492: this .getterName = getterName;
493: this .setterName = setterName;
494: this .observeChanges = observeChanges;
495:
496: if (propertyName == null)
497: throw new NullPointerException(
498: "The property name must not be null.");
499: if (propertyName.length() == 0)
500: throw new IllegalArgumentException(
501: "The property name must not be empty.");
502: checkBeanChannelIdentityCheck(beanChannel);
503:
504: this .beanChannel
505: .addValueChangeListener(new BeanChangeHandler());
506:
507: B initialBean = getBean();
508: // Eagerly check the existence of the property to adapt.
509: if (initialBean != null) {
510: getPropertyDescriptor(initialBean);
511: addChangeHandlerTo(initialBean);
512: }
513: storedOldBean = initialBean;
514: }
515:
516: // Accessors ************************************************************
517:
518: /**
519: * Returns the Java Bean that holds the adapted property.
520: *
521: * @return the Bean that holds the adapted property
522: *
523: * @see #setBean(Object)
524: */
525: public B getBean() {
526: return (B) beanChannel.getValue();
527: }
528:
529: /**
530: * Sets a new Java Bean as holder of the adapted property.
531: * Notifies any registered value listeners if the value has changed.
532: * Also notifies listeners that have been registered with this adapter
533: * to observe the bound property <em>bean</em>.
534: *
535: * @param newBean the new holder of the property
536: *
537: * @see #getBean()
538: */
539: public void setBean(B newBean) {
540: beanChannel.setValue(newBean);
541: }
542:
543: /**
544: * Returns the name of the adapted Java Bean property.
545: *
546: * @return the name of the adapted property
547: */
548: public String getPropertyName() {
549: return propertyName;
550: }
551:
552: /**
553: * Answers whether this adapter observes changes in the
554: * adapted Bean property.
555: *
556: * @return true if this adapter observes changes, false if not
557: */
558: public boolean getObserveChanges() {
559: return observeChanges;
560: }
561:
562: /*
563: * Sets whether changes in the adapted Bean property shall be observed.
564: * As a requirement the property must be bound.
565: *
566: * @param newValue true to observe changes, false to ignore them
567: public void setObserveChanges(boolean newValue) {
568: if (newValue == getObserveChanges())
569: return;
570: observeChanges = newValue;
571: Object bean = getBean();
572: removePropertyChangeHandler(bean);
573: addPropertyChangeHandler(bean);
574: }
575: */
576:
577: // ValueModel Implementation ********************************************
578: /**
579: * Returns the value of the bean's adapted property, <code>null</code>
580: * if the current bean is <code>null</code>.<p>
581: *
582: * If the adapted bean property is write-only, this adapter is write-only
583: * too, and this operation is not supported and throws an exception.
584: *
585: * @return the value of the adapted bean property, null if the bean is null
586: * @throws UnsupportedOperationException if the property is write-only
587: * @throws PropertyNotFoundException if the property could not be found
588: * @throws PropertyAccessException if the value could not be read
589: */
590: public Object getValue() {
591: B bean = getBean();
592: if (bean == null) {
593: return null;
594: }
595: return getValue0(bean);
596: }
597:
598: /**
599: * Sets the given object as new value of the adapted bean property.
600: * Does nothing if the bean is <code>null</code>. If the bean setter
601: * throws a PropertyVetoException, it is silently ignored.
602: * This write operation is supported only for writable bean properties.<p>
603: *
604: * Notifies any registered value listeners if the bean reports
605: * a property change. Note that a bean may suppress PropertyChangeEvents
606: * if the old and new value are the same, or if the old and new value
607: * are equal.
608: *
609: * @param newValue the value to set
610: *
611: * @throws UnsupportedOperationException if the property is read-only
612: * @throws PropertyNotFoundException if the property could not be found
613: * @throws PropertyAccessException if the new value could not be set
614: */
615: public void setValue(Object newValue) {
616: B bean = getBean();
617: if (bean == null)
618: return;
619: try {
620: setValue0(bean, newValue);
621: } catch (PropertyVetoException e) {
622: // Silently ignore this situation.
623: }
624: }
625:
626: /**
627: * Sets the given object as new value of the adapted bean property.
628: * Does nothing if the bean is <code>null</code>. If the bean setter
629: * throws a PropertyVetoExeption, this method throws the same exception.
630: * This write operation is supported only for writable bean properties.<p>
631: *
632: * Notifies any registered value listeners if the bean reports
633: * a property change. Note that a bean may suppress PropertyChangeEvents
634: * if the old and new value are the same, or if the old and new value
635: * are equal.
636: *
637: * @param newValue the value to set
638: *
639: * @throws UnsupportedOperationException if the property is read-only
640: * @throws PropertyNotFoundException if the property could not be found
641: * @throws PropertyAccessException if the new value could not be set
642: * @throws PropertyVetoException if the invoked bean setter
643: * throws a PropertyVetoException
644: *
645: * @since 1.1
646: */
647: public void setVetoableValue(Object newValue)
648: throws PropertyVetoException {
649: B bean = getBean();
650: if (bean == null)
651: return;
652: setValue0(getBean(), newValue);
653: }
654:
655: // Accessing the Changed State ********************************************
656:
657: /**
658: * Answers whether a bean property has changed since the changed state
659: * has been reset. The changed state is implicitly reset every time
660: * the target bean changes.
661: *
662: * @return true if a property of the current target bean
663: * has changed since the last reset
664: */
665: public boolean isChanged() {
666: return changed;
667: }
668:
669: /**
670: * Resets this tracker's changed state to <code>false</code>.
671: */
672: public void resetChanged() {
673: setChanged(false);
674: }
675:
676: /**
677: * Sets the changed state to the given value. Invoked by the global
678: * PropertyChangeHandler that observes all bean changes. Also invoked
679: * by <code>#resetChanged</code>.
680: *
681: * @param newValue the new changed state
682: */
683: private void setChanged(boolean newValue) {
684: boolean oldValue = isChanged();
685: changed = newValue;
686: firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
687: }
688:
689: // Releasing PropertyChangeListeners **************************************
690:
691: /**
692: * Removes the PropertyChangeHandler from the observed bean, if the bean
693: * is not <code>null</code> and if property changes are observed.<p>
694: *
695: * PropertyAdapters that observe changes have a PropertyChangeListener
696: * registered with the target bean. Hence, a bean has a reference to all
697: * PropertyAdapters that observe it. To avoid memory leaks it is recommended
698: * to remove this listener if the bean lives much longer than the
699: * PropertyAdapter, enabling the garbage collector to remove the adapter.
700: * To do so, you can call <code>setBean(null)</code> or set the
701: * bean channel's value to null.
702: * As an alternative you can use event listener lists in your beans
703: * that implement references with <code>WeakReference</code>.<p>
704: *
705: * Setting the bean to null has side-effects, for example
706: * this adapter fires a change event for the bound property <em>bean</em>
707: * and other properties. And this adpter's value may change.
708: * However, typically this is fine and setting the bean to null is
709: * the first choice for removing the reference from the bean to the adapter.
710: * Another way to clear the reference from the target bean is
711: * to call <code>#release</code>. It has no side-effects, but the adapter
712: * must not be used anymore once #release has been called.
713: *
714: * @see #setBean(Object)
715: * @see java.lang.ref.WeakReference
716: */
717: public void release() {
718: removeChangeHandlerFrom(getBean());
719: }
720:
721: // Changing the Bean & Adding and Removing the PropertyChangeHandler ******
722:
723: private void setBean0(B oldBean, B newBean) {
724: firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean,
725: true);
726: removeChangeHandlerFrom(oldBean);
727: forwardAdaptedValueChanged(oldBean, newBean);
728: resetChanged();
729: addChangeHandlerTo(newBean);
730: firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true);
731: firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean,
732: true);
733: }
734:
735: private void forwardAdaptedValueChanged(B oldBean, B newBean) {
736: Object oldValue = (oldBean == null)
737: || isWriteOnlyProperty(oldBean) ? null
738: : getValue0(oldBean);
739: Object newValue = (newBean == null)
740: || isWriteOnlyProperty(newBean) ? null
741: : getValue0(newBean);
742: if (oldValue != null || newValue != null) {
743: fireValueChange(oldValue, newValue, true);
744: }
745: }
746:
747: private void forwardAdaptedValueChanged(B newBean) {
748: Object newValue = (newBean == null)
749: || isWriteOnlyProperty(newBean) ? null
750: : getValue0(newBean);
751: fireValueChange(null, newValue);
752: }
753:
754: /**
755: * Adds a property change listener to the given bean if we observe changes
756: * and the bean is not null. First checks whether the bean class
757: * supports <em>bound properties</em>, i.e. it provides a pair of methods
758: * to register multicast property change event listeners;
759: * see section 7.4.1 of the Java Beans specification for details.
760: *
761: * @param bean the bean to add a property change handler.
762: * @throws PropertyUnboundException
763: * if the bean does not support bound properties
764: * @throws PropertyNotBindableException
765: * if the property change handler cannot be added successfully
766: *
767: * @see #removeChangeHandlerFrom(Object)
768: */
769: private void addChangeHandlerTo(B bean) {
770: if (!observeChanges || bean == null)
771: return;
772:
773: propertyChangeHandler = new PropertyChangeHandler();
774: BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean),
775: propertyChangeHandler);
776: }
777:
778: /**
779: * Removes the formerly added property change handler from the given bean
780: * if we observe changes and the bean is not null.
781: *
782: * @param bean the bean to remove the property change handler from.
783: * @throws PropertyUnboundException
784: * if the bean does not support bound properties
785: * @throws PropertyNotBindableException
786: * if the property change handler cannot be removed successfully
787: *
788: * @see #addChangeHandlerTo(Object)
789: */
790: private void removeChangeHandlerFrom(B bean) {
791: if (!observeChanges || bean == null)
792: return;
793:
794: BeanUtils.removePropertyChangeListener(bean,
795: getBeanClass(bean), propertyChangeHandler);
796: propertyChangeHandler = null;
797: }
798:
799: // Helper Methods to Get and Set a Property Value *************************
800:
801: /**
802: * Returns the Java Bean class used by this adapter.
803: * The current implementation just returns the given bean's class.<p>
804: *
805: * A future version may return a type other than the concrete
806: * class of the given bean. This beanClass could be specified
807: * in a new set of constructors. This is useful if the beans
808: * are specified by public interfaces, and implemented by
809: * package private classes. In this case, the class of the given bean
810: * object shall be checked against the specified type.
811: *
812: * @param bean the bean that may be used to lookup the class from
813: * @return the Java Bean class used for this adapter.
814: */
815: private Class<?> getBeanClass(B bean) {
816: return bean.getClass();
817: // The future version shall add a check like
818: // beanClass.isInstance(bean) if the beanClass
819: // has been specified in the constructor.
820: }
821:
822: /**
823: * Returns the current value of the bean's property, <code>null</code>
824: * if the current bean is <code>null</code>.
825: *
826: * @param bean the bean to read the value from
827: * @return the bean's property value
828: */
829: private Object getValue0(B bean) {
830: return bean == null ? null : BeanUtils.getValue(bean,
831: getPropertyDescriptor(bean));
832: }
833:
834: /**
835: * Sets the given object as new value of the adapted bean property.
836: * Does nothing if the bean is <code>null</code>. This operation
837: * is unsupported if the bean property is read-only.<p>
838: *
839: * The operation is delegated to the <code>BeanUtils</code> class.
840: *
841: * @param bean the bean that holds the adapted property
842: * @param newValue the property value to be set
843: *
844: * @throws NullPointerException if the bean is null
845: * @throws PropertyVetoException if the invoked bean setter
846: * throws a PropertyVetoException
847: */
848: private void setValue0(B bean, Object newValue)
849: throws PropertyVetoException {
850: BeanUtils.setValue(bean, getPropertyDescriptor(bean), newValue);
851: }
852:
853: /**
854: * Looks up, lazily initializes and returns a <code>PropertyDescriptor</code>
855: * for the given Java Bean and name of the adapted property.<p>
856: *
857: * The cached PropertyDescriptor is considered invalid if the
858: * bean's class has changed. In this case we recompute the
859: * PropertyDescriptor.<p>
860: *
861: * If a getter name or setter name is available, these are used
862: * to directly create a PropertyDescriptor. Otherwise, the standard
863: * Java Bean introspection is used to determine the property descriptor.
864: *
865: * @param bean the bean that holds the property
866: * @return the <code>PropertyDescriptor</code>
867: * @throws PropertyNotFoundException if the property could not be found
868: */
869: private PropertyDescriptor getPropertyDescriptor(B bean) {
870: Class<?> beanClass = getBeanClass(bean);
871: if ((cachedPropertyDescriptor == null)
872: || (beanClass != cachedBeanClass)) {
873:
874: cachedPropertyDescriptor = BeanUtils.getPropertyDescriptor(
875: beanClass, getPropertyName(), getterName,
876: setterName);
877: cachedBeanClass = beanClass;
878: }
879: return cachedPropertyDescriptor;
880: }
881:
882: /**
883: * Answers whether the adapted property has a setter but no getter.
884: * In this case the PropertyAdapter doesn't check for the old value
885: * when you set a new bean or a new value.
886: *
887: * @param bean the bean to test for the write only state
888: * @return true if the property has a setter but no getter, false otherwise
889: */
890: private boolean isWriteOnlyProperty(B bean) {
891: return null == getPropertyDescriptor(bean).getReadMethod();
892: }
893:
894: /**
895: * Throws an IllegalArgumentException if the given ValueModel
896: * is a ValueHolder that has the identityCheck feature disabled.
897: */
898: private void checkBeanChannelIdentityCheck(ValueModel valueModel) {
899: if (!(valueModel instanceof ValueHolder))
900: return;
901:
902: ValueHolder valueHolder = (ValueHolder) valueModel;
903: if (!valueHolder.isIdentityCheckEnabled())
904: throw new IllegalArgumentException(
905: "The bean channel must have the identity check enabled.");
906: }
907:
908: // Helper Classes *********************************************************
909:
910: /**
911: * Listens to changes of the bean.
912: */
913: private final class BeanChangeHandler implements
914: PropertyChangeListener {
915:
916: /**
917: * The bean has been changed. Uses the stored old bean instead of
918: * the event's old value, because the latter can be null.
919: * If the event's new value is null, the new bean is requested
920: * from the bean channel.
921: *
922: * @param evt the property change event to be handled
923: */
924: public void propertyChange(PropertyChangeEvent evt) {
925: B newBean = evt.getNewValue() != null ? (B) evt
926: .getNewValue() : getBean();
927: setBean0(storedOldBean, newBean);
928: storedOldBean = newBean;
929: }
930: }
931:
932: /**
933: * Listens to changes of all bean properties. Fires property changes
934: * if the associated property or an arbitrary set of properties has changed.
935: */
936: private final class PropertyChangeHandler implements
937: PropertyChangeListener {
938:
939: /**
940: * A bean property has been changed. Sets the changed state to true.
941: * Checks whether the observed
942: * property or multiple properties have changed.
943: * If so, notifies all registered listeners about the change.
944: *
945: * @param evt the property change event to be handled
946: */
947: public void propertyChange(PropertyChangeEvent evt) {
948: setChanged(true);
949: if (evt.getPropertyName() == null) {
950: forwardAdaptedValueChanged(getBean());
951: } else if (evt.getPropertyName().equals(getPropertyName())) {
952: fireValueChange(evt.getOldValue(), evt.getNewValue(),
953: true);
954: }
955: }
956: }
957:
958: }
|