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    }