Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* @param <T> the type being bound
* @author Phillip Webb
* @author Madhura Bhave
* @author Tommy Karlsson
*/
abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {

Expand Down Expand Up @@ -129,10 +130,9 @@ private void bindIndexed(ConfigurationPropertySource source, ConfigurationProper
private Set<String> getKnownIndexedChildren(IterableConfigurationPropertySource source,
ConfigurationPropertyName root) {
Set<String> knownIndexedChildren = new HashSet<>();
for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
ConfigurationPropertyName choppedName = name.chop(root.getNumberOfElements() + 1);
if (choppedName.isLastElementIndexed()) {
knownIndexedChildren.add(choppedName.getLastElement(Form.UNIFORM));
for (ConfigurationPropertyName child : source.getChildrenOf(root)) {
if (child.isLastElementIndexed()) {
knownIndexedChildren.add(child.getLastElement(Form.UNIFORM));
}
}
return knownIndexedChildren;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.springframework.boot.context.properties.source;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

Expand All @@ -34,6 +36,7 @@
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Tommy Karlsson
* @since 2.0.0
* @see ConfigurationPropertyName
* @see OriginTrackedValue
Expand Down Expand Up @@ -66,6 +69,31 @@ default ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyNam
return ConfigurationPropertyState.search(this, name::isAncestorOf);
}

/**
* Return the names directly beneath the given name. For example, if this source
* contains {@code foo.bar} and {@code foo.baz[0]}, the children of {@code foo} are
* {@code foo.bar} and {@code foo.baz}.
* <p>
* A returned name is not necessarily a name that this source has a value for. Given
* only {@code foo.bar.baz}, the children of {@code foo} are {@code foo.bar}, for
* which {@link #getConfigurationProperty(ConfigurationPropertyName)} returns
* {@code null}.
* <p>
* Implementations that can answer without inspecting every name they contain should
* override this method, since callers may ask about many names in turn.
* @param name the name whose children should be returned
* @return the child names (never {@code null})
* @since 4.2.0
*/
default Set<ConfigurationPropertyName> getChildrenOf(ConfigurationPropertyName name) {
Set<ConfigurationPropertyName> children = new LinkedHashSet<>();
int childElements = name.getNumberOfElements() + 1;
for (ConfigurationPropertyName candidate : this.filter(name::isAncestorOf)) {
children.add(candidate.chop(childElements));
}
return children;
}

@Override
default IterableConfigurationPropertySource filter(Predicate<ConfigurationPropertyName> filter) {
return new FilteredIterableConfigurationPropertiesSource(this, filter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
Expand Down Expand Up @@ -52,6 +53,7 @@
*
* @author Phillip Webb
* @author Madhura Bhave
* @author Tommy Karlsson
* @see PropertyMapper
*/
class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource
Expand Down Expand Up @@ -136,21 +138,20 @@ public Iterator<ConfigurationPropertyName> iterator() {
return new ConfigurationPropertyNamesIterator(getConfigurationPropertyNames());
}

@Override
public Set<ConfigurationPropertyName> getChildrenOf(ConfigurationPropertyName name) {
return getCache().getChildren(name);
}

@Override
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
ConfigurationPropertyState result = super.containsDescendantOf(name);
if (result != ConfigurationPropertyState.UNKNOWN) {
return result;
}
if (this.ancestorOfCheck == PropertyMapper.DEFAULT_ANCESTOR_OF_CHECK) {
Set<ConfigurationPropertyName> descendants = getCache().getDescendants();
if (descendants != null) {
if (name.isEmpty() && !descendants.isEmpty()) {
return ConfigurationPropertyState.PRESENT;
}
return !descendants.contains(name) ? ConfigurationPropertyState.ABSENT
: ConfigurationPropertyState.PRESENT;
}
return getCache().hasChildren(name) ? ConfigurationPropertyState.PRESENT
: ConfigurationPropertyState.ABSENT;
}
result = (this.containsDescendantOfCache != null) ? this.containsDescendantOfCache.get(name) : null;
if (result == null) {
Expand Down Expand Up @@ -191,8 +192,7 @@ private Cache getCache() {

private Cache createCache() {
boolean immutable = isImmutablePropertySource();
boolean captureDescendants = this.ancestorOfCheck == PropertyMapper.DEFAULT_ANCESTOR_OF_CHECK;
return new Cache(getMappers(), immutable, captureDescendants, isSystemEnvironmentSource());
return new Cache(getMappers(), immutable, isSystemEnvironmentSource());
}

private Cache updateCache(Cache cache) {
Expand Down Expand Up @@ -224,17 +224,13 @@ private static class Cache {

private final boolean immutable;

private final boolean captureDescendants;

private final boolean systemEnvironmentSource;

private volatile @Nullable Data data;

Cache(PropertyMapper[] mappers, boolean immutable, boolean captureDescendants,
boolean systemEnvironmentSource) {
Cache(PropertyMapper[] mappers, boolean immutable, boolean systemEnvironmentSource) {
this.mappers = mappers;
this.immutable = immutable;
this.captureDescendants = captureDescendants;
this.systemEnvironmentSource = systemEnvironmentSource;
}

Expand Down Expand Up @@ -267,7 +263,6 @@ private void tryUpdate(EnumerablePropertySource<?> propertySource) {
(data != null) ? data.mappings() : null, size);
Map<String, ConfigurationPropertyName> reverseMappings = cloneOrCreate(
(data != null) ? data.reverseMappings() : null, size);
Set<ConfigurationPropertyName> descendants = (!this.captureDescendants) ? null : new HashSet<>();
Map<String, Object> systemEnvironmentCopy = (!this.systemEnvironmentSource) ? null
: copySource(propertySource);
for (PropertyMapper propertyMapper : this.mappers) {
Expand All @@ -281,14 +276,15 @@ private void tryUpdate(EnumerablePropertySource<?> propertySource) {
}
}
}
Map<ConfigurationPropertyName, Set<ConfigurationPropertyName>> children = new HashMap<>(size);
for (String propertyName : propertyNames) {
addParents(descendants, reverseMappings.get(propertyName));
addChildren(children, reverseMappings.get(propertyName));
}
ConfigurationPropertyName[] configurationPropertyNames = this.immutable
? reverseMappings.values().toArray(new ConfigurationPropertyName[0]) : null;
lastUpdated = this.immutable ? null : propertyNames;
this.data = new Data(mappings, reverseMappings, descendants, configurationPropertyNames,
systemEnvironmentCopy, lastUpdated);
this.data = new Data(mappings, reverseMappings, children, configurationPropertyNames, systemEnvironmentCopy,
lastUpdated);
}

@SuppressWarnings("unchecked")
Expand All @@ -300,16 +296,33 @@ private <K, V> Map<K, V> cloneOrCreate(@Nullable Map<K, V> source, int size) {
return (source != null) ? new LinkedHashMap<>(source) : new LinkedHashMap<>(size);
}

private void addParents(@Nullable Set<ConfigurationPropertyName> descendants,
/**
* Record the given name against its parent, and each of its ancestors against
* theirs. The walk stops as soon as a parent is already known, since whichever
* name first recorded it walked the rest of the way up.
* @param children the children to update
* @param name the name to record
*/
private void addChildren(Map<ConfigurationPropertyName, Set<ConfigurationPropertyName>> children,
@Nullable ConfigurationPropertyName name) {
if (descendants == null || name == null || name.isEmpty()) {
if (name == null || name.isEmpty()) {
return;
}
ConfigurationPropertyName child = name;
ConfigurationPropertyName parent = name.getParent();
while (!parent.isEmpty()) {
if (!descendants.add(parent)) {
while (true) {
Set<ConfigurationPropertyName> known = children.get(parent);
if (known != null) {
known.add(child);
return;
}
Set<ConfigurationPropertyName> added = new LinkedHashSet<>();
added.add(child);
children.put(parent, added);
if (parent.isEmpty()) {
return;
}
child = parent;
parent = parent.getParent();
}
}
Expand Down Expand Up @@ -342,10 +355,10 @@ Set<String> getMapped(ConfigurationPropertyName configurationPropertyName) {
return names;
}

@Nullable Set<ConfigurationPropertyName> getDescendants() {
boolean hasChildren(ConfigurationPropertyName name) {
Data data = this.data;
Assert.state(data != null, "'data' must not be null");
return data.descendants();
return data.children().containsKey(name);
}

@Nullable Object getSystemEnvironmentProperty(String name) {
Expand All @@ -356,9 +369,15 @@ Set<String> getMapped(ConfigurationPropertyName configurationPropertyName) {
return systemEnvironmentCopy.get(name);
}

Set<ConfigurationPropertyName> getChildren(ConfigurationPropertyName name) {
Data data = this.data;
Assert.state(data != null, "'data' must not be null");
return data.children().getOrDefault(name, Collections.emptySet());
}

private record Data(Map<ConfigurationPropertyName, Set<String>> mappings,
Map<String, ConfigurationPropertyName> reverseMappings,
@Nullable Set<ConfigurationPropertyName> descendants,
Map<ConfigurationPropertyName, Set<ConfigurationPropertyName>> children,
ConfigurationPropertyName @Nullable [] configurationPropertyNames,
@Nullable Map<String, Object> systemEnvironmentCopy, String @Nullable [] lastUpdated) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
* @author Phillip Webb
* @author Madhura Bhave
* @author Fahim Farook
* @author Tommy Karlsson
*/
class SpringIterableConfigurationPropertySourceTests {

Expand Down Expand Up @@ -143,6 +144,82 @@ void getValueWhenOriginCapableShouldIncludeSourceOrigin() {
assertThat(configurationProperty.getOrigin()).hasToString("TestOrigin key");
}

@Test
void getChildrenOfShouldReturnDirectChildren() {
Map<String, Object> source = new LinkedHashMap<>();
source.put("foo.bar", "value");
source.put("foo.baz[0]", "value");
source.put("foo.baz[1]", "value");
source.put("foo.qux.deep", "value");
source.put("faf", "value");
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
new OriginCapablePropertySource<>(new MapPropertySource("test", source)), false,
DefaultPropertyMapper.INSTANCE);
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("foo"))).containsExactlyInAnyOrder(
ConfigurationPropertyName.of("foo.bar"), ConfigurationPropertyName.of("foo.baz"),
ConfigurationPropertyName.of("foo.qux"));
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("foo.baz"))).containsExactlyInAnyOrder(
ConfigurationPropertyName.of("foo.baz[0]"), ConfigurationPropertyName.of("foo.baz[1]"));
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("faf"))).isEmpty();
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("missing"))).isEmpty();
}

@Test
void getChildrenOfShouldMatchDefaultImplementation() {
Map<String, Object> source = new LinkedHashMap<>();
source.put("foo.bar-baz[0].name", "value");
source.put("foo.bar-baz[1].name", "value");
source.put("foo.bazBar", "value");
source.put("foo.other", "value");
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
new OriginCapablePropertySource<>(new MapPropertySource("test", source)), false,
DefaultPropertyMapper.INSTANCE);
for (String name : new String[] { "", "foo", "foo.bar-baz", "foo.barbaz", "foo.bar-baz[0]", "foo.bazbar" }) {
ConfigurationPropertyName propertyName = ConfigurationPropertyName.of(name);
assertThat(adapter.getChildrenOf(propertyName)).as("children of '%s'", name)
.isEqualTo(defaultGetChildrenOf(adapter, propertyName));
}
}

private Set<ConfigurationPropertyName> defaultGetChildrenOf(IterableConfigurationPropertySource source,
ConfigurationPropertyName name) {
Set<ConfigurationPropertyName> children = new LinkedHashSet<>();
int childElements = name.getNumberOfElements() + 1;
for (ConfigurationPropertyName candidate : source.filter(name::isAncestorOf)) {
children.add(candidate.chop(childElements));
}
return children;
}

@Test
void getChildrenOfShouldCombineChildrenOfEquivalentNames() {
Map<String, Object> source = new LinkedHashMap<>();
source.put("foo-bar.baz", "x");
source.put("fooBar.zoo", "y");
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
new MapPropertySource("test", source), false, DefaultPropertyMapper.INSTANCE);
Set<ConfigurationPropertyName> expected = Set.of(ConfigurationPropertyName.of("foo-bar.baz"),
ConfigurationPropertyName.of("foobar.zoo"));
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("foo-bar"))).isEqualTo(expected);
assertThat(adapter.getChildrenOf(ConfigurationPropertyName.of("foobar"))).isEqualTo(expected);
}

@Test
void getChildrenOfShouldMatchDefaultImplementationForSystemEnvironmentSource() {
Map<String, Object> source = new LinkedHashMap<>();
source.put("FOO_BARBAZ_BONG", "bing");
source.put("FOO_BAR_BAZ", "value");
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, source);
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, true, SystemEnvironmentPropertyMapper.INSTANCE);
for (String name : new String[] { "", "foo", "foo.bar-baz", "foo.barbaz" }) {
ConfigurationPropertyName propertyName = ConfigurationPropertyName.of(name);
assertThat(adapter.getChildrenOf(propertyName)).as("children of '%s'", name)
.isEqualTo(defaultGetChildrenOf(adapter, propertyName));
}
}

@Test
void containsDescendantOfShouldCheckSourceNames() {
Map<String, Object> source = new LinkedHashMap<>();
Expand Down
Loading