001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.camel.impl.converter;
019    
020    import org.apache.camel.RuntimeCamelException;
021    import org.apache.camel.TypeConverter;
022    import org.apache.camel.impl.ReflectionInjector;
023    import org.apache.camel.spi.Injector;
024    import org.apache.camel.util.ObjectHelper;
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Set;
033    
034    /**
035     * @version $Revision: 546882 $
036     */
037    public class DefaultTypeConverter implements TypeConverter, TypeConverterRegistry {
038        private static final transient Log log = LogFactory.getLog(DefaultTypeConverter.class);
039        private Map<TypeMapping, TypeConverter> typeMappings = new HashMap<TypeMapping, TypeConverter>();
040        private Injector injector;
041        private List<TypeConverterLoader> typeConverterLoaders = new ArrayList<TypeConverterLoader>();
042        private List<TypeConverter> fallbackConverters = new ArrayList<TypeConverter>();
043        private boolean loaded;
044    
045        public DefaultTypeConverter() {
046            typeConverterLoaders.add(new AnnotationTypeConverterLoader());
047            fallbackConverters.add(new PropertyEditorTypeConverter());
048            fallbackConverters.add(new ToStringTypeConverter());
049            fallbackConverters.add(new ArrayTypeConverter());
050        }
051    
052        public DefaultTypeConverter(Injector injector) {
053            this();
054            this.injector = injector;
055        }
056    
057        public <T> T convertTo(Class<T> toType, Object value) {
058            if (toType.isInstance(value)) {
059                return toType.cast(value);
060            }
061            checkLoaded();
062            TypeConverter converter = getOrFindTypeConverter(toType, value);
063            if (converter != null) {
064                return converter.convertTo(toType, value);
065            }
066    
067            for (TypeConverter fallback : fallbackConverters) {
068                T rc = fallback.convertTo(toType, value);
069                if (rc != null) {
070                    return rc;
071                }
072            }
073    
074            // lets avoid NullPointerException when converting to boolean for null values
075            if (boolean.class.isAssignableFrom(toType)) {
076                return (T) Boolean.FALSE;
077            }
078            return null;
079        }
080    
081        public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
082            TypeMapping key = new TypeMapping(toType, fromType);
083            synchronized (typeMappings) {
084                TypeConverter converter = typeMappings.get(key);
085                if (converter != null) {
086                    log.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
087                }
088                typeMappings.put(key, typeConverter);
089            }
090        }
091    
092        public TypeConverter getTypeConverter(Class toType, Class fromType) {
093            TypeMapping key = new TypeMapping(toType, fromType);
094            synchronized (typeMappings) {
095                return typeMappings.get(key);
096            }
097        }
098    
099        public Injector getInjector() {
100            if (injector == null) {
101                injector = new ReflectionInjector();
102            }
103            return injector;
104        }
105    
106        public void setInjector(Injector injector) {
107            this.injector = injector;
108        }
109    
110        protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
111            Class fromType = null;
112            if (value != null) {
113                fromType = value.getClass();
114            }
115            TypeMapping key = new TypeMapping(toType, fromType);
116            TypeConverter converter;
117            synchronized (typeMappings) {
118                converter = typeMappings.get(key);
119                if (converter == null) {
120                    converter = findTypeConverter(toType, fromType, value);
121                    if (converter != null) {
122                        typeMappings.put(key, converter);
123                    }
124                }
125            }
126            return converter;
127        }
128    
129        /**
130         * Tries to auto-discover any available type converters
131         */
132        protected TypeConverter findTypeConverter(Class toType, Class fromType, Object value) {
133            // lets try the super classes of the from type
134            if (fromType != null) {
135                Class fromSuperClass = fromType.getSuperclass();
136                if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
137    
138                    TypeConverter converter = getTypeConverter(toType, fromSuperClass);
139                    if (converter == null) {
140                        converter = findTypeConverter(toType, fromSuperClass, value);
141                    }
142                    if (converter != null) {
143                        return converter;
144                    }
145                }
146                for (Class type : fromType.getInterfaces()) {
147                    TypeConverter converter = getTypeConverter(toType, type);
148                    if (converter != null) {
149                        return converter;
150                    }
151                }
152    
153                // lets test for arrays
154                if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) {
155                    // TODO can we try walking the inheritence-tree for the element types?
156                    if (!fromType.equals(Object[].class)) {
157                        fromSuperClass = Object[].class;
158    
159                        TypeConverter converter = getTypeConverter(toType, fromSuperClass);
160                        if (converter == null) {
161                            converter = findTypeConverter(toType, fromSuperClass, value);
162                        }
163                        if (converter != null) {
164                            return converter;
165                        }
166                    }
167                }
168            }
169    
170            // lets try classes derived from this toType
171            if (fromType != null) {
172                    Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
173                    for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
174                        TypeMapping key = entry.getKey();
175                        Class aToType = key.getToType();
176                        if (toType.isAssignableFrom(aToType)) {
177                            if (fromType.isAssignableFrom(key.getFromType())) {
178                                return entry.getValue();
179                            }
180                        }
181                    }
182            }
183    
184            // TODO look at constructors of toType?
185            return null;
186        }
187    
188        /**
189         * Checks if the registry is loaded and if not lazily load it
190         */
191        protected synchronized void checkLoaded() {
192            if (!loaded) {
193                loaded = true;
194                for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
195                    try {
196                        typeConverterLoader.load(this);
197                    }
198                    catch (Exception e) {
199                        throw new RuntimeCamelException(e);
200                    }
201                }
202            }
203        }
204    
205        /**
206         * Represents a mapping from one type (which can be null) to another
207         */
208        protected static class TypeMapping {
209            Class toType;
210            Class fromType;
211    
212            public TypeMapping(Class toType, Class fromType) {
213                this.toType = toType;
214                this.fromType = fromType;
215            }
216    
217            public Class getFromType() {
218                return fromType;
219            }
220    
221            public Class getToType() {
222                return toType;
223            }
224    
225            @Override
226            public boolean equals(Object object) {
227                if (object instanceof TypeMapping) {
228                    TypeMapping that = (TypeMapping) object;
229                    return ObjectHelper.equals(this.fromType, that.fromType) && ObjectHelper.equals(this.toType, that.toType);
230                }
231                return false;
232            }
233    
234            @Override
235            public int hashCode() {
236                int answer = toType.hashCode();
237                if (fromType != null) {
238                    answer *= 37 + fromType.hashCode();
239                }
240                return answer;
241            }
242    
243            @Override
244            public String toString() {
245                return "[" + fromType + "=>" + toType + "]";
246            }
247        }
248    }