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.form.support;
017
018 import java.beans.PropertyChangeEvent;
019 import java.beans.PropertyChangeListener;
020 import java.util.Map;
021 import java.util.HashMap;
022
023 import org.springframework.binding.form.FormModel;
024 import org.springframework.binding.form.FieldMetadata;
025 import org.springframework.binding.value.support.AbstractPropertyChangePublisher;
026 import org.springframework.binding.value.support.DirtyTrackingValueModel;
027
028 /**
029 * Default implementation of FieldMetadata.
030 * <p>
031 * NOTE: This is a framework internal class and should not be
032 * instantiated in user code.
033 *
034 * @author Oliver Hutchison
035 */
036 public class DefaultFieldMetadata extends AbstractPropertyChangePublisher implements FieldMetadata {
037
038 private final FormModel formModel;
039
040 private final DirtyTrackingValueModel valueModel;
041
042 private final Class propertyType;
043
044 private final boolean forceReadOnly;
045
046 private final Map userMetadata = new HashMap();
047
048 private boolean oldReadOnly;
049
050 private boolean readOnly;
051
052 private boolean enabled = true;
053
054 private boolean oldEnabled = true;
055
056 private final DirtyChangeHandler dirtyChangeHandler = new DirtyChangeHandler();
057
058 private final PropertyChangeListener formChangeHandler = new FormModelChangeHandler();
059
060 /**
061 * Constructs a new instance of DefaultFieldMetadata.
062 *
063 * @param formModel the form model
064 * @param valueModel the value model for the property
065 * @param propertyType the type of the property
066 * @param forceReadOnly should readOnly be forced to true; this is
067 * required if the property can not be modified. e.g.
068 * at the PropertyAccessStrategy level.
069 * @param userMetadata map using String keys containing user defined
070 * metadata. As an example, tiger extensions
071 * currently use this to expose JDK 1.5 annotations on
072 * the backing object as property metadata. This
073 * parameter may be <code>null</code>.
074 */
075 public DefaultFieldMetadata(FormModel formModel, DirtyTrackingValueModel valueModel, Class propertyType, boolean forceReadOnly, Map userMetadata) {
076 this.formModel = formModel;
077 this.valueModel = valueModel;
078 this.valueModel.addPropertyChangeListener(DirtyTrackingValueModel.DIRTY_PROPERTY, dirtyChangeHandler);
079 this.propertyType = propertyType;
080 this.forceReadOnly = forceReadOnly;
081 this.formModel.addPropertyChangeListener(ENABLED_PROPERTY, formChangeHandler);
082 this.formModel.addPropertyChangeListener(READ_ONLY_PROPERTY, formChangeHandler);
083 this.oldReadOnly = isReadOnly();
084 this.oldEnabled = isEnabled();
085 if(userMetadata != null) {
086 this.userMetadata.putAll(userMetadata);
087 }
088 }
089
090 public void setReadOnly(boolean readOnly) {
091 this.readOnly = readOnly;
092 firePropertyChange(READ_ONLY_PROPERTY, oldReadOnly, isReadOnly());
093 oldReadOnly = isReadOnly();
094 }
095
096 public boolean isReadOnly() {
097 return forceReadOnly || readOnly || formModel.isReadOnly();
098 }
099
100 public void setEnabled(boolean enabled) {
101 this.enabled = enabled;
102 firePropertyChange(ENABLED_PROPERTY, oldEnabled, isEnabled());
103 oldEnabled = isEnabled();
104 }
105
106 public boolean isEnabled() {
107 return enabled && formModel.isEnabled();
108 }
109
110 public boolean isDirty() {
111 return valueModel.isDirty();
112 }
113
114 public Class getPropertyType() {
115 return propertyType;
116 }
117
118 public Object getUserMetadata(final String key) {
119 return userMetadata.get(key);
120 }
121
122 public Map getAllUserMetadata() {
123 return userMetadata;
124 }
125
126 /**
127 * Sets custom metadata to be associated with this property. A property
128 * change event will be fired (from this FieldMetadata, not from the
129 * associated form property) if <code>value</code> differs from the
130 * current value of the specified <code>key</code>. The property change
131 * event will use the value of <code>key</code> as the property name in
132 * the property change event.
133 *
134 * @param key
135 * @param value
136 */
137 public void setUserMetadata(final String key, final Object value) {
138 final Object old = userMetadata.put(key, value);
139 firePropertyChange(key, old, value);
140 }
141
142 /**
143 * Clears all custom metadata associated with this property. A property
144 * change event will be fired for every <code>key</code> that contained a
145 * non-null value before this method was invoked. It is possible for a
146 * PropertyChangeListener to mutate user metadata, by setting a key value
147 * for example, in response to one of these property change events fired
148 * during the course of the clear operation. Because of this, there is
149 * no guarantee that all user metadata is in fact completely clear and
150 * empty by the time this method returns.
151 */
152 public void clearUserMetadata() {
153 // Copy keys into array to avoid concurrent modification exceptions
154 // if any PropertyChangeListeners should modify user metadata during
155 // clear operation.
156 final Object[] keys = userMetadata.keySet().toArray();
157 for(int i = keys.length - 1;i >= 0;i--) {
158 final Object old = userMetadata.remove(keys[i]);
159 if(old != null) {
160 firePropertyChange((String)keys[i], old, null);
161 }
162 }
163 }
164
165 /**
166 * Propagates dirty changes from the value model on to
167 * the dirty change listeners attached to this class.
168 */
169 private class DirtyChangeHandler extends CommitListenerAdapter implements PropertyChangeListener {
170
171 public void propertyChange(PropertyChangeEvent evt) {
172 firePropertyChange(DIRTY_PROPERTY, evt.getOldValue(), evt.getNewValue());
173 }
174 }
175
176 /**
177 * Responsible for listening for changes to the enabled
178 * property of the FormModel
179 */
180 private class FormModelChangeHandler implements PropertyChangeListener {
181 public void propertyChange(PropertyChangeEvent evt) {
182 if (FormModel.ENABLED_PROPERTY.equals(evt.getPropertyName())) {
183 firePropertyChange(ENABLED_PROPERTY, Boolean.valueOf(oldEnabled), Boolean.valueOf(isEnabled()));
184 oldEnabled = isEnabled();
185 }
186 else if (FormModel.READONLY_PROPERTY.equals(evt.getPropertyName())) {
187 firePropertyChange(READ_ONLY_PROPERTY, Boolean.valueOf(oldReadOnly), Boolean.valueOf(isReadOnly()));
188 oldReadOnly = isReadOnly();
189 }
190 }
191 }
192 }