|  | 
|  | 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 java.util.Arrays.asList; | 
|  | 14 | +import static java.util.Collections.unmodifiableList; | 
|  | 15 | +import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; | 
|  | 16 | + | 
|  | 17 | +import java.io.File; | 
|  | 18 | +import java.math.BigDecimal; | 
|  | 19 | +import java.math.BigInteger; | 
|  | 20 | +import java.net.URI; | 
|  | 21 | +import java.net.URL; | 
|  | 22 | +import java.util.Currency; | 
|  | 23 | +import java.util.List; | 
|  | 24 | +import java.util.Locale; | 
|  | 25 | +import java.util.Optional; | 
|  | 26 | +import java.util.UUID; | 
|  | 27 | + | 
|  | 28 | +import org.junit.platform.commons.util.ClassLoaderUtils; | 
|  | 29 | + | 
|  | 30 | +/** | 
|  | 31 | + * {@code DefaultConversionService} is the default implementation of the | 
|  | 32 | + * {@link ConversionService} API. | 
|  | 33 | + * | 
|  | 34 | + * <p>The {@code DefaultConversionService} is able to convert from strings to a | 
|  | 35 | + * number of primitive types and their corresponding wrapper types (Byte, Short, | 
|  | 36 | + * Integer, Long, Float, and Double), date and time types from the | 
|  | 37 | + * {@code java.time} package, and some additional common Java types such as | 
|  | 38 | + * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, | 
|  | 39 | + * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. | 
|  | 40 | + * | 
|  | 41 | + * <p>If the source and target types are identical, the source object will not | 
|  | 42 | + * be modified. | 
|  | 43 | + * | 
|  | 44 | + * @since 1.12 | 
|  | 45 | + */ | 
|  | 46 | +class DefaultConversionService implements ConversionService { | 
|  | 47 | + | 
|  | 48 | +	private static final List<StringToObjectConverter> stringToObjectConverters = unmodifiableList(asList( // | 
|  | 49 | +		new StringToBooleanConverter(), // | 
|  | 50 | +		new StringToCharacterConverter(), // | 
|  | 51 | +		new StringToNumberConverter(), // | 
|  | 52 | +		new StringToClassConverter(), // | 
|  | 53 | +		new StringToEnumConverter(), // | 
|  | 54 | +		new StringToJavaTimeConverter(), // | 
|  | 55 | +		new StringToCommonJavaTypesConverter(), // | 
|  | 56 | +		new FallbackStringToObjectConverter() // | 
|  | 57 | +	)); | 
|  | 58 | + | 
|  | 59 | +	@Override | 
|  | 60 | +	public boolean canConvert(Object source, Class<?> targetType, ClassLoader classLoader) { | 
|  | 61 | +		return source instanceof String; | 
|  | 62 | +	} | 
|  | 63 | + | 
|  | 64 | +	/** | 
|  | 65 | +	 * Convert the supplied source {@code String} into an instance of the specified | 
|  | 66 | +	 * target type. | 
|  | 67 | +	 * | 
|  | 68 | +	 * <p>If the target type is {@code String}, the source {@code String} will not | 
|  | 69 | +	 * be modified. | 
|  | 70 | +	 * | 
|  | 71 | +	 * <p>Some forms of conversion require a {@link ClassLoader}. If none is | 
|  | 72 | +	 * provided, the {@linkplain ClassLoaderUtils#getDefaultClassLoader() default | 
|  | 73 | +	 * ClassLoader} will be used. | 
|  | 74 | +	 * | 
|  | 75 | +	 * <p>This method is able to convert strings into primitive types and their | 
|  | 76 | +	 * corresponding wrapper types ({@link Boolean}, {@link Character}, {@link Byte}, | 
|  | 77 | +	 * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, and | 
|  | 78 | +	 * {@link Double}), enum constants, date and time types from the | 
|  | 79 | +	 * {@code java.time} package, as well as common Java types such as {@link Class}, | 
|  | 80 | +	 * {@link java.io.File}, {@link java.nio.file.Path}, {@link java.nio.charset.Charset}, | 
|  | 81 | +	 * {@link java.math.BigDecimal}, {@link java.math.BigInteger}, | 
|  | 82 | +	 * {@link java.util.Currency}, {@link java.util.Locale}, {@link java.util.UUID}, | 
|  | 83 | +	 * {@link java.net.URI}, and {@link java.net.URL}. | 
|  | 84 | +	 * | 
|  | 85 | +	 * <p>If the target type is not covered by any of the above, a convention-based | 
|  | 86 | +	 * conversion strategy will be used to convert the source {@code String} into the | 
|  | 87 | +	 * given target type by invoking a static factory method or factory constructor | 
|  | 88 | +	 * defined in the target type. The search algorithm used in this strategy is | 
|  | 89 | +	 * outlined below. | 
|  | 90 | +	 * | 
|  | 91 | +	 * <h4>Search Algorithm</h4> | 
|  | 92 | +	 * | 
|  | 93 | +	 * <ol> | 
|  | 94 | +	 * <li>Search for a single, non-private static factory method in the target | 
|  | 95 | +	 * type that converts from a String to the target type. Use the factory method | 
|  | 96 | +	 * if present.</li> | 
|  | 97 | +	 * <li>Search for a single, non-private constructor in the target type that | 
|  | 98 | +	 * accepts a String. Use the constructor if present.</li> | 
|  | 99 | +	 * </ol> | 
|  | 100 | +	 * | 
|  | 101 | +	 * <p>If multiple suitable factory methods are discovered, they will be ignored. | 
|  | 102 | +	 * If neither a single factory method nor a single constructor is found, the | 
|  | 103 | +	 * convention-based conversion strategy will not apply. | 
|  | 104 | +	 * | 
|  | 105 | +	 * @param source the source {@code String} to convert; may be {@code null} | 
|  | 106 | +	 * but only if the target type is a reference type | 
|  | 107 | +	 * @param targetType the target type the source should be converted into; | 
|  | 108 | +	 * never {@code null} | 
|  | 109 | +	 * @param classLoader the {@code ClassLoader} to use; never {@code null} | 
|  | 110 | +	 * @return the converted object; may be {@code null} but only if the target | 
|  | 111 | +	 * type is a reference type | 
|  | 112 | +	 */ | 
|  | 113 | +	@Override | 
|  | 114 | +	public Object convert(Object source, Class<?> targetType, ClassLoader classLoader) { | 
|  | 115 | +		if (source == null) { | 
|  | 116 | +			if (targetType.isPrimitive()) { | 
|  | 117 | +				throw new ConversionException( | 
|  | 118 | +					"Cannot convert null to primitive value of type " + targetType.getTypeName()); | 
|  | 119 | +			} | 
|  | 120 | +			return null; | 
|  | 121 | +		} | 
|  | 122 | + | 
|  | 123 | +		if (String.class.equals(targetType)) { | 
|  | 124 | +			return source; | 
|  | 125 | +		} | 
|  | 126 | + | 
|  | 127 | +		// FIXME move/copy next three lines to canConvert? | 
|  | 128 | +		Class<?> targetTypeToUse = toWrapperType(targetType); | 
|  | 129 | +		Optional<StringToObjectConverter> converter = stringToObjectConverters.stream().filter( | 
|  | 130 | +			candidate -> candidate.canConvertTo(targetTypeToUse)).findFirst(); | 
|  | 131 | +		if (converter.isPresent()) { | 
|  | 132 | +			try { | 
|  | 133 | +				return converter.get().convert((String) source, targetTypeToUse, classLoader); | 
|  | 134 | +			} | 
|  | 135 | +			catch (Exception ex) { | 
|  | 136 | +				if (ex instanceof ConversionException) { | 
|  | 137 | +					// simply rethrow it | 
|  | 138 | +					throw (ConversionException) ex; | 
|  | 139 | +				} | 
|  | 140 | +				// else | 
|  | 141 | +				throw new ConversionException( | 
|  | 142 | +					String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex); | 
|  | 143 | +			} | 
|  | 144 | +		} | 
|  | 145 | + | 
|  | 146 | +		throw new ConversionException( | 
|  | 147 | +			"No built-in converter for source type java.lang.String and target type " + targetType.getTypeName()); | 
|  | 148 | +	} | 
|  | 149 | + | 
|  | 150 | +	private static Class<?> toWrapperType(Class<?> targetType) { | 
|  | 151 | +		Class<?> wrapperType = getWrapperType(targetType); | 
|  | 152 | +		return wrapperType != null ? wrapperType : targetType; | 
|  | 153 | +	} | 
|  | 154 | + | 
|  | 155 | +} | 
0 commit comments