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,49 @@ 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+ * Convert the supplied source object into an instance of the specified
61+ * target type.
62+ *
63+ * @param source the source object to convert; may be {@code null}
64+ * but only if the target type is a reference type
65+ * @param targetType the target type the source should be converted into;
66+ * never {@code null}
67+ * @param classLoader the {@code ClassLoader} to use; may be {@code null} to
68+ * use the default {@code ClassLoader}
69+ * @param <T> the type of the target
70+ * @return the converted object; may be {@code null} but only if the target
71+ * type is a reference type
72+ *
73+ * @since 1.12
74+ */
75+ @ API (status = EXPERIMENTAL , since = "1.12" )
76+ @ SuppressWarnings ("unchecked" )
77+ public static <T > T convert (Object source , Class <T > targetType , ClassLoader classLoader ) {
78+ ClassLoader classLoaderToUse = getClassLoader (classLoader );
79+ ServiceLoader <ConversionService > serviceLoader = ServiceLoader .load (ConversionService .class , classLoaderToUse );
13480
135- throw new ConversionException (
136- "No built-in converter for source type java.lang.String and target type " + targetType .getTypeName ());
81+ return (T ) Stream .concat ( //
82+ StreamSupport .stream (serviceLoader .spliterator (), false ), //
83+ Stream .of (DefaultConversionService .INSTANCE )) //
84+ .filter (candidate -> candidate .canConvert (source , targetType , classLoader )) //
85+ .findFirst () //
86+ .map (candidate -> candidate .convert (source , targetType , classLoaderToUse )) //
87+ .orElseThrow (() -> new ConversionException ("No built-in converter for source type "
88+ + source .getClass ().getTypeName () + " and target type " + targetType .getTypeName ()));
13789 }
13890
139- private static Class <?> toWrapperType (Class <?> targetType ) {
140- Class <?> wrapperType = getWrapperType (targetType );
141- return wrapperType != null ? wrapperType : targetType ;
91+ private static ClassLoader getClassLoader (ClassLoader classLoader ) {
92+ return classLoader != null ? classLoader : ClassLoaderUtils .getDefaultClassLoader ();
14293 }
14394
14495}
0 commit comments