Logo Search packages:      
Sourcecode: libjgoodies-binding-java version File versions

PresentationModel.java

/*
 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. 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 Karsten Lentzsch 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;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.JComponent;

import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.beans.*;
import com.jgoodies.binding.value.*;

/**
 * The standard base class to implement the <em>Presentation Model</em> pattern,
 * that represents the state and behavior of a presentation independently 
 * of the GUI components used in the interface. This 
 * <a href="http://martinfowler.com/eaaDev/PresentationModel.html">pattern</a>
 * is described in Martin Fowler's upcoming 
 * <a href="http://martinfowler.com/eaaDev/">addition</a>
 * to his "Patterns of Enterprise Application Architecture". More details 
 * around this implementation of the Presentation Model pattern and a 3-tier 
 * Swing client architecture with a presentation model layer can be found in 
 * the <a href="http://www.jgoodies.com/articles/binding.pdf">JGoodies 
 * Binding presentation</a>. This architecture is supported
 * by the JGoodies Binding library. The PresentationModel pattern is known 
 * to users of VisualWorks Smalltalk as <em>ApplicationModel</em>.<p>
 * 
 * This class minimizes the effort required to bind, edit, 
 * buffer, and observe the bound properties of an exchangeable bean.
 * Therefore it provides five groups of features that are described below:<ol>
 * <li>adapt bean properties,
 * <li>change the adapted bean,
 * <li>buffer values,
 * <li>observe the buffering state, and
 * <li>track changes in adapted bean properties.
 * </ol><p>
 * 
 * Typically this class will be extended to add custom models, Actions, 
 * presentation logic, model operations and other higher-level behavior. 
 * However, in simple cases you can use this class as-is.
 * Several methods are intended to be used as-is and a typical subclass
 * should not modify them. For example #isChanged, #isBuffering, 
 * #getBean, #setBean, #getBeanChannel, #getModel, #getBufferedModel, 
 * #getTriggerChannel, #setTriggerChannel, #triggerCommit and #triggerFlush.<p>
 * 
 * <strong>Adapting Bean Properties</strong><br> 
 * The method {@link #getModel(String)} vends ValueModels that adapt 
 * a bound bean property of an exchangable bean. These ValueModels will be 
 * requested from an underlying BeanAdapter.
 * To get such a model you specify the name of the bean property.
 * All properties adapted must be read-write and must comply with 
 * the Java Bean coding conventions. 
 * In case you need to adapt a read-only or write-only property, 
 * or if the bean uses custom names for the reader and writer, 
 * use {@link #getModel(String, String, String)}.
 * Also note that you must not mix calls to these methods for the same
 * property name. For details see the JavaDoc class comment in
 * {@link com.jgoodies.binding.beans.BeanAdapter}.<p> 
 * 
 * <strong>Changing the Adapted Bean</strong><br>
 * The adapted bean is not stored in this PresentationModel.
 * Instead it is held by a ValueModel, the <em>bean channel</em>
 * - just as in the PropertyAdapter and BeanAdapter.
 * This indirection enables you to manage the adapted bean outside
 * of this PresentationModel, and it enables you to share bean channels 
 * between multiple PresentationModels, PropertyAdapters, and BeanAdapters.
 * The bean channel is used by all adapting models created 
 * by the factory methods <code>#getModel</code>.
 * You can get and set the current bean by means of <code>#getBean</code>
 * and <code>#setBean</code>. Or you can set a new value to the bean channel.<p> 
 * 
 * PresentationModel fires three PropertyChangeEvents if the bean changes:
 * <i>beforeBean</i>, <i>bean</i> and <i>afterBean</i>. 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 <i>beforeBean</i> 
 * and <i>afterBean</i> events are guaranteed to be fired before and
 * after the bean change is fired. 
 * Note that <code>#getBean()</code> 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:<ol>
 * <li>the bean channel fires a <i>value</i> change,
 * <li>this model fires a <i>beforeBean</i> change,
 * <li>this model fires the <i>bean</i> change,
 * <li>this model fires an <i>afterBean</i> change.
 * </ol>
 * 
 * <strong>Buffering Values</strong><br>
 * At the core of this feature are the methods {@link #getBufferedModel(String)}
 * that vend BufferedValueModels that wrap an adapted bean property.
 * The buffer can be commited or flushed using <code>#triggerCommit</code>
 * and <code>#triggerFlush</code> respectively.<p>
 * 
 * The trigger channel is provided as a bound Java bean property 
 * <em>triggerChannel</em> that must be a non-<code>null</code> 
 * <code>ValueModel</code> with values of type <code>Boolean</code>. 
 * Attempts to read or write other value types may be rejected 
 * with runtime exceptions.
 * By default the trigger channel is initialized as an instance of
 * <code>Trigger</code>. As an alternative it can be set in the constructor.<p>
 * 
 * <strong>Observing the Buffering State</strong><br>
 * This class also provides support for observing the buffering state
 * of the BufferedValueModels created with this model. The buffering state
 * is useful for UI actions and operations that are enabled or disabled
 * if there are pending changes, for example on OK or APPLY button.
 * API users can request the buffering state via <code>#isBuffering</code> 
 * and can observe the bound property <em>buffering</em>.<p>  
 * 
 * <strong>Tracking Changes in the Adapted Bean</strong><br>
 * PresentationModel provides support for observing bean property changes 
 * and it tracks all changes to report the overall changed state. 
 * The latter is useful to detect whether the bean has changed at all,
 * for example to mark the bean as dirty, so it will be updated in a database.
 * API users can request the changed state via <code>#isChanged</code> 
 * and can observe the bound property <em>changed</em>.
 * If you want to track changes of other ValueModels, bean properties,
 * or of submodels, register them using <code>#observeChanged</code>.
 * To reset the changed state invoke <code>#resetChanged</code>.
 * In case you track the changed state of submodels you should override
 * <code>#resetChanged</code> to reset the changed state in these submodels.<p>
 * 
 * The changed state changes once only (from false to true). If you need
 * instant notifications about changes in the properties of the target bean,
 * you can register PropertyChangeListeners with this model. This is useful 
 * if you change the bean and don't want to move your listeners from one bean
 * to the other. And it's useful if you want to observe multiple bean 
 * properties at the same time. These listeners are managed by the method set
 * <code>#addBeanPropertyChangeListener</code> and
 * <code>#removeBeanPropertyChangeListener</code>.
 * Listeners registered via these methods will be removed 
 * from the old bean before the bean changes and will be re-added after 
 * the new bean has been set. Therefore these listeners will be notified 
 * about changes only if the current bean changes a property. They won't be
 * notified if the bean changes - and in turn the property value. If you want
 * to observes property changes caused by bean changes too, register with
 * the adapting ValueModel as returned by <code>#getModel(String)</code>.<p>
 * 
 * <strong>Instance Creation</strong><br>
 * PresentationModel can be instantiated using four different constructors:
 * you can specify the target bean directly, or you can provide a 
 * <em>bean channel</em> to access the bean indirectly. 
 * In the latter case you specify a <code>ValueModel</code>
 * that holds the bean that in turn holds the adapted property.
 * In both cases the target bean is accessed indirectly through 
 * the bean channel. In both cases you can specify a custom trigger channel,
 * or you can use a default trigger channel.<p>
 * 
 * <strong>Note:</strong> This PresentationModel provides bound bean properties
 * and you can register and deregister PropertyChangeListers as usual using 
 * <code>#addPropertyChangeListener</code> and 
 * <code>#removePropertyChangeListener</code>. Do not mix up 
 * the model listeners with the listeners registered with the bean.<p>
 * 
 * <strong>Warning:</strong> PresentationModels register a 
 * PropertyChangeListener with the target bean. Hence, a bean has a reference 
 * to all PresentationModels that hold it as target bean. To avoid memory leaks 
 * it is recommended to remove this listener if the bean lives much longer
 * than the PresentationModel, enabling the garbage collector to remove 
 * the PresentationModel.
 * Setting a PresentationModel's target bean to null removes this listener,
 * which in turn clears the reference from the bean to the PresentationModel.
 * To do so, you can call <code>setBean(null)</code> 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</code>.
 * Setting the bean to null has side effects, which is fine in most cases.
 * However, you can release all listeners by calling <code>#release</code>.<p>
 * 
 * TODO: Further improve the class comment.<p>
 * 
 * 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.<p>
 * 
 * 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 <a href="https://binding.dev.java.net/issues/show_bug.cgi?id=49">Issue
 * 49</a> for more information about the downsides.<p>
 * 
 * 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:
 * <ol>
 * <li>oldValue=null, newValue=null
 * <li>oldValue=null, newValue=the value set
 * <li>oldValue=value read before the set, newValue=the value set
 * <li>oldValue=value read before the set, newValue=value read after the set
 * </ol>
 * 
 * @author  Karsten Lentzsch
 * @version $Revision: 1.13 $
 * 
 * @see     com.jgoodies.binding.beans.BeanAdapter
 * @see     com.jgoodies.binding.value.ValueModel
 * @see     com.jgoodies.binding.beans.PropertyAdapter
 * @see     com.jgoodies.binding.value.Trigger
 */
