From c0687a614fb9b9f60a1ea895aabbc152e992cc99 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Mon, 23 Dec 2024 02:31:04 +0100 Subject: [PATCH 1/3] Introduce `ConversionService` in `junit-platform-commons` --- .../params/converter/ArgumentConverter.java | 1 - .../converter/DefaultArgumentConverter.java | 22 +-- .../converter/TypedArgumentConverter.java | 3 + .../support/conversion/ConversionService.java | 66 +++++++ .../support/conversion/ConversionSupport.java | 127 ++++--------- .../conversion/DefaultConversionService.java | 173 ++++++++++++++++++ .../conversion/TypedConversionService.java | 73 ++++++++ .../DefaultArgumentConverterTests.java | 3 +- .../converter/LocaleConversionService.java | 29 +++ ...mmons.support.conversion.ConversionService | 1 + 10 files changed, 394 insertions(+), 104 deletions(-) create mode 100644 junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionService.java create mode 100644 junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java create mode 100644 junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/TypedConversionService.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/params/converter/LocaleConversionService.java create mode 100644 jupiter-tests/src/test/resources/META-INF/services/org.junit.platform.commons.support.conversion.ConversionService diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java index 78e4cc55e4d6..2e5495eae0da 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java @@ -40,7 +40,6 @@ * the {@link ParameterContext} to perform the conversion. * * @since 5.0 - * @see SimpleArgumentConverter * @see org.junit.jupiter.params.ParameterizedTest * @see org.junit.jupiter.params.converter.ConvertWith * @see org.junit.jupiter.params.support.AnnotationConsumer diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java index df77d1f759ac..6d89c9e06851 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java @@ -39,7 +39,7 @@ * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. * - *

If the source and target types are identical the source object will not + *

If the source and target types are identical, the source object will not * be modified. * * @since 5.0 @@ -74,20 +74,14 @@ public final Object convert(Object source, Class targetType, ParameterContext return source; } - if (source instanceof String) { - Class declaringClass = context.getDeclaringExecutable().getDeclaringClass(); - ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass); - try { - return ConversionSupport.convert((String) source, targetType, classLoader); - } - catch (ConversionException ex) { - throw new ArgumentConversionException(ex.getMessage(), ex); - } + Class declaringClass = context.getDeclaringExecutable().getDeclaringClass(); + ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass); + try { + return ConversionSupport.convert(source, targetType, classLoader); + } + catch (ConversionException ex) { + throw new ArgumentConversionException(ex.getMessage(), ex); } - - throw new ArgumentConversionException( - String.format("No built-in converter for source type %s and target type %s", - source.getClass().getTypeName(), targetType.getTypeName())); } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java index f229572a2a75..b78ff867bfc2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java @@ -46,6 +46,9 @@ protected TypedArgumentConverter(Class sourceType, Class targetType) { this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); } + /** + * {@inheritDoc} + */ @Override public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { if (source == null) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionService.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionService.java new file mode 100644 index 000000000000..c7e9e367db69 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.conversion; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code ConversionService} is an abstraction that allows an input object to + * be converted to an instance of a different class. + * + *

Implementations are loaded via the {@link java.util.ServiceLoader} and must + * follow the service provider requirements. They should not make any assumptions + * regarding when they are instantiated or how often they are called. Since + * instances may potentially be cached and called from different threads, they + * should be thread-safe. + * + *

