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