00242 public class PresentationModel extends Model {

    /**
     * The property name used in the PropertyChangeEvent that is fired
     * before the <em>bean</em> property fires its PropertyChangeEvent.
     * Useful to perform an operation before listeners that handle the
     * bean change are notified. See also the class comment.
     */
00250     public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean";

    /**
     * The name of the read-write bound property that holds the target bean.
     * 
     * @see #getBean()
     * @see #setBean(Object)
     */
00258     public static final String PROPERTYNAME_BEAN = "bean";

    /**
     * The property name used in the PropertyChangeEvent that is fired
     * after the <em>bean</em> property fires its PropertyChangeEvent.
     * Useful to perform an operation after listeners that handle the
     * bean change are notified. See also the class comment.
     */
00266     public static final String PROPERTYNAME_AFTER_BEAN = "afterBean";

    /**
     * The name of the read-write bound bean property for the
     * trigger channel that is shared by all PropertyAdapters 
     * that are created via <code>#getBufferedModel</code>.
     * 
     * @see #getTriggerChannel()
     * @see #setTriggerChannel(ValueModel)
     * @see #getBufferedModel(String)
     */
00277     public static final String PROPERTYNAME_TRIGGERCHANNEL = "triggerChannel";
    
    /**
     * The name of the read-only bound bean property that indicates
     * whether one of the buffered models is buffering.
     * 
     * @see #isBuffering()
     * @see #getBufferedModel(String)
     */
00286     public static final String PROPERTYNAME_BUFFERING = "buffering";
    
    /**
     * The name of the read-only bound bean property that 
     * indicates whether one of the observed models has changed.
     * 
     * @see #isChanged()
     * @see #resetChanged()
     * @see #observeChanged(ValueModel)
     * @see #observeChanged(Object, String)
     */
00297     public static final String PROPERTYNAME_CHANGED = "changed";

    
    // Fields *****************************************************************
    
    /**
     * Refers to the BeanAdapter that provides all underlying behavior
     * to vend adapting ValueModels, track bean changes, and to register
     * with bound bean properties.
     */
00307     private final BeanAdapter beanAdapter;
    
    /**
     * Holds a three-state trigger channel that can be used to trigger
     * commit and reset events in instances of BufferedValueModel.
     * The trigger value is changed to true in <code>#triggerCommit</code> 
     * and is changed to false in <code>#triggerFlush</code>.<p>
     * 
     * The trigger channel is initialized as a <code>Trigger</code>
     * but may be replaced by any other ValueModel that accepts booleans.
     * 
     * @see #getTriggerChannel()
     * @see #setTriggerChannel(ValueModel)
     * @see #getBufferedModel(String)
     */
00322     private ValueModel triggerChannel;
    
    /**
     * Maps property names to instances of the inner class WrappedBuffer.
     * These hold a BufferedValueModel associated with the property name,
     * as well as an optional getter and setter name. These accessor names
     * are used to check that multiple calls to <code>#getBufferedModel</code> 
     * use the same getter and setter for a given property name.<p>
     * 
     * The indirectly stored BufferedValueModel are checked whenever 
     * the buffering state is updated. And these model's trigger channel
     * is updated when the PresentationModel gets a new trigger channel.
     * 
     * @see #getBufferedModel(String)
     * @see #getBufferedModel(String, String, String)
     * @see #isBuffering()
     * @see #setTriggerChannel(ValueModel)
     */
00340     private final Map wrappedBuffers;

    /**
     * Listens to value changes and validates this model.
     * The validation result is available in the validationResultHolder.<p>
     * 
     * Also listens to changes of the <em>buffering</em> property in 
     * <code>BufferedValueModel</code>s and updates the buffering state
     * - if necessary.
     */
00350     private final PropertyChangeListener bufferingUpdateHandler;

    /**
     * Indicates whether a registered buffered model has a pending change,
     * in other words whether any of the values has been edited or not.
     */
00356     private boolean buffering = false;
    

    /**
     * Listens to property changes and updates the <em>changed</em> property.
     */
00362     private final PropertyChangeListener changedUpdateHandler;

    /**
     * Indicates whether a registered model has changed.
     */
00367     private boolean changed = false;
    

    /**
     * Maps property names to instances of ComponentValueModel.
     * Used to ensure that multiple calls to #getComponentModel
     * return the same instance.
     * 
     * @see #getComponentModel(String)
     */
00377     private final Map componentModels;

    /**
     * Maps property names to instances of ComponentValueModel.
     * Used to ensure that multiple calls to #getBufferedComponentModel 
     * return the same instance.
     * 
     * @see #getBufferedComponentModel(String)
     */
00386     private final Map bufferedComponentModels;


    // Instance Creation ******************************************************

    /**
     * Constructs a PresentationModel that adapts properties of the given bean.<p>
     * 
     * Installs a default bean channel that checks the identity not equity 
     * to ensure that listeners are reregistered properly if the old and
     * new bean are equal but not the same.<p>
     * 
     * Installs a Trigger as initial trigger channel.
     * 
     * @param bean   the bean that holds the properties to adapt 
     * @throws PropertyUnboundException  if the <code>bean</code> does not 
     *     provide a pair of methods to register a PropertyChangeListener
     */
00404     public PresentationModel(Object bean) {
        this(new ValueHolder(bean, true));
    }

    /**
     * Constructs a PresentationModel on the given bean using the given
     * trigger channel. The bean provides the properties to adapt.<p>
     * 
     * Installs a default bean channel that checks the identity not equity 
     * to ensure that listeners are reregistered properly if the old and
     * new bean are equal but not the same.<p>
     *  
     * The trigger channel is shared by all buffered models that are created 
     * using <code>#getBufferedModel</code>. 
     * It can be replaced by any other Boolean ValueModel later.
     * Note that PresentationModel observes trigger value changes,
     * not value state. Therefore you must ensure that customer triggers 
     * report value changes when asked to commit or flush. See the 
     * Trigger implementation for an example.
     * 
     * @param bean           the bean that holds the properties to adapt
     * @param triggerChannel the ValueModel that triggers commit and flush events
     */
00427     public PresentationModel(
        Object bean,
        ValueModel triggerChannel) {
        this(new ValueHolder(bean, true), triggerChannel);
    }

    
    /**
     * Constructs a PresentationModel on the given bean channel. This channel
     * holds a bean that in turn holds the properties to adapt.<p>
     * 
     * It is strongly recommended that the bean channel checks the identity 
     * not equity. This ensures that listeners are reregistered properly if 
     * the old and new bean are equal but not the same.<p>
     * 
     * The trigger channel is initialized as a <code>Trigger</code>.
     * It may be replaced by any other Boolean ValueModel later.
     * Note that PresentationModel observes trigger value changes,
     * not value state. Therefore you must ensure that customer triggers 
     * report value changes when asked to commit or flush. See the 
     * Trigger implementation for an example.
     * 
     * @param beanChannel   the ValueModel that holds the bean
     * 
     * @throws PropertyUnboundException  if the <code>bean</code> does not 
     *     provide a pair of methods to register a PropertyChangeListener
     */
00454     public PresentationModel(ValueModel beanChannel) {
        this(beanChannel, new Trigger());
    }
    

    /**
     * Constructs a PresentationModel on the given bean channel using the given
     * trigger channel. The bean channel holds a bean that in turn holds 
     * the properties to adapt.<p>
     * 
     * It is strongly recommended that the bean channel checks the identity 
     * not equity. This ensures that listeners are reregistered properly if 
     * the old and new bean are equal but not the same.<p>
     * 
     * The trigger channel is shared by all buffered
     * models that are created using <code>#buffer</code>. 
     * It can be replaced by any other Boolean ValueModel later.
     * Note that PresentationModel observes trigger value changes,
     * not value state. Therefore you must ensure that customer triggers 
     * report value changes when asked to commit or flush. See the 
     * Trigger implementation for an example.
     * 
     * @param beanChannel    the ValueModel that holds the bean
     * @param triggerChannel the ValueModel that triggers commit and flush events
     */
00479     public PresentationModel(
        ValueModel beanChannel,
        ValueModel triggerChannel) {
        this.beanAdapter = createBeanAdapter(beanChannel);
        this.triggerChannel = triggerChannel;
        this.wrappedBuffers = new HashMap();
        this.componentModels         = new HashMap();
        this.bufferedComponentModels = new HashMap();
        this.bufferingUpdateHandler = new BufferingStateHandler();
        this.changed = false;
        this.changedUpdateHandler = new UpdateHandler();
        
        beanAdapter.addPropertyChangeListener(new BeanChangeHandler());
        
        // By default we observe changes in the bean.
        observeChanged(beanAdapter, BeanAdapter.PROPERTYNAME_CHANGED);
    }
    
    
    /**
     *  Creates and returns a BeanAdapter for the given bean channel.
     *  For compatibility with the 1.0.x, 1.1.x, and 1.2.x series,
     *  this default implementation creates a BeanAdapter that always observes
     *  the bean. Subclasses may override to observe only observable beans.<p>
     *  
     *  Here's an example code for a custom implementation:
     *  <pre>
     *  boolean observe =
     *        (beanChannel == null)
     *     || (beanChannel.getValue() == null)
     *     || BeanUtils.supportsBoundProperties((beanChannel.getValue().getClass());
     *  return new BeanAdapter(beanChannel, observe);
     *  </pre><p>
     *  
     *  A future implementation shall return a BeanAdapter-like interface,
     *  not a BeanAdapter.
     *  
     *  @param beanChannel the ValueModel that holds the bean
     *  @return the created bean adapter
     *  @since 1.3
     */
00520     protected BeanAdapter createBeanAdapter(ValueModel beanChannel) {
        return new BeanAdapter(beanChannel, true);
    }
    

    // Managing the Target Bean **********************************************

    /**
     * Returns the ValueModel that holds the bean that in turn holds
     * the adapted properties. This bean channel is shared by the
     * PropertyAdapters created by the factory methods
     * <code>#getModel</code> and <code>#getBufferedModel</code>.
     * 
     * @return the ValueModel that holds the bean that in turn 
     *     holds the adapted properties
     * 
     * @see #getBean()
     * @see #setBean(Object)
     */
00539     public ValueModel getBeanChannel() {
        return beanAdapter.getBeanChannel();
    }

    /**
     * Returns the bean that holds the adapted properties. This bean
     * is the bean channel's content.
     * 
     * @return the bean that holds the adapted properties
     * 
     * @see #setBean(Object)
     * @see #getBeanChannel()
     */
00552     public Object getBean() {
        return getBeanChannel().getValue();
    }

    /**
     * Sets a new bean as content of the bean channel. 
     * All adapted properties will reflect this change.
     * 
     * @param newBean   the new bean
     *  
     * @see #getBean()
     * @see #getBeanChannel()
     */
00565     public void setBean(Object newBean) {
        getBeanChannel().setValue(newBean);
    }
    
    
    /**
     * The underlying BeanAdapter is about to change the bean.
     * Allows to perform actions before the bean change happens.
     * For example you can remove listeners that shall not be notified
     * if adapted properties change just because of the bean change.
     * Or you can reset values, set fields to <code>null</code> etc.<p>
     * 
     * The default behavior fires a PropertyChangeEvent for property
     * <code>#PROPERTYNAME_BEFORE_BEAN</code>.
     * <strong>Note:</strong> Subclasses that override this method 
     * must invoke super or perform the same behavior.<p>
     * 
     * This method is invoked by the BeanChangeHandler listening to the
     * <em>beforeBean</em> non-readable property of the BeanAdapter.
     * 
     * @param oldBean  the bean before the change
     * @param newBean  the bean that will be adapted after the change
     * 
     * @see #afterBeanChange(Object, Object)
     * @see #PROPERTYNAME_BEFORE_BEAN
     * @see #PROPERTYNAME_BEAN
     * @see #PROPERTYNAME_AFTER_BEAN
     * @see BeanAdapter
     */
00594     public void beforeBeanChange(Object oldBean, Object newBean) {
        firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean, true);
    }
    
    
    /**
     * The underlying BeanAdapter has changed the target bean.
     * Allows to perform actions after the bean changed.
     * For example you can re-add listeners that were removed in 
     * <code>#beforeBeanChange</code>. Or you can reset values,  
     * reset custom changed state, set fields to <code>null</code> etc.<p>
     * 
     * The default behavior resets the change tracker's <em>changed</em> state
     * and fires a PropertyChangeEvent for the property 
     * <code>#PROPERTYNAME_AFTER_BEAN</code>.
     * <strong>Note:</strong> Subclasses that override this method 
     * must invoke super or perform the same behavior.<p>
     * 
     * This method is invoked by the BeanChangeHandler listening to the
     * <em>afterBean</em> non-readable property of the BeanAdapter.
     * 
     * @param oldBean  the bean that was adapted before the change
     * @param newBean  the bean that is already the new target bean
     * 
     * @see #beforeBeanChange(Object, Object)
     * @see #PROPERTYNAME_BEFORE_BEAN
     * @see #PROPERTYNAME_BEAN
     * @see #PROPERTYNAME_AFTER_BEAN
     * @see BeanAdapter
     */
00624     public void afterBeanChange(Object oldBean, Object newBean) {
        setChanged(false); 
        firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean, true);
    }
    

    // Accessing Property Values **********************************************

    /**
     * Returns the value of specified bean property, <code>null</code> 
     * if the current bean is <code>null</code>.<p>
     * 
     * This operation is supported only for readable bean properties.
     * 
     * @param propertyName  the name of the property to be read
     * @return the value of the adapted bean property, null if the bean is null
     * 
     * @throws NullPointerException           if the property name 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
     * 
     * @since 1.1
     */
00648     public Object getValue(String propertyName) {
        return beanAdapter.getValue(propertyName);
    }

    
    /**
     * Sets the given new value for the specified bean property. Does nothing 
     * if this adapter's bean is <code>null</code>. If the setter associated
     * with the propertyName throws a PropertyVetoException, it is silently
     * ignored.<p>
     * 
     * Notifies the associated value change 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.<p>
     * 
     * This operation is supported only for writable bean properties.
     * 
     * @param propertyName   the name of the property to set
     * @param newValue       the value to set
     * 
     * @throws NullPointerException           if the property name is null 
     * @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
     * 
     * @since 1.1
     */
00676     public void setValue(String propertyName, Object newValue) {
        beanAdapter.setValue(propertyName, newValue);
    }
    

    /**
     * Sets a new value for the specified bean property. Does nothing if the
     * bean is <code>null</code>. If the setter associated with the propertyName 
     * throws a PropertyVetoException, this methods throws the same exception.<p>
     * 
     * Notifies the associated value change 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.<p>
     * 
     * This operation is supported only for writable bean properties.
     * 
     * @param propertyName   the name of the property to set
     * @param newValue       the value to set
     * 
     * @throws NullPointerException           if the property name is null 
     * @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 bean setter
     *     throws a PropertyVetoException
     *     
     * @since 1.1
     */
