Skip to content

Commit f13e3ad

Browse files
philwebbcbeams
authored andcommitted
Extend conditional conversion support
Introduce new ConditionalConversion interface that can be applied to Converter, ConverterFactory or GenericConverter interfaces to make them conditional. Prior to this commit the only ConditionalGenericConverter could be conditional. Issue: SPR-9928
1 parent 4dc2895 commit f13e3ad

File tree

7 files changed

+259
-36
lines changed

7 files changed

+259
-36
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 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 Keith Donald
37+
* @author Phillip Webb
38+
* @since 3.2
39+
* @see Converter
40+
* @see GenericConverter
41+
* @see ConverterFactory
42+
* @see ConditionalGenericConverter
43+
*/
44+
public interface ConditionalConversion {
45+
46+
/**
47+
* Should the converter 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: 10 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.
@@ -18,34 +18,19 @@
1818

1919
import org.springframework.core.convert.TypeDescriptor;
2020

21+
2122
/**
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.
23+
* A {@link GenericConverter} that may conditionally execute based on attributes of the
24+
* {@code source} and {@code target} {@link TypeDescriptor}. See
25+
* {@link ConditionalConversion} for details.
3626
*
3727
* @author Keith Donald
28+
* @author Phillip Webb
3829
* @since 3.0
30+
* @see GenericConverter
31+
* @see ConditionalConversion
3932
*/
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);
33+
public interface ConditionalGenericConverter extends GenericConverter,
34+
ConditionalConversion {
5035

5136
}

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 ConditionalConversion}.
24+
*
2325
* @author Keith Donald
26+
* @since 3.0
27+
* @see ConditionalConversion
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 ConditionalConversion}.
23+
*
2224
* @author Keith Donald
2325
* @since 3.0
26+
* @see ConditionalConversion
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 ConditionalConversion}.
38+
*
3739
* @author Keith Donald
3840
* @author Juergen Hoeller
3941
* @since 3.0
4042
* @see TypeDescriptor
4143
* @see Converter
4244
* @see ConverterFactory
45+
* @see ConditionalConversion
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 ConditionalConversion 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/GenericConversionService.java

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.core.convert.ConversionService;
3535
import org.springframework.core.convert.ConverterNotFoundException;
3636
import org.springframework.core.convert.TypeDescriptor;
37+
import org.springframework.core.convert.converter.ConditionalConversion;
3738
import org.springframework.core.convert.converter.ConditionalGenericConverter;
3839
import org.springframework.core.convert.converter.Converter;
3940
import org.springframework.core.convert.converter.ConverterFactory;
@@ -289,6 +290,10 @@ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
289290
if(!this.typeInfo.getTargetType().equals(targetType.getObjectType())) {
290291
return false;
291292
}
293+
if (this.converter instanceof ConditionalConversion) {
294+
return ((ConditionalConversion) this.converter).matches(sourceType,
295+
targetType);
296+
}
292297
return true;
293298
}
294299

@@ -310,7 +315,7 @@ public String toString() {
310315
* Adapts a {@link ConverterFactory} to a {@link GenericConverter}.
311316
*/
312317
@SuppressWarnings("unchecked")
313-
private final class ConverterFactoryAdapter implements GenericConverter {
318+
private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
314319

315320
private final ConvertiblePair typeInfo;
316321

@@ -327,6 +332,21 @@ public Set<ConvertiblePair> getConvertibleTypes() {
327332
return Collections.singleton(this.typeInfo);
328333
}
329334

335+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
336+
boolean matches = true;
337+
if (this.converterFactory instanceof ConditionalConversion) {
338+
matches = ((ConditionalConversion) this.converterFactory).matches(
339+
sourceType, targetType);
340+
}
341+
if(matches) {
342+
Converter<?, ?> converter = converterFactory.getConverter(targetType.getType());
343+
if(converter instanceof ConditionalConversion) {
344+
matches = ((ConditionalConversion) converter).matches(sourceType, targetType);
345+
}
346+
}
347+
return matches;
348+
}
349+
330350
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
331351
if (source == null) {
332352
return convertNullSource(sourceType, targetType);
@@ -393,15 +413,23 @@ private static class Converters {
393413
IGNORED_CLASSES = Collections.unmodifiableSet(ignored);
394414
}
395415

416+
private final Set<GenericConverter> globalConverters =
417+
new LinkedHashSet<GenericConverter>();
418+
396419
private final Map<ConvertiblePair, ConvertersForPair> converters =
397420
new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36);
398421

399422
public void add(GenericConverter converter) {
400423
Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
401-
Assert.state(converter.getConvertibleTypes() != null, "Converter does not specifiy ConvertibleTypes");
402-
for (ConvertiblePair convertiblePair : convertibleTypes) {
403-
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
404-
convertersForPair.add(converter);
424+
if (convertibleTypes == null) {
425+
Assert.state(converter instanceof ConditionalConversion,
426+
"Only conditional converters may return null convertible types");
427+
globalConverters.add(converter);
428+
} else {
429+
for (ConvertiblePair convertiblePair : convertibleTypes) {
430+
ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
431+
convertersForPair.add(converter);
432+
}
405433
}
406434
}
407435

@@ -454,6 +482,15 @@ private GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeD
454482
return converter;
455483
}
456484

485+
// Check ConditionalGenericConverter that match all types
486+
for (GenericConverter globalConverter : this.globalConverters) {
487+
if (((ConditionalConversion)globalConverter).matches(
488+
sourceCandidate,
489+
targetCandidate)) {
490+
return globalConverter;
491+
}
492+
}
493+
457494
return null;
458495
}
459496

0 commit comments

Comments
 (0)