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 }