001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.binding.form.support;
017    
018    import java.beans.PropertyChangeEvent;
019    import java.beans.PropertyChangeListener;
020    import java.util.Iterator;
021    
022    import org.springframework.binding.value.ValueChangeDetector;
023    import org.springframework.binding.value.ValueModel;
024    import org.springframework.binding.value.support.AbstractValueModelWrapper;
025    import org.springframework.binding.value.support.DirtyTrackingValueModel;
026    import org.springframework.binding.value.support.ValueHolder;
027    import org.springframework.richclient.application.ApplicationServicesLocator;
028    import org.springframework.richclient.util.EventListenerListHelper;
029    
030    /**
031     * <p>
032     * A value model wrapper that mediates between the (wrapped) data value model
033     * and the derived view value model. Allows for value change event delivery to
034     * be disabled.
035     * </p>
036     * 
037     * <p>
038     * Use the provided method {@link #setDeliverValueChangeEvents(boolean)} to
039     * enable/disable the event mechanism. This makes it possible to change all
040     * valueModels and only then fire all events (as it is used in
041     * {@link AbstractFormModel}). Events are handled internally by the
042     * {@link #dirtyChangeListeners} and the {@link #mediatedValueHolder}.
043     * </p>
044     * 
045     * <p>
046     * As this is also a {@link DirtyTrackingValueModel}, the implementation allows
047     * reverting to the original value by using {@link #revertToOriginal()}, which
048     * uses the value stored in {@link #originalValue} to reset the
049     * wrappedValueModel. This originalValue is updated to hold the wrappedValue
050     * when using {@link #clearDirty()}.
051     * </p>
052     * 
053     * <p>Small sketch to illustrate the positioning and usage:</p>
054     * <pre>
055     * &lt;setup&gt;
056     * External actor -> FormModelMediatingValueModel -> wrappedValueModel
057     *                   holds originalValue = ori       wrappedValue = ori
058     *                   events = enabled
059     * 
060     * &lt;use case&gt;
061     * events disabled -> events = disabled            -> wrappedValue = ori
062     * write value A   -> delagates to wrappedModel    -> wrappedValue = A
063     *                    originalValue = ori
064     *                    update dirty state
065     * events enabled  -> events = enabled             -> wrappedValue = A
066     *                    sends events (dirty...)
067     * clearDirty      -> originalValue = A            -> wrappedValue = A
068     * OR
069     * revertToOriginal-> set originalValue on wrapped -> wrappedValue = ori
070     *                    update dirty state                  
071     * </pre>
072     * 
073     * @author Oliver Hutchison
074     */
075    public class FormModelMediatingValueModel extends AbstractValueModelWrapper implements DirtyTrackingValueModel,
076                    PropertyChangeListener {
077    
078            /** Holds all propertyChangeListeners that are interested in Dirty changes. */
079            private final EventListenerListHelper dirtyChangeListeners = new EventListenerListHelper(
080                            PropertyChangeListener.class);
081    
082            /** Allows to turn off the tracking mechanism. */
083            private boolean deliverValueChangeEvents = true;
084    
085            /** Holds the originalValue. Used to register listeners. */
086            private final ValueHolder mediatedValueHolder;
087    
088            /** The original value of the wrapped ValueModel. */
089            private Object originalValue;
090    
091            /** Previous dirty state. */
092            private boolean oldDirty;
093    
094            /** Dirty tracking can be disabled. */
095            private final boolean trackDirty;
096    
097            /** The pluggable valueChangeDetector used to track dirty changes. */
098            private ValueChangeDetector valueChangeDetector;
099    
100            /**
101             * Constructor which defaults <code>trackDirty=true</code>.
102             * 
103             * @param propertyValueModel the ValueModel to mediate.
104             */
105            public FormModelMediatingValueModel(ValueModel propertyValueModel) {
106                    this(propertyValueModel, true);
107            }
108    
109            /**
110             * Constructor.
111             * 
112             * @param propertyValueModel the valueModel to mediate.
113             * @param trackDirty disable/enable dirty tracking.
114             */
115            public FormModelMediatingValueModel(ValueModel propertyValueModel, boolean trackDirty) {
116                    super(propertyValueModel);
117                    super.addValueChangeListener(this);
118                    this.originalValue = getValue();
119                    this.mediatedValueHolder = new ValueHolder(originalValue);
120                    this.trackDirty = trackDirty;
121            }
122    
123            public void setValueSilently(Object value, PropertyChangeListener listenerToSkip) {
124                    super.setValueSilently(value, this);
125                    if (deliverValueChangeEvents) {
126                            mediatedValueHolder.setValueSilently(value, listenerToSkip);
127                            updateDirtyState();
128                    }
129            }
130    
131            // called by the wrapped value model
132            public void propertyChange(PropertyChangeEvent evt) {
133                    originalValue = getValue();
134                    if (deliverValueChangeEvents) {
135                            mediatedValueHolder.setValue(originalValue);
136                            updateDirtyState();
137                    }
138            }
139    
140            /**
141             * <p>
142             * Enable/disable the event mechanism. Makes it possible to control the
143             * timing of event firing (delay the events).
144             * </p>
145             * 
146             * <p>
147             * When disabling, no dirty events will be fired and the mediating
148             * valueHolder will not set it's value. The latter results in not firing
149             * other events like valueChangedEvents.
150             * </p>
151             * <p>
152             * When enabling, original (stored) value is compared to the newer value in
153             * the wrapped model and the necessary events are fired
154             * (dirty/valueChanged).
155             * <p>
156             * 
157             * @param deliverValueChangeEvents boolean to enable/disable event
158             * mechanism.
159             */
160            public void setDeliverValueChangeEvents(boolean deliverValueChangeEvents) {
161                    boolean oldDeliverValueChangeEvents = this.deliverValueChangeEvents;
162                    this.deliverValueChangeEvents = deliverValueChangeEvents;
163                    if (!oldDeliverValueChangeEvents && deliverValueChangeEvents) {
164                            mediatedValueHolder.setValue(getValue());
165                            updateDirtyState();
166                    }
167            }
168    
169            public boolean isDirty() {
170                    return trackDirty && getValueChangeDetector().hasValueChanged(originalValue, getValue());
171            }
172    
173            public void clearDirty() {
174                    if (isDirty()) {
175                            originalValue = getValue();
176                            updateDirtyState();
177                    }
178            }
179    
180            public void revertToOriginal() {
181                    if (isDirty()) {
182                            setValue(originalValue);
183                    }
184            }
185    
186            /**
187             * Check the dirty state and fire events if needed.
188             */
189            protected void updateDirtyState() {
190                    boolean dirty = isDirty();
191                    if (oldDirty != dirty) {
192                            oldDirty = dirty;
193                            firePropertyChange(DIRTY_PROPERTY, !dirty, dirty);
194                    }
195            }
196    
197            /**
198             * @param valueChangeDetector set the {@link ValueChangeDetector} to use
199             * when checking the dirty state.
200             */
201            public void setValueChangeDetector(ValueChangeDetector valueChangeDetector) {
202                    this.valueChangeDetector = valueChangeDetector;
203            }
204    
205            /**
206             * @return a {@link ValueChangeDetector} to use when checking the dirty
207             * state.
208             */
209            protected ValueChangeDetector getValueChangeDetector() {
210                    if (valueChangeDetector == null) {
211                            valueChangeDetector = (ValueChangeDetector) ApplicationServicesLocator.services().getService(
212                                            ValueChangeDetector.class);
213                    }
214                    return valueChangeDetector;
215            }
216    
217            public void addValueChangeListener(PropertyChangeListener listener) {
218                    mediatedValueHolder.addValueChangeListener(listener);
219            }
220    
221            public void removeValueChangeListener(PropertyChangeListener listener) {
222                    mediatedValueHolder.removeValueChangeListener(listener);
223            }
224    
225            public void addPropertyChangeListener(PropertyChangeListener listener) {
226                    throw new UnsupportedOperationException("not implemented");
227            }
228    
229            public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
230                    if (DIRTY_PROPERTY.equals(propertyName)) {
231                            dirtyChangeListeners.add(listener);
232                    }
233                    else if (VALUE_PROPERTY.equals(propertyName)) {
234                            addValueChangeListener(listener);
235                    }
236            }
237    
238            public void removePropertyChangeListener(PropertyChangeListener listener) {
239                    throw new UnsupportedOperationException("not implemented");
240            }
241    
242            public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
243                    if (DIRTY_PROPERTY.equals(propertyName)) {
244                            dirtyChangeListeners.remove(listener);
245                    }
246                    else if (VALUE_PROPERTY.equals(propertyName)) {
247                            removeValueChangeListener(listener);
248                    }
249            }
250    
251            /**
252             * Handles the dirty event firing.
253             * 
254             * @param propertyName implementation only handles DIRTY_PROPERTY.
255             * @param oldValue
256             * @param newValue
257             */
258            protected final void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
259                    if (DIRTY_PROPERTY.equals(propertyName)) {
260                            PropertyChangeEvent evt = new PropertyChangeEvent(this, propertyName, Boolean.valueOf(oldValue), Boolean
261                                            .valueOf(newValue));
262                            for (Iterator i = dirtyChangeListeners.iterator(); i.hasNext();) {
263                                    ((PropertyChangeListener) i.next()).propertyChange(evt);
264                            }
265                    }
266            }
267    }