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
015package org.apache.tapestry.coerce;
016
017import java.beans.PropertyEditor;
018import java.beans.PropertyEditorManager;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.hivemind.ApplicationRuntimeException;
025import org.apache.hivemind.util.ConstructorUtils;
026import org.apache.hivemind.util.Defense;
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 */
035public class ValueConverterImpl implements ValueConverter
036{
037    private Map _converterMap = new HashMap();
038
039    /** List of {@link org.apache.tapestry.coerce.TypeConverterContribution}. */
040
041    public List _contributions;
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(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[]
174        { valueAsString });
175    }
176
177    private Class convertType(Class possiblePrimitiveType)
178    {
179        Class wrapperType = (Class) _primitiveToWrapper.get(possiblePrimitiveType);
180
181        return wrapperType == null ? possiblePrimitiveType : wrapperType;
182    }
183
184    public void setContributions(List contributions)
185    {
186        _contributions = contributions;
187    }
188}