Skip to content

Commit 7ea91b8

Browse files
committed
Introduce ConversionService in junit-platform-commons
1 parent 529da15 commit 7ea91b8

File tree

10 files changed

+394
-104
lines changed

10 files changed

+394
-104
lines changed

junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
* the {@link ParameterContext} to perform the conversion.
4141
*
4242
* @since 5.0
43-
* @see SimpleArgumentConverter
4443
* @see org.junit.jupiter.params.ParameterizedTest
4544
* @see org.junit.jupiter.params.converter.ConvertWith
4645
* @see org.junit.jupiter.params.support.AnnotationConsumer

junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
* {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency},
4040
* {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc.
4141
*
42-
* <p>If the source and target types are identical the source object will not
42+
* <p>If the source and target types are identical, the source object will not
4343
* be modified.
4444
*
4545
* @since 5.0
@@ -74,20 +74,14 @@ public final Object convert(Object source, Class<?> targetType, ParameterContext
7474
return source;
7575
}
7676

77-
if (source instanceof String) {
78-
Class<?> declaringClass = context.getDeclaringExecutable().getDeclaringClass();
79-
ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass);
80-
try {
81-
return ConversionSupport.convert((String) source, targetType, classLoader);
82-
}
83-
catch (ConversionException ex) {
84-
throw new ArgumentConversionException(ex.getMessage(), ex);
85-
}
77+
Class<?> declaringClass = context.getDeclaringExecutable().getDeclaringClass();
78+
ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass);
79+
try {
80+
return ConversionSupport.convert(source, targetType, classLoader);
81+
}
82+
catch (ConversionException ex) {
83+
throw new ArgumentConversionException(ex.getMessage(), ex);
8684
}
87-
88-
throw new ArgumentConversionException(
89-
String.format("No built-in converter for source type %s and target type %s",
90-
source.getClass().getTypeName(), targetType.getTypeName()));
9185
}
9286

9387
}

junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ protected TypedArgumentConverter(Class<S> sourceType, Class<T> targetType) {
4646
this.targetType = Preconditions.notNull(targetType, "targetType must not be null");
4747
}
4848

49+
/**
50+
* {@inheritDoc}
51+
*/
4952
@Override
5053
public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException {
5154
if (source == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.commons.support.conversion;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import org.apiguardian.api.API;
16+
17+
/**
18+
* {@code ConversionService} is an abstraction that allows an input object to
19+
* be converted to an instance of a different class.
20+
*
21+
* <p>Implementations are loaded via the {@link java.util.ServiceLoader} and must
22+
* follow the service provider requirements. They should not make any assumptions
23+
* regarding when they are instantiated or how often they are called. Since
24+
* instances may potentially be cached and called from different threads, they
25+
* should be thread-safe.
26+
*
27+
* <p>Extend {@link TypedConversionService} if your implementation always converts
28+
* from a given source type into a given target type and does not need access to
29+
* the {@link ClassLoader} to perform the conversion.
30+
*
31+
* @since 1.12
32+
* @see ConversionSupport
33+
* @see TypedConversionService
34+
*/
35+
@API(status = EXPERIMENTAL, since = "1.12")
36+
public interface ConversionService {
37+
38+
/**
39+
* Determine if the supplied source object can be converted into an instance
40+
* of the specified target type.
41+
*
42+
* @param source the source object to convert; may be {@code null} but only
43+
* if the target type is a reference type
44+
* @param targetType the target type the source should be converted into;
45+
* never {@code null}
46+
* @param classLoader the {@code ClassLoader} to use; never {@code null}
47+
* @return {@code true} if the supplied source can be converted
48+
*/
49+
boolean canConvert(Object source, Class<?> targetType, ClassLoader classLoader);
50+
51+
/**
52+
* Convert the supplied source object into an instance of the specified
53+
* target type.
54+
*
55+
* @param source the source object to convert; may be {@code null} but only
56+
* if the target type is a reference type
57+
* @param targetType the target type the source should be converted into;
58+
* never {@code null}
59+
* @param classLoader the {@code ClassLoader} to use; never {@code null}
60+
* @return the converted object; may be {@code null} but only if the target
61+
* type is a reference type
62+
* @throws ConversionException if an error occurs during the conversion
63+
*/
64+
Object convert(Object source, Class<?> targetType, ClassLoader classLoader) throws ConversionException;
65+
66+
}

junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java

Lines changed: 39 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@
1010

1111
package org.junit.platform.commons.support.conversion;
1212

13-
import static java.util.Arrays.asList;
14-
import static java.util.Collections.unmodifiableList;
1513
import 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

2119
import org.apiguardian.api.API;
2220
import org.junit.platform.commons.util.ClassLoaderUtils;
@@ -30,17 +28,6 @@
3028
@API(status = EXPERIMENTAL, since = "1.11")
3129
public 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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy