Skip to content

Commit e8244d8

Browse files
committed
Add CharSequenceToObjectConverter support
Update `ApplicationConversionService` with support for converting `CharSequence` source types by using existing `String` based converters. The addition is primarily to allow `ConfigTreePropertySource` values to be converted correctly. Closes gh-24171
1 parent e220536 commit e8244d8

File tree

5 files changed

+177
-1
lines changed

5 files changed

+177
-1
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ public static void addApplicationConverters(ConverterRegistry registry) {
122122
registry.addConverter(new InputStreamSourceToByteArrayConverter());
123123
registry.addConverterFactory(new LenientStringToEnumConverterFactory());
124124
registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
125+
if (registry instanceof ConversionService) {
126+
addApplicationConverters(registry, (ConversionService) registry);
127+
}
128+
}
129+
130+
private static void addApplicationConverters(ConverterRegistry registry, ConversionService conversionService) {
131+
registry.addConverter(new CharSequenceToObjectConverter(conversionService));
125132
}
126133

127134
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.convert;
18+
19+
import java.util.Collections;
20+
import java.util.Set;
21+
22+
import org.springframework.core.convert.ConversionService;
23+
import org.springframework.core.convert.TypeDescriptor;
24+
import org.springframework.core.convert.converter.ConditionalGenericConverter;
25+
26+
/**
27+
* {@link ConditionalGenericConverter} to convert {@link CharSequence} type by delegating
28+
* to existing {@link String} converters.
29+
*
30+
* @author Phillip Webb
31+
*/
32+
class CharSequenceToObjectConverter implements ConditionalGenericConverter {
33+
34+
private static final TypeDescriptor STRING = TypeDescriptor.valueOf(String.class);
35+
36+
private static final Set<ConvertiblePair> TYPES;
37+
38+
private final ThreadLocal<Boolean> disable = new ThreadLocal<>();
39+
40+
static {
41+
TYPES = Collections.singleton(new ConvertiblePair(CharSequence.class, Object.class));
42+
}
43+
44+
private final ConversionService conversionService;
45+
46+
CharSequenceToObjectConverter(ConversionService conversionService) {
47+
this.conversionService = conversionService;
48+
}
49+
50+
@Override
51+
public Set<ConvertiblePair> getConvertibleTypes() {
52+
return TYPES;
53+
}
54+
55+
@Override
56+
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
57+
if (sourceType.getType() == String.class || this.disable.get() == Boolean.TRUE) {
58+
return false;
59+
}
60+
this.disable.set(Boolean.TRUE);
61+
try {
62+
return !this.conversionService.canConvert(sourceType, targetType)
63+
&& this.conversionService.canConvert(STRING, targetType);
64+
}
65+
finally {
66+
this.disable.set(null);
67+
}
68+
}
69+
70+
@Override
71+
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
72+
return this.conversionService.convert(source.toString(), STRING, targetType);
73+
}
74+
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.convert;
18+
19+
import java.util.stream.Stream;
20+
21+
import org.junit.jupiter.params.provider.Arguments;
22+
23+
import org.springframework.core.convert.ConversionService;
24+
import org.springframework.core.convert.converter.Converter;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
/**
29+
* Tests for {@link CharSequenceToObjectConverter}
30+
*
31+
* @author Phillip Webb
32+
*/
33+
class CharSequenceToObjectConverterTests {
34+
35+
@ConversionServiceTest
36+
void convertWhenCanConvertViaToString(ConversionService conversionService) {
37+
assertThat(conversionService.convert(new StringBuilder("1"), Integer.class)).isEqualTo(1);
38+
}
39+
40+
@ConversionServiceTest
41+
void convertWhenCanConvertDirectlySkipsStringConversion(ConversionService conversionService) {
42+
assertThat(conversionService.convert(new String("1"), Long.class)).isEqualTo(1);
43+
System.out.println(conversionService.getClass());
44+
if (!ConversionServiceArguments.isApplicationConversionService(conversionService)) {
45+
assertThat(conversionService.convert(new StringBuilder("1"), Long.class)).isEqualTo(2);
46+
}
47+
}
48+
49+
static Stream<? extends Arguments> conversionServices() {
50+
return ConversionServiceArguments.with((conversionService) -> {
51+
conversionService.addConverter(new StringToIntegerConverter());
52+
conversionService.addConverter(new StringToLongConverter());
53+
conversionService.addConverter(new CharSequenceToLongConverter());
54+
conversionService.addConverter(new CharSequenceToObjectConverter(conversionService));
55+
});
56+
}
57+
58+
static class StringToIntegerConverter implements Converter<String, Integer> {
59+
60+
@Override
61+
public Integer convert(String source) {
62+
return Integer.valueOf(source);
63+
}
64+
65+
}
66+
67+
static class StringToLongConverter implements Converter<String, Long> {
68+
69+
@Override
70+
public Long convert(String source) {
71+
return Long.valueOf(source);
72+
}
73+
74+
}
75+
76+
static class CharSequenceToLongConverter implements Converter<CharSequence, Long> {
77+
78+
@Override
79+
public Long convert(CharSequence source) {
80+
return Long.valueOf(source.toString()) + 1;
81+
}
82+
83+
}
84+
85+
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/convert/ConversionServiceArguments.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ static Stream<? extends Arguments> with(Consumer<FormattingConversionService> in
5757
"Application conversion service")));
5858
}
5959

60+
static boolean isApplicationConversionService(ConversionService conversionService) {
61+
if (conversionService instanceof NamedConversionService) {
62+
return isApplicationConversionService(((NamedConversionService) conversionService).delegate);
63+
}
64+
return conversionService instanceof ApplicationConversionService;
65+
}
66+
6067
static class NamedConversionService implements ConversionService {
6168

6269
private final ConversionService delegate;

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ConfigTreePropertySourceTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ void createWhenSourceIsFileThrowsException() throws Exception {
7979
@Test
8080
void getPropertyNamesFromFlatReturnsPropertyNames() throws Exception {
8181
ConfigTreePropertySource propertySource = getFlatPropertySource();
82-
assertThat(propertySource.getPropertyNames()).containsExactly("a", "b", "c");
82+
assertThat(propertySource.getPropertyNames()).containsExactly("a", "b", "c", "one");
8383
}
8484

8585
@Test
@@ -154,6 +154,7 @@ void getPropertyViaEnvironmentSupportsConversion() throws Exception {
154154
assertThat(environment.getProperty("b")).isEqualTo("B");
155155
assertThat(environment.getProperty("c", InputStreamSource.class).getInputStream()).hasContent("C");
156156
assertThat(environment.getProperty("c", byte[].class)).contains('C');
157+
assertThat(environment.getProperty("one", Integer.class)).isEqualTo(1);
157158
}
158159

159160
@Test
@@ -227,6 +228,7 @@ private ConfigTreePropertySource getFlatPropertySource() throws IOException {
227228
addProperty("a", "A");
228229
addProperty("b", "B");
229230
addProperty("c", "C");
231+
addProperty("one", "1");
230232
return new ConfigTreePropertySource("test", this.directory);
231233
}
232234

0 commit comments

Comments
 (0)