All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgoodies.binding.beans.PropertyAdapter Maven / Gradle / Ivy

Go to download

The JGoodies Binding library connects object properties to Swing user interface components. And it helps you represent the state and behavior of a presentation independently of the GUI components used in the interface.

There is a newer version: 2.13.0
Show newest version
/*
 * Copyright (c) 2002-2013 JGoodies Software GmbH. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  o Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  o Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  o Neither the name of JGoodies Software GmbH nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package com.jgoodies.binding.beans;

import static com.jgoodies.common.base.Preconditions.checkNotBlank;
import static com.jgoodies.common.internal.Messages.MUST_NOT_BE_BLANK;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.beans.PropertyVetoException;
import java.lang.reflect.Method;

import com.jgoodies.binding.value.AbstractValueModel;
import com.jgoodies.binding.value.ValueHolder;
import com.jgoodies.binding.value.ValueModel;


/**
 * Converts a single Java Bean property into the generic ValueModel interface.
 * The bean property must be a single value as described by the
 * Java
 * Bean Specification. See below for a comparison with the more frequently
 * used BeanAdapter and PresentationModel classes.

* * The constructors accept either a property name or a triple of * (property name, getter name, setter name). If you just specify the * property name, the adapter uses the standard Java Bean introspection * to lookup the available properties and how to read and write the * property value. In case of custom readers and writers you may * specify a custom BeanInfo class, or as a shortcut use the constructors * that accept the optional getter and setter name. If these are specified, * introspection will be bypassed and a PropertyDescriptor will be * created for the given property name, getter and setter name.

* * Optionally the PropertyAdapter can observe changes in bound * properties as described in section 7.4 of the Bean specification. * You can enable this feature by setting the constructor parameter * {@code observeChanges} to {@code true}. * If the adapter observes changes, it will fire value change events, * i.e. PropertyChangeEvents for the property {@code "value"}. * Even if you ignore property changes, you can access the adapted * property value via {@code #getValue()}. * It's just that you won't be notified about changes.

* * The PropertyAdapter provides two access styles to the target bean * that holds the adapted property: you can specify a bean directly, * or you can use a bean channel to access the bean indirectly. * In the latter case you specify a {@code ValueModel} * that holds the bean that in turn holds the adapted property.

* * If the adapted bean is {@code null} the PropertyAdapter can * neither read nor set a value. In this case {@code #getValue} * returns {@code null} and {@code #setValue} will silently * ignore the new value.

* * This adapter throws three PropertyChangeEvents if the bean changes: * beforeBean, bean and afterBean. This is useful * when sharing a bean channel and you must perform an operation before * or after other listeners handle a bean change. Since you cannot rely * on the order listeners will be notified, only the beforeBean * and afterBean events are guaranteed to be fired before and * after the bean change is fired. * Note that {@code #getBean()} returns the new bean before * any of these three PropertyChangeEvents is fired. Therefore listeners * that handle these events must use the event's old and new value * to determine the old and new bean. * The order of events fired during a bean change is:

    *
  1. this adapter's bean channel fires a value change, *
  2. this adapter fires a beforeBean change, *
  3. this adapter fires the bean change, *
  4. this adapter fires an afterBean change. *

* * Note: * PropertyAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference * to any PropertyAdapter that observes it. To avoid memory leaks * it is recommended to remove this listener if the bean lives much longer than * the PropertyAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call {@code setBean(null)} or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with {@code WeakReference}.

* * Setting the bean to null has side-effects, for example the adapter fires * a change event for the bound property bean and other properties. * And the adpter's value may change. * However, typically this is fine and setting the bean to null * is the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call {@code #release}. It has no side-effects, but the adapter * must not be used anymore once #release has been called.

* * Constraints: If property changes shall be observed, * the bean class must support bound properties, i. e. it must provide * the following pair of methods for registration of multicast property * change event listeners: *

 * public void addPropertyChangeListener(PropertyChangeListener x);
 * public void removePropertyChangeListener(PropertyChangeListener x);
 * 
* * PropertyAdapter vs. BeanAdapter vs. PresentationModel
* If you adapt multiple properties of the same bean, you better use * a {@link com.jgoodies.binding.beans.BeanAdapter}. The BeanAdapter * registers only a single PropertyChangeListener with the bean, * where multiple PropertyAdapters would register multiple listeners. * If you adapt bean properties for an editor, you will typically use the * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is * more powerful than the BeanAdapter. It adds support for buffered models, * and provides an extensible mechanism for observing the change state * of the bean and related objects.

* * Basic Examples: *

 * // Direct access, ignores changes
 * Address address = new Address()
 * PropertyAdapter adapter = new PropertyAdapter(address, "street");
 * adapter.setValue("Broadway");
 * System.out.println(address.getStreet());    // Prints "Broadway"
 * address.setStreet("Franz-Josef-Strasse");
 * System.out.println(adapter.getValue());     // Prints "Franz-Josef-Strasse"
 *
 *
 * //Direct access, observes changes
 * PropertyAdapter adapter = new PropertyAdapter(address, "street", true);
 *
 *
 * // Indirect access, ignores changes
 * ValueHolder addressHolder = new ValueHolder(address1);
 * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street");
 * adapter.setValue("Broadway");               // Sets the street in address1
 * System.out.println(address1.getValue());    // Prints "Broadway"
 * adapter.setBean(address2);
 * adapter.setValue("Robert-Koch-Strasse");    // Sets the street in address2
 * System.out.println(address2.getValue());    // Prints "Robert-Koch-Strasse"
 *
 *
 * // Indirect access, observes changes
 * ValueHolder addressHolder = new ValueHolder();
 * PropertyAdapter adapter = new PropertyAdapter(addressHolder, "street", true);
 * addressHolder.setValue(address1);
 * address1.setStreet("Broadway");
 * System.out.println(adapter.getValue());     // Prints "Broadway"
 * 
* * Adapter Chain Example: *
Builds an adapter chain from a domain model to the presentation layer. *
 * Country country = new Country();
 * country.setName("Germany");
 * country.setEuMember(true);
 *
 * JTextField nameField = new JTextField();
 * nameField.setDocument(new DocumentAdapter(
 *      new PropertyAdapter(country, "name", true)));
 *
 * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
 * euMemberBox.setModel(new ToggleButtonAdapter(
 *      new PropertyAdapter(country, "euMember", true)));
 *
 * // Using factory methods
 * JTextField nameField   = Factory.createTextField(country, "name");
 * JCheckBox  euMemberBox = Factory.createCheckBox (country, "euMember");
 * euMemberBox.setText("Is EU Member");
 * 

* * TODO: Consider adding a feature to ensure that update notifications * are performed in the event dispatch thread. In case the adapted bean * is changed in a thread other than the event dispatch thread, such * a feature would help complying with Swing's single thread rule. * The feature could be implemented by an extended PropertyChangeSupport.

* * TODO: I plan to improve the support for adapting beans that do not fire * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter, * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's * internal SimplePropertyAdapter's shall be able to optionally self-fire * a PropertyChangeEvent in case the bean does not. There are several * downsides with self-firing events compared to bound bean properties. * See Issue * 49 for more information about the downsides.

* * The observeChanges constructor parameter shall be replaced by a more * fine-grained choice to not observe (former observeChanges=false), * to observe bound properties (former observeChanges=true), and a new * setting for self-firing PropertyChangeEvents if a value is set. * The latter case may be further splitted up to specify how the * self-fired PropertyChangeEvent is created: *

    *
  1. oldValue=null, newValue=null *
  2. oldValue=null, newValue=the value set *
  3. oldValue=value read before the set, newValue=the value set *
  4. oldValue=value read before the set, newValue=value read after the set *
* * @author Karsten Lentzsch * @version $Revision: 1.24 $ * * @see com.jgoodies.binding.beans.BeanAdapter * @see ValueModel * @see ValueModel#getValue() * @see ValueModel#setValue(Object) * @see PropertyChangeEvent * @see PropertyChangeListener * @see java.beans.Introspector * @see java.beans.BeanInfo * @see PropertyDescriptor * * @param the type of the adapted bean */ public final class PropertyAdapter extends AbstractValueModel { // Property Names ********************************************************* /** * The property name used in the PropertyChangeEvent that is fired * before the bean property fires its PropertyChangeEvent. * Useful to perform an operation before listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTY_BEFORE_BEAN = "beforeBean"; /** * The name of the read-write bound property that holds the target bean. * * @see #getBean() * @see #setBean(Object) */ public static final String PROPERTY_BEAN = "bean"; /** * The property name used in the PropertyChangeEvent that is fired * after the bean property fires its PropertyChangeEvent. * Useful to perform an operation after listeners that handle the * bean change are notified. See also the class comment. */ public static final String PROPERTY_AFTER_BEAN = "afterBean"; /** * The name of the read-only bound bean property that * indicates whether one of the observed properties has changed. * * @see #isChanged() */ public static final String PROPERTY_CHANGED = "changed"; // Fields ***************************************************************** /** * Holds a {@code ValueModel} that holds the bean, that in turn * holds the adapted property. * * @see #getBean() * @see #setBean(Object) */ private final ValueModel beanChannel; /** * Holds the name of the adapted property. * * @see #getPropertyName() */ private final String propertyName; /** * Holds the optional name of the property's getter. */ private final String getterName; /** * Holds the optional name of the property's setter. */ private final String setterName; /** * Specifies whether we observe property changes and in turn * fire state changes. * * @see #getObserveChanges() */ private final boolean observeChanges; /** * Refers to the old bean. Used as old value if the bean changes. * Updated after a bean change in the BeanChangeHandler. */ private B storedOldBean; /** * Indicates whether a property in the current target been has changed. * Will be reset to {@code false} every time the target bean changes. * * @see #isChanged() * @see #setBean(Object) */ private boolean changed = false; /** * The {@code PropertyChangeListener} used to handle changes * in the adapted bean property. A new instance is created every time * the target bean changes. */ private PropertyChangeListener propertyChangeHandler; /** * Describes the property access; basically a getter and setter. */ private transient PropertyAccessor cachedPropertyAccessor; /** * Holds the bean class associated with the cached property descriptor. */ private Class cachedBeanClass; // Instance creation **************************************************** /** * Constructs a {@code PropertyAdapter} for the given * bean and property name; does not observe changes. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @throws NullPointerException if {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty */ public PropertyAdapter(B bean, String propertyName) { this(bean, propertyName, false); } /** * Constructs a {@code PropertyAdapter} for the given * bean and property name; observes changes if specified. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty * @throws PropertyUnboundException if {@code observeChanges} * is true but the property is unbound, i. e. the {@code bean} * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public PropertyAdapter( B bean, String propertyName, boolean observeChanges) { this(bean, propertyName, null, null, observeChanges); } /** * Constructs a {@code PropertyAdapter} for the given bean, * property name, getter and setter name; does not observe changes. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @throws NullPointerException if {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty */ public PropertyAdapter(B bean, String propertyName, String getterName, String setterName) { this(bean, propertyName, getterName, setterName, false); } /** * Constructs a {@code PropertyAdapter} for the given bean, * property name, getter and setter name; observes changes if specified. * * @param bean the bean that owns the property * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty * @throws PropertyUnboundException if {@code observeChanges} * is true but the property is unbound, i. e. the {@code bean} * does not provide a pair of methods to register a multicast * PropertyChangeListener */ public PropertyAdapter( B bean, String propertyName, String getterName, String setterName, boolean observeChanges) { this(new ValueHolder(bean, true), propertyName, getterName, setterName, observeChanges); } /** * Constructs a {@code PropertyAdapter} for the given * bean channel and property name; does not observe changes. * * @param beanChannel the {@code ValueModel} that holds the bean * @param propertyName the name of the adapted property * @throws NullPointerException if {@code beanChannel} or * {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty */ public PropertyAdapter(ValueModel beanChannel, String propertyName) { this(beanChannel, propertyName, false); } /** * Constructs a {@code PropertyAdapter} for the given * bean channel and property name; observes changes if specified. * * @param beanChannel the {@code ValueModel} that holds the bean * @param propertyName the name of the adapted property * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * @throws NullPointerException if {@code beanChannel} or * {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty * @throws PropertyUnboundException if {@code observeChanges} * is true but the property is unbound, i. e. the {@code bean} * does not provide a pair of methods to register a multicast * PropertyChangeListener * @throws PropertyAccessException if the {@code beanChannel}'s value * does not provide a property descriptor for {@code propertyName} */ public PropertyAdapter( ValueModel beanChannel, String propertyName, boolean observeChanges) { this(beanChannel, propertyName, null, null, observeChanges); } /** * Constructs a {@code PropertyAdapter} for the given bean channel, * property name, getter and setter name; does not observe changes. * * @param beanChannel the {@code ValueModel} that holds the bean * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @throws NullPointerException if {@code beanChannel} or * {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty */ public PropertyAdapter(ValueModel beanChannel, String propertyName, String getterName, String setterName) { this(beanChannel, propertyName, getterName, setterName, false); } /** * Constructs a {@code PropertyAdapter} for the given bean channel, * property name, getter and setter name; observes changes if specified. * * @param beanChannel the {@code ValueModel} that holds the bean * @param propertyName the name of the adapted property * @param getterName the optional name of the property reader * @param setterName the optional name of the property writer * @param observeChanges {@code true} to observe changes of bound * or constrained properties, {@code false} to ignore changes * * @throws NullPointerException if {@code propertyName} is {@code null} * @throws IllegalArgumentException if {@code propertyName} is empty * @throws IllegalArgumentException if the bean channel is a ValueHolder * that has the identityCheck feature disabled * @throws PropertyUnboundException if {@code observeChanges} * is true but the property is unbound, i. e. the {@code bean} * does not provide a pair of methods to register a multicast * PropertyChangeListener * @throws PropertyAccessException if the {@code beanChannel}'s value * does not provide a property descriptor for {@code propertyName} */ public PropertyAdapter( ValueModel beanChannel, String propertyName, String getterName, String setterName, boolean observeChanges) { this.beanChannel = beanChannel != null ? beanChannel : new ValueHolder(null, true); this.propertyName = propertyName; this.getterName = getterName; this.setterName = setterName; this.observeChanges = observeChanges; checkNotBlank(propertyName, MUST_NOT_BE_BLANK, "property name"); checkBeanChannelIdentityCheck(beanChannel); this.beanChannel.addValueChangeListener(new BeanChangeHandler()); B initialBean = getBean(); // Eagerly check the existence of the property to adapt. if (initialBean != null) { getPropertyAccessor(initialBean); addChangeHandlerTo(initialBean); } storedOldBean = initialBean; } // Accessors ************************************************************ /** * Returns the Java Bean that holds the adapted property. * * @return the Bean that holds the adapted property * * @see #setBean(Object) */ public B getBean() { return (B) beanChannel.getValue(); } /** * Sets a new Java Bean as holder of the adapted property. * Notifies any registered value listeners if the value has changed. * Also notifies listeners that have been registered with this adapter * to observe the bound property bean. * * @param newBean the new holder of the property * * @see #getBean() */ public void setBean(B newBean) { beanChannel.setValue(newBean); } /** * Returns the name of the adapted Java Bean property. * * @return the name of the adapted property */ public String getPropertyName() { return propertyName; } /** * Answers whether this adapter observes changes in the * adapted Bean property. * * @return true if this adapter observes changes, false if not */ public boolean getObserveChanges() { return observeChanges; } /* * Sets whether changes in the adapted Bean property shall be observed. * As a requirement the property must be bound. * * @param newValue true to observe changes, false to ignore them public void setObserveChanges(boolean newValue) { if (newValue == getObserveChanges()) return; observeChanges = newValue; Object bean = getBean(); removePropertyChangeHandler(bean); addPropertyChangeHandler(bean); } */ // ValueModel Implementation ******************************************** /** * Returns the value of the bean's adapted property, {@code null} * if the current bean is {@code null}.

* * If the adapted bean property is write-only, this adapter is write-only * too, and this operation is not supported and throws an exception. * * @return the value of the adapted bean property, null if the bean is null * @throws UnsupportedOperationException if the property is write-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the value could not be read */ @Override public Object getValue() { return getValue0(getBean()); } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoException, it is silently ignored. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set */ @Override public void setValue(Object newValue) { B bean = getBean(); if (bean == null) { return; } try { setValue0(bean, newValue); } catch (PropertyVetoException e) { // Silently ignore this situation. } } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. If the bean setter * throws a PropertyVetoExeption, this method throws the same exception. * This write operation is supported only for writable bean properties.

* * Notifies any registered value listeners if the bean reports * a property change. Note that a bean may suppress PropertyChangeEvents * if the old and new value are the same, or if the old and new value * are equal. * * @param newValue the value to set * * @throws UnsupportedOperationException if the property is read-only * @throws PropertyNotFoundException if the property could not be found * @throws PropertyAccessException if the new value could not be set * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException * * @since 1.1 */ public void setVetoableValue(Object newValue) throws PropertyVetoException { B bean = getBean(); if (bean == null) { return; } setValue0(getBean(), newValue); } // Accessing the Changed State ******************************************** /** * Answers whether a bean property has changed since the changed state * has been reset. The changed state is implicitly reset every time * the target bean changes. * * @return true if a property of the current target bean * has changed since the last reset */ public boolean isChanged() { return changed; } /** * Resets this tracker's changed state to {@code false}. */ public void resetChanged() { setChanged(false); } /** * Sets the changed state to the given value. Invoked by the global * PropertyChangeHandler that observes all bean changes. Also invoked * by {@code #resetChanged}. * * @param newValue the new changed state */ private void setChanged(boolean newValue) { boolean oldValue = isChanged(); changed = newValue; firePropertyChange(PROPERTY_CHANGED, oldValue, newValue); } // Releasing PropertyChangeListeners ************************************** /** * Removes the PropertyChangeHandler from the observed bean, if the bean * is not {@code null} and if property changes are observed.

* * PropertyAdapters that observe changes have a PropertyChangeListener * registered with the target bean. Hence, a bean has a reference to all * PropertyAdapters that observe it. To avoid memory leaks it is recommended * to remove this listener if the bean lives much longer than the * PropertyAdapter, enabling the garbage collector to remove the adapter. * To do so, you can call {@code setBean(null)} or set the * bean channel's value to null. * As an alternative you can use event listener lists in your beans * that implement references with {@code WeakReference}.

* * Setting the bean to null has side-effects, for example * this adapter fires a change event for the bound property bean * and other properties. And this adpter's value may change. * However, typically this is fine and setting the bean to null is * the first choice for removing the reference from the bean to the adapter. * Another way to clear the reference from the target bean is * to call {@code #release}. It has no side-effects, but the adapter * must not be used anymore once #release has been called. * * @see #setBean(Object) * @see java.lang.ref.WeakReference */ public void release() { removeChangeHandlerFrom(getBean()); } // Overriding Superclass Behavior ***************************************** @Override protected String paramString() { B bean = getBean(); String beanType = null; Object value = getValue(); String valueType = null; String propertyAccessorName = null; String propertyType = null; Method propertySetter = null; if (bean != null) { beanType = bean.getClass().getName(); valueType = value == null ? null : value.getClass().getName(); PropertyAccessor propertyAccessor = getPropertyAccessor(bean); propertyAccessorName = propertyAccessor.getPropertyName(); propertyType = propertyAccessor.getPropertyType().getName(); propertySetter = propertyAccessor.getWriteMethod(); } return "bean=" + bean + "; bean type=" + beanType + "; value=" + value + "; value type=" + valueType + "; property name=" + propertyAccessorName + "; property type=" + propertyType + "; property setter=" + propertySetter; } // Changing the Bean & Adding and Removing the PropertyChangeHandler ****** private void setBean0(B oldBean, B newBean) { firePropertyChange(PROPERTY_BEFORE_BEAN, oldBean, newBean, true); removeChangeHandlerFrom(oldBean); forwardAdaptedValueChanged(oldBean, newBean); resetChanged(); addChangeHandlerTo(newBean); firePropertyChange(PROPERTY_BEAN, oldBean, newBean, true); firePropertyChange(PROPERTY_AFTER_BEAN, oldBean, newBean, true); } private void forwardAdaptedValueChanged(B oldBean, B newBean) { Object oldValue = oldBean == null || isWriteOnlyProperty(oldBean) ? null : getValue0(oldBean); Object newValue = newBean == null || isWriteOnlyProperty(newBean) ? null : getValue0(newBean); if (oldValue != null || newValue != null) { fireValueChange(oldValue, newValue, true); } } private void forwardAdaptedValueChanged(B newBean) { Object newValue = newBean == null || isWriteOnlyProperty(newBean) ? null : getValue0(newBean); fireValueChange(null, newValue); } /** * Adds a property change listener to the given bean if we observe changes * and the bean is not null. First checks whether the bean class * supports bound properties, i.e. it provides a pair of methods * to register multicast property change event listeners; * see section 7.4.1 of the Java Beans specification for details. * * @param bean the bean to add a property change handler. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be added successfully * * @see #removeChangeHandlerFrom(Object) */ private void addChangeHandlerTo(B bean) { if (!observeChanges || bean == null) { return; } propertyChangeHandler = new PropertyChangeHandler(); BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); } /** * Removes the formerly added property change handler from the given bean * if we observe changes and the bean is not null and we haven't called * this method before. * * @param bean the bean to remove the property change handler from. * @throws PropertyUnboundException * if the bean does not support bound properties * @throws PropertyNotBindableException * if the property change handler cannot be removed successfully * * @see #addChangeHandlerTo(Object) */ private void removeChangeHandlerFrom(B bean) { if (!observeChanges || bean == null || propertyChangeHandler == null) { return; } BeanUtils.removePropertyChangeListener(bean, getBeanClass(bean), propertyChangeHandler); propertyChangeHandler = null; } // Helper Methods to Get and Set a Property Value ************************* /** * Returns the Java Bean class used by this adapter. * The current implementation just returns the given bean's class.

* * A future version may return a type other than the concrete * class of the given bean. This beanClass could be specified * in a new set of constructors. This is useful if the beans * are specified by public interfaces, and implemented by * package private classes. In this case, the class of the given bean * object shall be checked against the specified type. * * @param bean the bean that may be used to lookup the class from * @return the Java Bean class used for this adapter. */ private Class getBeanClass(B bean) { return bean.getClass(); // The future version shall add a check like // beanClass.isInstance(bean) if the beanClass // has been specified in the constructor. } /** * Returns the current value of the bean's property, {@code null} * if the current bean is {@code null}. * * @param bean the bean to read the value from * @return the bean's property value */ private Object getValue0(B bean) { return bean == null ? null : getPropertyAccessor(bean).getValue(bean); } /** * Sets the given object as new value of the adapted bean property. * Does nothing if the bean is {@code null}. This operation * is unsupported if the bean property is read-only.

* * The operation is delegated to the property accessor. * * @param bean the bean that holds the adapted property * @param newValue the property value to be set * * @throws NullPointerException if the bean is null * @throws PropertyVetoException if the invoked bean setter * throws a PropertyVetoException */ private void setValue0(B bean, Object newValue) throws PropertyVetoException { getPropertyAccessor(bean).setValue(bean, newValue); } /** * Looks up, lazily initializes and returns a PropertyAccessor * for the given Java Bean and name of the adapted property. * The concrete lookup is done by the current PropertyAccessorProvider, * which is by default a provider that uses the Java Bean introspection.

* * The cached accessor is considered invalid, if the bean's class * has changed. In this case we recompute the accessor. * * @param bean the bean that holds the property * @return the PropertyAccessor as returned by the provider * @throws PropertyNotFoundException if the property could not be found */ private PropertyAccessor getPropertyAccessor(B bean) { Class beanClass = getBeanClass(bean); if (cachedPropertyAccessor == null || beanClass != cachedBeanClass) { cachedPropertyAccessor = PropertyAccessors.getProvider().getAccessor( beanClass, getPropertyName(), getterName, setterName); cachedBeanClass = beanClass; } return cachedPropertyAccessor; } /** * Answers whether the adapted property has a setter but no getter. * In this case the PropertyAdapter doesn't check for the old value * when you set a new bean or a new value. * * @param bean the bean to test for the write only state * @return true if the property has a setter but no getter, false otherwise */ private boolean isWriteOnlyProperty(B bean) { return getPropertyAccessor(bean).isWriteOnly(); } /** * Throws an IllegalArgumentException if the given ValueModel * is a ValueHolder that has the identityCheck feature disabled. */ private static void checkBeanChannelIdentityCheck(ValueModel valueModel) { if (!(valueModel instanceof ValueHolder)) { return; } ValueHolder valueHolder = (ValueHolder) valueModel; if (!valueHolder.isIdentityCheckEnabled()) { throw new IllegalArgumentException( "The bean channel must have the identity check enabled."); } } // Helper Classes ********************************************************* /** * Listens to changes of the bean. */ private final class BeanChangeHandler implements PropertyChangeListener { /** * The bean has been changed. Uses the stored old bean instead of * the event's old value, because the latter can be null. * If the event's new value is null, the new bean is requested * from the bean channel. * * @param evt the property change event to be handled */ @Override public void propertyChange(PropertyChangeEvent evt) { B newBean = evt.getNewValue() != null ? (B) evt.getNewValue() : getBean(); setBean0(storedOldBean, newBean); storedOldBean = newBean; } } /** * Listens to changes of all bean properties. Fires property changes * if the associated property or an arbitrary set of properties has changed. */ private final class PropertyChangeHandler implements PropertyChangeListener { /** * A bean property has been changed. Sets the changed state to true. * Checks whether the observed * property or multiple properties have changed. * If so, notifies all registered listeners about the change. * * @param evt the property change event to be handled */ @Override public void propertyChange(PropertyChangeEvent evt) { setChanged(true); if (evt.getPropertyName() == null) { forwardAdaptedValueChanged(getBean()); } else if (evt.getPropertyName().equals(getPropertyName())) { fireValueChange(evt.getOldValue(), evt.getNewValue(), true); } } } }