Skip to content

Commit 7ada32c

Browse files
mhalbritterphilwebb
andcommitted
Make ConfigDataLocation.of non-nullable
Update `ConfigDataLocation.of` to return an empty location rather than null. Filtering of empty elements now happens in `ConfigDataProperties` and `ConfigDataEnvironment` which allows us to simplify `ConfigDataLocationBindHandler`. Closes spring-projectsgh-47221 Co-authored-by: Phillip Webb <phil.webb@broadcom.com>
1 parent 51eb412 commit 7ada32c

File tree

6 files changed

+79
-64
lines changed

6 files changed

+79
-64
lines changed

core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, C
214214
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
215215
ConfigDataLocation[] locations) {
216216
for (int i = locations.length - 1; i >= 0; i--) {
217-
initialContributors.add(createInitialImportContributor(locations[i]));
217+
if (ConfigDataLocation.isNotEmpty(locations[i])) {
218+
initialContributors.add(createInitialImportContributor(locations[i]));
219+
}
218220
}
219221
}
220222

core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
*/
3939
public final class ConfigDataLocation implements OriginProvider {
4040

41+
private static final ConfigDataLocation EMPTY = new ConfigDataLocation(false, "", null);
42+
4143
/**
4244
* Prefix used to indicate that a {@link ConfigDataResource} is optional.
4345
*/
@@ -89,10 +91,7 @@ public boolean hasPrefix(String prefix) {
8991
* @return the value with the prefix removed
9092
*/
9193
public String getNonPrefixedValue(String prefix) {
92-
if (hasPrefix(prefix)) {
93-
return this.value.substring(prefix.length());
94-
}
95-
return this.value;
94+
return (!hasPrefix(prefix)) ? this.value : this.value.substring(prefix.length());
9695
}
9796

9897
@Override
@@ -118,17 +117,26 @@ public ConfigDataLocation[] split() {
118117
* @since 2.4.7
119118
*/
120119
public ConfigDataLocation[] split(String delimiter) {
120+
Assert.state(!this.value.isEmpty(), "Unable to split empty locations");
121121
String[] values = StringUtils.delimitedListToStringArray(toString(), delimiter);
122122
ConfigDataLocation[] result = new ConfigDataLocation[values.length];
123123
for (int i = 0; i < values.length; i++) {
124124
int index = i;
125125
ConfigDataLocation configDataLocation = of(values[index]);
126-
Assert.state(configDataLocation != null, () -> "Unable to parse '%s'".formatted(values[index]));
127126
result[i] = configDataLocation.withOrigin(getOrigin());
128127
}
129128
return result;
130129
}
131130

131+
/**
132+
* Create a new {@link ConfigDataLocation} with a specific {@link Origin}.
133+
* @param origin the origin to set
134+
* @return a new {@link ConfigDataLocation} instance.
135+
*/
136+
ConfigDataLocation withOrigin(@Nullable Origin origin) {
137+
return new ConfigDataLocation(this.optional, this.value, origin);
138+
}
139+
132140
@Override
133141
public boolean equals(Object obj) {
134142
if (this == obj) {
@@ -151,35 +159,19 @@ public String toString() {
151159
return (!this.optional) ? this.value : OPTIONAL_PREFIX + this.value;
152160
}
153161

154-
/**
155-
* Create a new {@link ConfigDataLocation} with a specific {@link Origin}.
156-
* @param origin the origin to set
157-
* @return a new {@link ConfigDataLocation} instance.
158-
*/
159-
ConfigDataLocation withOrigin(@Nullable Origin origin) {
160-
return new ConfigDataLocation(this.optional, this.value, origin);
161-
}
162-
163162
/**
164163
* Factory method to create a new {@link ConfigDataLocation} from a string.
165164
* @param location the location string
166-
* @return a {@link ConfigDataLocation} instance or {@code null} if no location was
167-
* provided
165+
* @return the {@link ConfigDataLocation} (which may be empty)
168166
*/
169-
public static @Nullable ConfigDataLocation of(@Nullable String location) {
167+
public static ConfigDataLocation of(@Nullable String location) {
170168
boolean optional = location != null && location.startsWith(OPTIONAL_PREFIX);
171-
String value;
172-
if (optional) {
173-
Assert.state(location != null, "'location' can't be null here");
174-
value = location.substring(OPTIONAL_PREFIX.length());
175-
}
176-
else {
177-
value = location;
178-
}
179-
if (!StringUtils.hasText(value)) {
180-
return null;
181-
}
182-
return new ConfigDataLocation(optional, value, null);
169+
String value = (location != null && optional) ? location.substring(OPTIONAL_PREFIX.length()) : location;
170+
return (StringUtils.hasText(value)) ? new ConfigDataLocation(optional, value, null) : EMPTY;
171+
}
172+
173+
static boolean isNotEmpty(@Nullable ConfigDataLocation location) {
174+
return (location != null) && !location.getValue().isEmpty();
183175
}
184176

185177
}

core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.List;
22-
import java.util.Objects;
2322
import java.util.stream.Collectors;
2423

24+
import org.jspecify.annotations.Nullable;
25+
2526
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
2627
import org.springframework.boot.context.properties.bind.BindContext;
2728
import org.springframework.boot.context.properties.bind.BindHandler;
2829
import org.springframework.boot.context.properties.bind.Bindable;
30+
import org.springframework.boot.context.properties.source.ConfigurationProperty;
2931
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
3032
import org.springframework.boot.origin.Origin;
3133

@@ -39,32 +41,35 @@
3941
class ConfigDataLocationBindHandler extends AbstractBindHandler {
4042

4143
@Override
42-
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
44+
public @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
45+
Object result) {
46+
OriginMapper originMapper = new OriginMapper(context.getConfigurationProperty());
4347
if (result instanceof ConfigDataLocation location) {
44-
return withOrigin(context, location);
48+
return originMapper.map(location);
4549
}
46-
if (result instanceof List<?> list) {
47-
return list.stream()
48-
.filter(Objects::nonNull)
49-
.map((element) -> (element instanceof ConfigDataLocation location) ? withOrigin(context, location)
50-
: element)
51-
.collect(Collectors.toCollection(ArrayList::new));
50+
if (result instanceof List<?> locations) {
51+
return locations.stream().map(originMapper::mapIfPossible).collect(Collectors.toCollection(ArrayList::new));
5252
}
53-
if (result instanceof ConfigDataLocation[] unfilteredLocations) {
54-
return Arrays.stream(unfilteredLocations)
55-
.filter(Objects::nonNull)
56-
.map((element) -> withOrigin(context, element))
57-
.toArray(ConfigDataLocation[]::new);
53+
if (result instanceof ConfigDataLocation[] locations) {
54+
return Arrays.stream(locations).map(originMapper::mapIfPossible).toArray(ConfigDataLocation[]::new);
5855
}
5956
return result;
6057
}
6158

62-
private ConfigDataLocation withOrigin(BindContext context, ConfigDataLocation result) {
63-
if (result.getOrigin() != null) {
64-
return result;
59+
private record OriginMapper(@Nullable ConfigurationProperty property) {
60+
61+
@Nullable Object mapIfPossible(@Nullable Object object) {
62+
return (object instanceof ConfigDataLocation location) ? map(location) : object;
6563
}
66-
Origin origin = Origin.from(context.getConfigurationProperty());
67-
return result.withOrigin(origin);
64+
65+
@Nullable ConfigDataLocation map(@Nullable ConfigDataLocation location) {
66+
if (location == null) {
67+
return null;
68+
}
69+
Origin origin = Origin.from(location);
70+
return (origin != null) ? location : location.withOrigin(Origin.from(property()));
71+
}
72+
6873
}
6974

7075
}

core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ class ConfigDataProperties {
5353
* @param activate the activate properties
5454
*/
5555
ConfigDataProperties(@Nullable @Name("import") List<ConfigDataLocation> imports, @Nullable Activate activate) {
56-
this.imports = (imports != null) ? imports : Collections.emptyList();
56+
this.imports = (imports != null) ? imports.stream().filter(ConfigDataLocation::isNotEmpty).toList()
57+
: Collections.emptyList();
5758
this.activate = activate;
5859
}
5960

core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationBindHandlerTests.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,24 @@ void bindToArrayFromCommaStringPropertySetsOrigin() {
5656
}
5757

5858
@Test
59-
void bindToArrayFromCommaStringPropertyIgnoresEmptyElements() {
59+
void bindToArrayFromCommaStringPropertyDoesNotFailOnEmptyElements() {
6060
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
6161
source.put("locations", ",a,,b,c,");
6262
Binder binder = new Binder(source);
6363
ConfigDataLocation[] bound = binder.bind("locations", ARRAY, this.handler).get();
6464
String expectedLocation = "\"locations\" from property source \"source\"";
65-
assertThat(bound[0]).hasToString("a");
65+
assertThat(bound[0]).hasToString("");
6666
assertThat(bound[0].getOrigin()).hasToString(expectedLocation);
67-
assertThat(bound[1]).hasToString("b");
67+
assertThat(bound[1]).hasToString("a");
6868
assertThat(bound[1].getOrigin()).hasToString(expectedLocation);
69-
assertThat(bound[2]).hasToString("c");
69+
assertThat(bound[2]).hasToString("");
7070
assertThat(bound[2].getOrigin()).hasToString(expectedLocation);
71+
assertThat(bound[3]).hasToString("b");
72+
assertThat(bound[3].getOrigin()).hasToString(expectedLocation);
73+
assertThat(bound[4]).hasToString("c");
74+
assertThat(bound[4].getOrigin()).hasToString(expectedLocation);
75+
assertThat(bound[5]).hasToString("");
76+
assertThat(bound[5].getOrigin()).hasToString(expectedLocation);
7177
}
7278

7379
@Test
@@ -102,18 +108,24 @@ void bindToValueObjectFromCommaStringPropertySetsOrigin() {
102108
}
103109

104110
@Test
105-
void bindToValueObjectFromCommaStringPropertyIgnoresEmptyElements() {
111+
void bindToValueObjectFromCommaStringPropertyDoesNotFailOnEmptyElements() {
106112
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
107113
source.put("test.locations", ",a,b,,c,");
108114
Binder binder = new Binder(source);
109115
ValueObject bound = binder.bind("test", VALUE_OBJECT, this.handler).get();
110116
String expectedLocation = "\"test.locations\" from property source \"source\"";
111-
assertThat(bound.getLocation(0)).hasToString("a");
117+
assertThat(bound.getLocation(0)).hasToString("");
112118
assertThat(bound.getLocation(0).getOrigin()).hasToString(expectedLocation);
113-
assertThat(bound.getLocation(1)).hasToString("b");
119+
assertThat(bound.getLocation(1)).hasToString("a");
114120
assertThat(bound.getLocation(1).getOrigin()).hasToString(expectedLocation);
115-
assertThat(bound.getLocation(2)).hasToString("c");
121+
assertThat(bound.getLocation(2)).hasToString("b");
116122
assertThat(bound.getLocation(2).getOrigin()).hasToString(expectedLocation);
123+
assertThat(bound.getLocation(3)).hasToString("");
124+
assertThat(bound.getLocation(3).getOrigin()).hasToString(expectedLocation);
125+
assertThat(bound.getLocation(4)).hasToString("c");
126+
assertThat(bound.getLocation(4).getOrigin()).hasToString(expectedLocation);
127+
assertThat(bound.getLocation(5)).hasToString("");
128+
assertThat(bound.getLocation(5).getOrigin()).hasToString(expectedLocation);
117129
}
118130

119131
@Test

core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationTests.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,18 +115,21 @@ void withOriginSetsOrigin() {
115115
}
116116

117117
@Test
118-
void ofWhenNullValueReturnsNull() {
119-
assertThat(ConfigDataLocation.of(null)).isNull();
118+
void ofWhenNullValueReturnsEmptyLocation() {
119+
ConfigDataLocation location = ConfigDataLocation.of(null);
120+
assertThat(location.getValue().isEmpty()).isTrue();
120121
}
121122

122123
@Test
123-
void ofWhenEmptyValueReturnsNull() {
124-
assertThat(ConfigDataLocation.of("")).isNull();
124+
void ofWhenEmptyValueReturnsEmptyLocation() {
125+
ConfigDataLocation location = ConfigDataLocation.of("");
126+
assertThat(location.getValue().isEmpty()).isTrue();
125127
}
126128

127129
@Test
128-
void ofWhenEmptyOptionalValueReturnsNull() {
129-
assertThat(ConfigDataLocation.of("optional:")).isNull();
130+
void ofWhenEmptyOptionalValueReturnsEmptyLocation() {
131+
ConfigDataLocation location = ConfigDataLocation.of("optional:");
132+
assertThat(location.getValue().isEmpty()).isTrue();
130133
}
131134

132135
@Test

0 commit comments

Comments
 (0)