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 }