00705     public void setVetoableValue(String propertyName, Object newValue) throws PropertyVetoException {
        beanAdapter.setVetoableValue(propertyName, newValue);
    }
    

    /**
     * Returns the value of specified buffered bean property.
     * It is a shorthand for writing
     * <pre>getBufferedModel(propertyName).getValue()</pre>
     * As a side-effect, this method may create a buffered model.
     * 
     * @param propertyName  the name of the property to be read
     * @return the value of the adapted bean property, null if the bean is null
     * 
     * @throws NullPointerException           if the property name 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
     * 
     * @since 1.1
     */
00726     public Object getBufferedValue(String propertyName) {
        return getBufferedModel(propertyName).getValue();
    }

    
    /**
     * Buffers the given value for the specified bean property. 
     * It is a shorthand for writing
     * <pre>getBufferedModel(propertyName).setValue(newValue)</pre>
     * As a side-effect, this method may create a buffered model.
     * 
     * @param propertyName   the name of the property to set
     * @param newValue       the value to set
     * 
     * @throws NullPointerException           if the property name is null 
     * @throws PropertyNotFoundException      if the property could not be found
     * @throws PropertyAccessException        if the new value could not be set
     * 
     * @since 1.1
     */
00746     public void setBufferedValue(String propertyName, Object newValue) {
        getBufferedModel(propertyName).setValue(newValue);
    }
    

    // Factory Methods for Bound Models ***************************************
    
    /**
     * Looks up and lazily creates a ValueModel that adapts 
     * the bound property with the specified name. Uses the 
     * Bean introspection to look up the getter and setter names.<p>
     * 
     * Subsequent calls to this method with the same property name 
     * return the same ValueModel.<p>
     * 
     * To prevent potential runtime errors it eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getModel(String, String, String)</code> must use 
     * the same getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially once
     * you've called this method you must not call 
     * <code>#getModel(String, String, String)</code> with a non-null
     * getter or setter name. And vice versa, once you've called the latter 
     * method with a non-null getter or setter name, you must not call 
     * this method.<p>
     * 
     * This method uses a return type of AbstractValueModel, not a ValueModel.
     * This makes the AbstractValueModel convenience type converters available,
     * which can significantly shrink the source code necessary to read and 
     * write values from/to these models.
     * 
     * @param propertyName   the name of the property to adapt
     * @return a ValueModel that adapts the property with the specified name
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found
     * @throws IllegalArgumentException   
     *     if <code>#getModel(String, String, String)</code> has been 
     *     called before with the same property name and a non-null getter 
     *     or setter name
     * 
     * @see AbstractValueModel
     * @see BeanAdapter
     * @see #getModel(String, String, String)
     * @see #getBufferedModel(String)
     */
