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 }