001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.coerce;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.hivemind.util.ConstructorUtils;
019    import org.apache.hivemind.util.Defense;
020    
021    import java.beans.PropertyEditor;
022    import java.beans.PropertyEditorManager;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    /**
029     * Implementation of {@link org.apache.tapestry.coerce.ValueConverter}. Selects an appropriate type
030     * converter and delegates to it.
031     * 
032     * @author Howard M. Lewis Ship
033     * @since 4.0
034     */
035    public class ValueConverterImpl implements ValueConverter
036    {
037        /** List of {@link org.apache.tapestry.coerce.TypeConverterContribution}. */
038    
039        public List _contributions;
040        
041        private Map _converterMap = new HashMap();
042    
043        private Map _primitiveToWrapper = new HashMap();
044    
045        private Map _wrapperToPrimitive = new HashMap();
046    
047        {
048            store(boolean.class, Boolean.class);
049            store(byte.class, Byte.class);
050            store(short.class, Short.class);
051            store(char.class, Character.class);
052            store(int.class, Integer.class);
053            store(long.class, Long.class);
054            store(float.class, Float.class);
055            store(double.class, Double.class);
056        }
057    
058        private void store(Class primitive, Class wrapper)
059        {
060            _primitiveToWrapper.put(primitive, wrapper);
061    
062            _wrapperToPrimitive.put(wrapper, primitive);
063        }
064    
065        public void initializeService()
066        {
067            Iterator i = _contributions.iterator();
068            while (i.hasNext())
069            {
070                TypeConverterContribution c = (TypeConverterContribution) i.next();
071    
072                _converterMap.put(c.getSubjectClass(), c.getConverter());
073            }
074        }
075    
076        public Object coerceValue(Object value, Class desiredType)
077        {
078            Defense.notNull(desiredType, "desiredType");
079    
080            Class effectiveType = convertType(desiredType);
081    
082            // Already the correct type? Go no further!
083    
084            if (value != null && effectiveType.isAssignableFrom(value.getClass()))
085                return value;
086            
087            Object result = convertNumberToNumber(value, effectiveType);
088    
089            if (result != null)
090                return result;
091    
092            result = convertUsingPropertyEditor(value, effectiveType);
093    
094            if (result != null)
095                return result;
096    
097            TypeConverter converter = (TypeConverter) _converterMap.get(effectiveType);
098    
099            // null value and no converter for the given type? Just return null.
100    
101            if (value == null && converter == null)
102                return null;
103    
104            if (converter == null)
105                throw new ApplicationRuntimeException(CoerceMessages.noConverter(value.getClass(), effectiveType));
106    
107            return converter.convertValue(value);
108        }
109    
110        /**
111         * Attempts to use {@link java.beans.PropertyEditor}to perform a conversion from a string to a
112         * numeric type. Returns null if no property editor can be found.
113         * 
114         * @param value
115         *            The value to convert
116         * @param targetType
117         *            The type to convert to (must be a wrapper type, not a primitive type)
118         */
119    
120        private Number convertUsingPropertyEditor(Object value, Class targetType)
121        {
122            // Convert from wrapper type back to primitive type, because
123            // PropertyEditorManager expects primitive types not
124            // wrapper types.
125    
126            if (value == null || value.getClass() != String.class
127                    || !Number.class.isAssignableFrom(targetType))
128                return null;
129    
130            Class primitiveType = (Class) _wrapperToPrimitive.get(targetType);
131    
132            // Note a primitive type.
133    
134            if (primitiveType == null)
135                return null;
136    
137            // Looks like a conversion from String to Number, let's see.
138    
139            PropertyEditor editor = PropertyEditorManager.findEditor(primitiveType);
140    
141            // This should not happen, since we've filtered down to just the
142            // primitive types that do have property editors.
143    
144            if (editor == null)
145                return null;
146    
147            String text = (String) value;
148    
149            try
150            {
151                editor.setAsText(text);
152    
153                return (Number) editor.getValue();
154            }
155            catch (Exception ex)
156            {
157                throw new ApplicationRuntimeException(CoerceMessages.stringToNumberConversionError(
158                        text,
159                        targetType,
160                        ex), ex);
161            }
162    
163        }
164    
165        private Number convertNumberToNumber(Object value, Class targetType)
166        {
167            if (value == null || !Number.class.isAssignableFrom(value.getClass())
168                    || !Number.class.isAssignableFrom(targetType))
169                return null;
170    
171            String valueAsString = value.toString();
172    
173            return (Number) ConstructorUtils.invokeConstructor(targetType, new Object[] { valueAsString });
174        }
175    
176        private Class convertType(Class possiblePrimitiveType)
177        {
178            Class wrapperType = (Class) _primitiveToWrapper.get(possiblePrimitiveType);
179    
180            return wrapperType == null ? possiblePrimitiveType : wrapperType;
181        }
182    
183        public void setContributions(List contributions)
184        {
185            _contributions = contributions;
186        }
187    }