00794     public AbstractValueModel getModel(String propertyName) {
        return beanAdapter.getValueModel(propertyName);
    }
    

    /**
     * Looks up and lazily creates a ValueModel that adapts the bound property 
     * with the given name. Unlike <code>#getModel(String)</code>
     * this method bypasses the Bean Introspection and uses the given getter 
     * and setter names to setup the access to the adapted Bean property.<p>
     * 
     * Subsequent calls to this method with the same parameters 
     * will return the same ValueModel.<p>
     * 
     * To prevent potential runtime errors this method eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getModel(String)</code> must use the same 
     * getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially 
     * once you've called this method with a non-null getter or setter name, 
     * you must not call <code>#getModel(String)</code>. And vice versa, 
     * once you've called the latter method you must not call this method 
     * with a non-null getter or setter name.<p>
     * 
     * This method uses a return type of AbstractValueModel, not a ValueModel.
     * This makes the AbstractValueModel convenience type converters available,
     * which can significantly shrink the source code necessary to read and 
     * write values from/to these models.
     * 
     * @param propertyName   the name of the property to adapt
     * @param getterName     the name of the method that reads the value
     * @param setterName     the name of the method that sets the value
     * @return a ValueModel that adapts the property with the specified name
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found
     * @throws IllegalArgumentException   if this method has been called before
     *     with the same property name and different getter or setter names
     * 
     * @see AbstractValueModel
     * @see BeanAdapter
     * @see #getModel(String, String, String)
     * @see #getBufferedModel(String)
     */
00840     public AbstractValueModel getModel(String propertyName, 
                                      String getterName, 
                                      String setterName) {
        return beanAdapter.getValueModel(propertyName, getterName, setterName);
    }
    
    
    /**
     * Looks up and lazily creates a ComponentValueModel that adapts 
     * the bound property with the specified name. Uses the standard 
     * Bean introspection to look up the getter and setter names.<p>
     * 
     * Subsequent calls to this method with the same property name 
     * return the same ComponentValueModel.<p>
     * 
     * To prevent potential runtime errors it eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getModel(String, String, String)</code> must use 
     * the same getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially once
     * you've called this method you must not call 
     * <code>#getModel(String, String, String)</code> with a non-null
     * getter or setter name. And vice versa, once you've called the latter 
     * method with a non-null getter or setter name, you must not call 
     * this method.<p>
     * 
     * This returned ComponentValueModel provides convenience type converter
     * method from AbstractValueModel and allows to modify GUI state such as
     * enabled, visible, and editable in this presentation model. 
     * This can significantly shrink the source code necessary to handle 
     * GUI state changes.
     * 
     * @param propertyName   the name of the property to adapt
     * @return a ValueModel that adapts the property with the specified name
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found
     * @throws IllegalArgumentException   
     *     if <code>#getModel(String, String, String)</code> has been 
     *     called before with the same property name and a non-null getter 
     *     or setter name
     * 
     * @see ComponentValueModel
     * @see AbstractValueModel
     * @see BeanAdapter
     * @see #getModel(String, String, String)
     * @see #getBufferedModel(String)
     * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel)
     * 
     * @since 1.1
     */
00893     public ComponentValueModel getComponentModel(String propertyName) {
        ComponentValueModel componentModel = 
            (ComponentValueModel) componentModels.get(propertyName);
        if (componentModel == null) {
            AbstractValueModel model = getModel(propertyName);
            componentModel = new ComponentValueModel(model);
            componentModels.put(propertyName, componentModel);
        }
        return componentModel;
    }
    

    // Factory Methods for Buffered Models ************************************

    /**
     * Looks up or creates a buffered adapter to the read-write property 
     * with the given name on this PresentationModel's bean channel. Creates a 
     * BufferedValueModel that wraps a ValueModel that adapts the bean property 
     * with the specified name. The buffered model uses this PresentationModel's 
     * trigger channel to listen for commit and flush events.<p>
     * 
     * The created BufferedValueModel is stored in a Map. Hence 
     * subsequent calls to this method with the same property name 
     * return the same BufferedValueModel.<p>
     * 
     * To prevent potential runtime errors this method eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getBufferedModel(String, String, String)</code> must use 
     * the same getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially once
     * you've called this method you must not call 
     * <code>#getBufferedModel(String, String, String)</code> with a non-null
     * getter or setter name. And vice versa, once you've called the latter 
     * method with a non-null getter or setter name, you must not call 
     * this method.
     * 
     * @param propertyName the name of the read-write property to adapt
     * @return a buffered adapter to the property with the given name
     *    on this model's bean channel using this model's trigger channel
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found  
     * @throws IllegalArgumentException   
     *     if <code>#getBufferedModel(String, String, String)</code> has been 
     *     called before with the same property name and a non-null getter 
     *     or setter name
     *  
     * @see BufferedValueModel
     * @see ValueModel
     * @see Trigger
     * @see BeanAdapter
     * @see #getModel(String)
     * @see #getBufferedModel(String, String, String)
     */
00949     public BufferedValueModel getBufferedModel(String propertyName) {
        return getBufferedModel(propertyName, null, null);
    }

    
    /**
     * Looks up or creates a buffered adapter to the read-write property 
     * with the given name on this PresentationModel's bean channel using
     * the specified getter and setter name to read and write values. Creates 
     * a <code>BufferedValueModel</code> that wraps a <code>ValueModel</code>
     * that adapts the bean property with the specified name.
     * The buffered model uses this PresentationModel's trigger channel 
     * to listen for commit and flush events.<p>
     * 
     * The created BufferedValueModel is stored in a Map so it can be
     * looked up if it is requested multiple times.<p>
     * 
     * To prevent potential runtime errors this method eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getBufferedModel(String)</code> must use the same 
     * getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially 
     * once you've called this method with a non-null getter or setter name, 
     * you must not call <code>#getBufferedModel(String)</code>. And vice versa, 
     * once you've called the latter method you must not call this method 
     * with a non-null getter or setter name.
     * 
     * @param propertyName   the name of the property to adapt
     * @param getterName     the name of the method that reads the value
     * @param setterName     the name of the method that sets the value
     * @return a buffered adapter to the property with the given name
     *    on this model's bean channel using this model's trigger channel
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found
     * @throws IllegalArgumentException   if this method has been called before
     *     with the same property name and different getter or setter names
     * 
     * @see BufferedValueModel
     * @see ValueModel
     * @see Trigger
     * @see BeanAdapter
     * @see #getModel(String)
     * @see #getBufferedModel(String)
     */
