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 }