Skip to content

Commit a57ff50

Browse files
committed
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
2 parents d8469d1 + 222eec5 commit a57ff50

File tree

16 files changed

+810
-393
lines changed

16 files changed

+810
-393
lines changed

spring-core/src/main/java/org/springframework/core/convert/ConversionService.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
2121
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
2222
*
2323
* @author Keith Donald
24+
* @author Phillip Webb
2425
* @since 3.0
2526
*/
2627
public interface ConversionService {
@@ -54,6 +55,30 @@ public interface ConversionService {
5455
*/
5556
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
5657

58+
/**
59+
* Returns true if conversion between the sourceType and targetType can be bypassed.
60+
* More precisely this method will return true if objects of sourceType can be
61+
* converted to the targetType by returning the source object unchanged.
62+
* @param sourceType context about the source type to convert from (may be null if source is null)
63+
* @param targetType context about the target type to convert to (required)
64+
* @return true if conversion can be bypassed
65+
* @throws IllegalArgumentException if targetType is null
66+
* @since 3.2
67+
*/
68+
boolean canBypassConvert(Class<?> sourceType, Class<?> targetType);
69+
70+
/**
71+
* Returns true if conversion between the sourceType and targetType can be bypassed.
72+
* More precisely this method will return true if objects of sourceType can be
73+
* converted to the targetType by returning the source object unchanged.
74+
* @param sourceType context about the source type to convert from (may be null if source is null)
75+
* @param targetType context about the target type to convert to (required)
76+
* @return true if conversion can be bypassed
77+
* @throws IllegalArgumentException if targetType is null
78+
* @since 3.2
79+
*/
80+
boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
81+
5782
/**
5883
* Convert the source to targetType.
5984
* @param source the source object to convert (may be null)

spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424

2525
import org.springframework.core.MethodParameter;
26+
import org.springframework.util.Assert;
2627
import org.springframework.util.ClassUtils;
2728
import org.springframework.util.ObjectUtils;
2829

@@ -249,6 +250,24 @@ public TypeDescriptor narrow(Object value) {
249250
this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
250251
}
251252

253+
/**
254+
* Cast this {@link TypeDescriptor} to a superclass or implemented interface
255+
* preserving annotations and nested type context.
256+
*
257+
* @param superType the super type to cast to (can be {@code null}
258+
* @return a new TypeDescriptor for the up-cast type
259+
* @throws IllegalArgumentException if this type is not assignable to the super-type
260+
* @since 3.2
261+
*/
262+
public TypeDescriptor upcast(Class<?> superType) {
263+
if (superType == null) {
264+
return null;
265+
}
266+
Assert.isAssignable(superType, getType());
267+
return new TypeDescriptor(superType, this.elementTypeDescriptor,
268+
this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
269+
}
270+
252271
/**
253272
* Returns the name of this type: the fully qualified class name.
254273
*/
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.convert.converter;
18+
19+
import org.springframework.core.convert.TypeDescriptor;
20+
21+
/**
22+
* Allows a {@link Converter}, {@link GenericConverter} or {@link ConverterFactory} to
23+
* conditionally execute based on attributes of the {@code source} and {@code target}
24+
* {@link TypeDescriptor}.
25+
*
26+
* <p>Often used to selectively match custom conversion logic based on the presence of a
27+
* field or class-level characteristic, such as an annotation or method. For example, when
28+
* converting from a String field to a Date field, an implementation might return
29+
*
30+
* {@code true} if the target field has also been annotated with {@code @DateTimeFormat}.
31+
*
32+
* <p>As another example, when converting from a String field to an {@code Account} field, an
33+
* implementation might return {@code true} if the target Account class defines a
34+
* {@code public static findAccount(String)} method.
35+
*
36+
* @author Phillip Webb
37+
* @author Keith Donald
38+
* @since 3.2
39+
* @see Converter
40+
* @see GenericConverter
41+
* @see ConverterFactory
42+
* @see ConditionalGenericConverter
43+
*/
44+
public interface ConditionalConverter {
45+
46+
/**
47+
* Should the conversion from {@code sourceType} to {@code targetType} currently under
48+
* consideration be selected?
49+
*
50+
* @param sourceType the type descriptor of the field we are converting from
51+
* @param targetType the type descriptor of the field we are converting to
52+
* @return true if conversion should be performed, false otherwise
53+
*/
54+
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
55+
}
Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,33 +19,17 @@
1919
import org.springframework.core.convert.TypeDescriptor;
2020

2121
/**
22-
* A generic converter that conditionally executes.
23-
*
24-
* <p>Applies a rule that determines if a converter between a set of
25-
* {@link #getConvertibleTypes() convertible types} matches given a client request to
26-
* convert between a source field of convertible type S and a target field of convertible type T.
27-
*
28-
* <p>Often used to selectively match custom conversion logic based on the presence of
29-
* a field or class-level characteristic, such as an annotation or method. For example,
30-
* when converting from a String field to a Date field, an implementation might return
31-
* <code>true</code> if the target field has also been annotated with <code>@DateTimeFormat</code>.
32-
*
33-
* <p>As another example, when converting from a String field to an Account field,
34-
* an implementation might return true if the target Account class defines a
35-
* <code>public static findAccount(String)</code> method.
22+
* A {@link GenericConverter} that may conditionally execute based on attributes of the
23+
* {@code source} and {@code target} {@link TypeDescriptor}. See
24+
* {@link ConditionalConverter} for details.
3625
*
3726
* @author Keith Donald
27+
* @author Phillip Webb
3828
* @since 3.0
29+
* @see GenericConverter
30+
* @see ConditionalConverter
3931
*/
40-
public interface ConditionalGenericConverter extends GenericConverter {
41-
42-
/**
43-
* Should the converter from <code>sourceType</code> to <code>targetType</code>
44-
* currently under consideration be selected?
45-
* @param sourceType the type descriptor of the field we are converting from
46-
* @param targetType the type descriptor of the field we are converting to
47-
* @return true if conversion should be performed, false otherwise
48-
*/
49-
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
32+
public interface ConditionalGenericConverter extends GenericConverter,
33+
ConditionalConverter {
5034

5135
}

spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,10 +20,13 @@
2020
* A converter converts a source object of type S to a target of type T.
2121
* Implementations of this interface are thread-safe and can be shared.
2222
*
23+
* <p>Implementations may additionally implement {@link ConditionalConverter}.
24+
*
2325
* @author Keith Donald
26+
* @since 3.0
27+
* @see ConditionalConverter
2428
* @param <S> The source type
2529
* @param <T> The target type
26-
* @since 3.0
2730
*/
2831
public interface Converter<S, T> {
2932

spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,8 +19,11 @@
1919
/**
2020
* A factory for "ranged" converters that can convert objects from S to subtypes of R.
2121
*
22+
* <p>Implementations may additionally implement {@link ConditionalConverter}.
23+
*
2224
* @author Keith Donald
2325
* @since 3.0
26+
* @see ConditionalConverter
2427
* @param <S> The source type converters created by this factory can convert from
2528
* @param <R> The target range (or base) type converters created by this factory can convert to;
2629
* for example {@link Number} for a set of number subtypes.

spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,18 +34,24 @@
3434
* <p>This interface should generally not be used when the simpler {@link Converter} or
3535
* {@link ConverterFactory} interfaces are sufficient.
3636
*
37+
* <p>Implementations may additionally implement {@link ConditionalConverter}.
38+
*
3739
* @author Keith Donald
3840
* @author Juergen Hoeller
3941
* @since 3.0
4042
* @see TypeDescriptor
4143
* @see Converter
4244
* @see ConverterFactory
45+
* @see ConditionalConverter
4346
*/
4447
public interface GenericConverter {
4548

4649
/**
47-
* Return the source and target types which this converter can convert between.
48-
* <p>Each entry is a convertible source-to-target type pair.
50+
* Return the source and target types which this converter can convert between. Each
51+
* entry is a convertible source-to-target type pair.
52+
* <p>
53+
* For {@link ConditionalConverter conditional} converters this method may return
54+
* {@code null} to indicate all source-to-target pairs should be considered. *
4955
*/
5056
Set<ConvertiblePair> getConvertibleTypes();
5157

spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Collections;
21+
import java.util.List;
2122
import java.util.Set;
2223

2324
import org.springframework.core.convert.ConversionService;
@@ -26,18 +27,22 @@
2627
import org.springframework.util.ObjectUtils;
2728

2829
/**
29-
* Converts an Array to another Array.
30-
* First adapts the source array to a List, then delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
30+
* Converts an Array to another Array. First adapts the source array to a List, then
31+
* delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
3132
*
3233
* @author Keith Donald
34+
* @author Phillip Webb
3335
* @since 3.0
3436
*/
3537
final class ArrayToArrayConverter implements ConditionalGenericConverter {
3638

3739
private final CollectionToArrayConverter helperConverter;
3840

41+
private final ConversionService conversionService;
42+
3943
public ArrayToArrayConverter(ConversionService conversionService) {
4044
this.helperConverter = new CollectionToArrayConverter(conversionService);
45+
this.conversionService = conversionService;
4146
}
4247

4348
public Set<ConvertiblePair> getConvertibleTypes() {
@@ -48,8 +53,14 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
4853
return this.helperConverter.matches(sourceType, targetType);
4954
}
5055

51-
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
52-
return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
56+
public Object convert(Object source, TypeDescriptor sourceType,
57+
TypeDescriptor targetType) {
58+
if (conversionService.canBypassConvert(sourceType.getElementTypeDescriptor(),
59+
targetType.getElementTypeDescriptor())) {
60+
return source;
61+
}
62+
List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
63+
return this.helperConverter.convert(sourceList, sourceType, targetType);
5364
}
5465

5566
}

spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public static void addDefaultConverters(ConverterRegistry converterRegistry) {
5959
// internal helpers
6060

6161
private static void addScalarConverters(ConverterRegistry converterRegistry) {
62+
ConversionService conversionService = (ConversionService) converterRegistry;
6263
converterRegistry.addConverter(new StringToBooleanConverter());
6364
converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
6465

@@ -74,7 +75,7 @@ private static void addScalarConverters(ConverterRegistry converterRegistry) {
7475
converterRegistry.addConverterFactory(new CharacterToNumberFactory());
7576

7677
converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
77-
converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter());
78+
converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService));
7879

7980
converterRegistry.addConverter(new StringToLocaleConverter());
8081
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());

spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,35 @@
1616

1717
package org.springframework.core.convert.support;
1818

19+
import org.springframework.core.convert.ConversionService;
20+
import org.springframework.core.convert.TypeDescriptor;
21+
import org.springframework.core.convert.converter.ConditionalConverter;
1922
import org.springframework.core.convert.converter.Converter;
23+
import org.springframework.util.ClassUtils;
2024

2125
/**
22-
* Simply calls {@link Enum#name()} to convert a source Enum to a String.
26+
* Calls {@link Enum#name()} to convert a source Enum to a String. This converter will
27+
* not match enums with interfaces that can be converterd.
2328
* @author Keith Donald
29+
* @author Phillip Webb
2430
* @since 3.0
2531
*/
26-
final class EnumToStringConverter implements Converter<Enum<?>, String> {
32+
final class EnumToStringConverter implements Converter<Enum<?>, String>, ConditionalConverter {
33+
34+
private final ConversionService conversionService;
35+
36+
public EnumToStringConverter(ConversionService conversionService) {
37+
this.conversionService = conversionService;
38+
}
39+
40+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
41+
for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
42+
if (conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
43+
return false;
44+
}
45+
}
46+
return true;
47+
}
2748

2849
public String convert(Enum<?> source) {
2950
return source.name();

0 commit comments

Comments
 (0)