00996     public BufferedValueModel getBufferedModel(String propertyName, 
                                      String getterName, 
                                      String setterName) {
        WrappedBuffer wrappedBuffer = (WrappedBuffer) wrappedBuffers.get(propertyName);
        if (wrappedBuffer == null) {
            wrappedBuffer = new WrappedBuffer(
                    buffer(getModel(propertyName, getterName, setterName)),
                    getterName,
                    setterName);
            wrappedBuffers.put(propertyName, wrappedBuffer);
        } else if    (!equals(getterName, wrappedBuffer.getterName)
                   || !equals(setterName, wrappedBuffer.setterName)) {
            throw new IllegalArgumentException(
                    "You must not invoke this method twice " 
                  + "with different getter and/or setter names."); 
     }
        return wrappedBuffer.buffer;
    }
    
    
    /**
     * Looks up or creates a buffered component adapter to the read-write 
     * property with the given name on this PresentationModel's bean channel. 
     * Creates a ComponentValueModel that wraps a BufferedValueModel that 
     * in turn wraps a ValueModel that adapts the bean property with the 
     * specified name. The buffered model uses this PresentationModel's 
     * trigger channel to listen for commit and flush events. 
     * The ComponentValueModel allows to set component state in this 
     * presentation model.<p>
     * 
     * The created ComponentValueModel is stored in a Map. Hence 
     * subsequent calls to this method with the same property name 
     * return the same ComponentValueModel.<p>
     * 
     * To prevent potential runtime errors this method eagerly looks up 
     * the associated PropertyDescriptor if the target bean is not null.<p>
     * 
     * For each property name all calls to this method 
     * and to <code>#getBufferedModel(String, String, String)</code> must use 
     * the same getter and setter names. Attempts to violate this constraint 
     * will be rejected with an IllegalArgumentException. Especially once
     * you've called this method you must not call 
     * <code>#getBufferedModel(String, String, String)</code> with a non-null
     * getter or setter name. And vice versa, once you've called the latter 
     * method with a non-null getter or setter name, you must not call 
     * this method.
     * 
     * @param propertyName the name of the read-write property to adapt
     * @return a ComponentValueModel that wraps a buffered adapter 
     *    to the property with the given name
     *    on this model's bean channel using this model's trigger channel
     * 
     * @throws NullPointerException       if the property name is null 
     * @throws PropertyNotFoundException  if the property could not be found  
     * @throws IllegalArgumentException   
     *     if <code>#getBufferedModel(String, String, String)</code> has been 
     *     called before with the same property name and a non-null getter 
     *     or setter name
     *  
     * @see ComponentValueModel
     * @see BufferedValueModel
     * @see ValueModel
     * @see Trigger
     * @see BeanAdapter
     * @see #getModel(String)
     * @see #getBufferedModel(String)
     * @see #getComponentModel(String)
     * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel)
     * 
     * @since 1.1
     */
01067     public ComponentValueModel getBufferedComponentModel(String propertyName) {
        ComponentValueModel bufferedComponentModel = 
            (ComponentValueModel) bufferedComponentModels.get(propertyName);
        if (bufferedComponentModel == null) {
            AbstractValueModel model = getBufferedModel(propertyName);
            bufferedComponentModel = new ComponentValueModel(model);
            bufferedComponentModels.put(propertyName, bufferedComponentModel);
        }
        return bufferedComponentModel;
    }

        
    /**
     * Wraps the given ValueModel with a BufferedValueModel that 
     * uses this model's trigger channel to trigger commit and flush events.
     * 
     * @param valueModel  the ValueModel to be buffered
     * @return a BufferedValueModel triggered by the model's trigger channel 
     * 
     * @see BufferedValueModel
     * @see ValueModel
     * @see Trigger
     * @see #getBufferedModel(String)
     */
01091     private BufferedValueModel buffer(ValueModel valueModel) {
        BufferedValueModel bufferedModel = new BufferedValueModel(
                valueModel, getTriggerChannel());
        bufferedModel.addPropertyChangeListener(BufferedValueModel.PROPERTYNAME_BUFFERING, bufferingUpdateHandler);
        return bufferedModel;
    }
    
    
    // Accessing the Trigger Channel ******************************************

    /**
     * Returns a ValueModel that can be shared and used to trigger commit 
     * and flush events in BufferedValueModels. The trigger channel's value 
     * changes to true in <code>#triggerCommit</code> and it changes to false 
     * in <code>#triggerFlush</code>.<p>
     * 
     * This trigger channel is used to commit and flush values
     * in the BufferedValueModels returned by <code>#getBufferedModel</code>.
     *
     * @return this model's trigger channel
     * 
     * @see BufferedValueModel
     * @see ValueModel
     * @see #setTriggerChannel(ValueModel)
     */
01116     public ValueModel getTriggerChannel() {
        return triggerChannel;
    }

    /**
     * Sets the given ValueModel as this model's new trigger channel.
     * Sets the new trigger channel in all existing BufferedValueModels
     * that have been created using <code>#getBufferedModel</code>.
     * Subsequent invocations of <code>#triggerCommit</code> and 
     * <code>#triggerFlush</code> will trigger commit and flush events 
     * using the new trigger channel. 
     *
     * @param newTriggerChannel  the ValueModel to be set as 
     *     this model's new trigger channel
     * @throws NullPointerException  if the new trigger channel is <code>null</code>
     * 
     * @see BufferedValueModel
     * @see ValueModel
     * @see #getTriggerChannel()
     */
01136     public void setTriggerChannel(ValueModel newTriggerChannel) {
        if (newTriggerChannel == null)
            throw new NullPointerException("The trigger channel must not be null.");

        ValueModel oldTriggerChannel = getTriggerChannel();
        triggerChannel = newTriggerChannel;
        
        for (Iterator iter = wrappedBuffers.values().iterator(); iter.hasNext();) {
            WrappedBuffer wrappedBuffer = (WrappedBuffer) iter.next();
            wrappedBuffer.buffer.setTriggerChannel(triggerChannel);
        }
        
        firePropertyChange(
            PROPERTYNAME_TRIGGERCHANNEL,
            oldTriggerChannel,
            newTriggerChannel);
    }

    /**
     * Sets the trigger channel to true which in turn triggers commit
     * events in all BufferedValueModels that share this trigger.
     * 
     * @see #triggerFlush() 
     */
01160     public void triggerCommit() {
        if (Boolean.TRUE.equals(getTriggerChannel().getValue()))
            getTriggerChannel().setValue(null);
        getTriggerChannel().setValue(Boolean.TRUE);
    }

    /**
     * Sets the trigger channel to false which in turn triggers flush
     * events in all BufferedValueModels that share this trigger.
     * 
     * @see #triggerCommit()
     */
01172     public void triggerFlush() {
        if (Boolean.FALSE.equals(getTriggerChannel().getValue()))
            getTriggerChannel().setValue(null);
        getTriggerChannel().setValue(Boolean.FALSE);
    }

    
    // Managing the Buffering State *******************************************

    /**
     * Answers whether any of the buffered models is buffering.
     * Useful to enable and disable UI actions and operations
     * that depend on the buffering state.
     * 
     * @return true if any of the buffered models is buffering,
     *     false, if all buffered models write-through
     */
01189     public boolean isBuffering() {
        return buffering;
    }

    
    /**
     * Sets the buffering state to the specified value.
     * 
     * @param newValue  the new buffering state
     */
