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 * <setup>
056 * External actor -> FormModelMediatingValueModel -> wrappedValueModel
057 * holds originalValue = ori wrappedValue = ori
058 * events = enabled
059 *
060 * <use case>
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 }