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 }