01199     private void setBuffering(boolean newValue) {
        boolean oldValue = isBuffering();
        buffering = newValue;
        firePropertyChange(PROPERTYNAME_BUFFERING, oldValue, newValue);
    }

    
    private void updateBufferingState(boolean latestBufferingStateChange) {
        if (buffering == latestBufferingStateChange)
            return;
        boolean nowBuffering = false;
        for (Iterator it = wrappedBuffers.values().iterator(); it.hasNext();) {
            WrappedBuffer wrappedBuffer = (WrappedBuffer) it.next();
            BufferedValueModel model = wrappedBuffer.buffer;
            nowBuffering = nowBuffering || model.isBuffering();
            if (!buffering && nowBuffering) {
                setBuffering(true);
                return;
            }
        }
        setBuffering(nowBuffering);
    }

    
    // Changed State *********************************************************

    /**
     * Answers whether one of the registered ValueModels has changed
     * since the changed state has been reset last time.<p>
     * 
     * <strong>Note:</strong> Unlike <code>#resetChanged</code> this method
     * is not intended to be overriden by subclasses.
     * If you want to track changes of other ValueModels, bean properties, or 
     * of submodels, register them by means of <code>#observeChanged</code>.
     * Overriding <code>#isChanged</code> to include the changed state 
     * of submodels would return the correct changed value, but it would bypass 
     * the change notification from submodels to this model. 
     * Therefore submodels must be observed, which can be achieve using
     * <code>#observeChanged</code>.<p>
     * 
     * To reset the changed state invoke <code>#resetChanged</code>. 
     * In case you track the changed state of submodels override 
     * <code>#resetChanged</code> to reset the changed state in these 
     * submodels too.
     * 
     * @return true if an observed property has changed since the last reset
     * 
     * @see #observeChanged(ValueModel)
     * @see #observeChanged(Object, String)
     * @see #resetChanged()
     */
01250     public boolean isChanged() {
        return changed;
    }
    
    
    /**
     * Resets this model's changed state to <code>false</code>.
     * Therefore it resets the changed states of the change tracker
     * and the underlying bean adapter.<p>
     * 
     * Subclasses may override this method to reset the changed state
     * of submodels. The overriding method must invoke this super behavior.
     * For example if you have a MainModel that is composed of 
     * two submodels Submodel1 and Submodel2, you may write:
     * <pre>
     * public void resetChanged() {
     *     super.resetChanged();
     *     getSubmodel1().resetChanged();
     *     getSubmodel2().resetChanged();
     * }
     * </pre>
     * 
     * @see #isChanged()
     * @see #observeChanged(ValueModel)
     * @see #observeChanged(Object, String)
     */
01276     public void resetChanged() {
        setChanged(false);
        beanAdapter.resetChanged();
    }
    

    protected void setChanged(boolean newValue) {
        boolean oldValue = isChanged();
        changed = newValue;
        firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
    }

       
    // Observing Changes in ValueModel and Bean Properties *******************

    /**
     * Observes the specified readable bound bean property in the given bean.
     * 
     * @param bean           the bean to be observed
     * @param propertyName   the name of the readable bound bean property
     * @throws NullPointerException if the bean or propertyName is null
     * @throws PropertyNotBindableException if this class can't add
     *     the PropertyChangeListener from the bean
     * 
     * @see #retractInterestFor(Object, String)
     * @see #observeChanged(ValueModel)
     */
01303     public void observeChanged(Object bean, String propertyName) {
        if (bean == null)
            throw new NullPointerException("The bean must not be null.");
        if (propertyName == null)
            throw new NullPointerException("The property name must not be null.");
        
        BeanUtils.addPropertyChangeListener(bean, propertyName, changedUpdateHandler);
    }
    
    
    /**
     * Observes value changes in the given ValueModel.
     * 
     * @param valueModel   the ValueModel to observe
     * @throws NullPointerException if the valueModel is null
     * 
     * @see #retractInterestFor(ValueModel)
     * @see #observeChanged(Object, String)
     */
01322     public void observeChanged(ValueModel valueModel) {
        if (valueModel == null)
            throw new NullPointerException("The ValueModel must not be null.");
        valueModel.addValueChangeListener(changedUpdateHandler);
    }
    
    
    /**
     * Retracts interest for the specified readable bound bean property
     * in the given bean. 
     * 
     * @param bean           the bean to be observed
     * @param propertyName   the name of the readable bound bean property
     * @throws NullPointerException if the bean or propertyName is null
     * @throws PropertyNotBindableException if this class can't remove
     *     the PropertyChangeListener from the bean
     * 
     * @see #observeChanged(Object, String)
     * @see #retractInterestFor(ValueModel)
     */
01342     public void retractInterestFor(Object bean, String propertyName) {
        if (bean == null)
            throw new NullPointerException("The bean must not be null.");
        if (propertyName == null)
            throw new NullPointerException("The property name must not be null.");
        
        BeanUtils.removePropertyChangeListener(bean, propertyName, changedUpdateHandler);
    }


    /**
     * Retracts interest for value changes in the given ValueModel.
     * 
     * @param valueModel   the ValueModel to observe
     * @throws NullPointerException if the valueModel is null
     * 
     * @see #observeChanged(ValueModel)
     * @see #retractInterestFor(Object, String)
     */
01361     public void retractInterestFor(ValueModel valueModel) {
        if (valueModel == null)
            throw new NullPointerException("The ValueModel must not be null.");
        valueModel.removeValueChangeListener(changedUpdateHandler);
    }
    
    
    // Managing Bean Property Change Listeners *******************************

    /**
     * Adds a PropertyChangeListener to the list of bean listeners. The 
     * listener is registered for all bound properties of the target bean.<p>
     * 
     * The listener will be notified if and only if this BeanAdapter's current 
     * bean changes a property. It'll not be notified if the bean changes.<p>
     *  
     * If listener is <code>null</code>, no exception is thrown and 
     * no action is performed.
     *
     * @param listener      the PropertyChangeListener to be added
     *
     * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
     * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners()
     */
01387     public synchronized void addBeanPropertyChangeListener(
                                            PropertyChangeListener listener) {
        beanAdapter.addBeanPropertyChangeListener(listener);
    }
    
    
    /**
     * Removes a PropertyChangeListener from the list of bean listeners. 
     * This method should be used to remove PropertyChangeListeners that 
     * were registered for all bound properties of the target bean.<p>
     * 
     * If listener is <code>null</code>, no exception is thrown and 
     * no action is performed.
     *
     * @param listener      the PropertyChangeListener to be removed
     * 
     * @see #addBeanPropertyChangeListener(PropertyChangeListener)
     * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners()
     */
