001 /*
002 * Copyright 2002-2005 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.value.support;
017
018 import java.beans.PropertyChangeEvent;
019 import java.beans.PropertyChangeListener;
020
021 import org.springframework.binding.value.CommitTrigger;
022 import org.springframework.binding.value.CommitTriggerListener;
023 import org.springframework.binding.value.ValueModel;
024 import org.springframework.core.style.ToStringCreator;
025 import org.springframework.util.Assert;
026
027 /**
028 * A value model that wraps another value model; delaying or buffering changes
029 * until a commit is triggered.
030 *
031 * TODO: more class docs...
032 *
033 * @author Karsten Lentzsch
034 * @author Keith Donald
035 * @author Oliver Hutchison
036 */
037 public class BufferedValueModel extends AbstractValueModel implements ValueModelWrapper {
038
039 /**
040 * Name of the bound property <em>buffering</em>.
041 */
042 public static final String BUFFERING_PROPERTY = "buffering";
043
044 private final ValueModel wrappedModel;
045
046 private final PropertyChangeListener wrappedModelChangeHandler;
047
048 private Object bufferedValue;
049
050 private CommitTrigger commitTrigger;
051
052 private CommitTriggerListener commitTriggerHandler;
053
054 private boolean buffering;
055
056 /**
057 * Constructs a <code>BufferedValueHolder</code> that wraps the given wrappedModel.
058 *
059 * @param wrappedModel the value model to be buffered
060 */
061 public BufferedValueModel(ValueModel wrappedModel) {
062 this(wrappedModel, null);
063 }
064
065 /**
066 * Constructs a <code>BufferedValueHolder</code> that wraps the given wrappedModel
067 * and listens to the provided commitTrigger for commit and revert events.
068 *
069 * @param wrappedModel the value model to be buffered
070 * @param commitTrigger the commit trigger that triggers the commit or flush event
071 */
072 public BufferedValueModel(ValueModel wrappedModel, CommitTrigger commitTrigger) {
073 Assert.notNull(wrappedModel, "Wrapped value model can not be null.");
074 this.wrappedModel = wrappedModel;
075 this.wrappedModelChangeHandler = new WrappedModelValueChangeHandler();
076 this.wrappedModel.addValueChangeListener(wrappedModelChangeHandler);
077 this.bufferedValue = wrappedModel.getValue();
078 setCommitTrigger(commitTrigger);
079 }
080
081 /**
082 * Returns the CommitTrigger that is used to trigger commit and flush events.
083 *
084 * @return the CommitTrigger that is used to trigger commit and flush events
085 */
086 public final CommitTrigger getCommitTrigger() {
087 return commitTrigger;
088 }
089
090 /**
091 * Sets the <code>CommitTrigger</code> that triggers the commit and flush events.
092 *
093 * @param commitTrigger the commit trigger; or null to deregister the
094 * existing trigger.
095 */
096 public final void setCommitTrigger(CommitTrigger commitTrigger) {
097 if (this.commitTrigger == commitTrigger) {
098 return;
099 }
100 if (this.commitTrigger != null) {
101 this.commitTrigger.removeCommitTriggerListener(commitTriggerHandler);
102 this.commitTrigger = null;
103 }
104 if (commitTrigger != null) {
105 if (this.commitTriggerHandler == null) {
106 this.commitTriggerHandler = new CommitTriggerHandler();
107 }
108 this.commitTrigger = commitTrigger;
109 this.commitTrigger.addCommitTriggerListener(commitTriggerHandler);
110 }
111 }
112
113 /**
114 * Returns whether this model buffers a value or not, that is, whether
115 * a value has been assigned since the last commit or flush.
116 *
117 * @return true if a value has been assigned since the last commit or revert
118 */
119 public boolean isBuffering() {
120 return buffering;
121 }
122
123 /**
124 * Returns the wrappedModel value if no value has been set since the last
125 * commit or flush, and returns the buffered value otherwise.
126 *
127 * @return the buffered value
128 */
129 public Object getValue() {
130 return bufferedValue;
131 }
132
133 /**
134 * Sets a new buffered value and turns this BufferedValueModel into
135 * the buffering state. The buffered value is not provided to the
136 * underlying model until the trigger channel indicates a commit.
137 *
138 * @param value the value to be buffered
139 */
140 public void setValue(Object value) {
141 if (logger.isDebugEnabled()) {
142 logger.debug("Setting buffered value to '" + value + "'");
143 }
144 final Object oldValue = getValue();
145 this.bufferedValue = value;
146 updateBuffering();
147 fireValueChange(oldValue, bufferedValue);
148 }
149
150 /**
151 * Returns the wrappedModel, i.e. the underlying ValueModel that provides
152 * the unbuffered value.
153 *
154 * @return the ValueModel that provides the unbuffered value
155 */
156 public final ValueModel getWrappedValueModel() {
157 return wrappedModel;
158 }
159
160 /**
161 * Returns the inner most wrappedModel; i.e. the root ValueModel that provides
162 * the unbuffered value. This is found by repeatedly unwrapping any ValueModelWrappers
163 * until we find the inner most value model.
164 *
165 * @return the inner most ValueModel that provides the unbuffered value
166 *
167 * @see ValueModelWrapper#getInnerMostWrappedValueModel()
168 */
169 public final ValueModel getInnerMostWrappedValueModel() {
170 if (wrappedModel instanceof ValueModelWrapper)
171 return ((ValueModelWrapper)wrappedModel).getInnerMostWrappedValueModel();
172
173 return wrappedModel;
174 }
175
176 /**
177 * Called when the value held by the wrapped value model changes.
178 */
179 protected void onWrappedValueChanged() {
180 if (logger.isDebugEnabled()) {
181 logger.debug("Wrapped model value has changed; new value is '" + wrappedModel.getValue() + "'");
182 }
183 setValue(wrappedModel.getValue());
184 }
185
186 /**
187 * Commits the value buffered by this value model back to the
188 * wrapped value model.
189 */
190 public void commit() {
191 if (isBuffering()) {
192 if (logger.isDebugEnabled()) {
193 logger.debug("Committing buffered value '" + getValue() + "' to wrapped value model '" + wrappedModel
194 + "'");
195 }
196 wrappedModel.setValueSilently(getValueToCommit(), wrappedModelChangeHandler);
197 setValue(wrappedModel.getValue()); // check if the wrapped model changed the committed value
198 }
199 else {
200 if (logger.isDebugEnabled()) {
201 logger.debug("No buffered edit to commit; nothing to do...");
202 }
203 }
204 }
205
206 /**
207 * Provides a hook that allows for modification of the value that is committed
208 * to the underlying value model.
209 */
210 protected Object getValueToCommit() {
211 return bufferedValue;
212 }
213
214 /**
215 * Updates the value of the buffering property. Fires a property change event
216 * if there's been a change.
217 */
218 private void updateBuffering() {
219 boolean wasBuffering = isBuffering();
220 buffering = hasValueChanged(wrappedModel.getValue(), bufferedValue);
221 firePropertyChange(BUFFERING_PROPERTY, wasBuffering, buffering);
222 }
223
224 /**
225 * Reverts the value held by the value model back to the value held by the
226 * wrapped value model.
227 */
228 public final void revert() {
229 if (isBuffering()) {
230 if (logger.isDebugEnabled()) {
231 logger.debug("Reverting buffered value '" + getValue() + "' to value '" + wrappedModel.getValue() + "'");
232 }
233 setValue(wrappedModel.getValue());
234 }
235 else {
236 if (logger.isDebugEnabled()) {
237 logger.debug("No buffered edit to commit; nothing to do...");
238 }
239 }
240 }
241
242 public String toString() {
243 return new ToStringCreator(this).append("bufferedValue", bufferedValue).toString();
244 }
245
246 /**
247 * Listens for changes to the wrapped value model.
248 */
249 private class WrappedModelValueChangeHandler implements PropertyChangeListener {
250 public void propertyChange(PropertyChangeEvent evt) {
251 onWrappedValueChanged();
252 }
253 }
254
255 /**
256 * Listens for commit/revert on the commitTrigger.
257 */
258 private class CommitTriggerHandler implements CommitTriggerListener {
259 public void commit() {
260 if (logger.isDebugEnabled()) {
261 logger.debug("Commit trigger fired commit event.");
262 }
263 BufferedValueModel.this.commit();
264 }
265
266 public void revert() {
267 if (logger.isDebugEnabled()) {
268 logger.debug("Commit trigger fired revert event.");
269 }
270 BufferedValueModel.this.revert();
271 }
272 }
273
274 }