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

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

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2022 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
 *
 *      https://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.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CopyOnWriteArraySet;

import org.springframework.core.DecoratingProxy;
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.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
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");


	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.getClass(), Converter.class);
		if (typeInfo == null && converter instanceof DecoratingProxy decoratingProxy) {
			typeInfo = getRequiredTypeInfo(decoratingProxy.getDecoratedClass(), Converter.class);
		}
		if (typeInfo == null) {
			throw new IllegalArgumentException("Unable to determine source type  and target type  for your " +
					"Converter [" + converter.getClass().getName() + "]; does the class parameterize those 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 factory) {
		ResolvableType[] typeInfo = getRequiredTypeInfo(factory.getClass(), ConverterFactory.class);
		if (typeInfo == null && factory instanceof DecoratingProxy decoratingProxy) {
			typeInfo = getRequiredTypeInfo(decoratingProxy.getDecoratedClass(), ConverterFactory.class);
		}
		if (typeInfo == null) {
			throw new IllegalArgumentException("Unable to determine source type  and target type  for your " +
					"ConverterFactory [" + factory.getClass().getName() + "]; does the class parameterize those types?");
		}
		addConverter(new ConverterFactoryAdapter(factory,
				new ConvertiblePair(typeInfo[0].toClass(), typeInfo[1].toClass())));
	}

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


	// ConversionService implementation

	@Override
	public boolean canConvert(@Nullable Class sourceType, Class targetType) {
		Assert.notNull(targetType, "Target type to convert to cannot be null");
		return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null),
				TypeDescriptor.valueOf(targetType));
	}

	@Override
	public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
		Assert.notNull(targetType, "Target type to convert to cannot be null");
		if (sourceType == null) {
			return true;
		}
		GenericConverter converter = getConverter(sourceType, targetType);
		return (converter != null);
	}

	/**
	 * Return whether conversion between the source type and the target type can be bypassed.
	 * 

More precisely, this method will return true if objects of sourceType can be * converted to the target type 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(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); if (sourceType == null) { return true; } GenericConverter converter = getConverter(sourceType, targetType); return (converter == NO_OP_CONVERTER); } @Override @SuppressWarnings("unchecked") @Nullable public T convert(@Nullable Object source, Class targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType)); } @Override @Nullable public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { Assert.notNull(targetType, "Target type to convert to cannot be null"); if (sourceType == null) { Assert.isTrue(source == null, "Source must be [null] if source type == [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 target type is a descriptor that provides additional conversion context. * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and * encapsulates the construction of the source type 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} */ @Nullable public Object convert(@Nullable 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 source type to convert from * @param targetType the target type to convert to * @return the converted null object */ @Nullable protected Object convertNullSource(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { if (targetType.getObjectType() == Optional.class) { return Optional.empty(); } return null; } /** * Hook method to look up 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) */ @Nullable 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 source type is assignable to the target type. * 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 */ @Nullable protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null); } // Internal helpers @Nullable private ResolvableType[] getRequiredTypeInfo(Class converterClass, Class genericIfc) { ResolvableType resolvableType = ResolvableType.forClass(converterClass).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(); } @Nullable private Object handleConverterNotFound( @Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { assertNotPrimitiveTargetType(sourceType, targetType); return null; } if ((sourceType == null || sourceType.isAssignableTo(targetType)) && targetType.getObjectType().isInstance(source)) { return source; } throw new ConverterNotFoundException(sourceType, targetType); } @Nullable private Object handleResult(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType, @Nullable Object result) { if (result == null) { assertNotPrimitiveTargetType(sourceType, targetType); } return result; } private void assertNotPrimitiveTargetType(@Nullable 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.toClass(), targetType.toClass()); 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() != 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) || conditionalConverter.matches(sourceType, targetType); } @Override @Nullable public Object convert(@Nullable 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 conditionalConverter) { matches = conditionalConverter.matches(sourceType, targetType); } if (matches) { Converter converter = this.converterFactory.getConverter(targetType.getType()); if (converter instanceof ConditionalConverter conditionalConverter) { matches = conditionalConverter.matches(sourceType, targetType); } } return matches; } @Override @Nullable public Object convert(@Nullable 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 implements Comparable { private final TypeDescriptor sourceType; private final TypeDescriptor targetType; public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) { this.sourceType = sourceType; this.targetType = targetType; } @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof ConverterCacheKey otherKey)) { return false; } return (this.sourceType.equals(otherKey.sourceType)) && this.targetType.equals(otherKey.targetType); } @Override public int hashCode() { return (this.sourceType.hashCode() * 29 + this.targetType.hashCode()); } @Override public String toString() { return ("ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]"); } @Override public int compareTo(ConverterCacheKey other) { int result = this.sourceType.getResolvableType().toString().compareTo( other.sourceType.getResolvableType().toString()); if (result == 0) { result = this.targetType.getResolvableType().toString().compareTo( other.targetType.getResolvableType().toString()); } return result; } } /** * Manages all converters registered with the service. */ private static class Converters { private final Set globalConverters = new CopyOnWriteArraySet<>(); private final Map converters = new ConcurrentHashMap<>(256); 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) { getMatchableConverters(convertiblePair).add(converter); } } } private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) { return this.converters.computeIfAbsent(convertiblePair, k -> new 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 */ @Nullable 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; } @Nullable 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 : this.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 Deque converters = new ConcurrentLinkedDeque<>(); public void add(GenericConverter converter) { this.converters.addFirst(converter); } @Nullable public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { for (GenericConverter converter : this.converters) { if (!(converter instanceof ConditionalGenericConverter genericConverter) || genericConverter.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 @Nullable public Set getConvertibleTypes() { return null; } @Override @Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return source; } @Override public String toString() { return this.name; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy