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 }