0001: /*
0002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
0003: *
0004: * Redistribution and use in source and binary forms, with or without
0005: * modification, are permitted provided that the following conditions are met:
0006: *
0007: * o Redistributions of source code must retain the above copyright notice,
0008: * this list of conditions and the following disclaimer.
0009: *
0010: * o Redistributions in binary form must reproduce the above copyright notice,
0011: * this list of conditions and the following disclaimer in the documentation
0012: * and/or other materials provided with the distribution.
0013: *
0014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
0015: * its contributors may be used to endorse or promote products derived
0016: * from this software without specific prior written permission.
0017: *
0018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
0019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
0020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
0022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
0025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
0026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
0027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
0028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0029: */
0030:
0031: package com.jgoodies.binding.beans;
0032:
0033: import java.beans.PropertyChangeEvent;
0034: import java.beans.PropertyChangeListener;
0035: import java.beans.PropertyDescriptor;
0036: import java.beans.PropertyVetoException;
0037: import java.util.HashMap;
0038: import java.util.LinkedList;
0039: import java.util.List;
0040: import java.util.Map;
0041:
0042: import com.jgoodies.binding.value.AbstractValueModel;
0043: import com.jgoodies.binding.value.ValueHolder;
0044: import com.jgoodies.binding.value.ValueModel;
0045:
0046: /**
0047: * Converts multiple Java Bean properties into ValueModels.
0048: * The bean properties must be single valued properties as described by the
0049: * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java
0050: * Bean Specification</a>. See below for a comparison with the more frequently
0051: * used PresentationModel class and the rarely used PropertyAdapter.<p>
0052: *
0053: * ValueModels can be created for a property name using
0054: * {@link #getValueModel(String)} or for a triple of (property name, getter
0055: * name, setter name) using {@link #getValueModel(String, String, String)}.
0056: * If you just specify the property name, the adapter uses the standard
0057: * Java Bean introspection to lookup the available properties and how to
0058: * read and write the property value. In case of custom readers and writers
0059: * you may specify a custom BeanInfo class, or as a shortcut use the method
0060: * that accepts the optional getter and setter name. If these are specified,
0061: * introspection will be bypassed and a PropertyDescriptor will be
0062: * created for the given property name, getter and setter name.
0063: * <strong>Note: </strong> For each property name subsequent calls
0064: * to these methods must use the same getter and setter names. Attempts
0065: * to violate this constraint are rejected with an IllegalArgumentException.<p>
0066: *
0067: * Property values for a given property name can be read using
0068: * {@link #getValue(String)}. To set a value for a for a property name
0069: * invoke {@link #setValue(String, Object)}.<p>
0070: *
0071: * Optionally the BeanAdapter can observe changes in <em>bound
0072: * properties</em> as described in section 7.4 of the Bean specification.
0073: * The bean then must provide support for listening on properties as described
0074: * in section 7.4 of this specification.
0075: * You can enable this feature by setting the constructor parameter
0076: * <code>observeChanges</code> to <code>true</code>.
0077: * If the adapter observes changes, the ValueModels returned by
0078: * <code>#getValueModel</code> will fire value change events,
0079: * i.e. PropertyChangeEvents for the property <code>"value"</code>.
0080: * Even if you ignore property changes, you can access the adapted
0081: * property value via <code>#getValue()</code>.
0082: * It's just that you won't be notified about changes.<p>
0083: *
0084: * In addition you can observe the bean's bound properties
0085: * by registering PropertyChangeListeners with the bean using
0086: * <code>#addBeanPropertyChangeListener</code>. These listeners will be removed
0087: * from the old bean before the bean changes and will be re-added after
0088: * the new bean has been set. Therefore these listeners will be notified
0089: * about changes only if the current bean changes a property. They won't be
0090: * notified if the bean changes - and in turn the property value. If you want
0091: * to observes property changes caused by bean changes too, register with the
0092: * adapting ValueModel as returned by <code>#getValueModel(String)</code>.<p>
0093: *
0094: * The BeanAdapter provides two access styles to the target bean
0095: * that holds the adapted property: you can specify a bean directly,
0096: * or you can use a <em>bean channel</em> to access the bean indirectly.
0097: * In the latter case you specify a <code>ValueModel</code>
0098: * that holds the bean that in turn holds the adapted properties.<p>
0099: *
0100: * If the adapted bean is <code>null</code> the BeanAdapter can
0101: * neither read nor set a value. In this case <code>#getValue</code>
0102: * returns <code>null</code> and <code>#setValue</code> will silently
0103: * ignore the new value.<p>
0104: *
0105: * This adapter throws three PropertyChangeEvents if the bean changes:
0106: * <em>beforeBean</em>, <em>bean</em> and <em>afterBean</em>. This is useful
0107: * when sharing a bean channel and you must perform an operation before
0108: * or after other listeners handle a bean change. Since you cannot rely
0109: * on the order listeners will be notified, only the <em>beforeBean</em>
0110: * and <em>afterBean</em> events are guaranteed to be fired before and
0111: * after the bean change is fired.
0112: * Note that <code>#getBean()</code> returns the new bean before
0113: * any of these three PropertyChangeEvents is fired. Therefore listeners
0114: * that handle these events must use the event's old and new value
0115: * to determine the old and new bean.
0116: * The order of events fired during a bean change is:<ol>
0117: * <li>this adapter's bean channel fires a <em>value</em> change,
0118: * <li>this adapter fires a <em>beforeBean</em> change,
0119: * <li>this adapter fires the <em>bean</em> change,
0120: * <li>this adapter fires an <em>afterBean</em> change.
0121: * </ol><p>
0122: *
0123: * <strong>Note:</strong>
0124: * BeanAdapters that observe changes have a PropertyChangeListener
0125: * registered with the target bean. Hence, a bean has a reference
0126: * to any BeanAdapter that observes it. To avoid memory leaks
0127: * it is recommended to remove this listener if the bean lives much longer
0128: * than the BeanAdapter, enabling the garbage collector to remove the adapter.
0129: * To do so, you can call <code>setBean(null)</code> or set the
0130: * bean channel's value to null.
0131: * As an alternative you can use event listener lists in your beans
0132: * that implement references with <code>WeakReference</code>.<p>
0133: *
0134: * Setting the bean to null has side-effects, for example the adapter
0135: * fires a change event for the bound property <em>bean</em> and other properties.
0136: * And the value of ValueModel's vended by this adapter may change.
0137: * However, typically this is fine and setting the bean to null
0138: * is the first choice for removing the reference from the bean to the adapter.
0139: * Another way to clear the reference from the target bean is
0140: * to call <code>#release</code>. It has no side-effects, but the adapter
0141: * must not be used anymore once #release has been called.<p>
0142: *
0143: * <strong>Constraints:</strong> If property changes shall be observed,
0144: * the bean class must support bound properties, i. e. it must provide
0145: * the following pair of methods for registration of multicast property
0146: * change event listeners:
0147: * <pre>
0148: * public void addPropertyChangeListener(PropertyChangeListener x);
0149: * public void removePropertyChangeListener(PropertyChangeListener x);
0150: * </pre>
0151: *
0152: * <strong>PropertyAdapter vs. BeanAdapter vs. PresentationModel</strong><br>
0153: * Basically the BeanAdapter does for multiple properties what the
0154: * {@link com.jgoodies.binding.beans.PropertyAdapter} does for a
0155: * single bean property.
0156: * If you adapt multiple properties of the same bean, you better use
0157: * the BeanAdapter. It registers a single PropertyChangeListener with the bean,
0158: * where multiple PropertyAdapters would register multiple listeners.
0159: * If you adapt bean properties for an editor, you will typically use the
0160: * {@link com.jgoodies.binding.PresentationModel}. The PresentationModel is
0161: * more powerful than the BeanAdapter. It adds support for buffered models,
0162: * and provides an extensible mechanism for observing the change state
0163: * of the bean and related objects.<p>
0164: *
0165: * <strong>Basic Examples:</strong>
0166: * <pre>
0167: * // Direct access, ignores changes
0168: * Address address = new Address()
0169: * BeanAdapter adapter = new BeanAdapter(address);
0170: * adapter.setValue("street", "Broadway");
0171: * System.out.println(address.getStreet()); // Prints "Broadway"
0172: * address.setStreet("Franz-Josef-Str.");
0173: * System.out.println(adapter.getValue("street")); // Prints "Franz-Josef-Str."
0174: *
0175: *
0176: * //Direct access, observes changes
0177: * BeanAdapter adapter = new BeanAdapter(address, true);
0178: *
0179: *
0180: * // Indirect access, ignores changes
0181: * ValueHolder addressHolder = new ValueHolder(address1);
0182: * BeanAdapter adapter = new BeanAdapter(addressHolder);
0183: * adapter.setValue("street", "Broadway"); // Sets the street in address1
0184: * System.out.println(address1.getStreet()); // Prints "Broadway"
0185: * adapter.setBean(address2);
0186: * adapter.setValue("street", "Robert-Koch-Str."); // Sets the street in address2
0187: * System.out.println(address2.getStreet()); // Prints "Robert-Koch-Str."
0188: *
0189: *
0190: * // Indirect access, observes changes
0191: * ValueHolder addressHolder = new ValueHolder();
0192: * BeanAdapter adapter = new BeanAdapter(addressHolder, true);
0193: * addressHolder.setValue(address1);
0194: * address1.setStreet("Broadway");
0195: * System.out.println(adapter.getValue("street")); // Prints "Broadway"
0196: *
0197: *
0198: * // Access through ValueModels
0199: * Address address = new Address();
0200: * BeanAdapter adapter = new BeanAdapter(address);
0201: * ValueModel streetModel = adapter.getValueModel("street");
0202: * ValueModel cityModel = adapter.getValueModel("city");
0203: * streetModel.setValue("Broadway");
0204: * System.out.println(address.getStreet()); // Prints "Broadway"
0205: * address.setCity("Hamburg");
0206: * System.out.println(cityModel.getValue()); // Prints "Hamburg"
0207: * </pre>
0208: *
0209: * <strong>Adapter Chain Example:</strong>
0210: * <br>Builds an adapter chain from a domain model to the presentation layer.
0211: * <pre>
0212: * Country country = new Country();
0213: * country.setName("Germany");
0214: * country.setEuMember(true);
0215: *
0216: * BeanAdapter countryAdapter = new BeanAdapter(country, true);
0217: *
0218: * JTextField nameField = new JTextField();
0219: * nameField.setDocument(new DocumentAdapter(
0220: * countryAdapter.getValueModel("name")));
0221: *
0222: * JCheckBox euMemberBox = new JCheckBox("Is EU Member");
0223: * euMemberBox.setModel(new ToggleButtonAdapter(
0224: * countryAdapter.getValueModel("euMember")));
0225: *
0226: * // Using factory methods
0227: * JTextField nameField = Factory.createTextField(country, "name");
0228: * JCheckBox euMemberBox = Factory.createCheckBox (country, "euMember");
0229: * euMemberBox.setText("Is EU Member");
0230: * </pre><p>
0231: *
0232: * As of version 1.3 this class is no longer marked as final,
0233: * but lacks the documentation for subclass constraints.
0234: * I plan to introduce an interface that shall describe the semantics
0235: * required by the PresentationModel class. Until then, this BeanAdapter
0236: * implementation describes the semantics and all constraints.<p>
0237: *
0238: * TODO: Improve the class comment and focus on the main features.<p>
0239: *
0240: * TODO: Consider adding a feature to ensure that update notifications
0241: * are performed in the event dispatch thread. In case the adapted bean
0242: * is changed in a thread other than the event dispatch thread, such
0243: * a feature would help complying with Swing's single thread rule.
0244: * The feature could be implemented by an extended PropertyChangeSupport.<p>
0245: *
0246: * TODO: I plan to improve the support for adapting beans that do not fire
0247: * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter,
0248: * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's
0249: * internal SimplePropertyAdapter's shall be able to optionally self-fire
0250: * a PropertyChangeEvent in case the bean does not. There are several
0251: * downsides with self-firing events compared to bound bean properties.
0252: * See <a href="https://binding.dev.java.net/issues/show_bug.cgi?id=49">Issue
0253: * 49</a> for more information about the downsides.<p>
0254: *
0255: * The observeChanges constructor parameter shall be replaced by a more
0256: * fine-grained choice to not observe (former observeChanges=false),
0257: * to observe bound properties (former observeChanges=true), and a new
0258: * setting for self-firing PropertyChangeEvents if a value is set.
0259: * The latter case may be further splitted up to specify how the
0260: * self-fired PropertyChangeEvent is created:
0261: * <ol>
0262: * <li>oldValue=null, newValue=null
0263: * <li>oldValue=null, newValue=the value set
0264: * <li>oldValue=value read before the set, newValue=the value set
0265: * <li>oldValue=value read before the set, newValue=value read after the set
0266: * </ol>
0267: *
0268: * @author Karsten Lentzsch
0269: * @version $Revision: 1.24 $
0270: *
0271: * @see com.jgoodies.binding.beans.PropertyAdapter
0272: * @see ValueModel
0273: * @see ValueModel#getValue()
0274: * @see ValueModel#setValue(Object)
0275: * @see PropertyChangeEvent
0276: * @see PropertyChangeListener
0277: * @see java.beans.Introspector
0278: * @see java.beans.BeanInfo
0279: * @see PropertyDescriptor
0280: *
0281: * @param <B> the type of the bean managed by this BeanAdapter
0282: */
0283: public class BeanAdapter<B> extends Model {
0284:
0285: /**
0286: * The property name used in the PropertyChangeEvent that is fired
0287: * before the <em>bean</em> property fires its PropertyChangeEvent.
0288: * Useful to perform an operation before listeners that handle the
0289: * bean change are notified. See also the class comment.
0290: */
0291: public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean";
0292:
0293: /**
0294: * The name of the read-write bound property that holds the target bean.
0295: *
0296: * @see #getBean()
0297: * @see #setBean(Object)
0298: */
0299: public static final String PROPERTYNAME_BEAN = "bean";
0300:
0301: /**
0302: * The property name used in the PropertyChangeEvent that is fired
0303: * after the <em>bean</em> property fires its PropertyChangeEvent.
0304: * Useful to perform an operation after listeners that handle the
0305: * bean change are notified. See also the class comment.
0306: */
0307: public static final String PROPERTYNAME_AFTER_BEAN = "afterBean";
0308:
0309: /**
0310: * The name of the read-only bound bean property that
0311: * indicates whether one of the observed properties has changed.
0312: *
0313: * @see #isChanged()
0314: */
0315: public static final String PROPERTYNAME_CHANGED = "changed";
0316:
0317: // Fields *****************************************************************
0318:
0319: /**
0320: * Holds a ValueModel that holds the bean
0321: * that in turn holds the adapted property.
0322: *
0323: * @see #getBean()
0324: * @see #setBean(Object)
0325: */
0326: private final ValueModel beanChannel;
0327:
0328: /**
0329: * Specifies whether we observe property changes and in turn
0330: * fire state changes.
0331: *
0332: * @see #getObserveChanges()
0333: */
0334: private final boolean observeChanges;
0335:
0336: /**
0337: * Maps property names to the associated SimplePropertyAdapters.
0338: *
0339: * @see #getValueModel(String)
0340: * @see #getValueModel(String, String, String)
0341: */
0342: private final Map<String, SimplePropertyAdapter> propertyAdapters;
0343:
0344: /**
0345: * Refers to the IndirectPropertyChangeSupport that is used to redirect
0346: * PropertyChangelisteners to the current target bean.
0347: */
0348: private IndirectPropertyChangeSupport indirectChangeSupport;
0349:
0350: /**
0351: * Refers to the old bean. Used as old value if the bean changes.
0352: * Updated after a bean change in the BeanChangeHandler.
0353: */
0354: B storedOldBean;
0355:
0356: /**
0357: * Indicates whether a property in the current target been has changed.
0358: * Will be reset to <code>false</code> every time the target bean changes.
0359: *
0360: * @see #isChanged()
0361: * @see #setBean(Object)
0362: */
0363: private boolean changed = false;
0364:
0365: /**
0366: * The <code>PropertyChangeListener</code> used to handle changes
0367: * in the bean properties. A new instance is created every time
0368: * the target bean changes.
0369: */
0370: private PropertyChangeListener propertyChangeHandler;
0371:
0372: // Instance creation ****************************************************
0373:
0374: /**
0375: * Constructs a BeanAdapter for the given bean;
0376: * does not observe changes.<p>
0377: *
0378: * Installs a default bean channel that checks the identity not equity
0379: * to ensure that listeners are reregistered properly if the old and
0380: * new bean are equal but not the same.
0381: *
0382: * @param bean the bean that owns the properties to adapt
0383: */
0384: public BeanAdapter(B bean) {
0385: this (bean, false);
0386: }
0387:
0388: /**
0389: * Constructs a BeanAdapter for the given bean;
0390: * observes changes if specified.<p>
0391: *
0392: * Installs a default bean channel that checks the identity not equity
0393: * to ensure that listeners are reregistered properly if the old and
0394: * new bean are equal but not the same.
0395: *
0396: * @param bean the bean that owns the properties to adapt
0397: * @param observeChanges <code>true</code> to observe changes of bound
0398: * or constrained properties, <code>false</code> to ignore changes
0399: * @throws PropertyUnboundException if <code>observeChanges</code>
0400: * is true but the property is unbound, i. e. the <code>bean</code>
0401: * does not provide a pair of methods to register a multicast
0402: * PropertyChangeListener
0403: */
0404: public BeanAdapter(B bean, boolean observeChanges) {
0405: this (new ValueHolder(bean, true), observeChanges);
0406: }
0407:
0408: /**
0409: * Constructs a BeanAdapter for the given bean channel;
0410: * does not observe changes.<p>
0411: *
0412: * It is strongly recommended that the bean channel checks the identity
0413: * not equity. This ensures that listeners are reregistered properly if
0414: * the old and new bean are equal but not the same.
0415: *
0416: * @param beanChannel the ValueModel that holds the bean
0417: */
0418: public BeanAdapter(ValueModel beanChannel) {
0419: this (beanChannel, false);
0420: }
0421:
0422: /**
0423: * Constructs a BeanAdapter for the given bean channel;
0424: * observes changes if specified.<p>
0425: *
0426: * It is strongly recommended that the bean channel checks the identity
0427: * not equity. This ensures that listeners are reregistered properly if
0428: * the old and new bean are equal but not the same.
0429: *
0430: * @param beanChannel the ValueModel that holds the bean
0431: * @param observeChanges <code>true</code> to observe changes of bound
0432: * or constrained properties, <code>false</code> to ignore changes
0433: *
0434: * @throws IllegalArgumentException if the beanChannel is a ValueHolder
0435: * that has the identityCheck feature disabled
0436: * @throws PropertyUnboundException if <code>observeChanges</code>
0437: * is true but the property is unbound, i. e. the <code>bean</code>
0438: * does not provide a pair of methods to register a multicast
0439: * PropertyChangeListener
0440: */
0441: public BeanAdapter(ValueModel beanChannel, boolean observeChanges) {
0442: this .beanChannel = beanChannel != null ? beanChannel
0443: : new ValueHolder(null, true);
0444: checkBeanChannelIdentityCheck(beanChannel);
0445:
0446: this .observeChanges = observeChanges;
0447: this .propertyAdapters = new HashMap<String, SimplePropertyAdapter>();
0448:
0449: this .beanChannel
0450: .addValueChangeListener(new BeanChangeHandler());
0451:
0452: B initialBean = getBean();
0453: if (initialBean != null) {
0454: if (observeChanges
0455: && !BeanUtils
0456: .supportsBoundProperties(getBeanClass(initialBean)))
0457: throw new PropertyUnboundException(
0458: "The bean must provide support for listening on property changes "
0459: + "as described in section 7.4.5 of the Java Bean Specification.");
0460: addChangeHandlerTo(initialBean);
0461: }
0462: storedOldBean = initialBean;
0463: }
0464:
0465: // Accessors ************************************************************
0466:
0467: /**
0468: * Returns the ValueModel that holds the bean that in turn holds
0469: * the adapted properties. This bean channel is shared by the
0470: * PropertyAdapters created by the factory method
0471: * <code>#getValueModel</code>.
0472: *
0473: * @return the ValueModel that holds the bean that in turn
0474: * holds the adapted properties
0475: *
0476: * @see #getBean()
0477: * @see #setBean(Object)
0478: *
0479: * @since 1.3
0480: */
0481: public ValueModel getBeanChannel() {
0482: return beanChannel;
0483: }
0484:
0485: /**
0486: * Returns the Java Bean that holds the adapted properties.
0487: *
0488: * @return the Bean that holds the adapted properties
0489: *
0490: * @see #setBean(Object)
0491: */
0492: public B getBean() {
0493: return (B) beanChannel.getValue();
0494: }
0495:
0496: /**
0497: * Sets a new Java Bean as holder of the adapted properties.
0498: * Notifies any registered value listeners that are registered
0499: * with the adapting ValueModels created in <code>#getValueModel</code>.
0500: * Also notifies listeners that have been registered with this adapter
0501: * to observe the bound property <em>bean</em>.<p>
0502: *
0503: * Resets the changed state to <code>false</code>.<p>
0504: *
0505: * If this adapter observes bean changes, the bean change handler
0506: * will be removed from the former bean and will be added to the new bean.
0507: * Hence, if the new bean is <code>null</code>, this adapter has no
0508: * listener registered with a bean.
0509: * And so, <code>setBean(null)</code> can be used as a clean release method
0510: * that allows to use this adapter later again.
0511: *
0512: * @param newBean the new holder of the adapted properties
0513: *
0514: * @see #getBean()
0515: * @see #isChanged()
0516: * @see #resetChanged()
0517: * @see #release()
0518: */
0519: public void setBean(B newBean) {
0520: beanChannel.setValue(newBean);
0521: resetChanged();
0522: }
0523:
0524: /**
0525: * Answers whether this adapter observes changes in the
0526: * adapted Bean properties.
0527: *
0528: * @return true if this adapter observes changes, false if not
0529: */
0530: public boolean getObserveChanges() {
0531: return observeChanges;
0532: }
0533:
0534: /*
0535: * Sets whether changes in the bean's bound property shall be observed.
0536: * As a requirement the bean must provide support for listenening
0537: * on property changes.
0538: *
0539: * @param newValue true to observe changes, false to ignore them
0540: public void setObserveChanges(boolean newValue) {
0541: if (newValue == getObserveChanges())
0542: return;
0543: observeChanges = newValue;
0544: Object bean = getBean();
0545: removePropertyChangeHandler(bean);
0546: addPropertyChangeHandler(bean);
0547: }
0548: */
0549:
0550: // Accessing Property Values **********************************************
0551: /**
0552: * Returns the value of specified bean property, <code>null</code>
0553: * if the current bean is <code>null</code>.<p>
0554: *
0555: * This operation is supported only for readable bean properties.
0556: *
0557: * @param propertyName the name of the property to be read
0558: * @return the value of the adapted bean property, null if the bean is null
0559: *
0560: * @throws NullPointerException if propertyName is null
0561: * @throws UnsupportedOperationException if the property is write-only
0562: * @throws PropertyNotFoundException if the property could not be found
0563: * @throws PropertyAccessException if the value could not be read
0564: */
0565: public Object getValue(String propertyName) {
0566: return getValueModel(propertyName).getValue();
0567: }
0568:
0569: /**
0570: * Sets the given new value for the specified bean property. Does nothing
0571: * if this adapter's bean is <code>null</code>. If the setter associated
0572: * with the propertyName throws a PropertyVetoException, it is silently
0573: * ignored.<p>
0574: *
0575: * Notifies the associated value change listeners if the bean reports
0576: * a property change. Note that a bean may suppress PropertyChangeEvents
0577: * if the old and new value are the same, or if the old and new value
0578: * are equal.<p>
0579: *
0580: * This operation is supported only for writable bean properties.
0581: *
0582: * @param propertyName the name of the property to set
0583: * @param newValue the value to set
0584: *
0585: * @throws NullPointerException if propertyName is null
0586: * @throws UnsupportedOperationException if the property is read-only
0587: * @throws PropertyNotFoundException if the property could not be found
0588: * @throws PropertyAccessException if the new value could not be set
0589: */
0590: public void setValue(String propertyName, Object newValue) {
0591: getValueModel(propertyName).setValue(newValue);
0592: }
0593:
0594: /**
0595: * Sets a new value for the specified bean property. Does nothing if the
0596: * bean is <code>null</code>. If the setter associated with the propertyName
0597: * throws a PropertyVetoException, this methods throws the same exception.<p>
0598: *
0599: * Notifies the associated value change listeners if the bean reports
0600: * a property change. Note that a bean may suppress PropertyChangeEvents
0601: * if the old and new value are the same, or if the old and new value
0602: * are equal.<p>
0603: *
0604: * This operation is supported only for writable bean properties.
0605: *
0606: * @param propertyName the name of the property to set
0607: * @param newValue the value to set
0608: *
0609: * @throws NullPointerException if propertyName is null
0610: * @throws UnsupportedOperationException if the property is read-only
0611: * @throws PropertyNotFoundException if the property could not be found
0612: * @throws PropertyAccessException if the new value could not be set
0613: * @throws PropertyVetoException if the bean setter
0614: * throws a PropertyVetoException
0615: *
0616: * @since 1.1
0617: */
0618: public void setVetoableValue(String propertyName, Object newValue)
0619: throws PropertyVetoException {
0620: getValueModel(propertyName).setVetoableValue(newValue);
0621: }
0622:
0623: // Creating and Accessing Adapting ValueModels ****************************
0624:
0625: /**
0626: * Looks up and lazily creates a ValueModel that adapts
0627: * the bound property with the specified name. Uses the
0628: * Bean introspection to look up the getter and setter names.<p>
0629: *
0630: * Subsequent calls to this method with the same property name
0631: * return the same ValueModel.<p>
0632: *
0633: * To prevent potential runtime errors this method eagerly looks up
0634: * the associated PropertyDescriptor if the target bean is not null.<p>
0635: *
0636: * For each property name all calls to this method and to
0637: * <code>#getValueModel(String, String, String)</code> must use
0638: * the same getter and setter names. Attempts to violate this constraint
0639: * will be rejected with an IllegalArgumentException. Especially once
0640: * you've called this method you must not call
0641: * <code>#getValueModel(String, String, String)</code> with a non-null
0642: * getter or setter name. And vice versa, once you've called the latter
0643: * method with a non-null getter or setter name, you must not call
0644: * this method.<p>
0645: *
0646: * This method uses a return type of AbstractValueModel, not a ValueModel.
0647: * This makes {@link #setVetoableValue(String, Object)} visible. It also
0648: * makes the AbstractValueModel convenience type converters available,
0649: * which can significantly shrink the source code necessary to read and
0650: * write values from/to these models.
0651: *
0652: * @param propertyName the name of the property to adapt
0653: * @return a ValueModel that adapts the property with the specified name
0654: *
0655: * @throws NullPointerException if propertyName is null
0656: * @throws PropertyNotFoundException if the property could not be found
0657: * @throws IllegalArgumentException
0658: * if <code>#getValueModel(String, String, String)</code> has been
0659: * called before with the same property name and a non-null getter
0660: * or setter name
0661: */
0662: public SimplePropertyAdapter getValueModel(String propertyName) {
0663: return getValueModel(propertyName, null, null);
0664: }
0665:
0666: /**
0667: * Looks up and lazily creates a ValueModel that adapts the bound property
0668: * with the specified name. Unlike <code>#getValueModel(String)</code>
0669: * this method bypasses the Bean Introspection and uses the given getter
0670: * and setter names to setup the access to the adapted Bean property.<p>
0671: *
0672: * Subsequent calls to this method with the same parameters
0673: * will return the same ValueModel.<p>
0674: *
0675: * To prevent potential runtime errors this method eagerly looks up
0676: * the associated PropertyDescriptor if the target bean is not null.<p>
0677: *
0678: * For each property name all calls to this method
0679: * and to <code>#getValueModel(String)</code> must use the same
0680: * getter and setter names. Attempts to violate this constraint
0681: * will be rejected with an IllegalArgumentException. Especially
0682: * once you've called this method with a non-null getter or setter name,
0683: * you must not call <code>#getValueModel(String)</code>. And vice versa,
0684: * once you've called the latter method you must not call this method
0685: * with a non-null getter or setter name.<p>
0686: *
0687: * This method uses a return type of AbstractValueModel, not a ValueModel.
0688: * This makes {@link #setVetoableValue(String, Object)} visible. It also
0689: * makes the AbstractValueModel convenience type converters available,
0690: * which can significantly shrink the source code necessary to read and
0691: * write values from/to these models.
0692: *
0693: * @param propertyName the name of the property to adapt
0694: * @param getterName the name of the method that reads the value
0695: * @param setterName the name of the method that sets the value
0696: * @return a ValueModel that adapts the property with the specified name
0697: *
0698: * @throws NullPointerException if propertyName is null
0699: * @throws PropertyNotFoundException if the property could not be found
0700: * @throws IllegalArgumentException if this method has been called before
0701: * with the same property name and different getter or setter names
0702: */
0703: public SimplePropertyAdapter getValueModel(String propertyName,
0704: String getterName, String setterName) {
0705: if (propertyName == null)
0706: throw new NullPointerException(
0707: "The property name must not be null.");
0708:
0709: SimplePropertyAdapter adaptingModel = getPropertyAdapter(propertyName);
0710: if (adaptingModel == null) {
0711: adaptingModel = createPropertyAdapter(propertyName,
0712: getterName, setterName);
0713: propertyAdapters.put(propertyName, adaptingModel);
0714: } else if (!equals(getterName, adaptingModel.getterName)
0715: || !equals(setterName, adaptingModel.setterName)) {
0716: throw new IllegalArgumentException(
0717: "You must not invoke this method twice "
0718: + "with different getter and/or setter names.");
0719: }
0720: return adaptingModel;
0721: }
0722:
0723: /**
0724: * Looks up and returns the SimplePropertyAdapter that adapts
0725: * the bound property with the specified name.
0726: *
0727: * @param propertyName the name of the adapted property
0728: * @return a SimplePropertyAdapter that adapts the bound property
0729: * with the specified name or null, if none has been created before
0730: */
0731: SimplePropertyAdapter getPropertyAdapter(String propertyName) {
0732: return propertyAdapters.get(propertyName);
0733: }
0734:
0735: /**
0736: * Creates and returns a SimplePropertyAdapter that adapts
0737: * the bound property with the specified name.
0738: *
0739: * @param propertyName the name of the property to adapt
0740: * @param getterName the name of the method that reads the value
0741: * @param setterName the name of the method that sets the value
0742: * @return a SimplePropertyAdapter that adapts the property
0743: * with the specified name
0744: *
0745: * @since 1.4
0746: */
0747: protected SimplePropertyAdapter createPropertyAdapter(
0748: String propertyName, String getterName, String setterName) {
0749: return new SimplePropertyAdapter(propertyName, getterName,
0750: setterName);
0751: }
0752:
0753: // Accessing the Changed State ********************************************
0754:
0755: /**
0756: * Answers whether a bean property has changed since the changed state
0757: * has been reset. The changed state is implicitly reset every time
0758: * the target bean changes.
0759: *
0760: * @return true if a property of the current target bean
0761: * has changed since the last reset
0762: */
0763: public boolean isChanged() {
0764: return changed;
0765: }
0766:
0767: /**
0768: * Resets this tracker's changed state to <code>false</code>.
0769: */
0770: public void resetChanged() {
0771: setChanged(false);
0772: }
0773:
0774: /**
0775: * Sets the changed state to the given value. Invoked by the global
0776: * PropertyChangeHandler that observes all bean changes. Also invoked
0777: * by <code>#resetChanged</code>.
0778: *
0779: * @param newValue the new changed state
0780: */
0781: private void setChanged(boolean newValue) {
0782: boolean oldValue = isChanged();
0783: changed = newValue;
0784: firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
0785: }
0786:
0787: // Managing Bean Property Change Listeners *******************************
0788:
0789: /**
0790: * Adds a PropertyChangeListener to the list of bean listeners. The
0791: * listener is registered for all bound properties of the target bean.<p>
0792: *
0793: * The listener will be notified if and only if this BeanAdapter's current
0794: * bean changes a property. It'll not be notified if the bean changes.<p>
0795: *
0796: * If listener is <code>null</code>, no exception is thrown and no action
0797: * is performed.
0798: *
0799: * @param listener the PropertyChangeListener to be added
0800: *
0801: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
0802: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
0803: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
0804: * @see #getBeanPropertyChangeListeners()
0805: */
0806: public synchronized void addBeanPropertyChangeListener(
0807: PropertyChangeListener listener) {
0808: if (listener == null) {
0809: return;
0810: }
0811: if (indirectChangeSupport == null) {
0812: indirectChangeSupport = new IndirectPropertyChangeSupport(
0813: beanChannel);
0814: }
0815: indirectChangeSupport.addPropertyChangeListener(listener);
0816: }
0817:
0818: /**
0819: * Removes a PropertyChangeListener from the list of bean listeners.
0820: * This method should be used to remove PropertyChangeListeners that
0821: * were registered for all bound properties of the target bean.<p>
0822: *
0823: * If listener is <code>null</code>, no exception is thrown and no action is performed.
0824: *
0825: * @param listener the PropertyChangeListener to be removed
0826: *
0827: * @see #addBeanPropertyChangeListener(PropertyChangeListener)
0828: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
0829: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
0830: * @see #getBeanPropertyChangeListeners()
0831: */
0832: public synchronized void removeBeanPropertyChangeListener(
0833: PropertyChangeListener listener) {
0834: if (listener == null || indirectChangeSupport == null) {
0835: return;
0836: }
0837: indirectChangeSupport.removePropertyChangeListener(listener);
0838: }
0839:
0840: /**
0841: * Adds a PropertyChangeListener to the list of bean listeners for a
0842: * specific property. The specified property may be user-defined.<p>
0843: *
0844: * The listener will be notified if and only if this BeanAdapter's
0845: * current bean changes the specified property. It'll not be notified
0846: * if the bean changes. If you want to observe property changes and
0847: * bean changes, you may observe the ValueModel that adapts this property
0848: * - as returned by <code>#getValueModel(String)</code>.<p>
0849: *
0850: * Note that if the bean is inheriting a bound property, then no event
0851: * will be fired in response to a change in the inherited property.<p>
0852: *
0853: * If listener is <code>null</code>, no exception is thrown and no action is performed.
0854: *
0855: * @param propertyName one of the property names listed above
0856: * @param listener the PropertyChangeListener to be added
0857: *
0858: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
0859: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
0860: * @see #getBeanPropertyChangeListeners(String)
0861: */
0862: public synchronized void addBeanPropertyChangeListener(
0863: String propertyName, PropertyChangeListener listener) {
0864: if (listener == null) {
0865: return;
0866: }
0867: if (indirectChangeSupport == null) {
0868: indirectChangeSupport = new IndirectPropertyChangeSupport(
0869: beanChannel);
0870: }
0871: indirectChangeSupport.addPropertyChangeListener(propertyName,
0872: listener);
0873: }
0874:
0875: /**
0876: * Removes a PropertyChangeListener from the listener list for a specific
0877: * property. This method should be used to remove PropertyChangeListeners
0878: * that were registered for a specific bound property.<p>
0879: *
0880: * If listener is <code>null</code>, no exception is thrown and no action is performed.
0881: *
0882: * @param propertyName a valid property name
0883: * @param listener the PropertyChangeListener to be removed
0884: *
0885: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
0886: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
0887: * @see #getBeanPropertyChangeListeners(String)
0888: */
0889: public synchronized void removeBeanPropertyChangeListener(
0890: String propertyName, PropertyChangeListener listener) {
0891: if (listener == null || indirectChangeSupport == null) {
0892: return;
0893: }
0894: indirectChangeSupport.removePropertyChangeListener(
0895: propertyName, listener);
0896: }
0897:
0898: // Requesting Listener Sets ***********************************************
0899:
0900: /**
0901: * Returns an array of all the property change listeners
0902: * registered on this component.
0903: *
0904: * @return all of this component's <code>PropertyChangeListener</code>s
0905: * or an empty array if no property change
0906: * listeners are currently registered
0907: *
0908: * @see #addBeanPropertyChangeListener(PropertyChangeListener)
0909: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
0910: * @see #getBeanPropertyChangeListeners(String)
0911: * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners()
0912: */
0913: public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners() {
0914: if (indirectChangeSupport == null) {
0915: return new PropertyChangeListener[0];
0916: }
0917: return indirectChangeSupport.getPropertyChangeListeners();
0918: }
0919:
0920: /**
0921: * Returns an array of all the listeners which have been associated
0922: * with the named property.
0923: *
0924: * @param propertyName the name of the property to lookup listeners
0925: * @return all of the <code>PropertyChangeListeners</code> associated with
0926: * the named property or an empty array if no listeners have
0927: * been added
0928: *
0929: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
0930: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
0931: * @see #getBeanPropertyChangeListeners()
0932: */
0933: public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners(
0934: String propertyName) {
0935: if (indirectChangeSupport == null) {
0936: return new PropertyChangeListener[0];
0937: }
0938: return indirectChangeSupport
0939: .getPropertyChangeListeners(propertyName);
0940: }
0941:
0942: // Releasing PropertyChangeListeners **************************************
0943:
0944: /**
0945: * Removes the PropertyChangeHandler from the observed bean, if the bean
0946: * is not <code>null</code> and if bean property changes are observed.
0947: * Also removes all listeners from the bean that have been registered
0948: * with <code>#addBeanPropertyChangeListener</code> before.<p>
0949: *
0950: * BeanAdapters that observe changes have a PropertyChangeListener
0951: * registered with the target bean. Hence, a bean has a reference
0952: * to all BeanAdapters that observe it. To avoid memory leaks
0953: * it is recommended to remove this listener if the bean lives much longer
0954: * than the BeanAdapter, enabling the garbage collector to remove the adapter.
0955: * To do so, you can call <code>setBean(null)</code> or set the
0956: * bean channel's value to null.
0957: * As an alternative you can use event listener lists in your beans
0958: * that implement references with <code>WeakReference</code>.<p>
0959: *
0960: * Setting the bean to null has side-effects, for example the adapter
0961: * fires a change event for the bound property <em>bean</em> and other properties.
0962: * And the value of ValueModel's vended by this adapter may change.
0963: * However, typically this is fine and setting the bean to null
0964: * is the first choice for removing the reference from the bean to the adapter.
0965: * Another way to clear the reference from the target bean is
0966: * to call <code>#release</code>. It has no side-effects, but the adapter
0967: * must not be used anymore once #release has been called.
0968: *
0969: * @see #setBean(Object)
0970: * @see java.lang.ref.WeakReference
0971: */
0972: public synchronized void release() {
0973: removeChangeHandlerFrom(getBean());
0974: if (indirectChangeSupport != null) {
0975: indirectChangeSupport.removeAll();
0976: }
0977: }
0978:
0979: // Changing the Bean & Adding and Removing the PropertyChangeHandler ******
0980:
0981: private void setBean0(B oldBean, B newBean) {
0982: firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean,
0983: true);
0984: removeChangeHandlerFrom(oldBean);
0985: forwardAllAdaptedValuesChanged(oldBean, newBean);
0986: resetChanged();
0987: addChangeHandlerTo(newBean);
0988: firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true);
0989: firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean,
0990: true);
0991: }
0992:
0993: /**
0994: * Iterates over all internal property adapters to notify them
0995: * about a bean change from oldBean to newBean. These adapters then notify
0996: * their observers to inform them about a value change - if any.<p>
0997: *
0998: * Iterates over a copy of the property adapters to avoid
0999: * ConcurrentModifications that may be thrown if a listener creates
1000: * a new SimplePropertyAdapter by requesting an adapting model using
1001: * <code>#getValueModel</code>.
1002: *
1003: * @param oldBean the bean before the change
1004: * @param newBean the bean after the change
1005: */
1006: private void forwardAllAdaptedValuesChanged(B oldBean, B newBean) {
1007: List<SimplePropertyAdapter> copiedList = new LinkedList<SimplePropertyAdapter>(
1008: propertyAdapters.values());
1009: for (SimplePropertyAdapter adapter : copiedList) {
1010: adapter.setBean0(oldBean, newBean);
1011: }
1012: }
1013:
1014: /**
1015: * Iterates over all internal property adapters to notify them
1016: * about a value change in the bean. These adapters then notify
1017: * their observers to inform them about a value change - if any.<p>
1018: *
1019: * Iterates over a copy of the property adapters to avoid
1020: * ConcurrentModifications that may be thrown if a listener creates
1021: * a new SimplePropertyAdapter by requesting an adapting model using
1022: * <code>#getValueModel</code>.
1023: */
1024: private void forwardAllAdaptedValuesChanged() {
1025: B currentBean = getBean();
1026: List<SimplePropertyAdapter> copiedList = new LinkedList<SimplePropertyAdapter>(
1027: propertyAdapters.values());
1028: for (SimplePropertyAdapter adapter : copiedList) {
1029: adapter.fireChange(currentBean);
1030: }
1031: }
1032:
1033: /**
1034: * Adds a property change listener to the given bean if we observe changes
1035: * and the bean is not null. First checks whether the bean class
1036: * supports <em>bound properties</em>, i.e. it provides a pair of methods
1037: * to register multicast property change event listeners;
1038: * see section 7.4.1 of the Java Beans specification for details.
1039: *
1040: * @param bean the bean to add a property change handler.
1041: * @throws PropertyUnboundException
1042: * if the bean does not support bound properties
1043: * @throws PropertyNotBindableException
1044: * if the property change handler cannot be added successfully
1045: *
1046: * @see #removeChangeHandlerFrom(Object)
1047: */
1048: private void addChangeHandlerTo(B bean) {
1049: if (!observeChanges || bean == null) {
1050: return;
1051: }
1052: propertyChangeHandler = new PropertyChangeHandler();
1053: BeanUtils.addPropertyChangeListener(bean, getBeanClass(bean),
1054: propertyChangeHandler);
1055: }
1056:
1057: /**
1058: * Removes the formerly added property change handler from the given bean
1059: * if we observe changes and the bean is not null.
1060: *
1061: * @param bean the bean to remove the property change handler from.
1062: * @throws PropertyUnboundException
1063: * if the bean does not support bound properties
1064: * @throws PropertyNotBindableException
1065: * if the property change handler cannot be removed successfully
1066: *
1067: * @see #addChangeHandlerTo(Object)
1068: */
1069: private void removeChangeHandlerFrom(B bean) {
1070: if (!observeChanges || bean == null) {
1071: return;
1072: }
1073: BeanUtils.removePropertyChangeListener(bean,
1074: getBeanClass(bean), propertyChangeHandler);
1075: propertyChangeHandler = null;
1076: }
1077:
1078: // Helper Methods to Get and Set a Property Value *************************
1079:
1080: /**
1081: * Returns the Java Bean class used by this adapter.
1082: * The current implementation just returns the given bean's class.<p>
1083: *
1084: * A future version may return a type other than the concrete
1085: * class of the given bean. This beanClass could be specified
1086: * in a new set of constructors. This is useful if the beans
1087: * are specified by public interfaces, and implemented by
1088: * package private classes. In this case, the class of the given bean
1089: * object shall be checked against the specified type.
1090: *
1091: * @param bean the bean that may be used to lookup the class from
1092: * @return the Java Bean class used for this adapter.
1093: */
1094: private Class<?> getBeanClass(B bean) {
1095: return bean.getClass();
1096: // TODO: A future version shall add a check like
1097: // beanClass.isInstance(bean) if the beanClass
1098: // has been specified in the constructor.
1099: }
1100:
1101: /**
1102: * Returns the value of the specified property of the given bean,
1103: * <code>null</code> if the bean is <code>null</code>.
1104: *
1105: * @param bean the bean to read the value from
1106: * @param propertyDescriptor describes the property to be read
1107: * @return the bean's property value
1108: */
1109: private Object getValue0(B bean,
1110: PropertyDescriptor propertyDescriptor) {
1111: return bean == null ? null : BeanUtils.getValue(bean,
1112: propertyDescriptor);
1113: }
1114:
1115: /**
1116: * Sets the given object as new value of the specified property of the
1117: * given bean. Does nothing if the bean is null. This write operation is
1118: * supported only for writable bean properties.
1119: *
1120: * @param bean the bean that holds the adapted property
1121: * @param propertyDescriptor describes the property to be set
1122: * @param newValue the property value to be set
1123: *
1124: * @throws NullPointerException if the bean is null
1125: * @throws PropertyNotFoundException if the property could not be found
1126: * @throws PropertyAccessException if the write access failed
1127: * @throws PropertyVetoException if the invoked bean setter
1128: * throws a PropertyVetoException
1129: */
1130: private void setValue0(B bean,
1131: PropertyDescriptor propertyDescriptor, Object newValue)
1132: throws PropertyVetoException {
1133: BeanUtils.setValue(bean, propertyDescriptor, newValue);
1134: }
1135:
1136: /**
1137: * Throws an IllegalArgumentException if the given ValueModel
1138: * is a ValueHolder that has the identityCheck feature disabled.
1139: */
1140: private void checkBeanChannelIdentityCheck(ValueModel valueModel) {
1141: if (!(valueModel instanceof ValueHolder))
1142: return;
1143:
1144: ValueHolder valueHolder = (ValueHolder) valueModel;
1145: if (!valueHolder.isIdentityCheckEnabled())
1146: throw new IllegalArgumentException(
1147: "The bean channel must have the identity check enabled.");
1148: }
1149:
1150: // Helper Classes *********************************************************
1151:
1152: /**
1153: * Listens to changes of the bean.
1154: */
1155: private final class BeanChangeHandler implements
1156: PropertyChangeListener {
1157:
1158: /**
1159: * The bean has been changed. Uses the stored old bean instead of
1160: * the event's old value, because the latter can be null.
1161: * If the event's new value is null, the new bean is requested
1162: * from the bean channel.
1163: *
1164: * @param evt the property change event to be handled
1165: */
1166: public void propertyChange(PropertyChangeEvent evt) {
1167: B newBean = evt.getNewValue() != null ? (B) evt
1168: .getNewValue() : getBean();
1169: setBean0(storedOldBean, newBean);
1170: storedOldBean = newBean;
1171: }
1172: }
1173:
1174: /**
1175: * Listens to changes of all bean properties. Fires property changes
1176: * if the associated property or an arbitrary set of properties has changed.
1177: */
1178: private final class PropertyChangeHandler implements
1179: PropertyChangeListener {
1180:
1181: /**
1182: * A bean property has been changed. Sets the changed state to true.
1183: * Checks whether the observed or multiple properties have changed.
1184: * If so, notifies all registered listeners about the change.
1185: *
1186: * @param evt the property change event to be handled
1187: */
1188: public void propertyChange(PropertyChangeEvent evt) {
1189: setChanged(true);
1190: String propertyName = evt.getPropertyName();
1191: if (propertyName == null) {
1192: forwardAllAdaptedValuesChanged();
1193: } else {
1194: SimplePropertyAdapter adapter = getPropertyAdapter(propertyName);
1195: if (adapter != null) {
1196: adapter.fireValueChange(evt.getOldValue(), evt
1197: .getNewValue(), true);
1198: }
1199: }
1200: }
1201: }
1202:
1203: /**
1204: * Implements the access to the individual bean properties.
1205: * All SimplePropertyAdapters created by this BeanAdapter
1206: * share a single PropertyChangeListener that is used to
1207: * fire value changes in this SimplePropertyAdapter.<p>
1208: *
1209: * This class is public to enable reflection access.
1210: */
1211: public class SimplePropertyAdapter extends AbstractValueModel {
1212:
1213: /**
1214: * Holds the name of the adapted property.
1215: */
1216: private final String propertyName;
1217:
1218: /**
1219: * Holds the optional name of the property's getter.
1220: * Used to create the PropertyDescriptor.
1221: * Also used to reject potential misuse of
1222: * {@link BeanAdapter#getValueModel(String)} and
1223: * {@link BeanAdapter#getValueModel(String, String, String)}.
1224: * See the latter methods for details.
1225: */
1226: final String getterName;
1227:
1228: /**
1229: * Holds the optional name of the property's setter.
1230: * Used to create the PropertyDescriptor.
1231: * Also used to reject potential misuse of
1232: * {@link BeanAdapter#getValueModel(String)} and
1233: * {@link BeanAdapter#getValueModel(String, String, String)}.
1234: * See the latter methods for details.
1235: */
1236: final String setterName;
1237:
1238: /**
1239: * Describes the property accessor; basically a getter and setter.
1240: */
1241: private PropertyDescriptor cachedPropertyDescriptor;
1242:
1243: /**
1244: * Holds the bean class associated with the cached property descriptor.
1245: */
1246: private Class<?> cachedBeanClass;
1247:
1248: // Instance Creation --------------------------------------------------
1249:
1250: /**
1251: * Constructs a SimplePropertyAdapter for the given property name,
1252: * getter and setter name.
1253: *
1254: * @param propertyName the name of the property to adapt
1255: * @param getterName the name of the method that reads the value
1256: * @param setterName the name of the method that sets the value
1257: */
1258: protected SimplePropertyAdapter(String propertyName,
1259: String getterName, String setterName) {
1260: this .propertyName = propertyName;
1261: this .getterName = getterName;
1262: this .setterName = setterName;
1263:
1264: // Eagerly check the existence of the property to adapt.
1265: B bean = getBean();
1266: if (bean != null) {
1267: getPropertyDescriptor(bean);
1268: }
1269: }
1270:
1271: // Implementing ValueModel --------------------------------------------
1272:
1273: /**
1274: * Returns the value of the adapted bean property, or null
1275: * if the bean is null.
1276: *
1277: * @return the value of the adapted bean property,
1278: * null if the bean is null
1279: */
1280: public Object getValue() {
1281: B bean = getBean();
1282: return bean == null ? null : getValue0(bean,
1283: getPropertyDescriptor(bean));
1284: }
1285:
1286: /**
1287: * Sets the given object as new value of the adapted bean property.
1288: * Does nothing if the bean is <code>null</code>. If the bean setter
1289: * throws a PropertyVetoException, it is silently ignored.
1290: * This write operation is supported only for writable bean properties.<p>
1291: *
1292: * Notifies any registered value listener if the bean reports
1293: * a property change. Note that a bean may suppress PropertyChangeEvents
1294: * if the old and new value are the same, or if the old and new value
1295: * are equal.
1296: *
1297: * @param newValue the value to set
1298: *
1299: * @throws UnsupportedOperationException if the property is read-only
1300: * @throws PropertyNotFoundException if the property could not be found
1301: * @throws PropertyAccessException if the new value could not be set
1302: */
1303: public void setValue(Object newValue) {
1304: B bean = getBean();
1305: if (bean == null)
1306: return;
1307: try {
1308: setValue0(bean, getPropertyDescriptor(bean), newValue);
1309: } catch (PropertyVetoException e) {
1310: // Silently ignore that someone vetoed against this change
1311: }
1312: }
1313:
1314: /**
1315: * Sets the given object as new value of the adapted bean property.
1316: * Does nothing if the bean is <code>null</code>. If the bean setter
1317: * throws a PropertyVetoExeption, this method throws the same exception.
1318: * This write operation is supported only for writable bean properties.<p>
1319: *
1320: * Notifies any registered value listener if the bean reports
1321: * a property change. Note that a bean may suppress PropertyChangeEvents
1322: * if the old and new value are the same, or if the old and new value
1323: * are equal.
1324: *
1325: * @param newValue the value to set
1326: *
1327: * @throws UnsupportedOperationException if the property is read-only
1328: * @throws PropertyNotFoundException if the property could not be found
1329: * @throws PropertyAccessException if the new value could not be set
1330: * @throws PropertyVetoException if the invoked bean setter
1331: * throws a PropertyVetoException
1332: *
1333: * @since 1.1
1334: */
1335: public void setVetoableValue(Object newValue)
1336: throws PropertyVetoException {
1337: B bean = getBean();
1338: if (bean == null)
1339: return;
1340: setValue0(bean, getPropertyDescriptor(bean), newValue);
1341: }
1342:
1343: // Accessing the Cached Property Descriptor --------------------------
1344:
1345: /**
1346: * Looks up, lazily initializes and returns a <code>PropertyDescriptor</code>
1347: * for the given Java Bean and name of the adapted property.<p>
1348: *
1349: * The cached PropertyDescriptor is considered invalid if the
1350: * bean's class has changed. In this case we recompute the
1351: * PropertyDescriptor.<p>
1352: *
1353: * If a getter name or setter name is available, these are used
1354: * to directly create a PropertyDescriptor. Otherwise, the standard
1355: * Java Bean introspection is used to determine the property descriptor.
1356: *
1357: * @param bean the bean that holds the property
1358: * @return the <code>PropertyDescriptor</code>
1359: * @throws PropertyNotFoundException if the property could not be found
1360: */
1361: private PropertyDescriptor getPropertyDescriptor(B bean) {
1362: Class<?> beanClass = getBeanClass(bean);
1363: if ((cachedPropertyDescriptor == null)
1364: || (beanClass != cachedBeanClass)) {
1365:
1366: cachedPropertyDescriptor = BeanUtils
1367: .getPropertyDescriptor(beanClass, propertyName,
1368: getterName, setterName);
1369: cachedBeanClass = beanClass;
1370: }
1371: return cachedPropertyDescriptor;
1372: }
1373:
1374: protected void fireChange(B currentBean) {
1375: Object newValue;
1376: if (currentBean == null) {
1377: newValue = null;
1378: } else {
1379: PropertyDescriptor propertyDescriptor = getPropertyDescriptor(currentBean);
1380: boolean isWriteOnly = null == propertyDescriptor
1381: .getReadMethod();
1382: newValue = isWriteOnly ? null : getValue0(currentBean,
1383: propertyDescriptor);
1384: }
1385: fireValueChange(null, newValue);
1386: }
1387:
1388: protected void setBean0(B oldBean, B newBean) {
1389: Object oldValue;
1390: Object newValue;
1391: if (oldBean == null) {
1392: oldValue = null;
1393: } else {
1394: PropertyDescriptor propertyDescriptor = getPropertyDescriptor(oldBean);
1395: boolean isWriteOnly = null == propertyDescriptor
1396: .getReadMethod();
1397: oldValue = isWriteOnly ? null : getValue0(oldBean,
1398: propertyDescriptor);
1399: }
1400: if (newBean == null) {
1401: newValue = null;
1402: } else {
1403: PropertyDescriptor propertyDescriptor = getPropertyDescriptor(newBean);
1404: boolean isWriteOnly = null == propertyDescriptor
1405: .getReadMethod();
1406: newValue = isWriteOnly ? null : getValue0(newBean,
1407: propertyDescriptor);
1408: }
1409: if (oldValue != null || newValue != null) {
1410: fireValueChange(oldValue, newValue, true);
1411: }
1412: }
1413:
1414: }
1415:
1416: }
|