Merge pull request #132 from philwebb/SPR-9692
# By Phillip Webb (6) and Chris Beams (1) * SPR-9692: Review and polish pull request #132 Support conversion from Enum Interface Test SpEL unconditional argument conversion Bypass conversion when possible Extend conditional conversion support Refactor GenericConversionService Additional GenericConversionService Tests
This commit is contained in:
commit
a57ff506f2
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -21,6 +21,7 @@ package org.springframework.core.convert;
|
|||
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface ConversionService {
|
||||
|
|
@ -54,6 +55,30 @@ public interface ConversionService {
|
|||
*/
|
||||
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @since 3.2
|
||||
*/
|
||||
boolean canBypassConvert(Class<?> sourceType, Class<?> targetType);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @since 3.2
|
||||
*/
|
||||
boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||
|
||||
/**
|
||||
* Convert the source to targetType.
|
||||
* @param source the source object to convert (may be null)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
|
|
@ -249,6 +250,24 @@ public class TypeDescriptor {
|
|||
this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast this {@link TypeDescriptor} to a superclass or implemented interface
|
||||
* preserving annotations and nested type context.
|
||||
*
|
||||
* @param superType the super type to cast to (can be {@code null}
|
||||
* @return a new TypeDescriptor for the up-cast type
|
||||
* @throws IllegalArgumentException if this type is not assignable to the super-type
|
||||
* @since 3.2
|
||||
*/
|
||||
public TypeDescriptor upcast(Class<?> superType) {
|
||||
if (superType == null) {
|
||||
return null;
|
||||
}
|
||||
Assert.isAssignable(superType, getType());
|
||||
return new TypeDescriptor(superType, this.elementTypeDescriptor,
|
||||
this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this type: the fully qualified class name.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.converter;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* Allows a {@link Converter}, {@link GenericConverter} or {@link ConverterFactory} to
|
||||
* conditionally execute based on attributes of the {@code source} and {@code target}
|
||||
* {@link TypeDescriptor}.
|
||||
*
|
||||
* <p>Often used to selectively match custom conversion logic based on the presence of a
|
||||
* field or class-level characteristic, such as an annotation or method. For example, when
|
||||
* converting from a String field to a Date field, an implementation might return
|
||||
*
|
||||
* {@code true} if the target field has also been annotated with {@code @DateTimeFormat}.
|
||||
*
|
||||
* <p>As another example, when converting from a String field to an {@code Account} field, an
|
||||
* implementation might return {@code true} if the target Account class defines a
|
||||
* {@code public static findAccount(String)} method.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Keith Donald
|
||||
* @since 3.2
|
||||
* @see Converter
|
||||
* @see GenericConverter
|
||||
* @see ConverterFactory
|
||||
* @see ConditionalGenericConverter
|
||||
*/
|
||||
public interface ConditionalConverter {
|
||||
|
||||
/**
|
||||
* Should the conversion from {@code sourceType} to {@code targetType} currently under
|
||||
* consideration be selected?
|
||||
*
|
||||
* @param sourceType the type descriptor of the field we are converting from
|
||||
* @param targetType the type descriptor of the field we are converting to
|
||||
* @return true if conversion should be performed, false otherwise
|
||||
*/
|
||||
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -19,33 +19,17 @@ package org.springframework.core.convert.converter;
|
|||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* A generic converter that conditionally executes.
|
||||
*
|
||||
* <p>Applies a rule that determines if a converter between a set of
|
||||
* {@link #getConvertibleTypes() convertible types} matches given a client request to
|
||||
* convert between a source field of convertible type S and a target field of convertible type T.
|
||||
*
|
||||
* <p>Often used to selectively match custom conversion logic based on the presence of
|
||||
* a field or class-level characteristic, such as an annotation or method. For example,
|
||||
* when converting from a String field to a Date field, an implementation might return
|
||||
* <code>true</code> if the target field has also been annotated with <code>@DateTimeFormat</code>.
|
||||
*
|
||||
* <p>As another example, when converting from a String field to an Account field,
|
||||
* an implementation might return true if the target Account class defines a
|
||||
* <code>public static findAccount(String)</code> method.
|
||||
* A {@link GenericConverter} that may conditionally execute based on attributes of the
|
||||
* {@code source} and {@code target} {@link TypeDescriptor}. See
|
||||
* {@link ConditionalConverter} for details.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
* @see GenericConverter
|
||||
* @see ConditionalConverter
|
||||
*/
|
||||
public interface ConditionalGenericConverter extends GenericConverter {
|
||||
|
||||
/**
|
||||
* Should the converter from <code>sourceType</code> to <code>targetType</code>
|
||||
* currently under consideration be selected?
|
||||
* @param sourceType the type descriptor of the field we are converting from
|
||||
* @param targetType the type descriptor of the field we are converting to
|
||||
* @return true if conversion should be performed, false otherwise
|
||||
*/
|
||||
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||
public interface ConditionalGenericConverter extends GenericConverter,
|
||||
ConditionalConverter {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -20,10 +20,13 @@ package org.springframework.core.convert.converter;
|
|||
* A converter converts a source object of type S to a target of type T.
|
||||
* Implementations of this interface are thread-safe and can be shared.
|
||||
*
|
||||
* <p>Implementations may additionally implement {@link ConditionalConverter}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see ConditionalConverter
|
||||
* @param <S> The source type
|
||||
* @param <T> The target type
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface Converter<S, T> {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -19,8 +19,11 @@ package org.springframework.core.convert.converter;
|
|||
/**
|
||||
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
|
||||
*
|
||||
* <p>Implementations may additionally implement {@link ConditionalConverter}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @since 3.0
|
||||
* @see ConditionalConverter
|
||||
* @param <S> The source type converters created by this factory can convert from
|
||||
* @param <R> The target range (or base) type converters created by this factory can convert to;
|
||||
* for example {@link Number} for a set of number subtypes.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -34,18 +34,24 @@ import java.util.Set;
|
|||
* <p>This interface should generally not be used when the simpler {@link Converter} or
|
||||
* {@link ConverterFactory} interfaces are sufficient.
|
||||
*
|
||||
* <p>Implementations may additionally implement {@link ConditionalConverter}.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see TypeDescriptor
|
||||
* @see Converter
|
||||
* @see ConverterFactory
|
||||
* @see ConditionalConverter
|
||||
*/
|
||||
public interface GenericConverter {
|
||||
|
||||
/**
|
||||
* Return the source and target types which this converter can convert between.
|
||||
* <p>Each entry is a convertible source-to-target type pair.
|
||||
* Return the source and target types which this converter can convert between. Each
|
||||
* entry is a convertible source-to-target type pair.
|
||||
* <p>
|
||||
* For {@link ConditionalConverter conditional} converters this method may return
|
||||
* {@code null} to indicate all source-to-target pairs should be considered. *
|
||||
*/
|
||||
Set<ConvertiblePair> getConvertibleTypes();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -18,6 +18,7 @@ package org.springframework.core.convert.support;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
|
|
@ -26,18 +27,22 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
|||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Converts an Array to another Array.
|
||||
* First adapts the source array to a List, then delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
|
||||
* Converts an Array to another Array. First adapts the source array to a List, then
|
||||
* delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
*/
|
||||
final class ArrayToArrayConverter implements ConditionalGenericConverter {
|
||||
|
||||
private final CollectionToArrayConverter helperConverter;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public ArrayToArrayConverter(ConversionService conversionService) {
|
||||
this.helperConverter = new CollectionToArrayConverter(conversionService);
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
|
|
@ -48,8 +53,14 @@ final class ArrayToArrayConverter implements ConditionalGenericConverter {
|
|||
return this.helperConverter.matches(sourceType, targetType);
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
|
||||
public Object convert(Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
if (conversionService.canBypassConvert(sourceType.getElementTypeDescriptor(),
|
||||
targetType.getElementTypeDescriptor())) {
|
||||
return source;
|
||||
}
|
||||
List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
|
||||
return this.helperConverter.convert(sourceList, sourceType, targetType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class DefaultConversionService extends GenericConversionService {
|
|||
// internal helpers
|
||||
|
||||
private static void addScalarConverters(ConverterRegistry converterRegistry) {
|
||||
ConversionService conversionService = (ConversionService) converterRegistry;
|
||||
converterRegistry.addConverter(new StringToBooleanConverter());
|
||||
converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
|
||||
|
||||
|
|
@ -74,7 +75,7 @@ public class DefaultConversionService extends GenericConversionService {
|
|||
converterRegistry.addConverterFactory(new CharacterToNumberFactory());
|
||||
|
||||
converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter());
|
||||
converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService));
|
||||
|
||||
converterRegistry.addConverter(new StringToLocaleConverter());
|
||||
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -16,14 +16,35 @@
|
|||
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Simply calls {@link Enum#name()} to convert a source Enum to a String.
|
||||
* Calls {@link Enum#name()} to convert a source Enum to a String. This converter will
|
||||
* not match enums with interfaces that can be converterd.
|
||||
* @author Keith Donald
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
*/
|
||||
final class EnumToStringConverter implements Converter<Enum<?>, String> {
|
||||
final class EnumToStringConverter implements Converter<Enum<?>, String>, ConditionalConverter {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public EnumToStringConverter(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
|
||||
if (conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String convert(Enum<?> source) {
|
||||
return source.name();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -41,6 +41,9 @@ final class FallbackObjectToStringConverter implements ConditionalGenericConvert
|
|||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
Class<?> sourceClass = sourceType.getObjectType();
|
||||
if (String.class.equals(sourceClass)) {
|
||||
return false;
|
||||
}
|
||||
return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) ||
|
||||
ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -19,9 +19,8 @@ package org.springframework.core.convert.support;
|
|||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
|
@ -35,13 +34,17 @@ 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.
|
||||
|
|
@ -51,37 +54,24 @@ import org.springframework.util.ClassUtils;
|
|||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @since 3.0
|
||||
*/
|
||||
public class GenericConversionService implements ConfigurableConversionService {
|
||||
|
||||
private static final GenericConverter NO_OP_CONVERTER = new GenericConverter() {
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return null;
|
||||
}
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return source;
|
||||
}
|
||||
public String toString() {
|
||||
return "NO_OP";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* General NO-OP converter used when conversion is not required.
|
||||
*/
|
||||
private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
|
||||
|
||||
private static final GenericConverter NO_MATCH = new GenericConverter() {
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public String toString() {
|
||||
return "NO_MATCH";
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 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 Map<Class<?>, Map<Class<?>, MatchableConverters>> converters =
|
||||
new HashMap<Class<?>, Map<Class<?>, MatchableConverters>>(36);
|
||||
private final Converters converters = new Converters();
|
||||
|
||||
private final Map<ConverterCacheKey, GenericConverter> converterCache =
|
||||
new ConcurrentHashMap<ConverterCacheKey, GenericConverter>();
|
||||
|
|
@ -91,10 +81,8 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
|
||||
public void addConverter(Converter<?, ?> converter) {
|
||||
GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
|
||||
if (typeInfo == null) {
|
||||
throw new IllegalArgumentException("Unable to the determine sourceType <S> and targetType <T> which " +
|
||||
"your Converter<S, T> converts between; declare these generic types.");
|
||||
}
|
||||
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(typeInfo, converter));
|
||||
}
|
||||
|
||||
|
|
@ -104,41 +92,36 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
|
||||
public void addConverter(GenericConverter converter) {
|
||||
Set<GenericConverter.ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
|
||||
for (GenericConverter.ConvertiblePair convertibleType : convertibleTypes) {
|
||||
getMatchableConverters(convertibleType.getSourceType(), convertibleType.getTargetType()).add(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.");
|
||||
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(typeInfo, converterFactory));
|
||||
}
|
||||
|
||||
public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
|
||||
getSourceConverterMap(sourceType).remove(targetType);
|
||||
this.converters.remove(sourceType, targetType);
|
||||
invalidateCache();
|
||||
}
|
||||
|
||||
|
||||
// implementing ConversionService
|
||||
|
||||
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
|
||||
if (targetType == null) {
|
||||
throw new IllegalArgumentException("The targetType to convert to cannot be null");
|
||||
}
|
||||
return canConvert(sourceType != null ? TypeDescriptor.valueOf(sourceType) : null, TypeDescriptor.valueOf(targetType));
|
||||
Assert.notNull(targetType, "The 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) {
|
||||
if (targetType == null) {
|
||||
throw new IllegalArgumentException("The targetType to convert to cannot be null");
|
||||
}
|
||||
Assert.notNull(targetType,"The targetType to convert to cannot be null");
|
||||
if (sourceType == null) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -146,18 +129,30 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
return (converter != null);
|
||||
}
|
||||
|
||||
public boolean canBypassConvert(Class<?> sourceType, Class<?> targetType) {
|
||||
Assert.notNull(targetType, "The targetType to convert to cannot be null");
|
||||
return canBypassConvert(sourceType != null ?
|
||||
TypeDescriptor.valueOf(sourceType) : null,
|
||||
TypeDescriptor.valueOf(targetType));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (targetType == null) {
|
||||
throw new IllegalArgumentException("The targetType to convert to cannot be null");
|
||||
}
|
||||
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) {
|
||||
if (targetType == null) {
|
||||
throw new IllegalArgumentException("The targetType to convert to cannot be null");
|
||||
}
|
||||
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));
|
||||
|
|
@ -171,14 +166,15 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
|
||||
return handleResult(sourceType, targetType, result);
|
||||
}
|
||||
else {
|
||||
return handleConverterNotFound(source, sourceType, targetType);
|
||||
}
|
||||
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)}.
|
||||
* 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
|
||||
|
|
@ -191,21 +187,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
|
||||
public String toString() {
|
||||
List<String> converterStrings = new ArrayList<String>();
|
||||
for (Map<Class<?>, MatchableConverters> targetConverters : this.converters.values()) {
|
||||
for (MatchableConverters matchable : targetConverters.values()) {
|
||||
converterStrings.add(matchable.toString());
|
||||
}
|
||||
}
|
||||
Collections.sort(converterStrings);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("ConversionService converters = ").append("\n");
|
||||
for (String converterString : converterStrings) {
|
||||
builder.append("\t");
|
||||
builder.append(converterString);
|
||||
builder.append("\n");
|
||||
}
|
||||
return builder.toString();
|
||||
return this.converters.toString();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -231,7 +213,8 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
* 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</code> if no suitable converter was found
|
||||
* @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) {
|
||||
|
|
@ -240,20 +223,19 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
if (converter != null) {
|
||||
return (converter != NO_MATCH ? converter : null);
|
||||
}
|
||||
else {
|
||||
converter = findConverterForClassPair(sourceType, targetType);
|
||||
if (converter == null) {
|
||||
converter = getDefaultConverter(sourceType, targetType);
|
||||
}
|
||||
if (converter != null) {
|
||||
this.converterCache.put(key, converter);
|
||||
return converter;
|
||||
}
|
||||
else {
|
||||
this.converterCache.put(key, NO_MATCH);
|
||||
return 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -276,204 +258,19 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null);
|
||||
}
|
||||
|
||||
private MatchableConverters getMatchableConverters(Class<?> sourceType, Class<?> targetType) {
|
||||
Map<Class<?>, MatchableConverters> sourceMap = getSourceConverterMap(sourceType);
|
||||
MatchableConverters matchable = sourceMap.get(targetType);
|
||||
if (matchable == null) {
|
||||
matchable = new MatchableConverters();
|
||||
sourceMap.put(targetType, matchable);
|
||||
}
|
||||
return matchable;
|
||||
}
|
||||
|
||||
private void invalidateCache() {
|
||||
this.converterCache.clear();
|
||||
}
|
||||
|
||||
private Map<Class<?>, MatchableConverters> getSourceConverterMap(Class<?> sourceType) {
|
||||
Map<Class<?>, MatchableConverters> sourceMap = converters.get(sourceType);
|
||||
if (sourceMap == null) {
|
||||
sourceMap = new HashMap<Class<?>, MatchableConverters>();
|
||||
this.converters.put(sourceType, sourceMap);
|
||||
}
|
||||
return sourceMap;
|
||||
}
|
||||
|
||||
private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
Class<?> sourceObjectType = sourceType.getObjectType();
|
||||
if (sourceObjectType.isInterface()) {
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(sourceObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
|
||||
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?>[] interfaces = currentClass.getInterfaces();
|
||||
for (Class<?> ifc : interfaces) {
|
||||
classQueue.addFirst(ifc);
|
||||
}
|
||||
}
|
||||
Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
|
||||
return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
|
||||
}
|
||||
else if (sourceObjectType.isArray()) {
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(sourceObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
|
||||
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
|
||||
if (componentType.getSuperclass() != null) {
|
||||
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
|
||||
}
|
||||
else if (componentType.isInterface()) {
|
||||
classQueue.addFirst(Object[].class);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
HashSet<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(sourceObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(currentClass);
|
||||
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?> superClass = currentClass.getSuperclass();
|
||||
if (superClass != null && superClass != Object.class) {
|
||||
classQueue.addFirst(superClass);
|
||||
}
|
||||
for (Class<?> interfaceType : currentClass.getInterfaces()) {
|
||||
addInterfaceHierarchy(interfaceType, interfaces);
|
||||
}
|
||||
}
|
||||
for (Class<?> interfaceType : interfaces) {
|
||||
Map<Class<?>, MatchableConverters> converters = getTargetConvertersForSource(interfaceType);
|
||||
GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
Map<Class<?>, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
|
||||
return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Class<?>, MatchableConverters> getTargetConvertersForSource(Class<?> sourceType) {
|
||||
Map<Class<?>, MatchableConverters> converters = this.converters.get(sourceType);
|
||||
if (converters == null) {
|
||||
converters = Collections.emptyMap();
|
||||
}
|
||||
return converters;
|
||||
}
|
||||
|
||||
private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType,
|
||||
Map<Class<?>, MatchableConverters> converters) {
|
||||
Class<?> targetObjectType = targetType.getObjectType();
|
||||
if (targetObjectType.isInterface()) {
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(targetObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
MatchableConverters matchable = converters.get(currentClass);
|
||||
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?>[] interfaces = currentClass.getInterfaces();
|
||||
for (Class<?> ifc : interfaces) {
|
||||
classQueue.addFirst(ifc);
|
||||
}
|
||||
}
|
||||
return matchConverter(converters.get(Object.class), sourceType, targetType);
|
||||
}
|
||||
else if (targetObjectType.isArray()) {
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(targetObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
MatchableConverters matchable = converters.get(currentClass);
|
||||
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
|
||||
if (componentType.getSuperclass() != null) {
|
||||
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
|
||||
}
|
||||
else if (componentType.isInterface()) {
|
||||
classQueue.addFirst(Object[].class);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
|
||||
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
|
||||
classQueue.addFirst(targetObjectType);
|
||||
while (!classQueue.isEmpty()) {
|
||||
Class<?> currentClass = classQueue.removeLast();
|
||||
MatchableConverters matchable = converters.get(currentClass);
|
||||
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
Class<?> superClass = currentClass.getSuperclass();
|
||||
if (superClass != null && superClass != Object.class) {
|
||||
classQueue.addFirst(superClass);
|
||||
}
|
||||
for (Class<?> interfaceType : currentClass.getInterfaces()) {
|
||||
addInterfaceHierarchy(interfaceType, interfaces);
|
||||
}
|
||||
}
|
||||
for (Class<?> interfaceType : interfaces) {
|
||||
MatchableConverters matchable = converters.get(interfaceType);
|
||||
GenericConverter converter = matchConverter(matchable, sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
return matchConverter(converters.get(Object.class), sourceType, targetType);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInterfaceHierarchy(Class<?> interfaceType, Set<Class<?>> interfaces) {
|
||||
interfaces.add(interfaceType);
|
||||
for (Class<?> inheritedInterface : interfaceType.getInterfaces()) {
|
||||
addInterfaceHierarchy(inheritedInterface, interfaces);
|
||||
}
|
||||
}
|
||||
|
||||
private GenericConverter matchConverter(
|
||||
MatchableConverters matchable, TypeDescriptor sourceFieldType, TypeDescriptor targetFieldType) {
|
||||
if (matchable == null) {
|
||||
return null;
|
||||
}
|
||||
return matchable.matchConverter(sourceFieldType, targetFieldType);
|
||||
}
|
||||
|
||||
private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
assertNotPrimitiveTargetType(sourceType, targetType);
|
||||
return source;
|
||||
}
|
||||
else if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) {
|
||||
if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) {
|
||||
return source;
|
||||
}
|
||||
else {
|
||||
throw new ConverterNotFoundException(sourceType, targetType);
|
||||
}
|
||||
throw new ConverterNotFoundException(sourceType, targetType);
|
||||
}
|
||||
|
||||
private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) {
|
||||
|
|
@ -482,6 +279,7 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (targetType.isPrimitive()) {
|
||||
throw new ConversionFailedException(sourceType, targetType, null,
|
||||
|
|
@ -490,24 +288,35 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adapts a {@link Converter} to a {@link GenericConverter}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private final class ConverterAdapter implements GenericConverter {
|
||||
private final class ConverterAdapter implements ConditionalGenericConverter {
|
||||
|
||||
private final ConvertiblePair typeInfo;
|
||||
|
||||
private final Converter<Object, Object> converter;
|
||||
|
||||
|
||||
public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {
|
||||
this.converter = (Converter<Object, Object>) converter;
|
||||
this.typeInfo = typeInfo;
|
||||
}
|
||||
|
||||
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return Collections.singleton(this.typeInfo);
|
||||
}
|
||||
|
||||
public boolean matchesTargetType(TypeDescriptor targetType) {
|
||||
return this.typeInfo.getTargetType().equals(targetType.getObjectType());
|
||||
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) {
|
||||
|
|
@ -518,28 +327,48 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
|
||||
public String toString() {
|
||||
return this.typeInfo.getSourceType().getName() + " -> " + this.typeInfo.getTargetType().getName() +
|
||||
" : " + this.converter.toString();
|
||||
return this.typeInfo.getSourceType().getName() + " -> " +
|
||||
this.typeInfo.getTargetType().getName() + " : " +
|
||||
this.converter.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adapts a {@link ConverterFactory} to a {@link GenericConverter}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private final class ConverterFactoryAdapter implements GenericConverter {
|
||||
private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
|
||||
|
||||
private final ConvertiblePair typeInfo;
|
||||
|
||||
private final ConverterFactory<Object, Object> converterFactory;
|
||||
|
||||
|
||||
public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {
|
||||
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);
|
||||
|
|
@ -548,79 +377,29 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
}
|
||||
|
||||
public String toString() {
|
||||
return this.typeInfo.getSourceType().getName() + " -> " + this.typeInfo.getTargetType().getName() +
|
||||
" : " + this.converterFactory.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class MatchableConverters {
|
||||
|
||||
private LinkedList<ConditionalGenericConverter> conditionalConverters;
|
||||
|
||||
private GenericConverter defaultConverter;
|
||||
|
||||
public void add(GenericConverter converter) {
|
||||
if (converter instanceof ConditionalGenericConverter) {
|
||||
if (this.conditionalConverters == null) {
|
||||
this.conditionalConverters = new LinkedList<ConditionalGenericConverter>();
|
||||
}
|
||||
this.conditionalConverters.addFirst((ConditionalGenericConverter) converter);
|
||||
}
|
||||
else {
|
||||
this.defaultConverter = converter;
|
||||
}
|
||||
}
|
||||
|
||||
public GenericConverter matchConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (this.conditionalConverters != null) {
|
||||
for (ConditionalGenericConverter conditional : this.conditionalConverters) {
|
||||
if (conditional.matches(sourceType, targetType)) {
|
||||
return conditional;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.defaultConverter instanceof ConverterAdapter) {
|
||||
ConverterAdapter adapter = (ConverterAdapter) this.defaultConverter;
|
||||
if (!adapter.matchesTargetType(targetType)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return this.defaultConverter;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (this.conditionalConverters != null) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Iterator<ConditionalGenericConverter> it = this.conditionalConverters.iterator(); it.hasNext();) {
|
||||
builder.append(it.next());
|
||||
if (it.hasNext()) {
|
||||
builder.append(", ");
|
||||
}
|
||||
}
|
||||
if (this.defaultConverter != null) {
|
||||
builder.append(", ").append(this.defaultConverter);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
else {
|
||||
return this.defaultConverter.toString();
|
||||
}
|
||||
return this.typeInfo.getSourceType().getName() + " -> " +
|
||||
this.typeInfo.getTargetType().getName() + " : " +
|
||||
this.converterFactory.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
|
@ -629,16 +408,238 @@ public class GenericConversionService implements ConfigurableConversionService {
|
|||
return false;
|
||||
}
|
||||
ConverterCacheKey otherKey = (ConverterCacheKey) other;
|
||||
return this.sourceType.equals(otherKey.sourceType) && this.targetType.equals(otherKey.targetType);
|
||||
return ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType)
|
||||
&& ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return this.sourceType.hashCode() * 29 + this.targetType.hashCode();
|
||||
return ObjectUtils.nullSafeHashCode(this.sourceType) * 29
|
||||
+ ObjectUtils.nullSafeHashCode(this.targetType);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]";
|
||||
return "ConverterCacheKey [sourceType = " + this.sourceType
|
||||
+ ", targetType = " + this.targetType + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages all converters registered with the service.
|
||||
*/
|
||||
private static class Converters {
|
||||
|
||||
private static final Set<Class<?>> IGNORED_CLASSES;
|
||||
static {
|
||||
Set<Class<?>> ignored = new HashSet<Class<?>>();
|
||||
ignored.add(Object.class);
|
||||
ignored.add(Object[].class);
|
||||
IGNORED_CLASSES = Collections.unmodifiableSet(ignored);
|
||||
}
|
||||
|
||||
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");
|
||||
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) {
|
||||
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 though the class and interface
|
||||
* hierarchy of the types.
|
||||
* @param sourceType the source type
|
||||
* @param targetType the target type
|
||||
* @return a {@link GenericConverter} or <tt>null</tt>
|
||||
* @see #getTypeHierarchy(Class)
|
||||
*/
|
||||
public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
// Search the full type hierarchy
|
||||
List<TypeDescriptor> sourceCandidates = getTypeHierarchy(sourceType);
|
||||
List<TypeDescriptor> targetCandidates = getTypeHierarchy(targetType);
|
||||
for (TypeDescriptor sourceCandidate : sourceCandidates) {
|
||||
for (TypeDescriptor targetCandidate : targetCandidates) {
|
||||
GenericConverter converter = getRegisteredConverter(
|
||||
sourceType, targetType, sourceCandidate, targetCandidate);
|
||||
if(converter != null) {
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType,
|
||||
TypeDescriptor sourceCandidate, TypeDescriptor targetCandidate) {
|
||||
|
||||
// Check specifically registered converters
|
||||
ConvertersForPair convertersForPair = converters.get(new ConvertiblePair(
|
||||
sourceCandidate.getType(), targetCandidate.getType()));
|
||||
GenericConverter converter = convertersForPair == null ? null
|
||||
: convertersForPair.getConverter(sourceType, targetType);
|
||||
if (converter != null) {
|
||||
return converter;
|
||||
}
|
||||
|
||||
// Check ConditionalGenericConverter that match all types
|
||||
for (GenericConverter globalConverter : this.globalConverters) {
|
||||
if (((ConditionalConverter)globalConverter).matches(
|
||||
sourceCandidate, targetCandidate)) {
|
||||
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<TypeDescriptor> getTypeHierarchy(TypeDescriptor type) {
|
||||
if(type.isPrimitive()) {
|
||||
type = TypeDescriptor.valueOf(type.getObjectType());
|
||||
}
|
||||
Set<TypeDescriptor> typeHierarchy = new LinkedHashSet<TypeDescriptor>();
|
||||
collectTypeHierarchy(typeHierarchy, type);
|
||||
if(type.isArray()) {
|
||||
typeHierarchy.add(TypeDescriptor.valueOf(Object[].class));
|
||||
}
|
||||
typeHierarchy.add(TypeDescriptor.valueOf(Object.class));
|
||||
return new ArrayList<TypeDescriptor>(typeHierarchy);
|
||||
}
|
||||
|
||||
private void collectTypeHierarchy(Set<TypeDescriptor> typeHierarchy,
|
||||
TypeDescriptor type) {
|
||||
if(type != null && !IGNORED_CLASSES.contains(type.getType())) {
|
||||
if(typeHierarchy.add(type)) {
|
||||
Class<?> superclass = type.getType().getSuperclass();
|
||||
if (type.isArray()) {
|
||||
superclass = ClassUtils.resolvePrimitiveIfNecessary(superclass);
|
||||
}
|
||||
collectTypeHierarchy(typeHierarchy, createRelated(type, superclass));
|
||||
|
||||
for (Class<?> implementsInterface : type.getType().getInterfaces()) {
|
||||
collectTypeHierarchy(typeHierarchy, createRelated(type, implementsInterface));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TypeDescriptor createRelated(TypeDescriptor type, Class<?> relatedType) {
|
||||
if (relatedType == null && type.isArray()) {
|
||||
relatedType = Array.newInstance(relatedType, 0).getClass();
|
||||
}
|
||||
if(!type.getType().equals(relatedType)) {
|
||||
return type.upcast(relatedType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("ConversionService converters = ").append("\n");
|
||||
for (String converterString : getConverterStrings()) {
|
||||
builder.append("\t");
|
||||
builder.append(converterString);
|
||||
builder.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;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return StringUtils.collectionToCommaDelimitedString(this.converters);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Internal converter that performs no operation.
|
||||
*/
|
||||
private static class NoOpConverter implements GenericConverter {
|
||||
|
||||
private 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 name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2009 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
|
@ -38,12 +40,17 @@ import org.springframework.util.NumberUtils;
|
|||
* @see java.math.BigDecimal
|
||||
* @see NumberUtils
|
||||
*/
|
||||
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number> {
|
||||
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>,
|
||||
ConditionalConverter {
|
||||
|
||||
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
|
||||
return new NumberToNumber<T>(targetType);
|
||||
}
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return !sourceType.equals(targetType);
|
||||
}
|
||||
|
||||
private final static class NumberToNumber<T extends Number> implements Converter<Number, T> {
|
||||
|
||||
private final Class<T> targetType;
|
||||
|
|
|
|||
|
|
@ -28,14 +28,9 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Keith Donald
|
||||
|
|
@ -801,4 +796,26 @@ public class TypeDescriptorTests {
|
|||
|
||||
public Map<CharSequence, Number> isAssignableMapKeyValueTypes;
|
||||
|
||||
@Test
|
||||
public void testUpCast() throws Exception {
|
||||
Property property = new Property(getClass(), getClass().getMethod("getProperty"),
|
||||
getClass().getMethod("setProperty", Map.class));
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
|
||||
TypeDescriptor upCast = typeDescriptor.upcast(Object.class);
|
||||
assertTrue(upCast.getAnnotation(MethodAnnotation1.class) != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpCastNotSuper() throws Exception {
|
||||
Property property = new Property(getClass(), getClass().getMethod("getProperty"),
|
||||
getClass().getMethod("setProperty", Map.class));
|
||||
TypeDescriptor typeDescriptor = new TypeDescriptor(property);
|
||||
try {
|
||||
typeDescriptor.upcast(Collection.class);
|
||||
fail("Did not throw");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* Copyright 2002-2012 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.
|
||||
|
|
@ -16,14 +16,6 @@
|
|||
|
||||
package org.springframework.core.convert.support;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.SystemColor;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -31,7 +23,9 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -39,19 +33,25 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
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.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.io.DescriptiveResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Keith Donald
|
||||
* @author Juergen Hoeller
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class GenericConversionServiceTests {
|
||||
|
||||
|
|
@ -599,4 +599,236 @@ public class GenericConversionServiceTests {
|
|||
assertFalse(pair.hashCode() == pairOpposite.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertPrimitiveArray() throws Exception {
|
||||
GenericConversionService conversionService = new DefaultConversionService();
|
||||
byte[] byteArray = new byte[] { 1, 2, 3 };
|
||||
Byte[] converted = conversionService.convert(byteArray, Byte[].class);
|
||||
assertTrue(Arrays.equals(converted, new Byte[] { 1, 2, 3 }));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canConvertIllegalArgumentNullTargetTypeFromClass() {
|
||||
try {
|
||||
conversionService.canConvert(String.class, null);
|
||||
fail("Did not thow IllegalArgumentException");
|
||||
} catch(IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canConvertIllegalArgumentNullTargetTypeFromTypeDescriptor() {
|
||||
try {
|
||||
conversionService.canConvert(TypeDescriptor.valueOf(String.class), null);
|
||||
fail("Did not thow IllegalArgumentException");
|
||||
} catch(IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
public void convertHashMapValuesToList() throws Exception {
|
||||
GenericConversionService conversionService = new DefaultConversionService();
|
||||
Map<String, Integer> hashMap = new LinkedHashMap<String, Integer>();
|
||||
hashMap.put("1", 1);
|
||||
hashMap.put("2", 2);
|
||||
List converted = conversionService.convert(hashMap.values(), List.class);
|
||||
assertEquals(Arrays.asList(1, 2), converted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeConvertible() throws Exception {
|
||||
conversionService.addConverter(new ColorConverter());
|
||||
assertTrue(conversionService.canConvert(String.class, Color.class));
|
||||
conversionService.removeConvertible(String.class, Color.class);
|
||||
assertFalse(conversionService.canConvert(String.class, Color.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalConverter() throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
MyConditionalConverter converter = new MyConditionalConverter();
|
||||
conversionService.addConverter(new ColorConverter());
|
||||
conversionService.addConverter(converter);
|
||||
assertEquals(Color.BLACK, conversionService.convert("#000000", Color.class));
|
||||
assertTrue(converter.getMatchAttempts() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalConverterFactory() throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
MyConditionalConverterFactory converter = new MyConditionalConverterFactory();
|
||||
conversionService.addConverter(new ColorConverter());
|
||||
conversionService.addConverterFactory(converter);
|
||||
assertEquals(Color.BLACK, conversionService.convert("#000000", Color.class));
|
||||
assertTrue(converter.getMatchAttempts() > 0);
|
||||
assertTrue(converter.getNestedMatchAttempts() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotSuportNullConvertibleTypesFromNonConditionalGenericConverter()
|
||||
throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
GenericConverter converter = new GenericConverter() {
|
||||
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
try {
|
||||
conversionService.addConverter(converter);
|
||||
fail("Did not throw");
|
||||
} catch (IllegalStateException e) {
|
||||
assertEquals("Only conditional converters may return null convertible types", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionalConversionForAllTypes() throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
MyConditionalGenericConverter converter = new MyConditionalGenericConverter();
|
||||
conversionService.addConverter(converter);
|
||||
assertEquals((Integer) 3, conversionService.convert(3, Integer.class));
|
||||
Iterator<TypeDescriptor> iterator = converter.getSourceTypes().iterator();
|
||||
assertEquals(Integer.class, iterator.next().getType());
|
||||
assertEquals(Number.class, iterator.next().getType());
|
||||
TypeDescriptor last = null;
|
||||
while (iterator.hasNext()) {
|
||||
last = iterator.next();
|
||||
}
|
||||
assertEquals(Object.class, last.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertOptimizeArray() throws Exception {
|
||||
// SPR-9566
|
||||
GenericConversionService conversionService = new DefaultConversionService();
|
||||
byte[] byteArray = new byte[] { 1, 2, 3 };
|
||||
byte[] converted = conversionService.convert(byteArray, byte[].class);
|
||||
assertSame(byteArray, converted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertCannotOptimizeArray() throws Exception {
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
conversionService.addConverter(new Converter<Byte, Byte>() {
|
||||
public Byte convert(Byte source) {
|
||||
return (byte) (source + 1);
|
||||
}
|
||||
});
|
||||
DefaultConversionService.addDefaultConverters(conversionService);
|
||||
byte[] byteArray = new byte[] { 1, 2, 3 };
|
||||
byte[] converted = conversionService.convert(byteArray, byte[].class);
|
||||
assertNotSame(byteArray, converted);
|
||||
assertTrue(Arrays.equals(new byte[] { 2, 3, 4 }, converted));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumToStringConversion() {
|
||||
conversionService.addConverter(new EnumToStringConverter(conversionService));
|
||||
String result = conversionService.convert(MyEnum.A, String.class);
|
||||
assertEquals("A", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnumWithInterfaceToStringConversion() {
|
||||
// SPR-9692
|
||||
conversionService.addConverter(new EnumToStringConverter(conversionService));
|
||||
conversionService.addConverter(new MyEnumInterfaceToStringConverter<MyEnum>());
|
||||
String result = conversionService.convert(MyEnum.A, String.class);
|
||||
assertEquals("1", result);
|
||||
}
|
||||
|
||||
private static class MyConditionalConverter implements Converter<String, Color>,
|
||||
ConditionalConverter {
|
||||
|
||||
private int matchAttempts = 0;
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
matchAttempts++;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Color convert(String source) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public int getMatchAttempts() {
|
||||
return matchAttempts;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyConditionalGenericConverter implements GenericConverter,
|
||||
ConditionalConverter {
|
||||
|
||||
private Set<TypeDescriptor> sourceTypes = new LinkedHashSet<TypeDescriptor>();
|
||||
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
sourceTypes.add(sourceType);
|
||||
return false;
|
||||
}
|
||||
|
||||
public Object convert(Object source, TypeDescriptor sourceType,
|
||||
TypeDescriptor targetType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<TypeDescriptor> getSourceTypes() {
|
||||
return sourceTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyConditionalConverterFactory implements
|
||||
ConverterFactory<String, Color>, ConditionalConverter {
|
||||
|
||||
private MyConditionalConverter converter = new MyConditionalConverter();
|
||||
|
||||
private int matchAttempts = 0;
|
||||
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
matchAttempts++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Color> Converter<String, T> getConverter(Class<T> targetType) {
|
||||
return (Converter<String, T>) converter;
|
||||
}
|
||||
|
||||
public int getMatchAttempts() {
|
||||
return matchAttempts;
|
||||
}
|
||||
|
||||
public int getNestedMatchAttempts() {
|
||||
return converter.getMatchAttempts();
|
||||
}
|
||||
}
|
||||
|
||||
interface MyEnumInterface {
|
||||
String getCode();
|
||||
}
|
||||
|
||||
public static enum MyEnum implements MyEnumInterface {
|
||||
A {
|
||||
public String getCode() {
|
||||
return "1";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class MyEnumInterfaceToStringConverter<T extends MyEnumInterface>
|
||||
implements Converter<T, String> {
|
||||
public String convert(T source) {
|
||||
return source.getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
|
|
@ -29,6 +30,7 @@ import org.junit.Assert;
|
|||
import org.junit.Test;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionInvocationTargetException;
|
||||
|
|
@ -44,6 +46,7 @@ import org.springframework.expression.spel.testresources.PlaceOfBirth;
|
|||
* Tests invocation of methods.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class MethodInvocationTests extends ExpressionTestCase {
|
||||
|
||||
|
|
@ -369,4 +372,30 @@ public class MethodInvocationTests extends ExpressionTestCase {
|
|||
Object value = expression.getValue(new StandardEvaluationContext(String.class));
|
||||
assertEquals(value, "java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeMethodWithoutConversion() throws Exception {
|
||||
final BytesService service = new BytesService();
|
||||
byte[] bytes = new byte[100];
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(bytes);
|
||||
context.setBeanResolver(new BeanResolver() {
|
||||
public Object resolve(EvaluationContext context, String beanName)
|
||||
throws AccessException {
|
||||
if ("service".equals(beanName)) {
|
||||
return service;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Expression expression = parser.parseExpression("@service.handleBytes(#root)");
|
||||
byte[] outBytes = expression.getValue(context, byte[].class);
|
||||
assertSame(bytes, outBytes);
|
||||
}
|
||||
|
||||
public static class BytesService {
|
||||
|
||||
public byte[] handleBytes(byte[] bytes) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue