Skip to content

Commit 789d5d5

Browse files
committed
Add ConversionContext
1 parent 0825961 commit 789d5d5

18 files changed

+205
-111
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2015-2025 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+
import org.jspecify.annotations.Nullable;
17+
18+
/**
19+
* {@code ConversionContext} encapsulates the <em>context</em> in which the
20+
* current conversion is being executed.
21+
*
22+
* <p>{@link Converter Converters} are provided an instance of
23+
* {@code ConversionContext} to perform their work.
24+
*
25+
* @param sourceType
26+
* @param targetType
27+
* @param classLoader
28+
*
29+
* @since 6.0
30+
* @see Converter
31+
*/
32+
@API(status = EXPERIMENTAL, since = "6.0")
33+
public record ConversionContext(TypeDescriptor sourceType, TypeDescriptor targetType, ClassLoader classLoader) {
34+
35+
/**
36+
*
37+
* @param source
38+
* @param targetType
39+
* @param classLoader
40+
*/
41+
public ConversionContext(@Nullable Object source, TypeDescriptor targetType, ClassLoader classLoader) {
42+
this(TypeDescriptor.forInstance(source), targetType, classLoader);
43+
}
44+
45+
}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import org.apiguardian.api.API;
2121
import org.jspecify.annotations.Nullable;
22-
import org.junit.platform.commons.util.ClassLoaderUtils;
2322

2423
/**
2524
* {@code ConversionSupport} provides static utility methods for converting a
@@ -79,20 +78,19 @@ private ConversionSupport() {
7978
@SuppressWarnings({ "unchecked", "rawtypes" })
8079
public static <T> @Nullable T convert(@Nullable Object source, TypeDescriptor targetType,
8180
@Nullable ClassLoader classLoader) {
82-
TypeDescriptor sourceType = TypeDescriptor.forInstance(source);
83-
ClassLoader classLoaderToUse = classLoader != null ? classLoader : ClassLoaderUtils.getDefaultClassLoader();
84-
ServiceLoader<Converter> serviceLoader = ServiceLoader.load(Converter.class, classLoaderToUse);
81+
ConversionContext context = new ConversionContext(source, targetType, classLoader);
82+
ServiceLoader<Converter> serviceLoader = ServiceLoader.load(Converter.class, context.classLoader());
8583

8684
Converter converter = Stream.concat( //
8785
StreamSupport.stream(serviceLoader.spliterator(), false), //
8886
Stream.of(DefaultConverter.INSTANCE)) //
89-
.filter(candidate -> candidate.canConvert(sourceType, targetType)) //
87+
.filter(candidate -> candidate.canConvert(context)) //
9088
.findFirst() //
9189
.orElseThrow(() -> new ConversionException(
9290
"No registered or built-in converter for source '%s' and target type %s".formatted( //
9391
source, targetType.getTypeName())));
9492

95-
return (T) converter.convert(source, sourceType, targetType, classLoaderToUse);
93+
return (T) converter.convert(source, context);
9694
}
9795

9896
}

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

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
* from a given source type into a given target type and does not need access to
3030
* the {@link ClassLoader} to perform the conversion.
3131
*
32+
* @param <S>
33+
* @param <T>
34+
*
3235
* @since 6.0
3336
* @see ConversionSupport
3437
* @see TypedConverter
@@ -37,34 +40,26 @@
3740
public interface Converter<S, T> {
3841

3942
/**
40-
* Determine if the supplied source type can be converted into an instance
41-
* of the specified target type.
43+
* Determine if the supplied conversion context is supported.
4244
*
43-
* @param sourceType the descriptor of the source type; never {@code null}
44-
* @param targetType the descriptor of the type the source should be converted into;
45-
* never {@code null}
46-
* @return {@code true} if the supplied source type can be converted
45+
* @param context the context for the conversion; never {@code null}
46+
* @return {@code true} if the conversion is supported
4747
*/
48-
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
48+
boolean canConvert(ConversionContext context);
4949

5050
/**
51-
* Convert the supplied source object into an instance of the specified
52-
* target type.
53-
* <p>This method will only be invoked if {@link #canConvert(TypeDescriptor, TypeDescriptor)}
54-
* returned {@code true} for the same type descriptors.
51+
* Convert the supplied source object according to the supplied conversion context.
52+
* <p>This method will only be invoked if {@link #canConvert(ConversionContext)}
53+
* returned {@code true} for the same context.
5554
*
56-
* @param source the source object to convert; may be {@code null} but only
57-
* if the target type is a reference type
58-
* @param sourceType the descriptor of the source type; never {@code null}
59-
* @param targetType the descriptor of the type the source should be converted into;
60-
* never {@code null}
61-
* @param classLoader the {@code ClassLoader} to use; never {@code null}
55+
* @param source the source object to convert; may be {@code null}
56+
* but only if the target type is a reference type
57+
* @param context the context for the conversion; never {@code null}
6258
* @return the converted object; may be {@code null} but only if the target
6359
* type is a reference type
6460
* @throws ConversionException if an error occurs during the conversion
6561
*/
6662
@Nullable
67-
T convert(@Nullable S source, TypeDescriptor sourceType, TypeDescriptor targetType, ClassLoader classLoader)
68-
throws ConversionException;
63+
T convert(@Nullable S source, ConversionContext context) throws ConversionException;
6964

7065
}

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

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ public class DefaultConverter implements Converter<String, Object> {
4848

4949
static final DefaultConverter INSTANCE = new DefaultConverter();
5050

51-
private static final List<StringToObjectConverter> stringToObjectConverters = List.of(
52-
new StringToBooleanConverter(), //
51+
private static final List<Converter<String, ?>> stringToObjectConverters = List.of(new StringToBooleanConverter(), //
5352
new StringToCharacterConverter(), //
5453
new StringToNumberConverter(), //
5554
new StringToClassConverter(), //
@@ -64,30 +63,27 @@ private DefaultConverter() {
6463
}
6564

6665
/**
67-
* Determine if the supplied source type can be converted into an instance
68-
* of the specified target type.
66+
* Determine if the supplied conversion context is supported.
67+
* <p>FIXME add more content from {@link Converter#convert} about the conversion algorithm
6968
*
70-
* @param sourceType the descriptor of the source type; never {@code null}
71-
* @param targetType the target type the source should be converted into;
72-
* never {@code null}
73-
* @return {@code true} if the supplied source can be converted
69+
* @param context the context for the conversion; never {@code null}
70+
* @return {@code true} if the conversion is supported
7471
*/
7572
@Override
76-
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
77-
if (sourceType == TypeDescriptor.NONE) {
78-
return !targetType.isPrimitive();
73+
public boolean canConvert(ConversionContext context) {
74+
if (context.sourceType() == TypeDescriptor.NONE) {
75+
return !context.targetType().isPrimitive();
7976
}
8077

81-
if (!(String.class.equals(sourceType.getType()))) {
78+
if (!(String.class.equals(context.sourceType().getType()))) {
8279
return false;
8380
}
8481

85-
if (String.class.equals(targetType.getType())) {
82+
if (String.class.equals(context.targetType().getType())) {
8683
return true;
8784
}
8885

89-
return stringToObjectConverters.stream().anyMatch(
90-
candidate -> candidate.canConvert(targetType.getWrapperType()));
86+
return stringToObjectConverters.stream().anyMatch(candidate -> candidate.canConvert(context));
9187
}
9288

9389
/**
@@ -124,37 +120,32 @@ public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
124120
* If neither a single factory method nor a single constructor is found, the
125121
* convention-based conversion strategy will not apply.
126122
*
127-
* @param source the source {@link String} to convert; may be {@code null} but only
128-
* if the target type is a reference type
129-
* @param sourceType the descriptor of the source type; never {@code null}
130-
* @param targetType the descriptor of the type the source should be converted into;
131-
* never {@code null}
132-
* @param classLoader the {@code ClassLoader} to use; never {@code null}
123+
* @param source the source {@link String} to convert; may be {@code null}
124+
* but only if the target type is a reference type
125+
* @param context the context for the conversion; never {@code null}
133126
* @return the converted object; may be {@code null} but only if the target
134127
* type is a reference type
135128
* @throws ConversionException if an error occurs during the conversion
136129
*/
137130
@Override
138-
public @Nullable Object convert(@Nullable String source, TypeDescriptor sourceType, TypeDescriptor targetType,
139-
ClassLoader classLoader) throws ConversionException {
131+
public @Nullable Object convert(@Nullable String source, ConversionContext context) throws ConversionException {
140132
if (source == null) {
141-
if (targetType.isPrimitive()) {
133+
if (context.targetType().isPrimitive()) {
142134
throw new ConversionException(
143-
"Cannot convert null to primitive value of type " + targetType.getTypeName());
135+
"Cannot convert null to primitive value of type " + context.targetType().getTypeName());
144136
}
145137
return null;
146138
}
147139

148-
if (String.class.equals(targetType.getType())) {
140+
if (String.class.equals(context.targetType().getType())) {
149141
return source;
150142
}
151143

152-
Class<?> targetTypeToUse = targetType.getWrapperType();
153-
Optional<StringToObjectConverter> converter = stringToObjectConverters.stream().filter(
154-
candidate -> candidate.canConvert(targetTypeToUse)).findFirst();
144+
Optional<Converter<String, ?>> converter = stringToObjectConverters.stream().filter(
145+
candidate -> candidate.canConvert(context)).findFirst();
155146
if (converter.isPresent()) {
156147
try {
157-
return converter.get().convert(source, targetTypeToUse, classLoader);
148+
return converter.get().convert(source, context);
158149
}
159150
catch (Exception ex) {
160151
if (ex instanceof ConversionException) {
@@ -163,12 +154,13 @@ public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType)
163154
}
164155
// else
165156
throw new ConversionException(
166-
"Failed to convert String \"%s\" to type %s".formatted(source, targetType.getTypeName()), ex);
157+
"Failed to convert String \"%s\" to type %s".formatted(source, context.targetType().getTypeName()),
158+
ex);
167159
}
168160
}
169161

170-
throw new ConversionException(
171-
"No built-in converter for source type java.lang.String and target type " + targetType.getTypeName());
162+
throw new ConversionException("No built-in converter for source type java.lang.String and target type "
163+
+ context.targetType().getTypeName());
172164
}
173165

174166
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.junit.platform.commons.util.Preconditions;
3131

3232
/**
33-
* {@code FallbackStringToObjectConverter} is a {@link StringToObjectConverter}
33+
* {@code FallbackStringToObjectConverter} is a {@link StringToTargetTypeConverter}
3434
* that provides a fallback conversion strategy for converting from a
3535
* {@link String} to a given target type by invoking a static factory method
3636
* or factory constructor defined in the target type.
@@ -52,7 +52,7 @@
5252
* @since 1.11
5353
* @see ConversionSupport
5454
*/
55-
class FallbackStringToObjectConverter extends StringToObjectConverter {
55+
class FallbackStringToObjectConverter extends StringToTargetTypeConverter<Object> {
5656

5757
/**
5858
* Implementation of the NULL Object Pattern.
@@ -71,13 +71,13 @@ class FallbackStringToObjectConverter extends StringToObjectConverter {
7171
= new ConcurrentHashMap<>(64);
7272

7373
@Override
74-
public boolean canConvert(Class<?> targetType) {
74+
boolean canConvert(Class<?> targetType) {
7575
return findFactoryExecutable(targetType) != NULL_EXECUTABLE;
7676
}
7777

7878
@Override
7979
@Nullable
80-
public Object convert(String source, Class<?> targetType, ClassLoader classLoader) {
80+
Object convert(String source, Class<?> targetType) {
8181
Function<String, @Nullable Object> executable = findFactoryExecutable(targetType);
8282
Preconditions.condition(executable != NULL_EXECUTABLE,
8383
"Illegal state: convert() must not be called if canConvert() returned false");

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@
1010

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

13+
import org.jspecify.annotations.Nullable;
1314
import org.junit.platform.commons.util.Preconditions;
1415

15-
class StringToBooleanConverter extends StringToObjectConverter {
16+
class StringToBooleanConverter extends StringToWrapperTypeConverter<Boolean> {
1617

1718
@Override
18-
public boolean canConvert(Class<?> targetType) {
19+
boolean canConvert(Class<?> targetType) {
1920
return targetType == Boolean.class;
2021
}
2122

2223
@Override
23-
public Object convert(String source, Class<?> targetType, ClassLoader classLoader) {
24+
@Nullable
25+
Boolean convert(@Nullable String source, Class<?> targetType) throws ConversionException {
2426
boolean isTrue = "true".equalsIgnoreCase(source);
2527
Preconditions.condition(isTrue || "false".equalsIgnoreCase(source),
2628
() -> "String must be 'true' or 'false' (ignoring case): " + source);

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@
1010

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

13+
import org.jspecify.annotations.Nullable;
1314
import org.junit.platform.commons.util.Preconditions;
1415

15-
class StringToCharacterConverter extends StringToObjectConverter {
16+
class StringToCharacterConverter extends StringToWrapperTypeConverter<Character> {
1617

1718
@Override
18-
public boolean canConvert(Class<?> targetType) {
19+
boolean canConvert(Class<?> targetType) {
1920
return targetType == Character.class;
2021
}
2122

2223
@Override
23-
public Object convert(String source, Class<?> targetType, ClassLoader classLoader) {
24+
@Nullable
25+
Character convert(@Nullable String source, Class<?> targetType) throws ConversionException {
2426
Preconditions.condition(source.length() == 1, () -> "String must have length of 1: " + source);
2527
return source.charAt(0);
2628
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
import org.jspecify.annotations.Nullable;
1414
import org.junit.platform.commons.support.ReflectionSupport;
1515

16-
class StringToClassConverter extends StringToObjectConverter {
16+
class StringToClassConverter implements Converter<String, Class<?>> {
1717

1818
@Override
19-
public boolean canConvert(Class<?> targetType) {
20-
return targetType == Class.class;
19+
public boolean canConvert(ConversionContext context) {
20+
return context.targetType().getType() == Class.class;
2121
}
2222

2323
@Override
24-
public @Nullable Object convert(String className, Class<?> targetType, ClassLoader classLoader) {
24+
public @Nullable Class<?> convert(@Nullable String className, ConversionContext context) {
2525
// @formatter:off
26-
return ReflectionSupport.tryToLoadClass(className, classLoader)
26+
return ReflectionSupport.tryToLoadClass(className, context.classLoader())
2727
.getOrThrow(cause -> new ConversionException(
2828
"Failed to convert String \"" + className + "\" to type java.lang.Class", cause));
2929
// @formatter:on

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.jspecify.annotations.Nullable;
2727
import org.junit.platform.commons.util.Preconditions;
2828

29-
class StringToCommonJavaTypesConverter extends StringToObjectConverter {
29+
class StringToCommonJavaTypesConverter extends StringToTargetTypeConverter<Object> {
3030

3131
@SuppressWarnings("deprecation")
3232
private static final Map<Class<?>, Function<String, ?>> CONVERTERS = Map.of( //
@@ -44,12 +44,12 @@ class StringToCommonJavaTypesConverter extends StringToObjectConverter {
4444
);
4545

4646
@Override
47-
public boolean canConvert(@Nullable Class<?> targetType) {
47+
boolean canConvert(@Nullable Class<?> targetType) {
4848
return CONVERTERS.containsKey(targetType);
4949
}
5050

5151
@Override
52-
public Object convert(@Nullable String source, @Nullable Class<?> targetType, ClassLoader classLoader) {
52+
Object convert(@Nullable String source, @Nullable Class<?> targetType) {
5353
Function<String, ?> converter = Preconditions.notNull(CONVERTERS.get(targetType),
5454
() -> "No registered converter for %s".formatted(targetType != null ? targetType.getName() : null));
5555
return converter.apply(source);

0 commit comments

Comments
 (0)