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 }