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