diff --git a/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java
index 378016c30b40..b359453d0d0e 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java
@@ -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 @@
* 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,28 @@ 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
+ */
+ 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
+ */
+ boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
+
/**
* Convert the source to targetType.
* @param source the source object to convert (may be null)
diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
index 58585070a876..7e119c345488 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
@@ -23,6 +23,7 @@
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,23 @@ public TypeDescriptor narrow(Object value) {
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
+ */
+ 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.
*/
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConversion.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConversion.java
new file mode 100644
index 000000000000..1196b26fc4fb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConversion.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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}.
+ *
+ *
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}.
+ *
+ *
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 Keith Donald
+ * @author Phillip Webb
+ * @since 3.2
+ * @see Converter
+ * @see GenericConverter
+ * @see ConverterFactory
+ * @see ConditionalGenericConverter
+ */
+public interface ConditionalConversion {
+
+ /**
+ * Should the converter 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);
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java
index fd52ca7d871e..fc7ce7a1ab0c 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java
@@ -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.
@@ -18,34 +18,19 @@
import org.springframework.core.convert.TypeDescriptor;
+
/**
- * A generic converter that conditionally executes.
- *
- *
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.
- *
- *
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
- * true
if the target field has also been annotated with @DateTimeFormat
.
- *
- *
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
- * public static findAccount(String)
method.
+ * A {@link GenericConverter} that may conditionally execute based on attributes of the
+ * {@code source} and {@code target} {@link TypeDescriptor}. See
+ * {@link ConditionalConversion} for details.
*
* @author Keith Donald
+ * @author Phillip Webb
* @since 3.0
+ * @see GenericConverter
+ * @see ConditionalConversion
*/
-public interface ConditionalGenericConverter extends GenericConverter {
-
- /**
- * Should the converter from sourceType
to 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);
+public interface ConditionalGenericConverter extends GenericConverter,
+ ConditionalConversion {
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java
index 8417f581bc8b..a7b1a68d0318 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java
@@ -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 @@
* 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.
*
+ *
Implementations may additionally implement {@link ConditionalConversion}.
+ *
* @author Keith Donald
+ * @since 3.0
+ * @see ConditionalConversion
* @param The source type
* @param The target type
- * @since 3.0
*/
public interface Converter {
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
index fd4e5909dce1..bece4d3266b7 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
@@ -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 @@
/**
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
*
+ * Implementations may additionally implement {@link ConditionalConversion}.
+ *
* @author Keith Donald
* @since 3.0
+ * @see ConditionalConversion
* @param The source type converters created by this factory can convert from
* @param The target range (or base) type converters created by this factory can convert to;
* for example {@link Number} for a set of number subtypes.
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java
index 84c93c8b99fb..3bbb1149f64a 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java
@@ -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 @@
* This interface should generally not be used when the simpler {@link Converter} or
* {@link ConverterFactory} interfaces are sufficient.
*
+ *
Implementations may additionally implement {@link ConditionalConversion}.
+ *
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see TypeDescriptor
* @see Converter
* @see ConverterFactory
+ * @see ConditionalConversion
*/
public interface GenericConverter {
/**
- * Return the source and target types which this converter can convert between.
- *
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.
+ *
+ * For {@link ConditionalConversion conditional} converters this method may return
+ * {@code null} to indicate all source-to-target pairs should be considered. *
*/
Set getConvertibleTypes();
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java
index 288072f90b6b..9a20642a1766 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java
@@ -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 @@
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.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 getConvertibleTypes() {
@@ -48,8 +53,14 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
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 sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
+ return this.helperConverter.convert(sourceList, sourceType, targetType);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
index fe510e9a5349..86e580983b33 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
@@ -59,6 +59,7 @@ public static void addDefaultConverters(ConverterRegistry converterRegistry) {
// 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 @@ private static void addScalarConverters(ConverterRegistry converterRegistry) {
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());
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
index bb06f1773af0..b9eede1abdc4 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
@@ -16,14 +16,37 @@
package org.springframework.core.convert.support;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConversion;
import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
/**
- * 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, String> {
+final class EnumToStringConverter implements Converter, String>, ConditionalConversion {
+
+ 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();
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
index 204a912b0c5b..268f0300ab20 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
@@ -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 @@ public Set getConvertibleTypes() {
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);
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
index 5856489988da..e7ac42ff9fd6 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
@@ -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 @@
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.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConversion;
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 @@
* @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 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 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, Map, MatchableConverters>> converters =
- new HashMap, Map, MatchableConverters>>(36);
+ private final Converters converters = new Converters();
private final Map converterCache =
new ConcurrentHashMap();
@@ -91,10 +81,8 @@ public String toString() {
public void addConverter(Converter, ?> converter) {
GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
- if (typeInfo == null) {
- throw new IllegalArgumentException("Unable to the determine sourceType and targetType which " +
+ Assert.notNull(typeInfo, "Unable to the determine sourceType and targetType which " +
"your Converter converts between; declare these generic types.");
- }
addConverter(new ConverterAdapter(typeInfo, converter));
}
@@ -104,10 +92,7 @@ public void addConverter(Class> sourceType, Class> targetType, Converter,
}
public void addConverter(GenericConverter converter) {
- Set convertibleTypes = converter.getConvertibleTypes();
- for (GenericConverter.ConvertiblePair convertibleType : convertibleTypes) {
- getMatchableConverters(convertibleType.getSourceType(), convertibleType.getTargetType()).add(converter);
- }
+ this.converters.add(converter);
invalidateCache();
}
@@ -121,24 +106,19 @@ public void addConverterFactory(ConverterFactory, ?> 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");
- }
+ 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 +126,29 @@ public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
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 convert(Object source, Class 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,9 +162,7 @@ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor t
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
- else {
- return handleConverterNotFound(source, sourceType, targetType);
- }
+ return handleConverterNotFound(source, sourceType, targetType);
}
/**
@@ -191,21 +180,7 @@ public Object convert(Object source, TypeDescriptor targetType) {
}
public String toString() {
- List converterStrings = new ArrayList();
- for (Map, 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 +206,7 @@ protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor tar
* 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 null
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 +215,19 @@ protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescripto
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 +250,19 @@ private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object converter, C
return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null);
}
- private MatchableConverters getMatchableConverters(Class> sourceType, Class> targetType) {
- Map, 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, MatchableConverters> getSourceConverterMap(Class> sourceType) {
- Map, MatchableConverters> sourceMap = converters.get(sourceType);
- if (sourceMap == null) {
- sourceMap = new HashMap, MatchableConverters>();
- this.converters.put(sourceType, sourceMap);
- }
- return sourceMap;
- }
-
- private GenericConverter findConverterForClassPair(TypeDescriptor sourceType, TypeDescriptor targetType) {
- Class> sourceObjectType = sourceType.getObjectType();
- if (sourceObjectType.isInterface()) {
- LinkedList> classQueue = new LinkedList>();
- classQueue.addFirst(sourceObjectType);
- while (!classQueue.isEmpty()) {
- Class> currentClass = classQueue.removeLast();
- Map, 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, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
- return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
- }
- else if (sourceObjectType.isArray()) {
- LinkedList> classQueue = new LinkedList>();
- classQueue.addFirst(sourceObjectType);
- while (!classQueue.isEmpty()) {
- Class> currentClass = classQueue.removeLast();
- Map, 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> interfaces = new LinkedHashSet>();
- LinkedList> classQueue = new LinkedList>();
- classQueue.addFirst(sourceObjectType);
- while (!classQueue.isEmpty()) {
- Class> currentClass = classQueue.removeLast();
- Map, 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, MatchableConverters> converters = getTargetConvertersForSource(interfaceType);
- GenericConverter converter = getMatchingConverterForTarget(sourceType, targetType, converters);
- if (converter != null) {
- return converter;
- }
- }
- Map, MatchableConverters> objectConverters = getTargetConvertersForSource(Object.class);
- return getMatchingConverterForTarget(sourceType, targetType, objectConverters);
- }
- }
-
- private Map, MatchableConverters> getTargetConvertersForSource(Class> sourceType) {
- Map, MatchableConverters> converters = this.converters.get(sourceType);
- if (converters == null) {
- converters = Collections.emptyMap();
- }
- return converters;
- }
-
- private GenericConverter getMatchingConverterForTarget(TypeDescriptor sourceType, TypeDescriptor targetType,
- Map, MatchableConverters> converters) {
- Class> targetObjectType = targetType.getObjectType();
- if (targetObjectType.isInterface()) {
- LinkedList> classQueue = new LinkedList>();
- 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> classQueue = new LinkedList>();
- 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> interfaces = new LinkedHashSet>();
- LinkedList> classQueue = new LinkedList>();
- 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> 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 +271,7 @@ private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType
}
return result;
}
+
private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (targetType.isPrimitive()) {
throw new ConversionFailedException(sourceType, targetType, null,
@@ -490,24 +280,36 @@ private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescrip
}
+ /**
+ * 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 converter;
+
public ConverterAdapter(ConvertiblePair typeInfo, Converter, ?> converter) {
this.converter = (Converter) converter;
this.typeInfo = typeInfo;
}
+
public Set 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 ConditionalConversion) {
+ return ((ConditionalConversion) this.converter).matches(sourceType,
+ targetType);
+ }
+ return true;
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
@@ -524,22 +326,42 @@ public String 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 converterFactory;
+
public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory, ?> converterFactory) {
this.converterFactory = (ConverterFactory) converterFactory;
this.typeInfo = typeInfo;
}
+
public Set getConvertibleTypes() {
return Collections.singleton(this.typeInfo);
}
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ boolean matches = true;
+ if (this.converterFactory instanceof ConditionalConversion) {
+ matches = ((ConditionalConversion) this.converterFactory).matches(
+ sourceType, targetType);
+ }
+ if(matches) {
+ Converter, ?> converter = converterFactory.getConverter(targetType.getType());
+ if(converter instanceof ConditionalConversion) {
+ matches = ((ConditionalConversion) converter).matches(sourceType, targetType);
+ }
+ }
+ return matches;
+ }
+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return convertNullSource(sourceType, targetType);
@@ -554,91 +376,262 @@ public String toString() {
}
- private static class MatchableConverters {
+ /**
+ * Key for use with the converter cache.
+ */
+ private static final class ConverterCacheKey {
+
+ private final TypeDescriptor sourceType;
+
+ private final TypeDescriptor targetType;
+
- private LinkedList conditionalConverters;
+ public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ }
- private GenericConverter defaultConverter;
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof ConverterCacheKey)) {
+ return false;
+ }
+ ConverterCacheKey otherKey = (ConverterCacheKey) other;
+ return ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType)
+ && ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType);
+ }
+
+ public int hashCode() {
+ return ObjectUtils.nullSafeHashCode(this.sourceType) * 29
+ + ObjectUtils.nullSafeHashCode(this.targetType);
+ }
+
+ public String toString() {
+ return "ConverterCacheKey [sourceType = " + this.sourceType
+ + ", targetType = " + this.targetType + "]";
+ }
+ }
+
+ /**
+ * Manages all converters registered with the service.
+ */
+ private static class Converters {
+
+ private static final Set> IGNORED_CLASSES;
+ static {
+ Set> ignored = new HashSet>();
+ ignored.add(Object.class);
+ ignored.add(Object[].class);
+ IGNORED_CLASSES = Collections.unmodifiableSet(ignored);
+ }
+
+ private final Set globalConverters =
+ new LinkedHashSet();
+
+ private final Map converters =
+ new LinkedHashMap(36);
public void add(GenericConverter converter) {
- if (converter instanceof ConditionalGenericConverter) {
- if (this.conditionalConverters == null) {
- this.conditionalConverters = new LinkedList();
+ Set convertibleTypes = converter.getConvertibleTypes();
+ if (convertibleTypes == null) {
+ Assert.state(converter instanceof ConditionalConversion,
+ "Only conditional converters may return null convertible types");
+ globalConverters.add(converter);
+ } else {
+ for (ConvertiblePair convertiblePair : convertibleTypes) {
+ ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
+ convertersForPair.add(converter);
}
- 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;
+ 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 null
+ * @see #getTypeHierarchy(Class)
+ */
+ public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ // Search the full type hierarchy
+ List sourceCandidates = getTypeHierarchy(sourceType);
+ List targetCandidates = getTypeHierarchy(targetType);
+ for (TypeDescriptor sourceCandidate : sourceCandidates) {
+ for (TypeDescriptor targetCandidate : targetCandidates) {
+ GenericConverter converter = getRegisteredConverter(sourceType, targetType, sourceCandidate, targetCandidate);
+ if(converter != null) {
+ return converter;
}
}
}
- if (this.defaultConverter instanceof ConverterAdapter) {
- ConverterAdapter adapter = (ConverterAdapter) this.defaultConverter;
- if (!adapter.matchesTargetType(targetType)) {
- return null;
+ 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 (((ConditionalConversion)globalConverter).matches(
+ sourceCandidate,
+ targetCandidate)) {
+ return globalConverter;
}
}
- return this.defaultConverter;
+
+ return null;
}
- public String toString() {
- if (this.conditionalConverters != null) {
- StringBuilder builder = new StringBuilder();
- for (Iterator it = this.conditionalConverters.iterator(); it.hasNext();) {
- builder.append(it.next());
- if (it.hasNext()) {
- builder.append(", ");
+ /**
+ * 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 getTypeHierarchy(TypeDescriptor type) {
+ if(type.isPrimitive()) {
+ type = TypeDescriptor.valueOf(type.getObjectType());
+ }
+ Set typeHierarchy = new LinkedHashSet();
+ collectTypeHierarchy(typeHierarchy, type);
+ if(type.isArray()) {
+ typeHierarchy.add(TypeDescriptor.valueOf(Object[].class));
+ }
+ typeHierarchy.add(TypeDescriptor.valueOf(Object.class));
+ return new ArrayList(typeHierarchy);
+ }
+
+ private void collectTypeHierarchy(Set 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));
}
}
- if (this.defaultConverter != null) {
- builder.append(", ").append(this.defaultConverter);
- }
- return builder.toString();
}
- else {
- return this.defaultConverter.toString();
+ }
+
+ 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 static final class ConverterCacheKey {
+ private List getConverterStrings() {
+ List converterStrings = new ArrayList();
+ for (ConvertersForPair convertersForPair : converters.values()) {
+ converterStrings.add(convertersForPair.toString());
+ }
+ Collections.sort(converterStrings);
+ return converterStrings;
+ }
+ }
- private final TypeDescriptor sourceType;
- private final TypeDescriptor targetType;
+ /**
+ * Manages converters registered with a specific {@link ConvertiblePair}.
+ */
+ private static class ConvertersForPair {
- public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
- this.sourceType = sourceType;
- this.targetType = targetType;
+ private final LinkedList converters = new LinkedList();
+
+ public void add(GenericConverter converter) {
+ this.converters.addFirst(converter);
}
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof ConverterCacheKey)) {
- return false;
+ public GenericConverter getConverter(TypeDescriptor sourceType,
+ TypeDescriptor targetType) {
+ for (GenericConverter converter : this.converters) {
+ if (!(converter instanceof ConditionalGenericConverter)
+ || ((ConditionalGenericConverter) converter).matches(sourceType,
+ targetType)) {
+ return converter;
+ }
}
- ConverterCacheKey otherKey = (ConverterCacheKey) other;
- return this.sourceType.equals(otherKey.sourceType) && this.targetType.equals(otherKey.targetType);
+ return null;
}
- public int hashCode() {
- return this.sourceType.hashCode() * 29 + this.targetType.hashCode();
+ 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 getConvertibleTypes() {
+ return null;
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType,
+ TypeDescriptor targetType) {
+ return source;
+ }
+
+ @Override
public String toString() {
- return "ConverterCacheKey [sourceType = " + this.sourceType + ", targetType = " + this.targetType + "]";
+ return name;
}
}
-
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
index 273d36ab62ab..fc9475e8a956 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
@@ -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.ConditionalConversion;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.NumberUtils;
@@ -38,12 +40,17 @@
* @see java.math.BigDecimal
* @see NumberUtils
*/
-final class NumberToNumberConverterFactory implements ConverterFactory {
+final class NumberToNumberConverterFactory implements ConverterFactory,
+ ConditionalConversion {
public Converter getConverter(Class targetType) {
return new NumberToNumber(targetType);
}
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return !sourceType.equals(targetType);
+ }
+
private final static class NumberToNumber implements Converter {
private final Class targetType;
diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
index 20cefeeec354..e16a33242627 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java
@@ -16,6 +16,13 @@
package org.springframework.core.convert;
+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.assertTrue;
+import static org.junit.Assert.fail;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -28,15 +35,8 @@
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;
-
/**
* @author Keith Donald
* @author Andy Clement
@@ -801,4 +801,26 @@ public void isAssignableMapKeyValueTypes() throws Exception {
public Map 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());
+ }
+ }
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
index 27f15b76d09d..bba7379395d9 100644
--- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
+++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
@@ -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,12 +16,13 @@
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.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.awt.Color;
@@ -31,7 +32,9 @@
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;
@@ -42,7 +45,9 @@
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConversion;
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;
@@ -52,6 +57,7 @@
/**
* @author Keith Donald
* @author Juergen Hoeller
+ * @author Phillip Webb
*/
public class GenericConversionServiceTests {
@@ -599,4 +605,236 @@ public void testConvertiblePairDifferentEqualsAndHash() throws Exception {
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 hashMap = new LinkedHashMap();
+ 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 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 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() {
+ 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());
+ String result = conversionService.convert(MyEnum.A, String.class);
+ assertEquals("1", result);
+ }
+
+ private static class MyConditionalConverter implements Converter,
+ ConditionalConversion {
+
+ 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,
+ ConditionalConversion {
+
+ private Set sourceTypes = new LinkedHashSet();
+
+ public Set 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 getSourceTypes() {
+ return sourceTypes;
+ }
+ }
+
+ private static class MyConditionalConverterFactory implements
+ ConverterFactory, ConditionalConversion {
+
+ private MyConditionalConverter converter = new MyConditionalConverter();
+
+ private int matchAttempts = 0;
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ matchAttempts++;
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Converter getConverter(Class targetType) {
+ return (Converter) 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
+ implements Converter {
+ public String convert(T source) {
+ return source.getCode();
+ }
+ }
}
diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java
index 6134533eb1bd..56555b209d92 100644
--- a/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java
+++ b/spring-expression/src/test/java/org/springframework/expression/spel/MethodInvocationTests.java
@@ -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.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 @@
* Tests invocation of methods.
*
* @author Andy Clement
+ * @author Phillip Webb
*/
public class MethodInvocationTests extends ExpressionTestCase {
@@ -369,4 +372,30 @@ public void testMethodOfClass() throws Exception {
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;
+ }
+ }
}