All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.core.convert.support.GenericConversionService Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2015 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 org.springframework.core.ResolvableType;
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.ConcurrentReferenceHashMap;
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
 * @author David Haraburda
 * @since 3.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");


	/** Java 8's java.util.Optional.empty() */
	private static Object javaUtilOptionalEmpty = null;

	static {
		try {
			Class clazz = ClassUtils.forName("java.util.Optional", GenericConversionService.class.getClassLoader());
			javaUtilOptionalEmpty = ClassUtils.getMethod(clazz, "empty").invoke(null);
		}
		catch (Exception ex) {
			// Java 8 not available - conversion to Optional not supported then.
		}
	}


	private final Converters converters = new Converters();

	private final Map converterCache =
			new ConcurrentReferenceHashMap(64);


	// ConverterRegistry implementation

	@Override
	public void addConverter(Converter converter) {
		ResolvableType[] typeInfo = getRequiredTypeInfo(converter, Converter.class);
		Assert.notNull(typeInfo, "Unable to the determine sourceType  and targetType " +
				" which your Converter converts between; declare these generic types.");
		addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
	}

	@Override
	public void addConverter(Class sourceType, Class targetType, Converter converter) {
		addConverter(new ConverterAdapter(
				converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
	}

	@Override
	public void addConverter(GenericConverter converter) {
		this.converters.add(converter);
		invalidateCache();
	}

	@Override
	public void addConverterFactory(ConverterFactory converterFactory) {
		ResolvableType[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
		Assert.notNull(typeInfo, "Unable to the determine source type  and target range type R which your " +
				"ConverterFactory converts between; declare these generic types.");
		addConverter(new ConverterFactoryAdapter(converterFactory,
				new ConvertiblePair(typeInfo[0].resolve(), typeInfo[1].resolve())));
	}

	@Override
	public void removeConvertible(Class sourceType, Class targetType) {
		this.converters.remove(sourceType, targetType);
		invalidateCache();
	}


	// ConversionService implementation

	@Override
	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));
	}

	@Override
	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);
	}

	/**
	 * Return whether 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 {@code null} if source is {@code null}) * @param targetType context about the target type to convert to (required) * @return {@code true} if conversion can be bypassed; {@code false} otherwise * @throws IllegalArgumentException if targetType is {@code null} * @since 3.2 */ public boolean canBypassConvert(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 == NO_OP_CONVERTER); } @Override @SuppressWarnings("unchecked") public T convert(Object source, Class targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "targetType to convert to cannot be null"); if (sourceType == null) { Assert.isTrue(source == null, "source must be [null] if sourceType == [null]"); return handleResult(null, targetType, convertNullSource(null, targetType)); } if (source != null && !sourceType.getObjectType().isInstance(source)) { throw new IllegalArgumentException("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 {@code null}, * or sourceType is {@code null} but source is not {@code null} */ public Object convert(Object source, TypeDescriptor targetType) { return convert(source, TypeDescriptor.forObject(source), targetType); } @Override public String toString() { return this.converters.toString(); } // Protected template methods /** * Template method to convert a {@code null} source. *

The default implementation returns {@code null} or the Java 8 * {@link java.util.Optional#empty()} instance if the target type is * {@code java.util.Optional}. Subclasses may override this to return * custom {@code 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) { if (javaUtilOptionalEmpty != null && targetType.getObjectType().equals(javaUtilOptionalEmpty.getClass())) { return javaUtilOptionalEmpty; } 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. * @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. * @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 ResolvableType[] getRequiredTypeInfo(Object converter, Class genericIfc) { ResolvableType resolvableType = ResolvableType.forClass(converter.getClass()).as(genericIfc); ResolvableType[] generics = resolvableType.getGenerics(); if (generics.length < 2) { return null; } Class sourceType = generics[0].resolve(); Class targetType = generics[1].resolve(); if (sourceType == null || targetType == null) { return null; } return generics; } private void invalidateCache() { this.converterCache.clear(); } private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { assertNotPrimitiveTargetType(sourceType, targetType); return null; } 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 converter; private final ConvertiblePair typeInfo; private final ResolvableType targetType; public ConverterAdapter(Converter converter, ResolvableType sourceType, ResolvableType targetType) { this.converter = (Converter) converter; this.typeInfo = new ConvertiblePair(sourceType.resolve(Object.class), targetType.resolve(Object.class)); this.targetType = targetType; } @Override public Set getConvertibleTypes() { return Collections.singleton(this.typeInfo); } @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { // Check raw type first... if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) { return false; } // Full check for complex generic type match required? ResolvableType rt = targetType.getResolvableType(); if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) && !this.targetType.hasUnresolvableGenerics()) { return false; } return !(this.converter instanceof ConditionalConverter) || ((ConditionalConverter) this.converter).matches(sourceType, targetType); } @Override 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 converterFactory; private final ConvertiblePair typeInfo; public ConverterFactoryAdapter(ConverterFactory converterFactory, ConvertiblePair typeInfo) { this.converterFactory = (ConverterFactory) converterFactory; this.typeInfo = typeInfo; } @Override public Set getConvertibleTypes() { return Collections.singleton(this.typeInfo); } @Override 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; } @Override 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; } @Override 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 globalConverters = new LinkedHashSet(); private final Map converters = new LinkedHashMap(36); public void add(GenericConverter converter) { Set 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. *

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> sourceCandidates = getClassHierarchy(sourceType.getType()); List> 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 ConditionalConverters for a dynamic match 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> getClassHierarchy(Class type) { List> hierarchy = new ArrayList>(20); Set> visited = new HashSet>(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 (superclass != null && superclass != Object.class && superclass != Enum.class) { addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited); } addInterfacesToClassHierarchy(candidate, array, hierarchy, visited); i++; } if (Enum.class.isAssignableFrom(type)) { addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited); addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited); addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited); } addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited); addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited); return hierarchy; } private void addInterfacesToClassHierarchy(Class type, boolean asArray, List> hierarchy, Set> visited) { for (Class implementedInterface : type.getInterfaces()) { addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited); } } private void addToClassHierarchy(int index, Class type, boolean asArray, List> hierarchy, Set> 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 getConverterStrings() { List converterStrings = new ArrayList(); 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 converters = new LinkedList(); 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; } @Override public Set getConvertibleTypes() { return null; } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; } @Override public String toString() { return this.name; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy