diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index e3db0c096ee6..8bb7fa7d0d27 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -122,6 +122,13 @@ public static void addApplicationConverters(ConverterRegistry registry) { registry.addConverter(new InputStreamSourceToByteArrayConverter()); registry.addConverterFactory(new LenientStringToEnumConverterFactory()); registry.addConverterFactory(new LenientBooleanToEnumConverterFactory()); + if (registry instanceof ConversionService) { + addApplicationConverters(registry, (ConversionService) registry); + } + } + + private static void addApplicationConverters(ConverterRegistry registry, ConversionService conversionService) { + registry.addConverter(new CharSequenceToObjectConverter(conversionService)); } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java new file mode 100644 index 000000000000..6653d179736f --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.convert; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; + +/** + * {@link ConditionalGenericConverter} to convert {@link CharSequence} type by delegating + * to existing {@link String} converters. + * + * @author Phillip Webb + */ +class CharSequenceToObjectConverter implements ConditionalGenericConverter { + + private static final TypeDescriptor STRING = TypeDescriptor.valueOf(String.class); + + private static final Set TYPES; + + private final ThreadLocal disable = new ThreadLocal<>(); + + static { + TYPES = Collections.singleton(new ConvertiblePair(CharSequence.class, Object.class)); + } + + private final ConversionService conversionService; + + CharSequenceToObjectConverter(ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + public Set getConvertibleTypes() { + return TYPES; + } + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.getType() == String.class || this.disable.get() == Boolean.TRUE) { + return false; + } + this.disable.set(Boolean.TRUE); + try { + return !this.conversionService.canConvert(sourceType, targetType) + && this.conversionService.canConvert(STRING, targetType); + } + finally { + this.disable.set(null); + } + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return this.conversionService.convert(source.toString(), STRING, targetType); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/CharSequenceToObjectConverterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/CharSequenceToObjectConverterTests.java new file mode 100644 index 000000000000..c36831909c5a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/CharSequenceToObjectConverterTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.convert; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; + +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.converter.Converter; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CharSequenceToObjectConverter} + * + * @author Phillip Webb + */ +class CharSequenceToObjectConverterTests { + + @ConversionServiceTest + void convertWhenCanConvertViaToString(ConversionService conversionService) { + assertThat(conversionService.convert(new StringBuilder("1"), Integer.class)).isEqualTo(1); + } + + @ConversionServiceTest + void convertWhenCanConvertDirectlySkipsStringConversion(ConversionService conversionService) { + assertThat(conversionService.convert(new String("1"), Long.class)).isEqualTo(1); + System.out.println(conversionService.getClass()); + if (!ConversionServiceArguments.isApplicationConversionService(conversionService)) { + assertThat(conversionService.convert(new StringBuilder("1"), Long.class)).isEqualTo(2); + } + } + + static Stream conversionServices() { + return ConversionServiceArguments.with((conversionService) -> { + conversionService.addConverter(new StringToIntegerConverter()); + conversionService.addConverter(new StringToLongConverter()); + conversionService.addConverter(new CharSequenceToLongConverter()); + conversionService.addConverter(new CharSequenceToObjectConverter(conversionService)); + }); + } + + static class StringToIntegerConverter implements Converter { + + @Override + public Integer convert(String source) { + return Integer.valueOf(source); + } + + } + + static class StringToLongConverter implements Converter { + + @Override + public Long convert(String source) { + return Long.valueOf(source); + } + + } + + static class CharSequenceToLongConverter implements Converter { + + @Override + public Long convert(CharSequence source) { + return Long.valueOf(source.toString()) + 1; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ConversionServiceArguments.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ConversionServiceArguments.java index 6f11041f5453..502da7408cd9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ConversionServiceArguments.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ConversionServiceArguments.java @@ -57,6 +57,13 @@ static Stream with(Consumer in "Application conversion service"))); } + static boolean isApplicationConversionService(ConversionService conversionService) { + if (conversionService instanceof NamedConversionService) { + return isApplicationConversionService(((NamedConversionService) conversionService).delegate); + } + return conversionService instanceof ApplicationConversionService; + } + static class NamedConversionService implements ConversionService { private final ConversionService delegate; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ConfigTreePropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ConfigTreePropertySourceTests.java index 44de45fc403e..bc2f1cf3207b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ConfigTreePropertySourceTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ConfigTreePropertySourceTests.java @@ -79,7 +79,7 @@ void createWhenSourceIsFileThrowsException() throws Exception { @Test void getPropertyNamesFromFlatReturnsPropertyNames() throws Exception { ConfigTreePropertySource propertySource = getFlatPropertySource(); - assertThat(propertySource.getPropertyNames()).containsExactly("a", "b", "c"); + assertThat(propertySource.getPropertyNames()).containsExactly("a", "b", "c", "one"); } @Test @@ -154,6 +154,7 @@ void getPropertyViaEnvironmentSupportsConversion() throws Exception { assertThat(environment.getProperty("b")).isEqualTo("B"); assertThat(environment.getProperty("c", InputStreamSource.class).getInputStream()).hasContent("C"); assertThat(environment.getProperty("c", byte[].class)).contains('C'); + assertThat(environment.getProperty("one", Integer.class)).isEqualTo(1); } @Test @@ -227,6 +228,7 @@ private ConfigTreePropertySource getFlatPropertySource() throws IOException { addProperty("a", "A"); addProperty("b", "B"); addProperty("c", "C"); + addProperty("one", "1"); return new ConfigTreePropertySource("test", this.directory); }