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;
0032:
0033: import java.beans.PropertyChangeEvent;
0034: import java.beans.PropertyChangeListener;
0035: import java.beans.PropertyVetoException;
0036: import java.util.HashMap;
0037: import java.util.Map;
0038:
0039: import javax.swing.JComponent;
0040:
0041: import com.jgoodies.binding.adapter.Bindings;
0042: import com.jgoodies.binding.beans.*;
0043: import com.jgoodies.binding.value.*;
0044:
0045: /**
0046: * The standard base class to implement the <em>Presentation Model</em> pattern,
0047: * that represents the state and behavior of a presentation independently
0048: * of the GUI components used in the interface. This
0049: * <a href="http://martinfowler.com/eaaDev/PresentationModel.html">pattern</a>
0050: * is described in Martin Fowler's upcoming
0051: * <a href="http://martinfowler.com/eaaDev/">addition</a>
0052: * to his "Patterns of Enterprise Application Architecture". More details
0053: * around this implementation of the Presentation Model pattern and a 3-tier
0054: * Swing client architecture with a presentation model layer can be found in
0055: * the <a href="http://www.jgoodies.com/articles/binding.pdf">JGoodies
0056: * Binding presentation</a>. This architecture is supported
0057: * by the JGoodies Binding library. The PresentationModel pattern is known
0058: * to users of VisualWorks Smalltalk as <em>ApplicationModel</em>.<p>
0059: *
0060: * This class minimizes the effort required to bind, edit,
0061: * buffer, and observe the bound properties of an exchangeable bean.
0062: * Therefore it provides five groups of features that are described below:<ol>
0063: * <li>adapt bean properties,
0064: * <li>change the adapted bean,
0065: * <li>buffer values,
0066: * <li>observe the buffering state, and
0067: * <li>track changes in adapted bean properties.
0068: * </ol><p>
0069: *
0070: * Typically this class will be extended to add custom models, Actions,
0071: * presentation logic, model operations and other higher-level behavior.
0072: * However, in simple cases you can use this class as-is.
0073: * Several methods are intended to be used as-is and a typical subclass
0074: * should not modify them. For example #isChanged, #isBuffering,
0075: * #getBean, #setBean, #getBeanChannel, #getModel, #getBufferedModel,
0076: * #getTriggerChannel, #setTriggerChannel, #triggerCommit and #triggerFlush.<p>
0077: *
0078: * <strong>Adapting Bean Properties</strong><br>
0079: * The method {@link #getModel(String)} vends ValueModels that adapt
0080: * a bound bean property of an exchangeable bean. These ValueModels will be
0081: * requested from an underlying BeanAdapter.
0082: * To get such a model you specify the name of the bean property.
0083: * All properties adapted must be read-write and must comply with
0084: * the Java Bean coding conventions.
0085: * In case you need to adapt a read-only or write-only property,
0086: * or if the bean uses custom names for the reader and writer,
0087: * use {@link #getModel(String, String, String)}.
0088: * Also note that you must not mix calls to these methods for the same
0089: * property name. For details see the JavaDoc class comment in
0090: * {@link com.jgoodies.binding.beans.BeanAdapter}.<p>
0091: *
0092: * <strong>Changing the Adapted Bean</strong><br>
0093: * The adapted bean is not stored in this PresentationModel.
0094: * Instead it is held by a ValueModel, the <em>bean channel</em>
0095: * - just as in the PropertyAdapter and BeanAdapter.
0096: * This indirection enables you to manage the adapted bean outside
0097: * of this PresentationModel, and it enables you to share bean channels
0098: * between multiple PresentationModels, PropertyAdapters, and BeanAdapters.
0099: * The bean channel is used by all adapting models created
0100: * by the factory methods <code>#getModel</code>.
0101: * You can get and set the current bean by means of <code>#getBean</code>
0102: * and <code>#setBean</code>. Or you can set a new value to the bean channel.<p>
0103: *
0104: * PresentationModel fires three PropertyChangeEvents if the bean changes:
0105: * <i>beforeBean</i>, <i>bean</i> and <i>afterBean</i>. This is useful
0106: * when sharing a bean channel and you must perform an operation before
0107: * or after other listeners handle a bean change. Since you cannot rely
0108: * on the order listeners will be notified, only the <i>beforeBean</i>
0109: * and <i>afterBean</i> events are guaranteed to be fired before and
0110: * after the bean change is fired.
0111: * Note that <code>#getBean()</code> returns the new bean before
0112: * any of these three PropertyChangeEvents is fired. Therefore listeners
0113: * that handle these events must use the event's old and new value
0114: * to determine the old and new bean.
0115: * The order of events fired during a bean change is:<ol>
0116: * <li>the bean channel fires a <i>value</i> change,
0117: * <li>this model fires a <i>beforeBean</i> change,
0118: * <li>this model fires the <i>bean</i> change,
0119: * <li>this model fires an <i>afterBean</i> change.
0120: * </ol>
0121: *
0122: * <strong>Buffering Values</strong><br>
0123: * At the core of this feature are the methods {@link #getBufferedModel(String)}
0124: * that vend BufferedValueModels that wrap an adapted bean property.
0125: * The buffer can be committed or flushed using <code>#triggerCommit</code>
0126: * and <code>#triggerFlush</code> respectively.<p>
0127: *
0128: * The trigger channel is provided as a bound Java bean property
0129: * <em>triggerChannel</em> that must be a non-<code>null</code>
0130: * <code>ValueModel</code> with values of type <code>Boolean</code>.
0131: * Attempts to read or write other value types may be rejected
0132: * with runtime exceptions.
0133: * By default the trigger channel is initialized as an instance of
0134: * <code>Trigger</code>. As an alternative it can be set in the constructor.<p>
0135: *
0136: * <strong>Observing the Buffering State</strong><br>
0137: * This class also provides support for observing the buffering state
0138: * of the BufferedValueModels created with this model. The buffering state
0139: * is useful for UI actions and operations that are enabled or disabled
0140: * if there are pending changes, for example on OK or APPLY button.
0141: * API users can request the buffering state via <code>#isBuffering</code>
0142: * and can observe the bound property <em>buffering</em>.<p>
0143: *
0144: * <strong>Tracking Changes in the Adapted Bean</strong><br>
0145: * PresentationModel provides support for observing bean property changes
0146: * and it tracks all changes to report the overall changed state.
0147: * The latter is useful to detect whether the bean has changed at all,
0148: * for example to mark the bean as dirty, so it will be updated in a database.
0149: * API users can request the changed state via <code>#isChanged</code>
0150: * and can observe the bound property <em>changed</em>.
0151: * If you want to track changes of other ValueModels, bean properties,
0152: * or of submodels, register them using <code>#observeChanged</code>.
0153: * To reset the changed state invoke <code>#resetChanged</code>.
0154: * In case you track the changed state of submodels you should override
0155: * <code>#resetChanged</code> to reset the changed state in these submodels.<p>
0156: *
0157: * The changed state changes once only (from false to true). If you need
0158: * instant notifications about changes in the properties of the target bean,
0159: * you can register PropertyChangeListeners with this model. This is useful
0160: * if you change the bean and don't want to move your listeners from one bean
0161: * to the other. And it's useful if you want to observe multiple bean
0162: * properties at the same time. These listeners are managed by the method set
0163: * <code>#addBeanPropertyChangeListener</code> and
0164: * <code>#removeBeanPropertyChangeListener</code>.
0165: * Listeners registered via these methods will be removed
0166: * from the old bean before the bean changes and will be re-added after
0167: * the new bean has been set. Therefore these listeners will be notified
0168: * about changes only if the current bean changes a property. They won't be
0169: * notified if the bean changes - and in turn the property value. If you want
0170: * to observes property changes caused by bean changes too, register with
0171: * the adapting ValueModel as returned by <code>#getModel(String)</code>.<p>
0172: *
0173: * <strong>Instance Creation</strong><br>
0174: * PresentationModel can be instantiated using four different constructors:
0175: * you can specify the target bean directly, or you can provide a
0176: * <em>bean channel</em> to access the bean indirectly.
0177: * In the latter case you specify a <code>ValueModel</code>
0178: * that holds the bean that in turn holds the adapted property.
0179: * In both cases the target bean is accessed indirectly through
0180: * the bean channel. In both cases you can specify a custom trigger channel,
0181: * or you can use a default trigger channel.<p>
0182: *
0183: * <strong>Note:</strong> This PresentationModel provides bound bean properties
0184: * and you can register and unregister PropertyChangeListers as usual using
0185: * <code>#addPropertyChangeListener</code> and
0186: * <code>#removePropertyChangeListener</code>. Do not mix up
0187: * the model listeners with the listeners registered with the bean.<p>
0188: *
0189: * <strong>Warning:</strong> PresentationModels register a
0190: * PropertyChangeListener with the target bean. Hence, a bean has a reference
0191: * to all PresentationModels that hold it as target bean. To avoid memory leaks
0192: * it is recommended to remove this listener if the bean lives much longer
0193: * than the PresentationModel, enabling the garbage collector to remove
0194: * the PresentationModel.
0195: * Setting a PresentationModel's target bean to null removes this listener,
0196: * which in turn clears the reference from the bean to the PresentationModel.
0197: * To do so, you can call <code>setBean(null)</code> or set the
0198: * bean channel's value to null.
0199: * As an alternative you can use event listener lists in your beans
0200: * that implement references with <code>WeakReference</code>.
0201: * Setting the bean to null has side effects, which is fine in most cases.
0202: * However, you can release all listeners by calling <code>#release</code>.<p>
0203: *
0204: * TODO: Further improve the class comment.<p>
0205: *
0206: * TODO: Consider adding a feature to ensure that update notifications
0207: * are performed in the event dispatch thread. In case the adapted bean
0208: * is changed in a thread other than the event dispatch thread, such
0209: * a feature would help complying with Swing's single thread rule.
0210: * The feature could be implemented by an extended PropertyChangeSupport.<p>
0211: *
0212: * TODO: I plan to improve the support for adapting beans that do not fire
0213: * PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter,
0214: * and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's
0215: * internal SimplePropertyAdapter's shall be able to optionally self-fire
0216: * a PropertyChangeEvent in case the bean does not. There are several
0217: * downsides with self-firing events compared to bound bean properties.
0218: * See <a href="https://binding.dev.java.net/issues/show_bug.cgi?id=49">Issue
0219: * 49</a> for more information about the downsides.<p>
0220: *
0221: * The observeChanges constructor parameter shall be replaced by a more
0222: * fine-grained choice to not observe (former observeChanges=false),
0223: * to observe bound properties (former observeChanges=true), and a new
0224: * setting for self-firing PropertyChangeEvents if a value is set.
0225: * The latter case may be further split up to specify how the
0226: * self-fired PropertyChangeEvent is created:
0227: * <ol>
0228: * <li>oldValue=null, newValue=null
0229: * <li>oldValue=null, newValue=the value set
0230: * <li>oldValue=value read before the set, newValue=the value set
0231: * <li>oldValue=value read before the set, newValue=value read after the set
0232: * </ol>
0233: *
0234: * @author Karsten Lentzsch
0235: * @version $Revision: 1.17 $
0236: *
0237: * @see com.jgoodies.binding.beans.BeanAdapter
0238: * @see com.jgoodies.binding.value.ValueModel
0239: * @see com.jgoodies.binding.beans.PropertyAdapter
0240: * @see com.jgoodies.binding.value.Trigger
0241: *
0242: * @param <B> the type of the bean managed by this PresentationModel
0243: */
0244: public class PresentationModel<B> extends Model {
0245:
0246: /**
0247: * The property name used in the PropertyChangeEvent that is fired
0248: * before the <em>bean</em> property fires its PropertyChangeEvent.
0249: * Useful to perform an operation before listeners that handle the
0250: * bean change are notified. See also the class comment.
0251: */
0252: public static final String PROPERTYNAME_BEFORE_BEAN = "beforeBean";
0253:
0254: /**
0255: * The name of the read-write bound property that holds the target bean.
0256: *
0257: * @see #getBean()
0258: * @see #setBean(Object)
0259: */
0260: public static final String PROPERTYNAME_BEAN = "bean";
0261:
0262: /**
0263: * The property name used in the PropertyChangeEvent that is fired
0264: * after the <em>bean</em> property fires its PropertyChangeEvent.
0265: * Useful to perform an operation after listeners that handle the
0266: * bean change are notified. See also the class comment.
0267: */
0268: public static final String PROPERTYNAME_AFTER_BEAN = "afterBean";
0269:
0270: /**
0271: * The name of the read-write bound bean property for the
0272: * trigger channel that is shared by all PropertyAdapters
0273: * that are created via <code>#getBufferedModel</code>.
0274: *
0275: * @see #getTriggerChannel()
0276: * @see #setTriggerChannel(ValueModel)
0277: * @see #getBufferedModel(String)
0278: */
0279: public static final String PROPERTYNAME_TRIGGERCHANNEL = "triggerChannel";
0280:
0281: /**
0282: * The name of the read-only bound bean property that indicates
0283: * whether one of the buffered models is buffering.
0284: *
0285: * @see #isBuffering()
0286: * @see #getBufferedModel(String)
0287: */
0288: public static final String PROPERTYNAME_BUFFERING = "buffering";
0289:
0290: /**
0291: * The name of the read-only bound bean property that
0292: * indicates whether one of the observed models has changed.
0293: *
0294: * @see #isChanged()
0295: * @see #resetChanged()
0296: * @see #observeChanged(ValueModel)
0297: * @see #observeChanged(Object, String)
0298: */
0299: public static final String PROPERTYNAME_CHANGED = "changed";
0300:
0301: // Fields *****************************************************************
0302:
0303: /**
0304: * Refers to the BeanAdapter that provides all underlying behavior
0305: * to vend adapting ValueModels, track bean changes, and to register
0306: * with bound bean properties.
0307: */
0308: private final BeanAdapter<B> beanAdapter;
0309:
0310: /**
0311: * Holds a three-state trigger channel that can be used to trigger
0312: * commit and reset events in instances of BufferedValueModel.
0313: * The trigger value is changed to true in <code>#triggerCommit</code>
0314: * and is changed to false in <code>#triggerFlush</code>.<p>
0315: *
0316: * The trigger channel is initialized as a <code>Trigger</code>
0317: * but may be replaced by any other ValueModel that accepts booleans.
0318: *
0319: * @see #getTriggerChannel()
0320: * @see #setTriggerChannel(ValueModel)
0321: * @see #getBufferedModel(String)
0322: */
0323: private ValueModel triggerChannel;
0324:
0325: /**
0326: * Maps property names to instances of the inner class WrappedBuffer.
0327: * These hold a BufferedValueModel associated with the property name,
0328: * as well as an optional getter and setter name. These accessor names
0329: * are used to check that multiple calls to <code>#getBufferedModel</code>
0330: * use the same getter and setter for a given property name.<p>
0331: *
0332: * The indirectly stored BufferedValueModel are checked whenever
0333: * the buffering state is updated. And these model's trigger channel
0334: * is updated when the PresentationModel gets a new trigger channel.
0335: *
0336: * @see #getBufferedModel(String)
0337: * @see #getBufferedModel(String, String, String)
0338: * @see #isBuffering()
0339: * @see #setTriggerChannel(ValueModel)
0340: */
0341: private final Map<String, WrappedBuffer> wrappedBuffers;
0342:
0343: /**
0344: * Listens to value changes and validates this model.
0345: * The validation result is available in the validationResultHolder.<p>
0346: *
0347: * Also listens to changes of the <em>buffering</em> property in
0348: * <code>BufferedValueModel</code>s and updates the buffering state
0349: * - if necessary.
0350: */
0351: private final PropertyChangeListener bufferingUpdateHandler;
0352:
0353: /**
0354: * Indicates whether a registered buffered model has a pending change,
0355: * in other words whether any of the values has been edited or not.
0356: */
0357: private boolean buffering = false;
0358:
0359: /**
0360: * Listens to property changes and updates the <em>changed</em> property.
0361: */
0362: private final PropertyChangeListener changedUpdateHandler;
0363:
0364: /**
0365: * Indicates whether a registered model has changed.
0366: */
0367: private boolean changed = false;
0368:
0369: /**
0370: * Maps property names to instances of ComponentValueModel.
0371: * Used to ensure that multiple calls to #getComponentModel
0372: * return the same instance.
0373: *
0374: * @see #getComponentModel(String)
0375: */
0376: private final Map<String, ComponentValueModel> componentModels;
0377:
0378: /**
0379: * Maps property names to instances of ComponentValueModel.
0380: * Used to ensure that multiple calls to #getBufferedComponentModel
0381: * return the same instance.
0382: *
0383: * @see #getBufferedComponentModel(String)
0384: */
0385: private final Map<String, ComponentValueModel> bufferedComponentModels;
0386:
0387: // Instance Creation ******************************************************
0388:
0389: /**
0390: * Constructs a PresentationModel that adapts properties of the given bean.<p>
0391: *
0392: * Installs a default bean channel that checks the identity not equity
0393: * to ensure that listeners are unregistered properly if the old and
0394: * new bean are equal but not the same.<p>
0395: *
0396: * Installs a Trigger as initial trigger channel.
0397: *
0398: * @param bean the bean that holds the properties to adapt
0399: * @throws PropertyUnboundException if the <code>bean</code> does not
0400: * provide a pair of methods to register a PropertyChangeListener
0401: */
0402: public PresentationModel(Object bean) {
0403: this (new ValueHolder(bean, true));
0404: }
0405:
0406: /**
0407: * Constructs a PresentationModel on the given bean using the given
0408: * trigger channel. The bean provides the properties to adapt.<p>
0409: *
0410: * Installs a default bean channel that checks the identity not equity
0411: * to ensure that listeners are unregistered properly if the old and
0412: * new bean are equal but not the same.<p>
0413: *
0414: * The trigger channel is shared by all buffered models that are created
0415: * using <code>#getBufferedModel</code>.
0416: * It can be replaced by any other Boolean ValueModel later.
0417: * Note that PresentationModel observes trigger value changes,
0418: * not value state. Therefore you must ensure that customer triggers
0419: * report value changes when asked to commit or flush. See the
0420: * Trigger implementation for an example.
0421: *
0422: * @param bean the bean that holds the properties to adapt
0423: * @param triggerChannel the ValueModel that triggers commit and flush events
0424: */
0425: public PresentationModel(Object bean, ValueModel triggerChannel) {
0426: this (new ValueHolder(bean, true), triggerChannel);
0427: }
0428:
0429: /**
0430: * Constructs a PresentationModel on the given bean channel. This channel
0431: * holds a bean that in turn holds the properties to adapt.<p>
0432: *
0433: * It is strongly recommended that the bean channel checks the identity
0434: * not equity. This ensures that listeners are unregistered properly if
0435: * the old and new bean are equal but not the same.<p>
0436: *
0437: * The trigger channel is initialized as a <code>Trigger</code>.
0438: * It may be replaced by any other Boolean ValueModel later.
0439: * Note that PresentationModel observes trigger value changes,
0440: * not value state. Therefore you must ensure that customer triggers
0441: * report value changes when asked to commit or flush. See the
0442: * Trigger implementation for an example.
0443: *
0444: * @param beanChannel the ValueModel that holds the bean
0445: *
0446: * @throws PropertyUnboundException if the <code>bean</code> does not
0447: * provide a pair of methods to register a PropertyChangeListener
0448: */
0449: public PresentationModel(ValueModel beanChannel) {
0450: this (beanChannel, new Trigger());
0451: }
0452:
0453: /**
0454: * Constructs a PresentationModel on the given bean channel using the given
0455: * trigger channel. The bean channel holds a bean that in turn holds
0456: * the properties to adapt.<p>
0457: *
0458: * It is strongly recommended that the bean channel checks the identity
0459: * not equity. This ensures that listeners are unregistered properly if
0460: * the old and new bean are equal but not the same.<p>
0461: *
0462: * The trigger channel is shared by all buffered
0463: * models that are created using <code>#buffer</code>.
0464: * It can be replaced by any other Boolean ValueModel later.
0465: * Note that PresentationModel observes trigger value changes,
0466: * not value state. Therefore you must ensure that customer triggers
0467: * report value changes when asked to commit or flush. See the
0468: * Trigger implementation for an example.
0469: *
0470: * @param beanChannel the ValueModel that holds the bean
0471: * @param triggerChannel the ValueModel that triggers commit and flush events
0472: */
0473: public PresentationModel(ValueModel beanChannel,
0474: ValueModel triggerChannel) {
0475: this .beanAdapter = createBeanAdapter(beanChannel);
0476: this .triggerChannel = triggerChannel;
0477: this .wrappedBuffers = new HashMap<String, WrappedBuffer>();
0478: this .componentModels = new HashMap<String, ComponentValueModel>();
0479: this .bufferedComponentModels = new HashMap<String, ComponentValueModel>();
0480: this .bufferingUpdateHandler = new BufferingStateHandler();
0481: this .changed = false;
0482: this .changedUpdateHandler = new UpdateHandler();
0483:
0484: beanAdapter.addPropertyChangeListener(new BeanChangeHandler());
0485:
0486: // By default we observe changes in the bean.
0487: observeChanged(beanAdapter, BeanAdapter.PROPERTYNAME_CHANGED);
0488: }
0489:
0490: /**
0491: * Creates and returns a BeanAdapter for the given bean channel.
0492: * For compatibility with the 1.0.x, 1.1.x, and 1.2.x series,
0493: * this default implementation creates a BeanAdapter that always observes
0494: * the bean. Subclasses may override to observe only observable beans.<p>
0495: *
0496: * Here's an example code for a custom implementation:
0497: * <pre>
0498: * boolean observe =
0499: * (beanChannel == null)
0500: * || (beanChannel.getValue() == null)
0501: * || BeanUtils.supportsBoundProperties((beanChannel.getValue().getClass());
0502: * return new BeanAdapter(beanChannel, observe);
0503: * </pre><p>
0504: *
0505: * A future implementation shall return a BeanAdapter-like interface,
0506: * not a BeanAdapter.
0507: *
0508: * @param beanChannel the ValueModel that holds the bean
0509: * @return the created bean adapter
0510: * @since 1.3
0511: */
0512: protected BeanAdapter<B> createBeanAdapter(ValueModel beanChannel) {
0513: return new BeanAdapter<B>(beanChannel, true);
0514: }
0515:
0516: // Managing the Target Bean **********************************************
0517:
0518: /**
0519: * Returns the ValueModel that holds the bean that in turn holds
0520: * the adapted properties. This bean channel is shared by the
0521: * PropertyAdapters created by the factory methods
0522: * <code>#getModel</code> and <code>#getBufferedModel</code>.
0523: *
0524: * @return the ValueModel that holds the bean that in turn
0525: * holds the adapted properties
0526: *
0527: * @see #getBean()
0528: * @see #setBean(Object)
0529: */
0530: public ValueModel getBeanChannel() {
0531: return beanAdapter.getBeanChannel();
0532: }
0533:
0534: /**
0535: * Returns the bean that holds the adapted properties. This bean
0536: * is the bean channel's content.
0537: *
0538: * @return the bean that holds the adapted properties
0539: *
0540: * @see #setBean(Object)
0541: * @see #getBeanChannel()
0542: */
0543: public B getBean() {
0544: return (B) getBeanChannel().getValue();
0545: }
0546:
0547: /**
0548: * Sets a new bean as content of the bean channel.
0549: * All adapted properties will reflect this change.
0550: *
0551: * @param newBean the new bean
0552: *
0553: * @see #getBean()
0554: * @see #getBeanChannel()
0555: */
0556: public void setBean(B newBean) {
0557: getBeanChannel().setValue(newBean);
0558: }
0559:
0560: /**
0561: * The underlying BeanAdapter is about to change the bean.
0562: * Allows to perform actions before the bean change happens.
0563: * For example you can remove listeners that shall not be notified
0564: * if adapted properties change just because of the bean change.
0565: * Or you can reset values, set fields to <code>null</code> etc.<p>
0566: *
0567: * The default behavior fires a PropertyChangeEvent for property
0568: * <code>#PROPERTYNAME_BEFORE_BEAN</code>.
0569: * <strong>Note:</strong> Subclasses that override this method
0570: * must invoke super or perform the same behavior.<p>
0571: *
0572: * This method is invoked by the BeanChangeHandler listening to the
0573: * <em>beforeBean</em> non-readable property of the BeanAdapter.
0574: *
0575: * @param oldBean the bean before the change
0576: * @param newBean the bean that will be adapted after the change
0577: *
0578: * @see #afterBeanChange(Object, Object)
0579: * @see #PROPERTYNAME_BEFORE_BEAN
0580: * @see #PROPERTYNAME_BEAN
0581: * @see #PROPERTYNAME_AFTER_BEAN
0582: * @see BeanAdapter
0583: */
0584: public void beforeBeanChange(B oldBean, B newBean) {
0585: firePropertyChange(PROPERTYNAME_BEFORE_BEAN, oldBean, newBean,
0586: true);
0587: }
0588:
0589: /**
0590: * The underlying BeanAdapter has changed the target bean.
0591: * Allows to perform actions after the bean changed.
0592: * For example you can re-add listeners that were removed in
0593: * <code>#beforeBeanChange</code>. Or you can reset values,
0594: * reset custom changed state, set fields to <code>null</code> etc.<p>
0595: *
0596: * The default behavior resets the change tracker's <em>changed</em> state
0597: * and fires a PropertyChangeEvent for the property
0598: * <code>#PROPERTYNAME_AFTER_BEAN</code>.
0599: * <strong>Note:</strong> Subclasses that override this method
0600: * must invoke super or perform the same behavior.<p>
0601: *
0602: * This method is invoked by the BeanChangeHandler listening to the
0603: * <em>afterBean</em> non-readable property of the BeanAdapter.
0604: *
0605: * @param oldBean the bean that was adapted before the change
0606: * @param newBean the bean that is already the new target bean
0607: *
0608: * @see #beforeBeanChange(Object, Object)
0609: * @see #PROPERTYNAME_BEFORE_BEAN
0610: * @see #PROPERTYNAME_BEAN
0611: * @see #PROPERTYNAME_AFTER_BEAN
0612: * @see BeanAdapter
0613: */
0614: public void afterBeanChange(B oldBean, B newBean) {
0615: setChanged(false);
0616: firePropertyChange(PROPERTYNAME_AFTER_BEAN, oldBean, newBean,
0617: true);
0618: }
0619:
0620: // Accessing Property Values **********************************************
0621:
0622: /**
0623: * Returns the value of specified bean property, <code>null</code>
0624: * if the current bean is <code>null</code>.<p>
0625: *
0626: * This operation is supported only for readable bean properties.
0627: *
0628: * @param propertyName the name of the property to be read
0629: * @return the value of the adapted bean property, null if the bean is null
0630: *
0631: * @throws NullPointerException if the property name is null
0632: * @throws UnsupportedOperationException if the property is write-only
0633: * @throws PropertyNotFoundException if the property could not be found
0634: * @throws PropertyAccessException if the value could not be read
0635: *
0636: * @since 1.1
0637: */
0638: public Object getValue(String propertyName) {
0639: return beanAdapter.getValue(propertyName);
0640: }
0641:
0642: /**
0643: * Sets the given new value for the specified bean property. Does nothing
0644: * if this adapter's bean is <code>null</code>. If the setter associated
0645: * with the propertyName throws a PropertyVetoException, it is silently
0646: * ignored.<p>
0647: *
0648: * Notifies the associated value change listeners if the bean reports
0649: * a property change. Note that a bean may suppress PropertyChangeEvents
0650: * if the old and new value are the same, or if the old and new value
0651: * are equal.<p>
0652: *
0653: * This operation is supported only for writable bean properties.
0654: *
0655: * @param propertyName the name of the property to set
0656: * @param newValue the value to set
0657: *
0658: * @throws NullPointerException if the property name is null
0659: * @throws UnsupportedOperationException if the property is read-only
0660: * @throws PropertyNotFoundException if the property could not be found
0661: * @throws PropertyAccessException if the new value could not be set
0662: *
0663: * @since 1.1
0664: */
0665: public void setValue(String propertyName, Object newValue) {
0666: beanAdapter.setValue(propertyName, newValue);
0667: }
0668:
0669: /**
0670: * Sets a new value for the specified bean property. Does nothing if the
0671: * bean is <code>null</code>. If the setter associated with the propertyName
0672: * throws a PropertyVetoException, this methods throws the same exception.<p>
0673: *
0674: * Notifies the associated value change listeners if the bean reports
0675: * a property change. Note that a bean may suppress PropertyChangeEvents
0676: * if the old and new value are the same, or if the old and new value
0677: * are equal.<p>
0678: *
0679: * This operation is supported only for writable bean properties.
0680: *
0681: * @param propertyName the name of the property to set
0682: * @param newValue the value to set
0683: *
0684: * @throws NullPointerException if the property name is null
0685: * @throws UnsupportedOperationException if the property is read-only
0686: * @throws PropertyNotFoundException if the property could not be found
0687: * @throws PropertyAccessException if the new value could not be set
0688: * @throws PropertyVetoException if the bean setter
0689: * throws a PropertyVetoException
0690: *
0691: * @since 1.1
0692: */
0693: public void setVetoableValue(String propertyName, Object newValue)
0694: throws PropertyVetoException {
0695: beanAdapter.setVetoableValue(propertyName, newValue);
0696: }
0697:
0698: /**
0699: * Returns the value of specified buffered bean property.
0700: * It is a shorthand for writing
0701: * <pre>getBufferedModel(propertyName).getValue()</pre>
0702: * As a side-effect, this method may create a buffered model.
0703: *
0704: * @param propertyName the name of the property to be read
0705: * @return the value of the adapted bean property, null if the bean is null
0706: *
0707: * @throws NullPointerException if the property name is null
0708: * @throws UnsupportedOperationException if the property is write-only
0709: * @throws PropertyNotFoundException if the property could not be found
0710: * @throws PropertyAccessException if the value could not be read
0711: *
0712: * @since 1.1
0713: */
0714: public Object getBufferedValue(String propertyName) {
0715: return getBufferedModel(propertyName).getValue();
0716: }
0717:
0718: /**
0719: * Buffers the given value for the specified bean property.
0720: * It is a shorthand for writing
0721: * <pre>getBufferedModel(propertyName).setValue(newValue)</pre>
0722: * As a side-effect, this method may create a buffered model.
0723: *
0724: * @param propertyName the name of the property to set
0725: * @param newValue the value to set
0726: *
0727: * @throws NullPointerException if the property name is null
0728: * @throws PropertyNotFoundException if the property could not be found
0729: * @throws PropertyAccessException if the new value could not be set
0730: *
0731: * @since 1.1
0732: */
0733: public void setBufferedValue(String propertyName, Object newValue) {
0734: getBufferedModel(propertyName).setValue(newValue);
0735: }
0736:
0737: // Factory Methods for Bound Models ***************************************
0738:
0739: /**
0740: * Looks up and lazily creates a ValueModel that adapts
0741: * the bound property with the specified name. Uses the
0742: * Bean introspection to look up the getter and setter names.<p>
0743: *
0744: * Subsequent calls to this method with the same property name
0745: * return the same ValueModel.<p>
0746: *
0747: * To prevent potential runtime errors it eagerly looks up
0748: * the associated PropertyDescriptor if the target bean is not null.<p>
0749: *
0750: * For each property name all calls to this method
0751: * and to <code>#getModel(String, String, String)</code> must use
0752: * the same getter and setter names. Attempts to violate this constraint
0753: * will be rejected with an IllegalArgumentException. Especially once
0754: * you've called this method you must not call
0755: * <code>#getModel(String, String, String)</code> with a non-null
0756: * getter or setter name. And vice versa, once you've called the latter
0757: * method with a non-null getter or setter name, you must not call
0758: * this method.<p>
0759: *
0760: * This method uses a return type of AbstractValueModel, not a ValueModel.
0761: * This makes the AbstractValueModel convenience type converters available,
0762: * which can significantly shrink the source code necessary to read and
0763: * write values from/to these models.
0764: *
0765: * @param propertyName the name of the property to adapt
0766: * @return a ValueModel that adapts the property with the specified name
0767: *
0768: * @throws NullPointerException if the property name is null
0769: * @throws PropertyNotFoundException if the property could not be found
0770: * @throws IllegalArgumentException
0771: * if <code>#getModel(String, String, String)</code> has been
0772: * called before with the same property name and a non-null getter
0773: * or setter name
0774: *
0775: * @see AbstractValueModel
0776: * @see BeanAdapter
0777: * @see #getModel(String, String, String)
0778: * @see #getBufferedModel(String)
0779: */
0780: public AbstractValueModel getModel(String propertyName) {
0781: return beanAdapter.getValueModel(propertyName);
0782: }
0783:
0784: /**
0785: * Looks up and lazily creates a ValueModel that adapts the bound property
0786: * with the given name. Unlike <code>#getModel(String)</code>
0787: * this method bypasses the Bean Introspection and uses the given getter
0788: * and setter names to setup the access to the adapted Bean property.<p>
0789: *
0790: * Subsequent calls to this method with the same parameters
0791: * will return the same ValueModel.<p>
0792: *
0793: * To prevent potential runtime errors this method eagerly looks up
0794: * the associated PropertyDescriptor if the target bean is not null.<p>
0795: *
0796: * For each property name all calls to this method
0797: * and to <code>#getModel(String)</code> must use the same
0798: * getter and setter names. Attempts to violate this constraint
0799: * will be rejected with an IllegalArgumentException. Especially
0800: * once you've called this method with a non-null getter or setter name,
0801: * you must not call <code>#getModel(String)</code>. And vice versa,
0802: * once you've called the latter method you must not call this method
0803: * with a non-null getter or setter name.<p>
0804: *
0805: * This method uses a return type of AbstractValueModel, not a ValueModel.
0806: * This makes the AbstractValueModel convenience type converters available,
0807: * which can significantly shrink the source code necessary to read and
0808: * write values from/to these models.
0809: *
0810: * @param propertyName the name of the property to adapt
0811: * @param getterName the name of the method that reads the value
0812: * @param setterName the name of the method that sets the value
0813: * @return a ValueModel that adapts the property with the specified name
0814: *
0815: * @throws NullPointerException if the property name is null
0816: * @throws PropertyNotFoundException if the property could not be found
0817: * @throws IllegalArgumentException if this method has been called before
0818: * with the same property name and different getter or setter names
0819: *
0820: * @see AbstractValueModel
0821: * @see BeanAdapter
0822: * @see #getModel(String, String, String)
0823: * @see #getBufferedModel(String)
0824: */
0825: public AbstractValueModel getModel(String propertyName,
0826: String getterName, String setterName) {
0827: return beanAdapter.getValueModel(propertyName, getterName,
0828: setterName);
0829: }
0830:
0831: /**
0832: * Looks up and lazily creates a ComponentValueModel that adapts
0833: * the bound property with the specified name. Uses the standard
0834: * Bean introspection to look up the getter and setter names.<p>
0835: *
0836: * Subsequent calls to this method with the same property name
0837: * return the same ComponentValueModel.<p>
0838: *
0839: * To prevent potential runtime errors it eagerly looks up
0840: * the associated PropertyDescriptor if the target bean is not null.<p>
0841: *
0842: * For each property name all calls to this method
0843: * and to <code>#getModel(String, String, String)</code> must use
0844: * the same getter and setter names. Attempts to violate this constraint
0845: * will be rejected with an IllegalArgumentException. Especially once
0846: * you've called this method you must not call
0847: * <code>#getModel(String, String, String)</code> with a non-null
0848: * getter or setter name. And vice versa, once you've called the latter
0849: * method with a non-null getter or setter name, you must not call
0850: * this method.<p>
0851: *
0852: * This returned ComponentValueModel provides convenience type converter
0853: * method from AbstractValueModel and allows to modify GUI state such as
0854: * enabled, visible, and editable in this presentation model.
0855: * This can significantly shrink the source code necessary to handle
0856: * GUI state changes.
0857: *
0858: * @param propertyName the name of the property to adapt
0859: * @return a ValueModel that adapts the property with the specified name
0860: *
0861: * @throws NullPointerException if the property name is null
0862: * @throws PropertyNotFoundException if the property could not be found
0863: * @throws IllegalArgumentException
0864: * if <code>#getModel(String, String, String)</code> has been
0865: * called before with the same property name and a non-null getter
0866: * or setter name
0867: *
0868: * @see ComponentValueModel
0869: * @see AbstractValueModel
0870: * @see BeanAdapter
0871: * @see #getModel(String, String, String)
0872: * @see #getBufferedModel(String)
0873: * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel)
0874: *
0875: * @since 1.1
0876: */
0877: public ComponentValueModel getComponentModel(String propertyName) {
0878: ComponentValueModel componentModel = componentModels
0879: .get(propertyName);
0880: if (componentModel == null) {
0881: AbstractValueModel model = getModel(propertyName);
0882: componentModel = new ComponentValueModel(model);
0883: componentModels.put(propertyName, componentModel);
0884: }
0885: return componentModel;
0886: }
0887:
0888: // Factory Methods for Buffered Models ************************************
0889:
0890: /**
0891: * Looks up or creates a buffered adapter to the read-write property
0892: * with the given name on this PresentationModel's bean channel. Creates a
0893: * BufferedValueModel that wraps a ValueModel that adapts the bean property
0894: * with the specified name. The buffered model uses this PresentationModel's
0895: * trigger channel to listen for commit and flush events.<p>
0896: *
0897: * The created BufferedValueModel is stored in a Map. Hence
0898: * subsequent calls to this method with the same property name
0899: * return the same BufferedValueModel.<p>
0900: *
0901: * To prevent potential runtime errors this method eagerly looks up
0902: * the associated PropertyDescriptor if the target bean is not null.<p>
0903: *
0904: * For each property name all calls to this method
0905: * and to <code>#getBufferedModel(String, String, String)</code> must use
0906: * the same getter and setter names. Attempts to violate this constraint
0907: * will be rejected with an IllegalArgumentException. Especially once
0908: * you've called this method you must not call
0909: * <code>#getBufferedModel(String, String, String)</code> with a non-null
0910: * getter or setter name. And vice versa, once you've called the latter
0911: * method with a non-null getter or setter name, you must not call
0912: * this method.
0913: *
0914: * @param propertyName the name of the read-write property to adapt
0915: * @return a buffered adapter to the property with the given name
0916: * on this model's bean channel using this model's trigger channel
0917: *
0918: * @throws NullPointerException if the property name is null
0919: * @throws PropertyNotFoundException if the property could not be found
0920: * @throws IllegalArgumentException
0921: * if <code>#getBufferedModel(String, String, String)</code> has been
0922: * called before with the same property name and a non-null getter
0923: * or setter name
0924: *
0925: * @see BufferedValueModel
0926: * @see ValueModel
0927: * @see Trigger
0928: * @see BeanAdapter
0929: * @see #getModel(String)
0930: * @see #getBufferedModel(String, String, String)
0931: */
0932: public BufferedValueModel getBufferedModel(String propertyName) {
0933: return getBufferedModel(propertyName, null, null);
0934: }
0935:
0936: /**
0937: * Looks up or creates a buffered adapter to the read-write property
0938: * with the given name on this PresentationModel's bean channel using
0939: * the specified getter and setter name to read and write values. Creates
0940: * a <code>BufferedValueModel</code> that wraps a <code>ValueModel</code>
0941: * that adapts the bean property with the specified name.
0942: * The buffered model uses this PresentationModel's trigger channel
0943: * to listen for commit and flush events.<p>
0944: *
0945: * The created BufferedValueModel is stored in a Map so it can be
0946: * looked up if it is requested multiple times.<p>
0947: *
0948: * To prevent potential runtime errors this method eagerly looks up
0949: * the associated PropertyDescriptor if the target bean is not null.<p>
0950: *
0951: * For each property name all calls to this method
0952: * and to <code>#getBufferedModel(String)</code> must use the same
0953: * getter and setter names. Attempts to violate this constraint
0954: * will be rejected with an IllegalArgumentException. Especially
0955: * once you've called this method with a non-null getter or setter name,
0956: * you must not call <code>#getBufferedModel(String)</code>. And vice versa,
0957: * once you've called the latter method you must not call this method
0958: * with a non-null getter or setter name.
0959: *
0960: * @param propertyName the name of the property to adapt
0961: * @param getterName the name of the method that reads the value
0962: * @param setterName the name of the method that sets the value
0963: * @return a buffered adapter to the property with the given name
0964: * on this model's bean channel using this model's trigger channel
0965: *
0966: * @throws NullPointerException if the property name is null
0967: * @throws PropertyNotFoundException if the property could not be found
0968: * @throws IllegalArgumentException if this method has been called before
0969: * with the same property name and different getter or setter names
0970: *
0971: * @see BufferedValueModel
0972: * @see ValueModel
0973: * @see Trigger
0974: * @see BeanAdapter
0975: * @see #getModel(String)
0976: * @see #getBufferedModel(String)
0977: */
0978: public BufferedValueModel getBufferedModel(String propertyName,
0979: String getterName, String setterName) {
0980: WrappedBuffer wrappedBuffer = wrappedBuffers.get(propertyName);
0981: if (wrappedBuffer == null) {
0982: wrappedBuffer = new WrappedBuffer(buffer(getModel(
0983: propertyName, getterName, setterName)), getterName,
0984: setterName);
0985: wrappedBuffers.put(propertyName, wrappedBuffer);
0986: } else if (!equals(getterName, wrappedBuffer.getterName)
0987: || !equals(setterName, wrappedBuffer.setterName)) {
0988: throw new IllegalArgumentException(
0989: "You must not invoke this method twice "
0990: + "with different getter and/or setter names.");
0991: }
0992: return wrappedBuffer.buffer;
0993: }
0994:
0995: /**
0996: * Looks up or creates a buffered component adapter to the read-write
0997: * property with the given name on this PresentationModel's bean channel.
0998: * Creates a ComponentValueModel that wraps a BufferedValueModel that
0999: * in turn wraps a ValueModel that adapts the bean property with the
1000: * specified name. The buffered model uses this PresentationModel's
1001: * trigger channel to listen for commit and flush events.
1002: * The ComponentValueModel allows to set component state in this
1003: * presentation model.<p>
1004: *
1005: * The created ComponentValueModel is stored in a Map. Hence
1006: * subsequent calls to this method with the same property name
1007: * return the same ComponentValueModel.<p>
1008: *
1009: * To prevent potential runtime errors this method eagerly looks up
1010: * the associated PropertyDescriptor if the target bean is not null.<p>
1011: *
1012: * For each property name all calls to this method
1013: * and to <code>#getBufferedModel(String, String, String)</code> must use
1014: * the same getter and setter names. Attempts to violate this constraint
1015: * will be rejected with an IllegalArgumentException. Especially once
1016: * you've called this method you must not call
1017: * <code>#getBufferedModel(String, String, String)</code> with a non-null
1018: * getter or setter name. And vice versa, once you've called the latter
1019: * method with a non-null getter or setter name, you must not call
1020: * this method.
1021: *
1022: * @param propertyName the name of the read-write property to adapt
1023: * @return a ComponentValueModel that wraps a buffered adapter
1024: * to the property with the given name
1025: * on this model's bean channel using this model's trigger channel
1026: *
1027: * @throws NullPointerException if the property name is null
1028: * @throws PropertyNotFoundException if the property could not be found
1029: * @throws IllegalArgumentException
1030: * if <code>#getBufferedModel(String, String, String)</code> has been
1031: * called before with the same property name and a non-null getter
1032: * or setter name
1033: *
1034: * @see ComponentValueModel
1035: * @see BufferedValueModel
1036: * @see ValueModel
1037: * @see Trigger
1038: * @see BeanAdapter
1039: * @see #getModel(String)
1040: * @see #getBufferedModel(String)
1041: * @see #getComponentModel(String)
1042: * @see Bindings#addComponentPropertyHandler(JComponent, ValueModel)
1043: *
1044: * @since 1.1
1045: */
1046: public ComponentValueModel getBufferedComponentModel(
1047: String propertyName) {
1048: ComponentValueModel bufferedComponentModel = bufferedComponentModels
1049: .get(propertyName);
1050: if (bufferedComponentModel == null) {
1051: AbstractValueModel model = getBufferedModel(propertyName);
1052: bufferedComponentModel = new ComponentValueModel(model);
1053: bufferedComponentModels.put(propertyName,
1054: bufferedComponentModel);
1055: }
1056: return bufferedComponentModel;
1057: }
1058:
1059: /**
1060: * Wraps the given ValueModel with a BufferedValueModel that
1061: * uses this model's trigger channel to trigger commit and flush events.
1062: *
1063: * @param valueModel the ValueModel to be buffered
1064: * @return a BufferedValueModel triggered by the model's trigger channel
1065: *
1066: * @see BufferedValueModel
1067: * @see ValueModel
1068: * @see Trigger
1069: * @see #getBufferedModel(String)
1070: */
1071: private BufferedValueModel buffer(ValueModel valueModel) {
1072: BufferedValueModel bufferedModel = new BufferedValueModel(
1073: valueModel, getTriggerChannel());
1074: bufferedModel.addPropertyChangeListener(
1075: BufferedValueModel.PROPERTYNAME_BUFFERING,
1076: bufferingUpdateHandler);
1077: return bufferedModel;
1078: }
1079:
1080: // Accessing the Trigger Channel ******************************************
1081:
1082: /**
1083: * Returns a ValueModel that can be shared and used to trigger commit
1084: * and flush events in BufferedValueModels. The trigger channel's value
1085: * changes to true in <code>#triggerCommit</code> and it changes to false
1086: * in <code>#triggerFlush</code>.<p>
1087: *
1088: * This trigger channel is used to commit and flush values
1089: * in the BufferedValueModels returned by <code>#getBufferedModel</code>.
1090: *
1091: * @return this model's trigger channel
1092: *
1093: * @see BufferedValueModel
1094: * @see ValueModel
1095: * @see #setTriggerChannel(ValueModel)
1096: */
1097: public ValueModel getTriggerChannel() {
1098: return triggerChannel;
1099: }
1100:
1101: /**
1102: * Sets the given ValueModel as this model's new trigger channel.
1103: * Sets the new trigger channel in all existing BufferedValueModels
1104: * that have been created using <code>#getBufferedModel</code>.
1105: * Subsequent invocations of <code>#triggerCommit</code> and
1106: * <code>#triggerFlush</code> will trigger commit and flush events
1107: * using the new trigger channel.
1108: *
1109: * @param newTriggerChannel the ValueModel to be set as
1110: * this model's new trigger channel
1111: * @throws NullPointerException if the new trigger channel is <code>null</code>
1112: *
1113: * @see BufferedValueModel
1114: * @see ValueModel
1115: * @see #getTriggerChannel()
1116: */
1117: public void setTriggerChannel(ValueModel newTriggerChannel) {
1118: if (newTriggerChannel == null)
1119: throw new NullPointerException(
1120: "The trigger channel must not be null.");
1121:
1122: ValueModel oldTriggerChannel = getTriggerChannel();
1123: triggerChannel = newTriggerChannel;
1124: for (WrappedBuffer wrappedBuffer : wrappedBuffers.values()) {
1125: wrappedBuffer.buffer.setTriggerChannel(triggerChannel);
1126: }
1127: firePropertyChange(PROPERTYNAME_TRIGGERCHANNEL,
1128: oldTriggerChannel, newTriggerChannel);
1129: }
1130:
1131: /**
1132: * Sets the trigger channel to true which in turn triggers commit
1133: * events in all BufferedValueModels that share this trigger.
1134: *
1135: * @see #triggerFlush()
1136: */
1137: public void triggerCommit() {
1138: if (Boolean.TRUE.equals(getTriggerChannel().getValue()))
1139: getTriggerChannel().setValue(null);
1140: getTriggerChannel().setValue(Boolean.TRUE);
1141: }
1142:
1143: /**
1144: * Sets the trigger channel to false which in turn triggers flush
1145: * events in all BufferedValueModels that share this trigger.
1146: *
1147: * @see #triggerCommit()
1148: */
1149: public void triggerFlush() {
1150: if (Boolean.FALSE.equals(getTriggerChannel().getValue()))
1151: getTriggerChannel().setValue(null);
1152: getTriggerChannel().setValue(Boolean.FALSE);
1153: }
1154:
1155: // Managing the Buffering State *******************************************
1156:
1157: /**
1158: * Answers whether any of the buffered models is buffering.
1159: * Useful to enable and disable UI actions and operations
1160: * that depend on the buffering state.
1161: *
1162: * @return true if any of the buffered models is buffering,
1163: * false, if all buffered models write-through
1164: */
1165: public boolean isBuffering() {
1166: return buffering;
1167: }
1168:
1169: /**
1170: * Sets the buffering state to the specified value.
1171: *
1172: * @param newValue the new buffering state
1173: */
1174: private void setBuffering(boolean newValue) {
1175: boolean oldValue = isBuffering();
1176: buffering = newValue;
1177: firePropertyChange(PROPERTYNAME_BUFFERING, oldValue, newValue);
1178: }
1179:
1180: private void updateBufferingState(boolean latestBufferingStateChange) {
1181: if (buffering == latestBufferingStateChange)
1182: return;
1183: boolean nowBuffering = false;
1184: for (WrappedBuffer wrappedBuffer : wrappedBuffers.values()) {
1185: BufferedValueModel model = wrappedBuffer.buffer;
1186: nowBuffering = nowBuffering || model.isBuffering();
1187: if (!buffering && nowBuffering) {
1188: setBuffering(true);
1189: return;
1190: }
1191: }
1192: setBuffering(nowBuffering);
1193: }
1194:
1195: // Changed State *********************************************************
1196:
1197: /**
1198: * Answers whether one of the registered ValueModels has changed
1199: * since the changed state has been reset last time.<p>
1200: *
1201: * <strong>Note:</strong> Unlike <code>#resetChanged</code> this method
1202: * is not intended to be overridden by subclasses.
1203: * If you want to track changes of other ValueModels, bean properties, or
1204: * of submodels, register them by means of <code>#observeChanged</code>.
1205: * Overriding <code>#isChanged</code> to include the changed state
1206: * of submodels would return the correct changed value, but it would bypass
1207: * the change notification from submodels to this model.
1208: * Therefore submodels must be observed, which can be achieve using
1209: * <code>#observeChanged</code>.<p>
1210: *
1211: * To reset the changed state invoke <code>#resetChanged</code>.
1212: * In case you track the changed state of submodels override
1213: * <code>#resetChanged</code> to reset the changed state in these
1214: * submodels too.
1215: *
1216: * @return true if an observed property has changed since the last reset
1217: *
1218: * @see #observeChanged(ValueModel)
1219: * @see #observeChanged(Object, String)
1220: * @see #resetChanged()
1221: */
1222: public boolean isChanged() {
1223: return changed;
1224: }
1225:
1226: /**
1227: * Resets this model's changed state to <code>false</code>.
1228: * Therefore it resets the changed states of the change tracker
1229: * and the underlying bean adapter.<p>
1230: *
1231: * Subclasses may override this method to reset the changed state
1232: * of submodels. The overriding method must invoke this super behavior.
1233: * For example if you have a MainModel that is composed of
1234: * two submodels Submodel1 and Submodel2, you may write:
1235: * <pre>
1236: * public void resetChanged() {
1237: * super.resetChanged();
1238: * getSubmodel1().resetChanged();
1239: * getSubmodel2().resetChanged();
1240: * }
1241: * </pre>
1242: *
1243: * @see #isChanged()
1244: * @see #observeChanged(ValueModel)
1245: * @see #observeChanged(Object, String)
1246: */
1247: public void resetChanged() {
1248: setChanged(false);
1249: beanAdapter.resetChanged();
1250: }
1251:
1252: protected void setChanged(boolean newValue) {
1253: boolean oldValue = isChanged();
1254: changed = newValue;
1255: firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
1256: }
1257:
1258: // Observing Changes in ValueModel and Bean Properties *******************
1259:
1260: /**
1261: * Observes the specified readable bound bean property in the given bean.
1262: *
1263: * @param bean the bean to be observed
1264: * @param propertyName the name of the readable bound bean property
1265: * @throws NullPointerException if the bean or propertyName is null
1266: * @throws PropertyNotBindableException if this class can't add
1267: * the PropertyChangeListener from the bean
1268: *
1269: * @see #retractInterestFor(Object, String)
1270: * @see #observeChanged(ValueModel)
1271: */
1272: public void observeChanged(Object bean, String propertyName) {
1273: if (bean == null)
1274: throw new NullPointerException("The bean must not be null.");
1275: if (propertyName == null)
1276: throw new NullPointerException(
1277: "The property name must not be null.");
1278:
1279: BeanUtils.addPropertyChangeListener(bean, propertyName,
1280: changedUpdateHandler);
1281: }
1282:
1283: /**
1284: * Observes value changes in the given ValueModel.
1285: *
1286: * @param valueModel the ValueModel to observe
1287: * @throws NullPointerException if the valueModel is null
1288: *
1289: * @see #retractInterestFor(ValueModel)
1290: * @see #observeChanged(Object, String)
1291: */
1292: public void observeChanged(ValueModel valueModel) {
1293: if (valueModel == null)
1294: throw new NullPointerException(
1295: "The ValueModel must not be null.");
1296: valueModel.addValueChangeListener(changedUpdateHandler);
1297: }
1298:
1299: /**
1300: * Retracts interest for the specified readable bound bean property
1301: * in the given bean.
1302: *
1303: * @param bean the bean to be observed
1304: * @param propertyName the name of the readable bound bean property
1305: * @throws NullPointerException if the bean or propertyName is null
1306: * @throws PropertyNotBindableException if this class can't remove
1307: * the PropertyChangeListener from the bean
1308: *
1309: * @see #observeChanged(Object, String)
1310: * @see #retractInterestFor(ValueModel)
1311: */
1312: public void retractInterestFor(Object bean, String propertyName) {
1313: if (bean == null)
1314: throw new NullPointerException("The bean must not be null.");
1315: if (propertyName == null)
1316: throw new NullPointerException(
1317: "The property name must not be null.");
1318:
1319: BeanUtils.removePropertyChangeListener(bean, propertyName,
1320: changedUpdateHandler);
1321: }
1322:
1323: /**
1324: * Retracts interest for value changes in the given ValueModel.
1325: *
1326: * @param valueModel the ValueModel to observe
1327: * @throws NullPointerException if the valueModel is null
1328: *
1329: * @see #observeChanged(ValueModel)
1330: * @see #retractInterestFor(Object, String)
1331: */
1332: public void retractInterestFor(ValueModel valueModel) {
1333: if (valueModel == null)
1334: throw new NullPointerException(
1335: "The ValueModel must not be null.");
1336: valueModel.removeValueChangeListener(changedUpdateHandler);
1337: }
1338:
1339: // Managing Bean Property Change Listeners *******************************
1340:
1341: /**
1342: * Adds a PropertyChangeListener to the list of bean listeners. The
1343: * listener is registered for all bound properties of the target bean.<p>
1344: *
1345: * The listener will be notified if and only if this BeanAdapter's current
1346: * bean changes a property. It'll not be notified if the bean changes.<p>
1347: *
1348: * If listener is <code>null</code>, no exception is thrown and
1349: * no action is performed.
1350: *
1351: * @param listener the PropertyChangeListener to be added
1352: *
1353: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
1354: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
1355: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
1356: * @see #getBeanPropertyChangeListeners()
1357: */
1358: public synchronized void addBeanPropertyChangeListener(
1359: PropertyChangeListener listener) {
1360: beanAdapter.addBeanPropertyChangeListener(listener);
1361: }
1362:
1363: /**
1364: * Removes a PropertyChangeListener from the list of bean listeners.
1365: * This method should be used to remove PropertyChangeListeners that
1366: * were registered for all bound properties of the target bean.<p>
1367: *
1368: * If listener is <code>null</code>, no exception is thrown and
1369: * no action is performed.
1370: *
1371: * @param listener the PropertyChangeListener to be removed
1372: *
1373: * @see #addBeanPropertyChangeListener(PropertyChangeListener)
1374: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
1375: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
1376: * @see #getBeanPropertyChangeListeners()
1377: */
1378: public synchronized void removeBeanPropertyChangeListener(
1379: PropertyChangeListener listener) {
1380: beanAdapter.removeBeanPropertyChangeListener(listener);
1381: }
1382:
1383: /**
1384: * Adds a PropertyChangeListener to the list of bean listeners for a
1385: * specific property. The specified property may be user-defined.<p>
1386: *
1387: * The listener will be notified if and only if this BeanAdapter's
1388: * current bean changes the specified property. It'll not be notified
1389: * if the bean changes. If you want to observe property changes and
1390: * bean changes, you may observe the ValueModel that adapts this property
1391: * - as returned by <code>#getModel(String)</code>.<p>
1392: *
1393: * Note that if the bean is inheriting a bound property, then no event
1394: * will be fired in response to a change in the inherited property.<p>
1395: *
1396: * If listener is <code>null</code>, no exception is thrown and
1397: * no action is performed.
1398: *
1399: * @param propertyName one of the property names listed above
1400: * @param listener the PropertyChangeListener to be added
1401: *
1402: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
1403: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
1404: * @see #getBeanPropertyChangeListeners(String)
1405: */
1406: public synchronized void addBeanPropertyChangeListener(
1407: String propertyName, PropertyChangeListener listener) {
1408: beanAdapter.addBeanPropertyChangeListener(propertyName,
1409: listener);
1410: }
1411:
1412: /**
1413: * Removes a PropertyChangeListener from the listener list for a specific
1414: * property. This method should be used to remove PropertyChangeListeners
1415: * that were registered for a specific bound property.<p>
1416: *
1417: * If listener is <code>null</code>, no exception is thrown and
1418: * no action is performed.
1419: *
1420: * @param propertyName a valid property name
1421: * @param listener the PropertyChangeListener to be removed
1422: *
1423: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
1424: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
1425: * @see #getBeanPropertyChangeListeners(String)
1426: */
1427: public synchronized void removeBeanPropertyChangeListener(
1428: String propertyName, PropertyChangeListener listener) {
1429: beanAdapter.removeBeanPropertyChangeListener(propertyName,
1430: listener);
1431: }
1432:
1433: // Requesting Listener Sets ***********************************************
1434:
1435: /**
1436: * Returns an array of all the property change listeners
1437: * registered on this component.
1438: *
1439: * @return all of this component's <code>PropertyChangeListener</code>s
1440: * or an empty array if no property change
1441: * listeners are currently registered
1442: *
1443: * @see #addBeanPropertyChangeListener(PropertyChangeListener)
1444: * @see #removeBeanPropertyChangeListener(PropertyChangeListener)
1445: * @see #getBeanPropertyChangeListeners(String)
1446: * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners()
1447: */
1448: public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners() {
1449: return beanAdapter.getBeanPropertyChangeListeners();
1450: }
1451:
1452: /**
1453: * Returns an array of all the listeners which have been associated
1454: * with the named property.
1455: *
1456: * @param propertyName the name of the property to lookup listeners
1457: * @return all of the <code>PropertyChangeListeners</code> associated with
1458: * the named property or an empty array if no listeners have
1459: * been added
1460: *
1461: * @see #addBeanPropertyChangeListener(String, PropertyChangeListener)
1462: * @see #removeBeanPropertyChangeListener(String, PropertyChangeListener)
1463: * @see #getBeanPropertyChangeListeners()
1464: */
1465: public synchronized PropertyChangeListener[] getBeanPropertyChangeListeners(
1466: String propertyName) {
1467: return beanAdapter.getBeanPropertyChangeListeners(propertyName);
1468: }
1469:
1470: // Misc *******************************************************************
1471:
1472: /**
1473: * Removes the PropertyChangeHandler from the observed bean,
1474: * if the bean is not <code>null</code>.
1475: * Also removes all listeners from the bean that have been registered
1476: * with <code>#addBeanPropertyChangeListener</code> before.<p>
1477: *
1478: * PresentationModels have a PropertyChangeListener registered with
1479: * the target bean. Hence, a bean has a reference to all PresentationModels
1480: * that hold it as bean. To avoid memory leaks it is recommended to remove
1481: * this listener, if the bean lives much longer than the PresentationModel,
1482: * enabling the garbage collector to remove the PresentationModel.
1483: * To do so, you can call <code>setBean(null)</code> or set the
1484: * bean channel's value to null.
1485: * As an alternative you can use event listener lists in your beans
1486: * that implement references with <code>WeakReference</code>.<p>
1487: *
1488: * Setting the bean to null has side-effects, for example the model
1489: * fires a change event for the bound property <em>bean</em> and
1490: * other properties.
1491: * And the value of ValueModel's vent by this model may change.
1492: * However, typically this is fine and setting the bean to null
1493: * is the first choice for removing the reference from the bean to
1494: * the PresentationModel.
1495: * Another way to clear the reference from the target bean is to call
1496: * this <code>#release</code> method. It has no side-effects, but
1497: * the PresentationModel must not be used anymore once #release
1498: * has been called.
1499: *
1500: * @see #setBean(Object)
1501: * @see java.lang.ref.WeakReference
1502: *
1503: * @since 1.2
1504: */
1505: public void release() {
1506: beanAdapter.release();
1507: }
1508:
1509: // Helper Class ***********************************************************
1510:
1511: /**
1512: * Holds a BufferedValueModel together with the names of the getter
1513: * and setter. Used to look up models in <code>#getBufferedModel</code>.
1514: * Also ensures that there are no two buffered models with different
1515: * getter/setter pairs.
1516: *
1517: * @see PresentationModel#getBufferedModel(String)
1518: * @see PresentationModel#getBufferedModel(String, String, String)
1519: */
1520: private static final class WrappedBuffer {
1521:
1522: final BufferedValueModel buffer;
1523: final String getterName;
1524: final String setterName;
1525:
1526: WrappedBuffer(BufferedValueModel buffer, String getterName,
1527: String setterName) {
1528: this .buffer = buffer;
1529: this .getterName = getterName;
1530: this .setterName = setterName;
1531: }
1532: }
1533:
1534: // Event Handling and Forwarding Changes **********************************
1535:
1536: /**
1537: * Listens to changes of the bean, invoked the before and after methods,
1538: * and forwards the bean change events.
1539: */
1540: private final class BeanChangeHandler implements
1541: PropertyChangeListener {
1542:
1543: /**
1544: * The target bean will change, changes, or has changed.
1545: *
1546: * @param evt the property change event to be handled
1547: */
1548: public void propertyChange(PropertyChangeEvent evt) {
1549: B oldBean = (B) evt.getOldValue();
1550: B newBean = (B) evt.getNewValue();
1551: String propertyName = evt.getPropertyName();
1552: if (BeanAdapter.PROPERTYNAME_BEFORE_BEAN
1553: .equals(propertyName)) {
1554: beforeBeanChange(oldBean, newBean);
1555: } else if (BeanAdapter.PROPERTYNAME_BEAN
1556: .equals(propertyName)) {
1557: firePropertyChange(PROPERTYNAME_BEAN, oldBean, newBean,
1558: true);
1559: } else if (BeanAdapter.PROPERTYNAME_AFTER_BEAN
1560: .equals(propertyName)) {
1561: afterBeanChange(oldBean, newBean);
1562: }
1563: }
1564: }
1565:
1566: /**
1567: * Updates the buffering state if a model buffering state changed.
1568: */
1569: private final class BufferingStateHandler implements
1570: PropertyChangeListener {
1571:
1572: /**
1573: * A registered BufferedValueModel has reported a change in its
1574: * <em>buffering</em> state. Update this model's buffering state.
1575: *
1576: * @param evt describes the property change
1577: */
1578: public void propertyChange(PropertyChangeEvent evt) {
1579: updateBufferingState(((Boolean) evt.getNewValue())
1580: .booleanValue());
1581: }
1582:
1583: }
1584:
1585: /**
1586: * Listens to model changes and updates the changed state.
1587: */
1588: private final class UpdateHandler implements PropertyChangeListener {
1589:
1590: /**
1591: * A registered ValueModel has changed.
1592: * Updates the changed state. If the property that changed is
1593: * 'changed' we assume that this is another changed state and
1594: * forward only changes to true. For all other property names,
1595: * we just update our changed state to true.
1596: *
1597: * @param evt the event that describes the property change
1598: */
1599: public void propertyChange(PropertyChangeEvent evt) {
1600: String propertyName = evt.getPropertyName();
1601: if (!PROPERTYNAME_CHANGED.equals(propertyName)
1602: || ((Boolean) evt.getNewValue()).booleanValue()) {
1603: setChanged(true);
1604: }
1605: }
1606: }
1607:
1608: }
|