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: 563607 $
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 PropertyEditorTypeConverter());
051            addFallbackConverter(new ToStringTypeConverter());
052            addFallbackConverter(new ArrayTypeConverter());
053        }
054    
055        public <T> T convertTo(Class<T> toType, Object value) {
056            if (toType.isInstance(value)) {
057                return toType.cast(value);
058            } else if (toType.isPrimitive()) {
059                Class primitiveType = ObjectHelper.convertPrimitiveTypeToWrapperType(toType);
060                if (primitiveType != toType) {
061                    return (T)convertTo(primitiveType, value);
062                }
063            }
064            checkLoaded();
065            TypeConverter converter = getOrFindTypeConverter(toType, value);
066            if (converter != null) {
067                return converter.convertTo(toType, value);
068            }
069    
070            for (TypeConverter fallback : fallbackConverters) {
071                T rc = fallback.convertTo(toType, value);
072                if (rc != null) {
073                    return rc;
074                }
075            }
076    
077            // lets avoid NullPointerException when converting to boolean for null
078            // values
079            if (boolean.class.isAssignableFrom(toType)) {
080                return (T)Boolean.FALSE;
081            }
082    
083            return null;
084        }
085    
086        public void addTypeConverter(Class toType, Class fromType, TypeConverter typeConverter) {
087            TypeMapping key = new TypeMapping(toType, fromType);
088            synchronized (typeMappings) {
089                TypeConverter converter = typeMappings.get(key);
090                if (converter != null) {
091                    LOG.warn("Overriding type converter from: " + converter + " to: " + typeConverter);
092                }
093                typeMappings.put(key, typeConverter);
094            }
095        }
096    
097        public void addFallbackConverter(TypeConverter converter) {
098            fallbackConverters.add(converter);
099            if (converter instanceof TypeConverterAware) {
100                TypeConverterAware typeConverterAware = (TypeConverterAware)converter;
101                typeConverterAware.setTypeConverter(this);
102            }
103        }
104    
105        public TypeConverter getTypeConverter(Class toType, Class fromType) {
106            TypeMapping key = new TypeMapping(toType, fromType);
107            synchronized (typeMappings) {
108                return typeMappings.get(key);
109            }
110        }
111    
112        public Injector getInjector() {
113            return injector;
114        }
115    
116        public void setInjector(Injector injector) {
117            this.injector = injector;
118        }
119    
120        protected <T> TypeConverter getOrFindTypeConverter(Class toType, Object value) {
121            Class fromType = null;
122            if (value != null) {
123                fromType = value.getClass();
124            }
125            TypeMapping key = new TypeMapping(toType, fromType);
126            TypeConverter converter;
127            synchronized (typeMappings) {
128                converter = typeMappings.get(key);
129                if (converter == null) {
130                    converter = findTypeConverter(toType, fromType, value);
131                    if (converter != null) {
132                        typeMappings.put(key, converter);
133                    }
134                }
135            }
136            return converter;
137        }
138    
139        /**
140         * Tries to auto-discover any available type converters
141         */
142        protected TypeConverter findTypeConverter(Class toType, Class fromType, Object value) {
143            // lets try the super classes of the from type
144            if (fromType != null) {
145                Class fromSuperClass = fromType.getSuperclass();
146                if (fromSuperClass != null && !fromSuperClass.equals(Object.class)) {
147    
148                    TypeConverter converter = getTypeConverter(toType, fromSuperClass);
149                    if (converter == null) {
150                        converter = findTypeConverter(toType, fromSuperClass, value);
151                    }
152                    if (converter != null) {
153                        return converter;
154                    }
155                }
156                for (Class type : fromType.getInterfaces()) {
157                    TypeConverter converter = getTypeConverter(toType, type);
158                    if (converter != null) {
159                        return converter;
160                    }
161                }
162    
163                // lets test for arrays
164                if (fromType.isArray() && !fromType.getComponentType().isPrimitive()) {
165                    // TODO can we try walking the inheritence-tree for the element
166                    // types?
167                    if (!fromType.equals(Object[].class)) {
168                        fromSuperClass = Object[].class;
169    
170                        TypeConverter converter = getTypeConverter(toType, fromSuperClass);
171                        if (converter == null) {
172                            converter = findTypeConverter(toType, fromSuperClass, value);
173                        }
174                        if (converter != null) {
175                            return converter;
176                        }
177                    }
178                }
179            }
180    
181            // lets try classes derived from this toType
182            if (fromType != null) {
183                Set<Map.Entry<TypeMapping, TypeConverter>> entries = typeMappings.entrySet();
184                for (Map.Entry<TypeMapping, TypeConverter> entry : entries) {
185                    TypeMapping key = entry.getKey();
186                    Class aToType = key.getToType();
187                    if (toType.isAssignableFrom(aToType)) {
188                        if (fromType.isAssignableFrom(key.getFromType())) {
189                            return entry.getValue();
190                        }
191                    }
192                }
193            }
194    
195            // TODO look at constructors of toType?
196            return null;
197        }
198    
199        /**
200         * Checks if the registry is loaded and if not lazily load it
201         */
202        protected synchronized void checkLoaded() {
203            if (!loaded) {
204                loaded = true;
205                try {
206                    for (TypeConverterLoader typeConverterLoader : typeConverterLoaders) {
207                        typeConverterLoader.load(this);
208                    }
209    
210                    // lets try load any other failback converters
211                    try {
212                        loadFallbackTypeConverters();
213                    } catch (NoFactoryAvailableException e) {
214                        // ignore its fine to have none
215                    }
216                } catch (Exception e) {
217                    throw new RuntimeCamelException(e);
218                }
219            }
220        }
221    
222        protected void loadFallbackTypeConverters() throws IOException, ClassNotFoundException {
223            FactoryFinder finder = new FactoryFinder();
224            List<TypeConverter> converters = finder.newInstances("FallbackTypeConverter", getInjector(),
225                                                                 TypeConverter.class);
226            for (TypeConverter converter : converters) {
227                addFallbackConverter(converter);
228            }
229        }
230    
231        /**
232         * Represents a mapping from one type (which can be null) to another
233         */
234        protected static class TypeMapping {
235            Class toType;
236            Class fromType;
237    
238            public TypeMapping(Class toType, Class fromType) {
239                this.toType = toType;
240                this.fromType = fromType;
241            }
242    
243            public Class getFromType() {
244                return fromType;
245            }
246    
247            public Class getToType() {
248                return toType;
249            }
250    
251            @Override
252            public boolean equals(Object object) {
253                if (object instanceof TypeMapping) {
254                    TypeMapping that = (TypeMapping)object;
255                    return ObjectHelper.equals(this.fromType, that.fromType)
256                           && ObjectHelper.equals(this.toType, that.toType);
257                }
258                return false;
259            }
260    
261            @Override
262            public int hashCode() {
263                int answer = toType.hashCode();
264                if (fromType != null) {
265                    answer *= 37 + fromType.hashCode();
266                }
267                return answer;
268            }
269    
270            @Override
271            public String toString() {
272                return "[" + fromType + "=>" + toType + "]";
273            }
274        }
275    }