Extend {@link TypedConversionService} if your implementation always converts + * from a given source type into a given target type and does not need access to + * the {@link ClassLoader} to perform the conversion. + * + * @since 1.12 + * @see ConversionSupport + * @see TypedConversionService + */ +@API(status = EXPERIMENTAL, since = "1.12") +public interface ConversionService { + + /** + * Determine if the supplied source object can be converted into an instance + * of the specified target type. + * + * @param source the source object to convert; may be {@code null} but only + * if the target type is a reference type + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return {@code true} if the supplied source can be converted + */ + boolean canConvert(Object source, Class targetType, ClassLoader classLoader); + + /** + * Convert the supplied source object into an instance of the specified + * target type. + * + * @param source the source object to convert; may be {@code null} but only + * if the target type is a reference type + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ConversionException if an error occurs during the conversion + */ + Object convert(Object source, Class targetType, ClassLoader classLoader) throws ConversionException; + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java index fa95826f5ddd..3f3f9613a490 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java @@ -10,13 +10,11 @@ package org.junit.platform.commons.support.conversion; -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; -import java.util.List; -import java.util.Optional; +import java.util.ServiceLoader; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassLoaderUtils; @@ -30,17 +28,6 @@ @API(status = EXPERIMENTAL, since = "1.11") public final class ConversionSupport { - private static final List stringToObjectConverters = unmodifiableList(asList( // - new StringToBooleanConverter(), // - new StringToCharacterConverter(), // - new StringToNumberConverter(), // - new StringToClassConverter(), // - new StringToEnumConverter(), // - new StringToJavaTimeConverter(), // - new StringToCommonJavaTypesConverter(), // - new FallbackStringToObjectConverter() // - )); - private ConversionSupport() { /* no-op */ } @@ -49,43 +36,6 @@ private ConversionSupport() { * Convert the supplied source {@code String} into an instance of the specified * target type. * - *

If the target type is {@code String}, the source {@code String} will not - * be modified. - * - *

Some forms of conversion require a {@link ClassLoader}. If none is - * provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default - * ClassLoader} will be used. - * - *

This method is able to convert strings into primitive types and their - * corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte}, - * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and - * {@link Double}), enum constants, date and time types from the - * {@code java.time} package, as well as common Java types such as {@link Class}, - * {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset}, - * {@link java.math.BigDecimal}, {@link java.math.BigInteger}, - * {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID}, - * {@link java.net.URI}, and {@link java.net.URL}. - * - *

If the target type is not covered by any of the above, a convention-based - * conversion strategy will be used to convert the source {@code String} into the - * given target type by invoking a static factory method or factory constructor - * defined in the target type. The search algorithm used in this strategy is - * outlined below. - * - *

Search Algorithm

- * - *
    - *
  1. Search for a single, non-private static factory method in the target - * type that converts from a String to the target type. Use the factory method - * if present.
  2. - *
  3. Search for a single, non-private constructor in the target type that - * accepts a String. Use the constructor if present.
  4. - *
- * - *

If multiple suitable factory methods are discovered they will be ignored. - * If neither a single factory method nor a single constructor is found, the - * convention-based conversion strategy will not apply. - * * @param source the source {@code String} to convert; may be {@code null} * but only if the target type is a reference type * @param targetType the target type the source should be converted into; @@ -97,48 +47,49 @@ private ConversionSupport() { * type is a reference type * * @since 1.11 + * @see DefaultConversionService + * @deprecated Use {@link #convert(Object, Class, ClassLoader)} instead. */ @SuppressWarnings("unchecked") + @Deprecated public static T convert(String source, Class targetType, ClassLoader classLoader) { - if (source == null) { - if (targetType.isPrimitive()) { - throw new ConversionException( - "Cannot convert null to primitive value of type " + targetType.getTypeName()); - } - return null; - } - - if (String.class.equals(targetType)) { - return (T) source; - } + return (T) DefaultConversionService.INSTANCE.convert(source, targetType, getClassLoader(classLoader)); + } - Class targetTypeToUse = toWrapperType(targetType); - Optional converter = stringToObjectConverters.stream().filter( - candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst(); - if (converter.isPresent()) { - try { - ClassLoader classLoaderToUse = classLoader != null ? classLoader - : ClassLoaderUtils.getDefaultClassLoader(); - return (T) converter.get().convert(source, targetTypeToUse, classLoaderToUse); - } - catch (Exception ex) { - if (ex instanceof ConversionException) { - // simply rethrow it - throw (ConversionException) ex; - } - // else - throw new ConversionException( - String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex); - } - } + /** + * Convert the supplied source object into an instance of the specified + * target type. + * + * @param source the source object to convert; may be {@code null} + * but only if the target type is a reference type + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; may be {@code null} to + * use the default {@code ClassLoader} + * @param the type of the target + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + @SuppressWarnings("unchecked") + public static T convert(Object source, Class targetType, ClassLoader classLoader) { + ClassLoader classLoaderToUse = getClassLoader(classLoader); + ServiceLoader serviceLoader = ServiceLoader.load(ConversionService.class, classLoaderToUse); - throw new ConversionException( - "No built-in converter for source type java.lang.String and target type " + targetType.getTypeName()); + return (T) Stream.concat( // + StreamSupport.stream(serviceLoader.spliterator(), false), // + Stream.of(DefaultConversionService.INSTANCE)) // + .filter(candidate -> candidate.canConvert(source, targetType, classLoader)) // + .findFirst() // + .map(candidate -> candidate.convert(source, targetType, classLoaderToUse)) // + .orElseThrow(() -> new ConversionException("No built-in converter for source type " + + source.getClass().getTypeName() + " and target type " + targetType.getTypeName())); } - private static Class toWrapperType(Class targetType) { - Class wrapperType = getWrapperType(targetType); - return wrapperType != null ? wrapperType : targetType; + private static ClassLoader getClassLoader(ClassLoader classLoader) { + return classLoader != null ? classLoader : ClassLoaderUtils.getDefaultClassLoader(); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java new file mode 100644 index 000000000000..54d06b0d6640 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java @@ -0,0 +1,173 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.conversion; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.util.Currency; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; + +import org.junit.platform.commons.util.ClassLoaderUtils; + +/** + * {@code DefaultConversionService} is the default implementation of the + * {@link ConversionService} API. + * + *

The {@code DefaultConversionService} is able to convert from strings to a + * number of primitive types and their corresponding wrapper types (Byte, Short, + * Integer, Long, Float, and Double), date and time types from the + * {@code java.time} package, and some additional common Java types such as + * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, + * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. + * + *

If the source and target types are identical, the source object will not + * be modified. + * + * @since 1.12 + */ +class DefaultConversionService implements ConversionService { + + static final DefaultConversionService INSTANCE = new DefaultConversionService(); + + private static final List stringToObjectConverters = unmodifiableList(asList( // + new StringToBooleanConverter(), // + new StringToCharacterConverter(), // + new StringToNumberConverter(), // + new StringToClassConverter(), // + new StringToEnumConverter(), // + new StringToJavaTimeConverter(), // + new StringToCommonJavaTypesConverter(), // + new FallbackStringToObjectConverter() // + )); + + private DefaultConversionService() { + // nothing to initialize + } + + /** + * Determine if the supplied source object can be converted into an instance + * of the specified target type. + * + * @param source the source object to convert; may be {@code null} but only + * if the target type is a reference type + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return {@code true} if the supplied source can be converted + */ + @Override + public boolean canConvert(Object source, Class targetType, ClassLoader classLoader) { + return source instanceof String; + } + + /** + * Convert the supplied source object into an instance of the specified + * target type. + * + *

If the target type is {@code String}, the source {@code String} will not + * be modified. + * + *

Some forms of conversion require a {@link ClassLoader}. If none is + * provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default + * ClassLoader} will be used. + * + *

This method is able to convert strings into primitive types and their + * corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte}, + * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and + * {@link Double}), enum constants, date and time types from the + * {@code java.time} package, as well as common Java types such as {@link Class}, + * {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset}, + * {@link java.math.BigDecimal}, {@link java.math.BigInteger}, + * {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID}, + * {@link java.net.URI}, and {@link java.net.URL}. + * + *

If the target type is not covered by any of the above, a convention-based + * conversion strategy will be used to convert the source {@code String} into the + * given target type by invoking a static factory method or factory constructor + * defined in the target type. The search algorithm used in this strategy is + * outlined below. + * + *

Search Algorithm

+ * + *
    + *
  1. Search for a single, non-private static factory method in the target + * type that converts from a String to the target type. Use the factory method + * if present.
  2. + *
  3. Search for a single, non-private constructor in the target type that + * accepts a String. Use the constructor if present.
  4. + *
+ * + *

If multiple suitable factory methods are discovered, they will be ignored. + * If neither a single factory method nor a single constructor is found, the + * convention-based conversion strategy will not apply. + * + * @param source the source object to convert; may be {@code null} but only + * if the target type is a reference type + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ConversionException if an error occurs during the conversion + */ + @Override + public Object convert(Object source, Class targetType, ClassLoader classLoader) { + if (source == null) { + if (targetType.isPrimitive()) { + throw new ConversionException( + "Cannot convert null to primitive value of type " + targetType.getTypeName()); + } + return null; + } + + if (String.class.equals(targetType)) { + return source; + } + + // FIXME move/copy next three lines to canConvert? + Class targetTypeToUse = toWrapperType(targetType); + Optional converter = stringToObjectConverters.stream().filter( + candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst(); + if (converter.isPresent()) { + try { + return converter.get().convert((String) source, targetTypeToUse, classLoader); + } + catch (Exception ex) { + if (ex instanceof ConversionException) { + // simply rethrow it + throw (ConversionException) ex; + } + // else + throw new ConversionException( + String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex); + } + } + + throw new ConversionException( + "No built-in converter for source type java.lang.String and target type " + targetType.getTypeName()); + } + + private static Class toWrapperType(Class targetType) { + Class wrapperType = getWrapperType(targetType); + return wrapperType != null ? wrapperType : targetType; + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/TypedConversionService.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/TypedConversionService.java new file mode 100644 index 000000000000..acf21b5d465c --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/TypedConversionService.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.conversion; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * {@code TypedConversionService} is an abstract base class for + * {@link ConversionService} implementations that always convert objects of a + * given source type into a given target type. + * + * @param the type of the source argument to convert + * @param the type of the target object to create from the source + * @since 1.12 + */ +@API(status = EXPERIMENTAL, since = "1.12") +public abstract class TypedConversionService implements ConversionService { + + private final Class sourceType; + private final Class targetType; + + /** + * Create a new {@code TypedConversionService}. + * + * @param sourceType the type of the argument to convert; never {@code null} + * @param targetType the type of the target object to create from the source; + * never {@code null} + */ + protected TypedConversionService(Class sourceType, Class targetType) { + this.sourceType = Preconditions.notNull(sourceType, "sourceType must not be null"); + this.targetType = Preconditions.notNull(targetType, "targetType must not be null"); + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean canConvert(Object source, Class targetType, ClassLoader classLoader) { + return sourceType.isInstance(source) && ReflectionUtils.isAssignableTo(this.targetType, targetType); + } + + /** + * {@inheritDoc} + */ + @Override + public final Object convert(Object source, Class targetType, ClassLoader classLoader) { + return source == null ? convert(null) : convert(this.sourceType.cast(source)); + } + + /** + * Convert the supplied {@code source} object of type {@code S} into an object + * of type {@code T}. + * + * @param source the source object to convert; may be {@code null} + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * @throws ConversionException if an error occurs during the conversion + */ + protected abstract T convert(S source) throws ConversionException; + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java index 609f83cd95d3..6df0d03f3c92 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -347,7 +347,8 @@ void convertsStringToCurrency() { @SuppressWarnings("deprecation") void convertsStringToLocale() { assertConverts("en", Locale.class, Locale.ENGLISH); - assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); + assertConverts("en-US", Locale.class, Locale.US); // FIXME revert + assertConverts(null, Locale.class, null); // FIXME remove } @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/LocaleConversionService.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/LocaleConversionService.java new file mode 100644 index 000000000000..04049abd6653 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/LocaleConversionService.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.params.converter; + +import java.util.Locale; + +import org.junit.platform.commons.support.conversion.TypedConversionService; + +// FIXME delete +public class LocaleConversionService extends TypedConversionService { + + public LocaleConversionService() { + super(String.class, Locale.class); + } + + @Override + protected Locale convert(String source) { + return Locale.forLanguageTag(source); + } + +} diff --git a/jupiter-tests/src/test/resources/META-INF/services/org.junit.platform.commons.support.conversion.ConversionService b/jupiter-tests/src/test/resources/META-INF/services/org.junit.platform.commons.support.conversion.ConversionService new file mode 100644 index 000000000000..dc74fdab252b --- /dev/null +++ b/jupiter-tests/src/test/resources/META-INF/services/org.junit.platform.commons.support.conversion.ConversionService @@ -0,0 +1 @@ +org.junit.jupiter.params.converter.LocaleConversionService From 535aff06597a6d295865ee01d57064d6128ea095 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Thu, 2 Jan 2025 15:11:23 +0100 Subject: [PATCH 2/3] Apply review --- .../support/conversion/ConversionSupport.java | 4 +++- .../org.junit.platform.commons/module-info.java | 1 + .../converter/DefaultArgumentConverterTests.java | 13 ++++++++----- .../junit-platform-commons.expected.txt | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java index 3f3f9613a490..3c07dd61721f 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java @@ -10,6 +10,7 @@ package org.junit.platform.commons.support.conversion; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.ServiceLoader; @@ -52,6 +53,7 @@ private ConversionSupport() { */ @SuppressWarnings("unchecked") @Deprecated + @API(status = DEPRECATED, since = "5.12") public static T convert(String source, Class targetType, ClassLoader classLoader) { return (T) DefaultConversionService.INSTANCE.convert(source, targetType, getClassLoader(classLoader)); } @@ -84,7 +86,7 @@ public static T convert(Object source, Class targetType, ClassLoader clas .filter(candidate -> candidate.canConvert(source, targetType, classLoader)) // .findFirst() // .map(candidate -> candidate.convert(source, targetType, classLoaderToUse)) // - .orElseThrow(() -> new ConversionException("No built-in converter for source type " + .orElseThrow(() -> new ConversionException("No registered or built-in converter for source type " + source.getClass().getTypeName() + " and target type " + targetType.getTypeName())); } diff --git a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java index fb3fdba07a68..fafcfd8cc86b 100644 --- a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java +++ b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java @@ -53,5 +53,6 @@ org.junit.platform.suite.engine, org.junit.platform.testkit, org.junit.vintage.engine; + uses org.junit.platform.commons.support.conversion.ConversionService; uses org.junit.platform.commons.support.scanning.ClasspathScanner; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java index 6df0d03f3c92..28a4231a5265 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -186,24 +186,27 @@ void throwsExceptionOnInvalidStringForPrimitiveTypes() { void throwsExceptionWhenImplicitConverstionIsUnsupported() { assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert("foo", Enigma.class)) // - .withMessage("No built-in converter for source type java.lang.String and target type %s", + .withMessage("No built-in converter for source type java.lang.String and target type %s", // FIXME enhance DefaultConversionService::canConvert Enigma.class.getName()); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(new Enigma(), int[].class)) // - .withMessage("No built-in converter for source type %s and target type int[]", Enigma.class.getName()); + .withMessage("No registered or built-in converter for source type %s and target type int[]", + Enigma.class.getName()); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(new long[] {}, int[].class)) // - .withMessage("No built-in converter for source type long[] and target type int[]"); + .withMessage("No registered or built-in converter for source type long[] and target type int[]"); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(new String[] {}, boolean.class)) // - .withMessage("No built-in converter for source type java.lang.String[] and target type boolean"); + .withMessage( + "No registered or built-in converter for source type java.lang.String[] and target type boolean"); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(Class.class, int[].class)) // - .withMessage("No built-in converter for source type java.lang.Class and target type int[]"); + .withMessage( + "No registered or built-in converter for source type java.lang.Class and target type int[]"); } /** diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index 11e66a7ca44f..89889df45d09 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -9,6 +9,7 @@ requires java.base mandated requires java.logging requires java.management requires org.apiguardian.api static transitive +uses org.junit.platform.commons.support.conversion.ConversionService uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine From 098c9720fbd5b734f35faee9e8c520e396ed8642 Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Sun, 26 Jan 2025 10:58:24 +0100 Subject: [PATCH 3/3] Make `DefaultConversionService` public --- .../commons/support/conversion/DefaultConversionService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java index 54d06b0d6640..9772544a2841 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/DefaultConversionService.java @@ -12,6 +12,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; import java.io.File; @@ -25,6 +26,7 @@ import java.util.Optional; import java.util.UUID; +import org.apiguardian.api.API; import org.junit.platform.commons.util.ClassLoaderUtils; /** @@ -43,7 +45,8 @@ * * @since 1.12 */ -class DefaultConversionService implements ConversionService { +@API(status = INTERNAL, since = "5.12") +public class DefaultConversionService implements ConversionService { static final DefaultConversionService INSTANCE = new DefaultConversionService();