Skip to content

Commit

Permalink
#1001 Implement object- and list based property access
Browse files Browse the repository at this point in the history
  • Loading branch information
GuusLieben committed Dec 21, 2024
1 parent 879a058 commit e1dc83b
Show file tree
Hide file tree
Showing 26 changed files with 531 additions and 244 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -310,19 +310,19 @@ public <T extends Observer> Set<T> observers(Class<T> type) {
throw new IllegalArgumentException("type cannot be null");
}

Set<T> observers = new HashSet<>();
Set<T> allObservers = new HashSet<>();
this.observers.stream()
.filter(type::isInstance)
.map(type::cast)
.forEach(observers::add);
.forEach(allObservers::add);

this.lazyObservers.stream()
.filter(type::isAssignableFrom)
.map(this.applicationContext::get)
.map(type::cast)
.forEach(observers::add);
.forEach(allObservers::add);

return observers;
return allObservers;
}

private void printBanner(Class<?> mainClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

public class EnvironmentPropertyRegistryFactory {

public PropertyRegistry createRegistry(List<PropertySourceResolver> propertySourceResolvers, ResourceLookup resourceLookup) {
public PropertyRegistry createRegistry(Collection<PropertySourceResolver> propertySourceResolvers, ResourceLookup resourceLookup) {
Set<PropertyRegistryLoader> propertyRegistryLoaders = this.resolveRegistryLoaders();
PropertyRegistryLoader propertyRegistryLoader = this.createRegistryLoader(propertyRegistryLoaders);
PropertyRegistryFactory propertyRegistryFactory = new InstantLoadingPropertyRegistryFactory(propertyRegistryLoader);
Expand All @@ -35,7 +35,7 @@ public PropertyRegistry createRegistry(List<PropertySourceResolver> propertySour
}
}

private Set<URI> resolveResources(List<PropertySourceResolver> propertySourceResolvers, ResourceLookup resourceLookup) {
private Set<URI> resolveResources(Collection<PropertySourceResolver> propertySourceResolvers, ResourceLookup resourceLookup) {
Set<String> sources = propertySourceResolvers.stream()
.flatMap(resolver -> resolver.resolve().stream())
.collect(Collectors.toSet());
Expand All @@ -55,7 +55,7 @@ private Set<PropertyRegistryLoader> resolveRegistryLoaders() {
return propertyRegistryLoaders;
}

private PropertyRegistryLoader createRegistryLoader(Set<PropertyRegistryLoader> propertyRegistryLoaders) {
private PropertyRegistryLoader createRegistryLoader(Collection<PropertyRegistryLoader> propertyRegistryLoaders) {
PropertyRegistryLoader propertyRegistryLoader;
if (propertyRegistryLoaders.size() == 1) {
propertyRegistryLoader = CollectionUtilities.first(propertyRegistryLoaders);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import org.dockbox.hartshorn.properties.PropertyRegistry;
import org.dockbox.hartshorn.properties.loader.PropertyRegistryLoader;
import org.dockbox.hartshorn.properties.value.StandardPropertyParsers;
import org.dockbox.hartshorn.properties.value.StandardValuePropertyParsers;
import org.dockbox.hartshorn.util.ApplicationRuntimeException;

public class ConfigurationProfileRegistryFactory implements ProfileRegistryFactory {
Expand All @@ -36,7 +36,7 @@ public ProfileRegistry create(PropertyRegistry rootRegistry) {
profileRegistry.register(0, defaultProfile);

// TODO: Complex parser using predicate matching
List<EnvironmentProfile> additionalProfiles = rootRegistry.value("hartshorn.profiles", StandardPropertyParsers.STRING_LIST)
List<EnvironmentProfile> additionalProfiles = rootRegistry.value("hartshorn.profiles", StandardValuePropertyParsers.STRING_LIST)
.map(this::profiles)
.orElseGet(List::of);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package test.org.dockbox.hartshorn.profiles;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.util.Set;

import org.dockbox.hartshorn.profiles.ConfigurationProfileRegistryFactory;
import org.dockbox.hartshorn.profiles.ProfileRegistryFactory;
import org.dockbox.hartshorn.properties.MapPropertyRegistry;
import org.dockbox.hartshorn.properties.PropertyRegistry;
import org.dockbox.hartshorn.properties.loader.support.CompositePredicatePropertyRegistryLoader;
import org.dockbox.hartshorn.properties.loader.support.JacksonJavaPropsPropertyRegistryLoader;
import org.dockbox.hartshorn.properties.loader.support.JacksonYamlPropertyRegistryLoader;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;

public class ProfileLoaderTests {

@Test
Expand All @@ -22,13 +23,13 @@ void testLoadProfilesInOrder() throws IOException {
propertyRegistryLoader.addLoader(new JacksonYamlPropertyRegistryLoader());
propertyRegistryLoader.addLoader(new JacksonJavaPropsPropertyRegistryLoader());

var profileRegistryFactory = new ConfigurationProfileRegistryFactory(propertyRegistryLoader, name -> {
ProfileRegistryFactory profileRegistryFactory = new ConfigurationProfileRegistryFactory(propertyRegistryLoader, name -> {
return Set.of(
new File("C:\\Projects\\Temp\\Hartshorn\\hartshorn-profiles\\src\\test\\resources\\application-%s.yml".formatted(name)).toURI()
);
}, MapPropertyRegistry::new);

var rootRegistry = new MapPropertyRegistry();
PropertyRegistry rootRegistry = new MapPropertyRegistry();
propertyRegistryLoader.loadRegistry(rootRegistry, Path.of("C:\\Projects\\Temp\\Hartshorn\\hartshorn-profiles\\src\\test\\resources\\application.yml"));
var profileRegistry = profileRegistryFactory.create(rootRegistry);
var profilesInOrder = profileRegistry.profiles();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.dockbox.hartshorn.properties;

import org.dockbox.hartshorn.properties.list.SimpleListProperty;
import org.dockbox.hartshorn.properties.parse.support.ValueConfiguredPropertyParser;
import org.dockbox.hartshorn.properties.value.SimpleValueProperty;
import org.dockbox.hartshorn.properties.value.StandardValuePropertyParsers;
import org.dockbox.hartshorn.util.option.Option;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collectors;

public abstract class AbstractMapProperty<T> {

private final Map<String, ConfiguredProperty> properties;
private final String name;

protected AbstractMapProperty(String name, Map<String, ConfiguredProperty> properties) {
this.properties = new TreeMap<>(properties);
this.name = name;
}

protected Map<String, ConfiguredProperty> properties() {
return this.properties;
}

public String name() {
return this.name;
}

public Option<ValueProperty> get(T key) {
return Option.of(this.properties.get(valueAccessor(key)))
.flatMap(ValueConfiguredPropertyParser.INSTANCE::parse);
}

protected abstract String valueAccessor(T key);

public Option<ObjectProperty> object(T key) {
Map<String, ConfiguredProperty> propertyMap = this.collectToMap(key, (name, property) -> {
return name.startsWith(this.accessor(key) + ".");
}).entrySet().stream().collect(Collectors.toMap(
// Strip trailing . from key
entry -> entry.getKey().substring(1),
Map.Entry::getValue
));
ObjectProperty property = new MapObjectProperty(this.name() + this.accessor(key), propertyMap);
return Option.of(property);
}

protected abstract String accessor(T key);

public Option<ListProperty> list(T key) {
return this.list(key, value -> {
Option<String[]> values = StandardValuePropertyParsers.STRING_LIST.parse(value);
List<String> valueList = values.stream().flatMap(Arrays::stream).toList();
List<Property> listProperties = new ArrayList<>();
for (int i = 0; i < valueList.size(); i++) {
listProperties.add(i, new SimpleValueProperty(this.name() + this.accessor(key) + "[" + i + "]", valueList.get(i)));
}
return new SimpleListProperty(this.name() + this.accessor(key), listProperties);
});
}

public Option<ListProperty> list(T key, Function<ValueProperty, ListProperty> singleValueMapper) {
if (this.contains(key)) {
return this.get(key).map(singleValueMapper);
} else {
Map<String, ConfiguredProperty> propertyMap = this.collectToMap(key, (name, property) -> {
return name.startsWith(key + "[");
});
ListProperty property = new MapListProperty(this.name() + this.accessor(key), propertyMap);
return Option.of(property);
}
}

public boolean contains(T key) {
if (this.properties().containsKey(this.valueAccessor(key))) {
return true;
} else {
String accessor = this.accessor(key);
return this.properties().keySet().stream().anyMatch(propertyKey ->
propertyKey.startsWith(accessor + ".") || propertyKey.startsWith(accessor + "[")
);
}
}

public List<ConfiguredProperty> find(BiPredicate<String, ConfiguredProperty> predicate) {
return this.properties().entrySet().stream()
.filter(entry -> predicate.test(entry.getKey(), entry.getValue()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}

private Map<String, ConfiguredProperty> collectToMap(T key, BiPredicate<String, ConfiguredProperty> predicate) {
return this.find(predicate).stream()
.collect(Collectors.toMap(
property -> this.key(key, property),
Function.identity()
));
}

protected abstract String key(T prefix, ConfiguredProperty property);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package org.dockbox.hartshorn.properties;

import java.util.Collection;
import java.util.List;

import org.dockbox.hartshorn.properties.list.ListPropertyParser;
import org.dockbox.hartshorn.util.option.Option;

import java.util.Collection;

public non-sealed interface ListProperty extends Property {

List<Property> elements();
int size();

Option<ValueProperty> get(int index);

Option<ObjectProperty> object(int index);

Option<ListProperty> list(int index);

<T> Collection<T> parse(ListPropertyParser<T> parser);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.dockbox.hartshorn.properties;

import org.dockbox.hartshorn.properties.list.ListPropertyParser;
import org.dockbox.hartshorn.util.option.Option;

import java.util.Collection;
import java.util.Map;

public class MapListProperty extends AbstractMapProperty<Integer> implements ListProperty {

public MapListProperty(String name, Map<String, ConfiguredProperty> properties) {
super(name, properties);
}

@Override
protected String valueAccessor(Integer key) {
return "[" + key + "]";
}

@Override
protected String accessor(Integer key) {
return "[" + key + "]";
}

@Override
protected String key(Integer prefix, ConfiguredProperty property) {
return property.name().substring(this.name().length() + this.accessor(prefix).length());
}

@Override
public int size() {
return this.properties().keySet().stream()
.map(key -> key.split("\\[")[1].split("]")[0])
.map(Integer::parseInt)
.max(Integer::compareTo)
.map(i -> i + 1) // Add one to get the size, as the max index is zero-based
.orElse(0);
}

@Override
public Option<ValueProperty> get(int index) {
return this.get(Integer.valueOf(index));
}

@Override
public Option<ObjectProperty> object(int index) {
return this.object(Integer.valueOf(index));
}

@Override
public Option<ListProperty> list(int index) {
return this.list(Integer.valueOf(index));
}

@Override
public <T> Collection<T> parse(ListPropertyParser<T> parser) {
return parser.parse(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.dockbox.hartshorn.properties;

import org.dockbox.hartshorn.properties.object.ObjectPropertyParser;
import org.dockbox.hartshorn.util.option.Option;

import java.util.List;
import java.util.Map;

public class MapObjectProperty extends AbstractMapProperty<String> implements ObjectProperty {

public MapObjectProperty(String name, Map<String, ConfiguredProperty> properties) {
super(name, properties);
}

@Override
protected String valueAccessor(String key) {
return key;
}

@Override
protected String accessor(String key) {
return this.name().isBlank()
? key
: "." + key;
}

@Override
protected String key(String prefix, ConfiguredProperty property) {
// Remove prefix from name
return property.name().substring(this.name().length() + this.accessor(prefix).length());
}

@Override
public List<String> keys() {
return this.properties().keySet().stream()
.map(key -> key.split("\\.")[0])
.distinct()
.toList();
}

@Override
public <T> Option<T> parse(ObjectPropertyParser<T> parser) {
return parser.parse(this);
}
}
Loading

0 comments on commit e1dc83b

Please sign in to comment.