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.richclient.application.support;
017    
018    import java.beans.PropertyEditor;
019    import java.lang.reflect.Modifier;
020    import java.math.BigDecimal;
021    import java.text.DateFormat;
022    import java.util.Date;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.springframework.beans.propertyeditors.ClassEditor;
033    import org.springframework.beans.propertyeditors.CustomBooleanEditor;
034    import org.springframework.beans.propertyeditors.CustomDateEditor;
035    import org.springframework.beans.propertyeditors.CustomNumberEditor;
036    import org.springframework.richclient.util.ClassUtils;
037    import org.springframework.util.Assert;
038    
039    /**
040     * This provides a default implementation of {@link PropertyEditorRegistry}
041     * 
042     * @author Jim Moore
043     */
044    public class DefaultPropertyEditorRegistry 
045    //implements PropertyEditorRegistry 
046    {
047        private static final Log logger = LogFactory.getLog(
048            DefaultPropertyEditorRegistry.class);
049        
050        private Map propertyEditorByClass = new HashMap();
051        
052        private Map propertyEditorByClassAndProperty = new HashMap();
053        
054        public DefaultPropertyEditorRegistry() {
055            initDefaultEditors();
056        }
057        
058        /**
059         * Initialize the default property editors, covering primitive and other basic value types.
060         */
061        protected void initDefaultEditors() {
062            setPropertyEditor(int.class, DefaultIntegerEditor.class);
063            setPropertyEditor(Integer.class, DefaultIntegerEditor.class);
064            setPropertyEditor(long.class, DefaultLongEditor.class);
065            setPropertyEditor(Long.class, DefaultLongEditor.class);
066            setPropertyEditor(float.class, DefaultFloatEditor.class);
067            setPropertyEditor(Float.class, DefaultFloatEditor.class);
068            setPropertyEditor(double.class, DefaultDoubleEditor.class);
069            setPropertyEditor(Double.class, DefaultDoubleEditor.class);
070            setPropertyEditor(BigDecimal.class, DefaultBigDecimalEditor.class);
071            setPropertyEditor(Date.class, DefaultDateEditor.class);
072        }
073        
074        private static class DefaultIntegerEditor extends CustomNumberEditor {
075            public DefaultIntegerEditor() {
076                super(Integer.class, true);
077            }
078        }
079    
080        private static class DefaultLongEditor extends CustomNumberEditor {
081            public DefaultLongEditor() {
082                super(Long.class, true);
083            }
084        }
085    
086        private static class DefaultFloatEditor extends CustomNumberEditor {
087            public DefaultFloatEditor() {
088                super(Float.class, true);
089            }
090        }
091    
092        private static class DefaultDoubleEditor extends CustomNumberEditor {
093            public DefaultDoubleEditor() {
094                super(Double.class, true);
095            }
096        }
097        
098        private static class DefaultBigDecimalEditor extends CustomNumberEditor {
099            public DefaultBigDecimalEditor() {
100                super(BigDecimal.class, true);
101            }
102        }
103    
104        private static class DefaultBooleanEditor extends CustomBooleanEditor {
105            public DefaultBooleanEditor() {
106                super(true);
107            }
108        }
109    
110        private static class DefaultDateEditor extends CustomDateEditor {
111            public DefaultDateEditor() {
112                super(DateFormat.getDateInstance(), true);
113            }
114        }
115    
116        /**
117         * Adds a list of property editors to the registry extracting the object
118         * class, property name and property editor class from the properties
119         * "objectClass", "propertyName" and "propertyEditorClass".
120         * 
121         * @param propertyEditors
122         *            the list of property editors. Each element is expected to be
123         *            an instance of <code>java.lang.Properties</code>.
124         * @see DefaultPropertyEditorRegistry#setPropertyEditor(Properties)
125         */
126        public void setPropertyEditors(List propertyEditors) {
127            for (Iterator i = propertyEditors.iterator(); i.hasNext();) {
128                setPropertyEditor((Properties)i.next());            
129            }
130        }
131        
132        /**
133         * Adds a property editor to the registry extracting the object class,
134         * property name and property editor class from the properties
135         * "objectClass", "propertyName" and "propertyEditorClass".
136         * 
137         * @param properties
138         *            the properties
139         */
140        public void setPropertyEditor(Properties properties) {
141            ClassEditor classEditor = new ClassEditor();
142            
143            Class objectClass = null;
144            String propertyName = null;
145            Class propertyEditorClass = null;
146                    
147            if (properties.get("objectClass") != null) {
148                classEditor.setAsText((String) properties.get("objectClass"));
149                objectClass = (Class) classEditor.getValue();
150            }
151            propertyName = (String) properties.get("propertyName");
152            if (properties.get("propertyEditorClass") != null) {
153                classEditor.setAsText((String) properties.get("propertyEditorClass"));
154                propertyEditorClass = (Class) classEditor.getValue();
155            } else {
156                throw new IllegalArgumentException("propertyEditorClass is required");
157            }
158            
159            if (propertyName != null) {
160                setPropertyEditor(objectClass, propertyName, propertyEditorClass);
161            } else if (objectClass != null) {
162                setPropertyEditor(objectClass, propertyEditorClass);
163            } else {
164                throw new IllegalArgumentException("objectClass and/or propertyName are required");
165            }        
166        }
167    
168    
169        public void setPropertyEditor(final Class typeClass,
170                                      final Class propertyEditorClass) {
171            Assert.notNull(typeClass);
172            verifyPropertyEditorClass(propertyEditorClass);
173    
174            if (logger.isDebugEnabled()) {
175                logger.debug("Setting " + propertyEditorClass +
176                    " as the property editor for " + typeClass);
177            }
178            this.propertyEditorByClass.put(typeClass, propertyEditorClass);
179        }
180    
181    
182        private void verifyPropertyEditorClass(final Class propertyEditorClass) {
183            // do some checks so we "fail fast"
184            Assert.notNull(propertyEditorClass);
185            Assert.isTrue(PropertyEditor.class.isAssignableFrom(propertyEditorClass),
186                    propertyEditorClass + " is not a " + PropertyEditor.class);
187            try {
188                Assert.isTrue(Modifier.isPublic(propertyEditorClass.getConstructor(null).getModifiers()),
189                        propertyEditorClass + " does not have a public no-arg constructor");
190            }
191            catch (NoSuchMethodException e) {
192                throw new IllegalArgumentException(propertyEditorClass +
193                    " does not have a no-arg constructor");
194            }
195        }
196    
197    
198        public void setPropertyEditor(final Class objectType,
199                                      final String propertyName,
200                                      final Class propertyEditorClass) {
201            Assert.notNull(objectType);
202            Assert.notNull(propertyName);
203            Assert.isTrue(ClassUtils.isAProperty(objectType, propertyName), "'" + propertyName + "' is not a property of " + objectType);
204            verifyPropertyEditorClass(propertyEditorClass);
205            if (logger.isDebugEnabled()) {
206                logger.debug("Setting " + propertyEditorClass +
207                    " as the property editor for the '" + propertyName +
208                    "' property of " + objectType);
209            }
210            final ClassAndPropertyKey key =
211                new ClassAndPropertyKey(objectType, propertyName);
212            this.propertyEditorByClassAndProperty.put(key, propertyEditorClass);
213        }
214    
215    
216        public PropertyEditor getPropertyEditor(final Class typeClass) {
217            final Class editorClass = (Class)ClassUtils.getValueFromMapForClass(
218                typeClass, this.propertyEditorByClass);
219    
220            if (editorClass == null) {
221                if (logger.isDebugEnabled()) {
222                    logger.debug("Could not find a property editor for " +
223                        typeClass);
224                }
225                return null;
226            }
227    
228            return instantiatePropertyEditor(editorClass);
229        }
230    
231    
232        public PropertyEditor getPropertyEditor(final Class objectType,
233                                                final String propertyName) {
234            final ClassAndPropertyKey key = new ClassAndPropertyKey(objectType,
235                propertyName);
236            Class editorClass =
237                (Class)this.propertyEditorByClassAndProperty.get(key);
238    
239            if (editorClass != null) {
240                return instantiatePropertyEditor(editorClass);
241            }
242    
243            // maybe it's registered under a different class...
244            final Set keys = this.propertyEditorByClassAndProperty.keySet();
245            final Map map = new HashMap();
246            for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
247                ClassAndPropertyKey propertyKey = (ClassAndPropertyKey)iterator.next();
248                if (propertyName.equals(propertyKey.getPropertyName())) {
249                    map.put(propertyKey.getTheClass(),
250                        this.propertyEditorByClassAndProperty.get(propertyKey));
251                }
252            }
253    
254            editorClass =
255                (Class)ClassUtils.getValueFromMapForClass(objectType, map);
256            if (editorClass != null) {
257                // remember the lookup so it doesn't have to be discovered again
258                setPropertyEditor(objectType, propertyName, editorClass);
259                return instantiatePropertyEditor(editorClass);
260            }
261    
262            if (logger.isDebugEnabled()) {
263                logger.debug("Could not find a property editor for the " +
264                    propertyName + " property of " + objectType +
265                    ", so looking for it by class type");
266            }
267    
268            // didn't find it directly, so look for it by the class
269            final Class propertyClass =
270                ClassUtils.getPropertyClass(objectType, propertyName);
271    
272            return getPropertyEditor(propertyClass);
273        }
274    
275    
276        private static PropertyEditor instantiatePropertyEditor(Class propEdClass) {
277            try {
278                return (PropertyEditor)propEdClass.newInstance();
279            }
280            catch (InstantiationException e) {
281                IllegalStateException exp = new IllegalStateException(
282                    "Could not instantiate " + propEdClass);
283                exp.initCause(e);
284                throw exp;
285            }
286            catch (IllegalAccessException e) {
287                IllegalStateException exp = new IllegalStateException(
288                    "Could not instantiate " + propEdClass);
289                exp.initCause(e);
290                throw exp;
291            }
292        }
293    
294    
295        //***********************************************************************
296        // INNER CLASSES
297        //***********************************************************************
298        
299        private static class ClassAndPropertyKey {
300            private Class theClass;
301            private String propertyName;
302    
303            public ClassAndPropertyKey(Class theClass, String propertyName) {
304                if (theClass == null || propertyName == null) throw new NullPointerException();
305                this.propertyName = propertyName;
306                this.theClass = theClass;
307            }
308    
309            public String getPropertyName() {
310                return propertyName;
311            }
312    
313            public Class getTheClass() {
314                return theClass;
315            }
316    
317    
318            public boolean equals(Object o) {
319                if (this == o) return true;
320                if (!(o instanceof ClassAndPropertyKey)) return false;
321    
322                final ClassAndPropertyKey classAndPropertyKey = (ClassAndPropertyKey)o;
323    
324                if (propertyName != null ?
325                    !propertyName.equals(classAndPropertyKey.propertyName) :
326                    classAndPropertyKey.propertyName != null)
327                    return false;
328                if (theClass != null ?
329                    !theClass.equals(classAndPropertyKey.theClass) :
330                    classAndPropertyKey.theClass != null)
331                    return false;
332    
333                return true;
334            }
335    
336    
337            public int hashCode() {
338                int result;
339                result = (theClass != null ? theClass.hashCode() : 0);
340                result = 29 * result +
341                    (propertyName != null ? propertyName.hashCode() : 0);
342                return result;
343            }
344    
345        }
346    
347    }