01408     public synchronized void removeBeanPropertyChangeListener(
                                        PropertyChangeListener listener) {
        beanAdapter.removeBeanPropertyChangeListener(listener);
    }
    
    
    /**
     * Adds a PropertyChangeListener to the list of bean listeners for a 
     * specific property. The specified property may be user-defined.<p>
     * 
     * The listener will be notified if and only if this BeanAdapter's 
     * current bean changes the specified property. It'll not be notified 
     * if the bean changes. If you want to observe property changes and 
     * bean changes, you may observe the ValueModel that adapts this property 
     * - as returned by <code>#getModel(String)</code>.<p>
     * 
     * Note that if the bean is inheriting a bound property, then no event
     * will be fired in response to a change in the inherited property.<p>
     * 
     * If listener is <code>null</code>, no exception is thrown and 
     * no action is performed.
     *
     * @param propertyName      one of the property names listed above
     * @param listener          the PropertyChangeListener to be added
     *
     * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners(String)
     */
01437     public synchronized void addBeanPropertyChangeListener(
                                        String propertyName,
                                        PropertyChangeListener listener) {
        beanAdapter.addBeanPropertyChangeListener(propertyName, listener);
    }
    
    
    /**
     * Removes a PropertyChangeListener from the listener list for a specific
     * property. This method should be used to remove PropertyChangeListeners
     * that were registered for a specific bound property.<p>
     * 
     * If listener is <code>null</code>, no exception is thrown and 
     * no action is performed.
     *
     * @param propertyName      a valid property name
     * @param listener          the PropertyChangeListener to be removed
     *
     * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners(String)
     */
01459     public synchronized void removeBeanPropertyChangeListener(
                                        String propertyName,
                                        PropertyChangeListener listener) {
        beanAdapter.removeBeanPropertyChangeListener(propertyName, listener);
    }
    
    
    // Requesting Listener Sets ***********************************************

    /**
     * Returns an array of all the property change listeners
     * registered on this component.
     *
     * @return all of this component's <code>PropertyChangeListener</code>s
     *         or an empty array if no property change
     *         listeners are currently registered
     *
     * @see #addBeanPropertyChangeListener(PropertyChangeListener)
     * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners(String)
     * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners()
     */
01481     public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners() {
        return beanAdapter.getBeanPropertyChangeListeners();
    }

    
    /**
     * Returns an array of all the listeners which have been associated 
     * with the named property.
     *
     * @param propertyName   the name of the property to lookup listeners
     * @return all of the <code>PropertyChangeListeners</code> associated with
     *         the named property or an empty array if no listeners have 
     *         been added
     *
     * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
     * @see #getBeanPropertyChangeListeners()
     */
01499     public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners(String propertyName) {
        return beanAdapter.getBeanPropertyChangeListeners(propertyName);
    }
    
    
    // Misc *******************************************************************
    
    /**
     * Removes the PropertyChangeHandler from the observed bean, 
     * if the bean is not <code>null</code>.
     * Also removes all listeners from the bean that have been registered 
     * with <code>#addBeanPropertyChangeListener</code> before.<p>
     * 
     * PresentationModels have a PropertyChangeListener registered with 
     * the target bean. Hence, a bean has a reference to all PresentationModels
     * that hold it as bean. To avoid memory leaks it is recommended to remove 
     * this listener, if the bean lives much longer than the PresentationModel, 
     * enabling the garbage collector to remove the PresentationModel.
     * To do so, you can call <code>setBean(null)</code> 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</code>.<p>
     * 
     * Setting the bean to null has side-effects, for example the model
     * fires a change event for the bound property <em>bean</em> and 
     * other properties.
     * And the value of ValueModel's vended by this model 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 PresentationModel. 
     * Another way to clear the reference from the target bean is to call 
     * this <code>#release</code> method. It has no side-effects, but 
     * the PresentationModel must not be used anymore once #release 
     * has been called.
     * 
     * @see #setBean(Object)
     * @see java.lang.ref.WeakReference
     * 
     * @since 1.2
     */
01539     public void release() {
        beanAdapter.release();
    }
    

    // Helper Class ***********************************************************
    
    /**
     * Holds a BufferedValueModel together with the names of the getter
     * and setter. Used to look up models in <code>#getBufferedModel</code>.
     * Also ensures that there are no two buffered models with different
     * getter/setter pairs.
     * 
     * @see PresentationModel#getBufferedModel(String)
     * @see PresentationModel#getBufferedModel(String, String, String)
     */
01555     private static final class WrappedBuffer {
        
        final BufferedValueModel buffer;
        final String getterName;
        final String setterName;
        
        WrappedBuffer(
                BufferedValueModel buffer, 
                String getterName, 
                String setterName) {
            this.buffer = buffer;
            this.getterName = getterName;
            this.setterName = setterName;
        }
    }
    
    // Event Handling and Forwarding Changes **********************************
    
    /**
     * Listens to changes of the bean, invoked the before and after methods,
     * and forwards the bean change events.
     */
01577     private final class BeanChangeHandler implements PropertyChangeListener {

        /**
         * The target bean will change, changes, or has changed. 
         * 
         * @param evt   the property change event to be handled
         */
01584         public void propertyChange(PropertyChangeEvent evt) {
            Object oldBean = evt.getOldValue();
            Object newBean = evt.getNewValue();
            String propertyName = evt.getPropertyName();
            if (BeanAdapter.PROPERTYNAME_BEFORE_BEAN.equals(propertyName)) {
                beforeBeanChange(oldBean, newBean);
            } else if (BeanAdapter.PROPERTYNAME_BEAN.equals(propertyName)) {
                firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean, true);
            } else if (BeanAdapter.PROPERTYNAME_AFTER_BEAN.equals(propertyName)) {
                afterBeanChange(oldBean, newBean);
            }
        }
    }
    
    
    /**
     * Updates the buffering state if a model buffering state changed.
     */
01602     private final class BufferingStateHandler implements PropertyChangeListener {

        /**
         * A registered BufferedValueModel has reported a change in its
         * <em>buffering</em> state. Update this model's buffering state.
         * 
         * @param evt   describes the property change
         */
01610         public void propertyChange(PropertyChangeEvent evt) {
            updateBufferingState(((Boolean) evt.getNewValue()).booleanValue());
        } 
        
    }

    
    /**
     * Listens to model changes and updates the changed state.
     */
01620     private final class UpdateHandler implements PropertyChangeListener {

        /**
         * A registered ValueModel has changed.
         * Updates the changed state. If the property that changed is
         * 'changed' we assume that this is another changed state and
         * forward only changes to true. For all other property names,
         * we just update our changed state to true.
         * 
         * @param evt   the event that describes the property change
         */
01631         public void propertyChange(PropertyChangeEvent evt) {
            String propertyName = evt.getPropertyName();
            if (!PROPERTYNAME_CHANGED.equals(propertyName)
                || ((Boolean) evt.getNewValue()).booleanValue()) {
                setChanged(true);
            }
        }
    }
     
    
}

Generated by  Doxygen 1.6.0   Back to index