10
10
11
11
package org .junit .platform .commons .support .conversion ;
12
12
13
- import static java .util .Arrays .asList ;
14
- import static java .util .Collections .unmodifiableList ;
13
+ import static org .apiguardian .api .API .Status .DEPRECATED ;
15
14
import static org .apiguardian .api .API .Status .EXPERIMENTAL ;
16
- import static org .junit .platform .commons .util .ReflectionUtils .getWrapperType ;
17
15
18
- import java .util .List ;
19
- import java .util .Optional ;
16
+ import java .util .ServiceLoader ;
17
+ import java .util .stream .Stream ;
18
+ import java .util .stream .StreamSupport ;
20
19
21
20
import org .apiguardian .api .API ;
22
21
import org .jspecify .annotations .Nullable ;
31
30
@ API (status = EXPERIMENTAL , since = "1.11" )
32
31
public final class ConversionSupport {
33
32
34
- private static final List <StringToObjectConverter > stringToObjectConverters = unmodifiableList (asList ( //
35
- new StringToBooleanConverter (), //
36
- new StringToCharacterConverter (), //
37
- new StringToNumberConverter (), //
38
- new StringToClassConverter (), //
39
- new StringToEnumConverter (), //
40
- new StringToJavaTimeConverter (), //
41
- new StringToCommonJavaTypesConverter (), //
42
- new FallbackStringToObjectConverter () //
43
- ));
44
-
45
33
private ConversionSupport () {
46
34
/* no-op */
47
35
}
@@ -50,43 +38,6 @@ private ConversionSupport() {
50
38
* Convert the supplied source {@code String} into an instance of the specified
51
39
* target type.
52
40
*
53
- * <p>If the target type is {@code String}, the source {@code String} will not
54
- * be modified.
55
- *
56
- * <p>Some forms of conversion require a {@link ClassLoader}. If none is
57
- * provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default
58
- * ClassLoader} will be used.
59
- *
60
- * <p>This method is able to convert strings into primitive types and their
61
- * corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte},
62
- * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and
63
- * {@link Double}), enum constants, date and time types from the
64
- * {@code java.time} package, as well as common Java types such as {@link Class},
65
- * {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset},
66
- * {@link java.math.BigDecimal}, {@link java.math.BigInteger},
67
- * {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID},
68
- * {@link java.net.URI}, and {@link java.net.URL}.
69
- *
70
- * <p>If the target type is not covered by any of the above, a convention-based
71
- * conversion strategy will be used to convert the source {@code String} into the
72
- * given target type by invoking a static factory method or factory constructor
73
- * defined in the target type. The search algorithm used in this strategy is
74
- * outlined below.
75
- *
76
- * <h4>Search Algorithm</h4>
77
- *
78
- * <ol>
79
- * <li>Search for a single, non-private static factory method in the target
80
- * type that converts from a String to the target type. Use the factory method
81
- * if present.</li>
82
- * <li>Search for a single, non-private constructor in the target type that
83
- * accepts a String. Use the constructor if present.</li>
84
- * </ol>
85
- *
86
- * <p>If multiple suitable factory methods are discovered they will be ignored.
87
- * If neither a single factory method nor a single constructor is found, the
88
- * convention-based conversion strategy will not apply.
89
- *
90
41
* @param source the source {@code String} to convert; may be {@code null}
91
42
* but only if the target type is a reference type
92
43
* @param targetType the target type the source should be converted into;
@@ -98,49 +49,51 @@ private ConversionSupport() {
98
49
* type is a reference type
99
50
*
100
51
* @since 1.11
52
+ * @see DefaultConverter
53
+ * @deprecated Use {@link #convert(Object, TypeDescriptor, ClassLoader)} instead.
101
54
*/
102
- @ SuppressWarnings ("unchecked" )
103
- public static <T > @ Nullable T convert (@ Nullable String source , Class <T > targetType ,
104
- @ Nullable ClassLoader classLoader ) {
105
- if (source == null ) {
106
- if (targetType .isPrimitive ()) {
107
- throw new ConversionException (
108
- "Cannot convert null to primitive value of type " + targetType .getTypeName ());
109
- }
110
- return null ;
111
- }
55
+ @ Deprecated
56
+ @ API (status = DEPRECATED , since = "5.13" )
57
+ public static <T > T convert (String source , Class <T > targetType , ClassLoader classLoader ) {
58
+ return convert (source , TypeDescriptor .forClass (targetType ), getClassLoader (classLoader ));
59
+ }
112
60
113
- if (String .class .equals (targetType )) {
114
- return (T ) source ;
115
- }
61
+ /**
62
+ * Convert the supplied source object into an instance of the specified
63
+ * target type.
64
+ *
65
+ * @param source the source object to convert; may be {@code null}
66
+ * but only if the target type is a reference type
67
+ * @param targetType the target type the source should be converted into;
68
+ * never {@code null}
69
+ * @param classLoader the {@code ClassLoader} to use; may be {@code null} to
70
+ * use the default {@code ClassLoader}
71
+ * @param <T> the type of the target
72
+ * @return the converted object; may be {@code null} but only if the target
73
+ * type is a reference type
74
+ *
75
+ * @since 1.13
76
+ */
77
+ @ API (status = EXPERIMENTAL , since = "1.13" )
78
+ @ SuppressWarnings ("unchecked" )
79
+ public static <T > @ Nullable T convert (@ Nullable Object source , TypeDescriptor targetType , @ Nullable ClassLoader classLoader ) {
80
+ TypeDescriptor sourceType = TypeDescriptor .forInstance (source );
81
+ ClassLoader classLoaderToUse = getClassLoader (classLoader );
82
+ ServiceLoader <Converter > serviceLoader = ServiceLoader .load (Converter .class , classLoaderToUse );
116
83
117
- Class <?> targetTypeToUse = toWrapperType (targetType );
118
- Optional <StringToObjectConverter > converter = stringToObjectConverters .stream ().filter (
119
- candidate -> candidate .canConvertTo (targetTypeToUse )).findFirst ();
120
- if (converter .isPresent ()) {
121
- try {
122
- ClassLoader classLoaderToUse = classLoader != null ? classLoader
123
- : ClassLoaderUtils .getDefaultClassLoader ();
124
- return (T ) converter .get ().convert (source , targetTypeToUse , classLoaderToUse );
125
- }
126
- catch (Exception ex ) {
127
- if (ex instanceof ConversionException conversionException ) {
128
- // simply rethrow it
129
- throw conversionException ;
130
- }
131
- // else
132
- throw new ConversionException (
133
- "Failed to convert String \" %s\" to type %s" .formatted (source , targetType .getTypeName ()), ex );
134
- }
135
- }
84
+ Converter converter = Stream .concat ( //
85
+ StreamSupport .stream (serviceLoader .spliterator (), false ), //
86
+ Stream .of (DefaultConverter .INSTANCE )) //
87
+ .filter (candidate -> candidate .canConvert (source , sourceType , targetType )) //
88
+ .findFirst () //
89
+ .orElseThrow (() -> new ConversionException ("No registered or built-in converter for source '" + source
90
+ + "' and target type " + targetType .getType ().getTypeName ()));
136
91
137
- throw new ConversionException (
138
- "No built-in converter for source type java.lang.String and target type " + targetType .getTypeName ());
92
+ return (T ) converter .convert (source , sourceType , targetType , classLoaderToUse );
139
93
}
140
94
141
- private static Class <?> toWrapperType (Class <?> targetType ) {
142
- Class <?> wrapperType = getWrapperType (targetType );
143
- return wrapperType != null ? wrapperType : targetType ;
95
+ private static ClassLoader getClassLoader (ClassLoader classLoader ) {
96
+ return classLoader != null ? classLoader : ClassLoaderUtils .getDefaultClassLoader ();
144
97
}
145
98
146
99
}
0 commit comments