/* * Copyright 2002-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.core.convert.support; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalConverter; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Base {@link ConversionService} implementation suitable for use in most environments. * Indirectly implements {@link ConverterRegistry} as registration API through the * {@link ConfigurableConversionService} interface. * * @author Keith Donald * @author Juergen Hoeller * @author Chris Beams * @author Phillip Webb * @since 2.0 */ public class GenericConversionService implements ConfigurableConversionService { /** * General NO-OP converter used when conversion is not required. */ private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP"); /** * Used as a cache entry when no converter is available. This converter is never * returned. */ private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH"); private final Converters converters = new Converters(); private final Map<ConverterCacheKey, GenericConverter> converterCache = new ConcurrentHashMap<ConverterCacheKey, GenericConverter>(64); // implementing ConverterRegistry public void addConverter(Converter<?, ?> converter) { GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class); Assert.notNull(typeInfo, "Unable to the determine sourceType <S> and targetType " + "<T> which your Converter<S, T> converts between; declare these generic types."); addConverter(new ConverterAdapter(converter, typeInfo)); } public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) { GenericConverter.ConvertiblePair typeInfo = new GenericConverter.ConvertiblePair(sourceType, targetType); addConverter(new ConverterAdapter(converter, typeInfo)); } public void addConverter(GenericConverter converter) { this.converters.add(converter); invalidateCache(); } public void addConverterFactory(ConverterFactory<?, ?> converterFactory) { GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class); if (typeInfo == null) { throw new IllegalArgumentException("Unable to the determine sourceType <S> and " + "targetRangeType R which your ConverterFactory<S, R> converts between; " + "declare these generic types."); } addConverter(new ConverterFactoryAdapter(converterFactory, typeInfo)); } public void removeConvertible(Class<?> sourceType, Class<?> targetType) { this.converters.remove(sourceType, targetType); invalidateCache(); } // implementing ConversionService public boolean canConvert(Class<?> sourceType, Class<?> targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null), TypeDescriptor.valueOf(targetType)); } public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); if (sourceType == null) { return true; } GenericConverter converter = getConverter(sourceType, targetType); return (converter != null); } /** * Returns true if conversion between the sourceType and targetType can be bypassed. * More precisely this method will return true if objects of sourceType can be * converted to the targetType by returning the source object unchanged. * @param sourceType context about the source type to convert from (may be null if source is null) * @param targetType context about the target type to convert to (required) * @return true if conversion can be bypassed * @throws IllegalArgumentException if targetType is null */ public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "The targetType to convert to cannot be null"); if (sourceType == null) { return true; } GenericConverter converter = getConverter(sourceType, targetType); return (converter == NO_OP_CONVERTER); } @SuppressWarnings("unchecked") public <T> T convert(Object source, Class<T> targetType) { Assert.notNull(targetType,"The targetType to convert to cannot be null"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType,"The targetType to convert to cannot be null"); if (sourceType == null) { Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]"); return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType)); } if (source != null && !sourceType.getObjectType().isInstance(source)) { throw new IllegalArgumentException("The source to convert from must be an instance of " + sourceType + "; instead it was a " + source.getClass().getName()); } GenericConverter converter = getConverter(sourceType, targetType); if (converter != null) { Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); return handleResult(sourceType, targetType, result); } return handleConverterNotFound(source, sourceType, targetType); } /** * Convenience operation for converting a source object to the specified targetType, * where the targetType is a descriptor that provides additional conversion context. * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and * encapsulates the construction of the sourceType descriptor using * {@link TypeDescriptor#forObject(Object)}. * @param source the source object * @param targetType the target type * @return the converted value * @throws ConversionException if a conversion exception occurred * @throws IllegalArgumentException if targetType is null, * or sourceType is null but source is not null */ public Object convert(Object source, TypeDescriptor targetType) { return convert(source, TypeDescriptor.forObject(source), targetType); } @Override public String toString() { return this.converters.toString(); } // subclassing hooks /** * Template method to convert a null source. * <p>Default implementation returns {@code null}. * Subclasses may override to return custom null objects for specific target types. * @param sourceType the sourceType to convert from * @param targetType the targetType to convert to * @return the converted null object */ protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) { return null; } /** * Hook method to lookup the converter for a given sourceType/targetType pair. * First queries this ConversionService's converter cache. * On a cache miss, then performs an exhaustive search for a matching converter. * If no converter matches, returns the default converter. * Subclasses may override. * @param sourceType the source type to convert from * @param targetType the target type to convert to * @return the generic converter that will perform the conversion, or {@code null} if * no suitable converter was found * @see #getDefaultConverter(TypeDescriptor, TypeDescriptor) */ protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType); GenericConverter converter = this.converterCache.get(key); if (converter != null) { return (converter != NO_MATCH ? converter : null); } converter = this.converters.find(sourceType, targetType); if (converter == null) { converter = getDefaultConverter(sourceType, targetType); } if (converter != null) { this.converterCache.put(key, converter); return converter; } this.converterCache.put(key, NO_MATCH); return null; } /** * Return the default converter if no converter is found for the given sourceType/targetType pair. * Returns a NO_OP Converter if the sourceType is assignable to the targetType. * Returns {@code null} otherwise, indicating no suitable converter could be found. * Subclasses may override. * @param sourceType the source type to convert from * @param targetType the target type to convert to * @return the default generic converter that will perform the conversion */ protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null); } // internal helpers private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object converter, Class<?> genericIfc) { Class<?>[] args = GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc); return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null); } private void invalidateCache() { this.converterCache.clear(); } private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { assertNotPrimitiveTargetType(sourceType, targetType); return source; } if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) { return source; } throw new ConverterNotFoundException(sourceType, targetType); } private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) { if (result == null) { assertNotPrimitiveTargetType(sourceType, targetType); } return result; } private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) { if (targetType.isPrimitive()) { throw new ConversionFailedException(sourceType, targetType, null, new IllegalArgumentException("A null value cannot be assigned to a primitive type")); } } /** * Adapts a {@link Converter} to a {@link GenericConverter}. */ @SuppressWarnings("unchecked") private final class ConverterAdapter implements ConditionalGenericConverter { private final Converter<Object, Object> converter; private final ConvertiblePair typeInfo; public ConverterAdapter(Converter<?, ?> converter, ConvertiblePair typeInfo) { this.converter = (Converter<Object, Object>) converter; this.typeInfo = typeInfo; } public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) { return false; } if (this.converter instanceof ConditionalConverter) { return ((ConditionalConverter) this.converter).matches(sourceType, targetType); } return true; } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return convertNullSource(sourceType, targetType); } return this.converter.convert(source); } @Override public String toString() { return this.typeInfo + " : " + this.converter; } } /** * Adapts a {@link ConverterFactory} to a {@link GenericConverter}. */ @SuppressWarnings("unchecked") private final class ConverterFactoryAdapter implements ConditionalGenericConverter { private final ConverterFactory<Object, Object> converterFactory; private final ConvertiblePair typeInfo; public ConverterFactoryAdapter(ConverterFactory<?, ?> converterFactory, ConvertiblePair typeInfo) { this.converterFactory = (ConverterFactory<Object, Object>) converterFactory; this.typeInfo = typeInfo; } public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(this.typeInfo); } public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { boolean matches = true; if (this.converterFactory instanceof ConditionalConverter) { matches = ((ConditionalConverter) this.converterFactory).matches(sourceType, targetType); } if (matches) { Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType()); if (converter instanceof ConditionalConverter) { matches = ((ConditionalConverter) converter).matches(sourceType, targetType); } } return matches; } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return convertNullSource(sourceType, targetType); } return this.converterFactory.getConverter(targetType.getObjectType()).convert(source); } @Override public String toString() { return this.typeInfo + " : " + this.converterFactory; } } /** * Key for use with the converter cache. */ private static final class ConverterCacheKey { private final TypeDescriptor sourceType; private final TypeDescriptor targetType; public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) { this.sourceType = sourceType; this.targetType = targetType; } public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof ConverterCacheKey)) { return false; } ConverterCacheKey otherKey = (ConverterCacheKey) other; return ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType) && ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType); } @Override public int hashCode() { return ObjectUtils.nullSafeHashCode(this.sourceType) * 29 + ObjectUtils.nullSafeHashCode(this.targetType); } @Override public String toString() { return "ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]"; } } /** * Manages all converters registered with the service. */ private static class Converters { private final Set<GenericConverter> globalConverters = new LinkedHashSet<GenericConverter>(); private final Map<ConvertiblePair, ConvertersForPair> converters = new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36); public void add(GenericConverter converter) { Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes(); if (convertibleTypes == null) { Assert.state(converter instanceof ConditionalConverter, "Only conditional converters may return null convertible types"); this.globalConverters.add(converter); } else { for (ConvertiblePair convertiblePair : convertibleTypes) { ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair); convertersForPair.add(converter); } } } private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) { ConvertersForPair convertersForPair = this.converters.get(convertiblePair); if (convertersForPair == null) { convertersForPair = new ConvertersForPair(); this.converters.put(convertiblePair, convertersForPair); } return convertersForPair; } public void remove(Class<?> sourceType, Class<?> targetType) { this.converters.remove(new ConvertiblePair(sourceType, targetType)); } /** * Find a {@link GenericConverter} given a source and target type. * <p>This method will attempt to match all possible converters by working * through the class and interface hierarchy of the types. * @param sourceType the source type * @param targetType the target type * @return a matching {@link GenericConverter}, or {@code null} if none found */ public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { // Search the full type hierarchy List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType()); List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType()); for (Class<?> sourceCandidate : sourceCandidates) { for (Class<?> targetCandidate : targetCandidates) { ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate); GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair); if (converter != null) { return converter; } } } return null; } private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType, ConvertiblePair convertiblePair) { // Check specifically registered converters ConvertersForPair convertersForPair = this.converters.get(convertiblePair); if (convertersForPair != null) { GenericConverter converter = convertersForPair.getConverter(sourceType, targetType); if (converter != null) { return converter; } } // Check ConditionalGenericConverter that match all types for (GenericConverter globalConverter : this.globalConverters) { if (((ConditionalConverter)globalConverter).matches(sourceType, targetType)) { return globalConverter; } } return null; } /** * Returns an ordered class hierarchy for the given type. * @param type the type * @return an ordered list of all classes that the given type extends or implements */ private List<Class<?>> getClassHierarchy(Class<?> type) { List<Class<?>> hierarchy = new ArrayList<Class<?>>(20); Set<Class<?>> visited = new HashSet<Class<?>>(20); addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited); boolean array = type.isArray(); int i = 0; while (i < hierarchy.size()) { Class<?> candidate = hierarchy.get(i); candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate)); Class<?> superclass = candidate.getSuperclass(); if (candidate.getSuperclass() != null && superclass != Object.class) { addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited); } for (Class<?> implementedInterface : candidate.getInterfaces()) { addToClassHierarchy(hierarchy.size(), implementedInterface, array, hierarchy, visited); } i++; } addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited); addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited); return hierarchy; } private void addToClassHierarchy(int index, Class<?> type, boolean asArray, List<Class<?>> hierarchy, Set<Class<?>> visited) { if (asArray) { type = Array.newInstance(type, 0).getClass(); } if (visited.add(type)) { hierarchy.add(index, type); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ConversionService converters =\n"); for (String converterString : getConverterStrings()) { builder.append('\t').append(converterString).append('\n'); } return builder.toString(); } private List<String> getConverterStrings() { List<String> converterStrings = new ArrayList<String>(); for (ConvertersForPair convertersForPair : converters.values()) { converterStrings.add(convertersForPair.toString()); } Collections.sort(converterStrings); return converterStrings; } } /** * Manages converters registered with a specific {@link ConvertiblePair}. */ private static class ConvertersForPair { private final LinkedList<GenericConverter> converters = new LinkedList<GenericConverter>(); public void add(GenericConverter converter) { this.converters.addFirst(converter); } public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { for (GenericConverter converter : this.converters) { if (!(converter instanceof ConditionalGenericConverter) || ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) { return converter; } } return null; } @Override public String toString() { return StringUtils.collectionToCommaDelimitedString(this.converters); } } /** * Internal converter that performs no operation. */ private static class NoOpConverter implements GenericConverter { private final String name; public NoOpConverter(String name) { this.name = name; } public Set<ConvertiblePair> getConvertibleTypes() { return null; } public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; } @Override public String toString() { return this.name; } } }