diff --git a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java index 5ecdb33a3..ac3b776b6 100644 --- a/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java +++ b/common/src/main/java/io/smallrye/config/common/utils/StringUtil.java @@ -268,7 +268,7 @@ public static String unquoted(final String name, final int begin, final int end) throw new StringIndexOutOfBoundsException("begin " + begin + ", end " + end + ", length " + name.length()); } - if (name.length() < 2) { + if (name.length() < 2 || name.length() <= begin) { return name; } @@ -279,6 +279,16 @@ public static String unquoted(final String name, final int begin, final int end) } } + public static int index(final String name) { + if (name.charAt(name.length() - 1) == ']') { + int start = name.lastIndexOf('['); + if (start != -1 && isNumeric(name, start + 1, name.length() - 1)) { + return Integer.parseInt(name.substring(start + 1, name.length() - 1)); + } + } + throw new IllegalArgumentException(); + } + public static String unindexed(final String name) { if (name.length() < 3) { return name; @@ -294,17 +304,31 @@ public static String unindexed(final String name) { return name; } + public static boolean isIndexed(final String name) { + if (name.length() < 3) { + return false; + } + + if (name.charAt(name.length() - 1) == ']') { + int begin = name.lastIndexOf('['); + return begin != -1 && isNumeric(name, begin + 1, name.length() - 1); + } + + return false; + } + public static String skewer(String camelHumps) { return skewer(camelHumps, '-'); } public static String skewer(String camelHumps, char separator) { - int end = camelHumps.length(); - StringBuilder b = new StringBuilder(); if (camelHumps.isEmpty()) { - throw new IllegalArgumentException("Method seems to have an empty name"); + return camelHumps; } + int end = camelHumps.length(); + StringBuilder b = new StringBuilder(); + for (int i = 0; i < end; i++) { char c = camelHumps.charAt(i); if (Character.isLowerCase(c)) { @@ -335,6 +359,8 @@ public static String skewer(String camelHumps, char separator) { i = j; } else if (Character.isDigit(c)) { b.append(c); + } else if (c == '.' || c == '*' || c == '[' || c == ']') { + b.append(c); } else { if (i > 0) { char last = camelHumps.charAt(i - 1); diff --git a/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java b/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java index cf1dc9fe6..20b064cc5 100644 --- a/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java +++ b/common/src/test/java/io/smallrye/config/common/utils/StringUtilTest.java @@ -17,7 +17,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -100,8 +99,8 @@ void escapingSituations() { void skewer() { assertEquals("sigusr1", StringUtil.skewer("sigusr1")); - assertThrows(IllegalArgumentException.class, () -> StringUtil.skewer("")); - assertThrows(IllegalArgumentException.class, () -> StringUtil.skewer("", '.')); + assertEquals("", StringUtil.skewer("")); + assertEquals(".", StringUtil.skewer(".")); assertEquals("my-property", StringUtil.skewer("myProperty")); assertEquals("my.property", StringUtil.skewer("myProperty", '.')); @@ -138,6 +137,11 @@ void skewer() { assertEquals("trend-breaker", StringUtil.skewer("TrendBreaker")); assertEquals("making-life-difficult", StringUtil.skewer("MAKING_LifeDifficult")); assertEquals("making-life-difficult", StringUtil.skewer("makingLifeDifficult")); + + assertEquals("foo.bar", StringUtil.skewer("foo.bar")); + assertEquals("foo.bar-baz", StringUtil.skewer("foo.barBaz")); + assertEquals("foo.bar-baz[0]", StringUtil.skewer("foo.barBaz[0]")); + assertEquals("foo.bar-baz[*]", StringUtil.skewer("foo.barBaz[*]")); } @Test @@ -186,10 +190,16 @@ void unquoted() { assertEquals("", StringUtil.unquoted("\"\"")); assertEquals("a", StringUtil.unquoted("a")); assertEquals("unquoted", StringUtil.unquoted("\"unquoted\"")); + assertEquals("my.\"unquoted\"", StringUtil.unquoted("my.\"unquoted\"")); assertEquals("unquoted", StringUtil.unquoted("my.\"unquoted\"", 3, 13)); assertEquals("unquoted", StringUtil.unquoted("my.unquoted", 3, 11)); } + @Test + void index() { + assertEquals(0, StringUtil.index("foo[0]")); + } + @Test void unIndexed() { assertEquals("", StringUtil.unindexed("")); diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java index 55cc43c87..9805f4802 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java @@ -8,6 +8,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.function.Function; + +import io.smallrye.config.common.utils.StringUtil; /** * This annotation may be placed in interfaces to group configuration properties with a common prefix. @@ -50,14 +53,28 @@ enum NamingStrategy { /** * The method name is used as is to map the configuration property. */ - VERBATIM, + VERBATIM(name -> name), /** * The method name is derived by replacing case changes with a dash to map the configuration property. */ - KEBAB_CASE, + KEBAB_CASE(name -> { + return StringUtil.skewer(name, '-'); + }), /** * The method name is derived by replacing case changes with an underscore to map the configuration property. */ - SNAKE_CASE + SNAKE_CASE(name -> { + return StringUtil.skewer(name, '_'); + }); + + private final Function function; + + private NamingStrategy(Function function) { + this.function = function; + } + + public String apply(final String name) { + return function.apply(name); + } } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java index 8d293c6b4..213986952 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java @@ -1,80 +1,93 @@ package io.smallrye.config; import static io.smallrye.config.ConfigValidationException.Problem; -import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; +import static io.smallrye.config.common.utils.StringUtil.unindexed; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; +import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.ConfigMappingInterface.NamingStrategy; +import io.smallrye.config.ConfigMapping.NamingStrategy; import io.smallrye.config._private.ConfigMessages; -import io.smallrye.config.common.utils.StringUtil; /** * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration * mapping has completed. */ public final class ConfigMappingContext { - private final Map, Map>> enclosedThings = new IdentityHashMap<>(); + private final SmallRyeConfig config; private final Map, Map> roots = new IdentityHashMap<>(); private final Map, Converter> converterInstances = new IdentityHashMap<>(); - private final List allInstances = new ArrayList<>(); - private final SmallRyeConfig config; + + private NamingStrategy namingStrategy; private final StringBuilder stringBuilder = new StringBuilder(); - private final Set unknownProperties = new HashSet<>(); + private final Set usedProperties = new HashSet<>(); private final List problems = new ArrayList<>(); - private NamingStrategy namingStrategy = new ConfigMappingInterface.KebabNamingStrategy(); - ConfigMappingContext(final SmallRyeConfig config) { - this.config = config; - } + private final ConfigMappingNames names; - public ConfigMappingObject getRoot(Class rootType, String rootPath) { - return roots.getOrDefault(rootType, Collections.emptyMap()).get(rootPath); - } + public ConfigMappingContext( + final SmallRyeConfig config, + final Map>> roots, + final Map>> names) { - public void registerRoot(Class rootType, String rootPath, ConfigMappingObject root) { - roots.computeIfAbsent(rootType, x -> new HashMap<>()).put(rootPath, root); - } + this.config = config; + this.names = new ConfigMappingNames(names); - public Object getEnclosedField(Class enclosingType, String key, Object enclosingObject) { - return enclosedThings - .getOrDefault(enclosingType, Collections.emptyMap()) - .getOrDefault(key, Collections.emptyMap()) - .get(enclosingObject); + for (Map.Entry>> entry : roots.entrySet()) { + String path = entry.getKey(); + for (Class root : entry.getValue()) { + registerRoot(root, path); + } + } } - public void registerEnclosedField(Class enclosingType, String key, Object enclosingObject, Object value) { - enclosedThings - .computeIfAbsent(enclosingType, x -> new HashMap<>()) - .computeIfAbsent(key, x -> new IdentityHashMap<>()) - .put(enclosingObject, value); + private void registerRoot(Class rootType, String rootPath) { + roots.computeIfAbsent(rootType, new Function, Map>() { + @Override + public Map apply(final Class mapping) { + return new HashMap<>(); + } + }).computeIfAbsent(rootPath, new Function() { + @Override + public ConfigMappingObject apply(final String path) { + namingStrategy = null; + stringBuilder.replace(0, stringBuilder.length(), rootPath); + return (ConfigMappingObject) constructRoot(rootType); + } + }); } public T constructRoot(Class interfaceType) { - this.namingStrategy = ConfigMappingInterface.getConfigurationInterface(interfaceType).getNamingStrategy(); return constructGroup(interfaceType); } public T constructGroup(Class interfaceType) { - final T mappingObject = ConfigMappingLoader.configMappingObject(interfaceType, this); - allInstances.add((ConfigMappingObject) mappingObject); + NamingStrategy namingStrategy = this.namingStrategy; + T mappingObject = ConfigMappingLoader.configMappingObject(interfaceType, this); + this.namingStrategy = applyNamingStrategy(namingStrategy); return mappingObject; } + @SuppressWarnings("unused") + public ObjectCreator constructObject(String path) { + return new ObjectCreator<>(path); + } + @SuppressWarnings("unchecked") public Converter getConverterInstance(Class> converterType) { return (Converter) converterInstances.computeIfAbsent(converterType, t -> { @@ -98,89 +111,551 @@ public Converter getConverterInstance(Class getProblems() { + return problems; } - public static Map createMapWithDefault(final V defaultValue) { - return new MapWithDefault<>(defaultValue); + Map, Map> getRootsMap() { + return roots; } - public static IntFunction> createCollectionFactory(final Class type) { - if (type == List.class) { - return ArrayList::new; + void reportUnknown(final List ignoredPaths) { + KeyMap ignoredProperties = new KeyMap<>(); + for (String[] ignoredPath : ignoredPaths) { + int len = ignoredPath.length; + KeyMap found; + if (ignoredPath[len - 1].equals("**")) { + found = ignoredProperties.findOrAdd(ignoredPath, 0, len - 1); + found.putRootValue(Boolean.TRUE); + ignoreRecursively(found); + } else { + found = ignoredProperties.findOrAdd(ignoredPath); + found.putRootValue(Boolean.TRUE); + } } - if (type == Set.class) { - return HashSet::new; + Set roots = new HashSet<>(); + for (Map value : this.roots.values()) { + roots.addAll(value.keySet()); } - throw new IllegalArgumentException(); - } + for (String name : filterPropertiesInRoots(config.getPropertyNames(), roots)) { + if (usedProperties.contains(name)) { + continue; + } - public NoSuchElementException noSuchElement(Class type) { - return new NoSuchElementException("A required configuration group of type " + type.getName() + " was not provided"); + if (!ignoredProperties.hasRootValue(name)) { + ConfigValue configValue = config.getConfigValue(name); + problems.add(new Problem( + ConfigMessages.msg.propertyDoesNotMapToAnyRoot(name, configValue.getLocation()))); + } + } } - void unknownProperty(final String unknownProperty) { - unknownProperties.add(unknownProperty); + private static void ignoreRecursively(KeyMap root) { + if (root.getRootValue() == null) { + root.putRootValue(Boolean.TRUE); + } + + if (root.getAny() == null) { + //noinspection CollectionAddedToSelf + root.putAny(root); + } else { + var any = root.getAny(); + if (root != any) { + ignoreRecursively(any); + } + } + + for (var value : root.values()) { + ignoreRecursively(value); + } } - void reportUnknown() { - // an unknown property may still be used if it was coming from the EnvSource - for (String unknownProperty : unknownProperties) { - boolean found = false; - String unknownEnvProperty = replaceNonAlphanumericByUnderscores(unknownProperty); - for (String userProperty : config.getPropertyNames()) { - if (unknownProperty.equals(userProperty)) { - continue; - } + /** + * Filters the full list of properties names in Config to only the property names that can match any of the + * prefixes (namespaces) registered in mappings. + * + * @param properties the available property names in Config. + * @param roots the registered mapping roots. + * + * @return the property names that match to at least one root. + */ + private static Iterable filterPropertiesInRoots(final Iterable properties, final Set roots) { + if (roots.isEmpty()) { + return properties; + } + + // Will match everything, so no point in filtering + if (roots.contains("")) { + return properties; + } - // Match another property with the same semantic meaning - if (StringUtil.equalsIgnoreCaseReplacingNonAlphanumericByUnderscores(unknownEnvProperty, userProperty)) { - found = true; + List matchedProperties = new ArrayList<>(); + for (String property : properties) { + for (String root : roots) { + if (isPropertyInRoot(property, root)) { + matchedProperties.add(property); break; } } - - if (!found) { - ConfigValue configValue = config.getConfigValue(unknownProperty); - problems.add(new Problem( - ConfigMessages.msg.propertyDoesNotMapToAnyRoot(unknownProperty, configValue.getLocation()))); - } } + return matchedProperties; } - void fillInOptionals() { - for (ConfigMappingObject instance : allInstances) { - instance.fillInOptionals(this); + private static boolean isPropertyInRoot(final String property, final String root) { + if (property.equals(root)) { + return true; } - } - public SmallRyeConfig getConfig() { - return config; - } + // if property is less than the root no way to match + if (property.length() <= root.length()) { + return false; + } - public StringBuilder getStringBuilder() { - return stringBuilder; - } + // foo.bar + // foo.bar."baz" + // foo.bar[0] + char c = property.charAt(root.length()); + if ((c == '.') || c == '[') { + return property.startsWith(root); + } - public void reportProblem(RuntimeException problem) { - problems.add(new Problem(problem.toString())); + return false; } - List getProblems() { - return problems; - } + @SuppressWarnings("unchecked") + public class ObjectCreator { + private T root; + private List>> creators; + + public ObjectCreator(final String path) { + this.creators = List.of(new Consumer>() { + @Override + public void accept(Function get) { + root = (T) get.apply(path); + } + }); + } - Map, Map> getRootsMap() { - return roots; + public ObjectCreator map( + final Class keyRawType, + final Class> keyConvertWith) { + return map(keyRawType, keyConvertWith, null); + } + + public ObjectCreator map( + final Class keyRawType, + final Class> keyConvertWith, + final String unnamedKey) { + return map(keyRawType, keyConvertWith, unnamedKey, (Class) null); + } + + public ObjectCreator map( + final Class keyRawType, + final Class> keyConvertWith, + final String unnamedKey, + final Class defaultClass) { + + Supplier supplier = null; + if (defaultClass != null) { + supplier = new Supplier() { + @Override + public V get() { + int length = stringBuilder.length(); + stringBuilder.append(".*"); + V defaultValue = constructGroup(defaultClass); + stringBuilder.setLength(length); + return defaultValue; + } + }; + } + + return map(keyRawType, keyConvertWith, unnamedKey, supplier); + } + + public ObjectCreator map( + final Class keyRawType, + final Class> keyConvertWith, + final String unnamedKey, + final Supplier defaultValue) { + Converter keyConverter = keyConvertWith == null ? config.requireConverter(keyRawType) + : getConverterInstance(keyConvertWith); + List>> nestedCreators = new ArrayList<>(); + for (Consumer> creator : this.creators) { + creator.accept(new Function() { + @Override + public Object apply(final String path) { + Map mapKeys = getMapKeys(path.length() > 0 && path.charAt(path.length() - 1) == '.' + ? path.substring(0, path.length() - 1) + : path); + Map map = defaultValue != null ? new MapWithDefault<>(defaultValue.get()) + : new HashMap<>(mapKeys.size()); + for (Map.Entry entry : mapKeys.entrySet()) { + nestedCreators.add(new Consumer<>() { + @Override + public void accept(Function get) { + V value = (V) get.apply(entry.getValue()); + if (value != null) { + map.putIfAbsent(keyConverter.convert(entry.getKey()), value); + } + } + }); + } + + if (unnamedKey != null) { + nestedCreators.add(new Consumer<>() { + @Override + public void accept(Function get) { + V value = (V) get.apply(path); + if (value != null) { + map.putIfAbsent(unnamedKey.equals("") ? null : keyConverter.convert(unnamedKey), value); + } + } + }); + } + + return map; + } + }); + } + this.creators = nestedCreators; + return this; + } + + public > ObjectCreator collection( + final Class collectionRawType) { + List>> nestedCreators = new ArrayList<>(); + IntFunction> collectionFactory = createCollectionFactory(collectionRawType); + for (Consumer> creator : this.creators) { + Collection collection = (Collection) collectionFactory.apply(0); + creator.accept(new Function() { + @Override + public Object apply(final String path) { + // This is ordered, so it shouldn't require a set by index + for (Integer index : config.getIndexedPropertiesIndexes(path)) { + nestedCreators.add(new Consumer>() { + @Override + public void accept(final Function get) { + collection.add((V) get.apply(path + "[" + index + "]")); + } + }); + } + return collection; + } + }); + } + this.creators = nestedCreators; + return this; + } + + public > ObjectCreator optionalCollection( + final Class collectionRawType) { + List>> nestedCreators = new ArrayList<>(); + IntFunction> collectionFactory = createCollectionFactory(collectionRawType); + for (Consumer> creator : this.creators) { + Collection collection = (Collection) collectionFactory.apply(0); + creator.accept(new Function() { + @Override + public Object apply(final String path) { + // This is ordered, so it shouldn't require a set by index + List indexes = config.getIndexedPropertiesIndexes(path); + for (Integer index : indexes) { + nestedCreators.add(new Consumer>() { + @Override + public void accept(final Function get) { + collection.add((V) get.apply(path + "[" + index + "]")); + } + }); + } + return indexes.isEmpty() ? Optional.empty() : Optional.of(collection); + } + }); + } + this.creators = nestedCreators; + return this; + } + + public ObjectCreator group(final Class groupType) { + for (Consumer> creator : this.creators) { + creator.accept(new Function() { + @Override + public G apply(final String path) { + StringBuilder sb = ConfigMappingContext.this.getStringBuilder(); + int length = sb.length(); + sb.append(path, length, path.length()); + G group = constructGroup(groupType); + sb.setLength(length); + return group; + } + }); + } + return this; + } + + public ObjectCreator lazyGroup(final Class groupType) { + for (Consumer> creator : this.creators) { + creator.accept(new Function() { + @Override + public G apply(final String path) { + if (createRequired(groupType, path)) { + StringBuilder sb = ConfigMappingContext.this.getStringBuilder(); + int length = sb.length(); + sb.append(path, length, path.length()); + G group = constructGroup(groupType); + sb.setLength(length); + return group; + } else { + return null; + } + } + }); + } + return this; + } + + public ObjectCreator optionalGroup(final Class groupType) { + for (Consumer> creator : this.creators) { + creator.accept(new Function() { + @Override + public Optional apply(final String path) { + if (createRequired(groupType, path)) { + StringBuilder sb = ConfigMappingContext.this.getStringBuilder(); + int length = sb.length(); + sb.append(path, length, path.length()); + G group = constructGroup(groupType); + sb.setLength(length); + return Optional.of(group); + } else { + return Optional.empty(); + } + } + }); + } + return this; + } + + public ObjectCreator value( + final Class valueRawType, + final Class> valueConvertWith) { + for (Consumer> creator : creators) { + creator.accept(new Function() { + @Override + public T apply(final String propertyName) { + usedProperties.add(propertyName); + Converter valueConverter = getConverter(valueRawType, valueConvertWith); + return config.getValue(propertyName, valueConverter); + } + }); + } + return this; + } + + public ObjectCreator optionalValue( + final Class valueRawType, + final Class> valueConvertWith) { + for (Consumer> creator : creators) { + creator.accept(new Function() { + @Override + public Optional apply(final String propertyName) { + usedProperties.add(propertyName); + Converter valueConverter = getConverter(valueRawType, valueConvertWith); + return config.getOptionalValue(propertyName, valueConverter); + } + }); + } + return this; + } + + public > ObjectCreator values( + final Class itemRawType, + final Class> itemConvertWith, + final Class collectionRawType) { + for (Consumer> creator : creators) { + creator.accept(new Function() { + @Override + public T apply(final String propertyName) { + usedProperties.add(propertyName); + usedProperties.addAll(config.getIndexedProperties(propertyName)); + Converter itemConverter = itemConvertWith == null ? config.requireConverter(itemRawType) + : getConverterInstance(itemConvertWith); + IntFunction collectionFactory = (IntFunction) createCollectionFactory(collectionRawType); + return (T) config.getValues(propertyName, itemConverter, collectionFactory); + } + }); + } + return this; + } + + public > ObjectCreator optionalValues( + final Class itemRawType, + final Class> itemConvertWith, + final Class collectionRawType) { + for (Consumer> creator : creators) { + creator.accept(new Function() { + @Override + public T apply(final String propertyName) { + usedProperties.add(propertyName); + usedProperties.addAll(config.getIndexedProperties(propertyName)); + Converter itemConverter = getConverter(itemRawType, itemConvertWith); + IntFunction collectionFactory = (IntFunction) createCollectionFactory(collectionRawType); + return (T) config.getOptionalValues(propertyName, itemConverter, collectionFactory); + } + }); + } + return this; + } + + public ObjectCreator values( + final Class keyRawType, + final Class> keyConvertWith, + final Class valueRawType, + final Class> valueConvertWith, + final String defaultValue) { + for (Consumer> creator : creators) { + Function values = new Function<>() { + @Override + public Object apply(final String propertyName) { + usedProperties.add(propertyName); + usedProperties.addAll(config.getMapKeys(propertyName).values()); + Converter keyConverter = getConverter(keyRawType, keyConvertWith); + Converter valueConverter = getConverter(valueRawType, valueConvertWith); + + if (defaultValue == null) { + // TODO - We should use getValues here, but this makes the Map to be required. This is a breaking change + return config.getOptionalValues(propertyName, keyConverter, valueConverter, HashMap::new) + .orElse(new HashMap<>()); + } else { + IntFunction> mapFactory = new IntFunction<>() { + @Override + public Map apply(final int value) { + return new MapWithDefault<>(valueConverter.convert(defaultValue)); + } + }; + return config.getOptionalValues(propertyName, keyConverter, valueConverter, mapFactory) + .orElse(mapFactory.apply(0)); + } + } + }; + creator.accept(values); + } + return this; + } + + public > ObjectCreator values( + final Class keyRawType, + final Class> keyConvertWith, + final Class valueRawType, + final Class> valueConvertWith, + final Class collectionRawType, + final String defaultValue) { + for (Consumer> creator : creators) { + Function values = new Function<>() { + @Override + public Object apply(final String propertyName) { + usedProperties.add(propertyName); + for (String mapKey : config.getMapKeys(propertyName).values()) { + usedProperties.add(mapKey); + usedProperties.addAll(config.getIndexedProperties(mapKey)); + } + Converter keyConverter = getConverter(keyRawType, keyConvertWith); + Converter valueConverter = getConverter(valueRawType, valueConvertWith); + + IntFunction collectionFactory = (IntFunction) createCollectionFactory(collectionRawType); + + if (defaultValue == null) { + // TODO - We should use getValues here, but this makes the Map to be required. This is a breaking change + return config.getOptionalValues(propertyName, keyConverter, valueConverter, HashMap::new, + collectionFactory).orElse(new HashMap<>()); + } else { + IntFunction> mapFactory = new IntFunction<>() { + @Override + public Map apply(final int value) { + return new MapWithDefault<>( + Converters.newCollectionConverter(valueConverter, collectionFactory) + .convert(defaultValue)); + } + }; + + return config.getOptionalValues(propertyName, keyConverter, valueConverter, mapFactory, + collectionFactory).orElse(mapFactory.apply(0)); + } + } + }; + creator.accept(values); + } + return this; + } + + public T get() { + return root; + } + + private Converter getConverter(final Class rawType, final Class> convertWith) { + return convertWith == null ? config.requireConverter(rawType) : getConverterInstance(convertWith); + } + + private Map getMapKeys(final String name) { + Map mapKeys = new HashMap<>(); + for (String propertyName : config.getPropertyNames()) { + if (propertyName.length() > name.length() + 1 + && (name.length() == 0 || propertyName.charAt(name.length()) == '.') + && propertyName.startsWith(name)) { + // Start at the map root name + NameIterator key = name.length() > 0 ? new NameIterator(unindexed(propertyName), name.length()) + : new NameIterator(unindexed(propertyName)); + // Move to the next key + key.next(); + // Record key and map root name plus key + mapKeys.put(unindexed(key.getPreviousSegment()), unindexed(propertyName.substring(0, key.getPosition()))); + } + } + return mapKeys; + } + + private boolean createRequired(final Class groupType, final String path) { + Set names = ConfigMappingContext.this.names.get(groupType.getName(), path); + if (names == null) { + return false; + } + + for (String propertyName : config.getPropertyNames()) { + if (propertyName.startsWith(path) && names.contains(new PropertyName(propertyName))) { + return true; + } + } + return false; + } + + private IntFunction> createCollectionFactory(final Class type) { + if (type == List.class) { + return ArrayList::new; + } + + if (type == Set.class) { + return HashSet::new; + } + + throw new IllegalArgumentException(); + } } static class MapWithDefault extends HashMap { diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java index ce0ae444e..4a3b787c3 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java @@ -1,19 +1,22 @@ package io.smallrye.config; import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; +import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_INTERFACE; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACONST_NULL; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARETURN; import static org.objectweb.asm.Opcodes.ASTORE; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.GETSTATIC; import static org.objectweb.asm.Opcodes.GOTO; -import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.I2C; import static org.objectweb.asm.Opcodes.ICONST_1; -import static org.objectweb.asm.Opcodes.IFNE; -import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.IFEQ; import static org.objectweb.asm.Opcodes.ILOAD; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; @@ -24,6 +27,7 @@ import static org.objectweb.asm.Opcodes.POP; import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.RETURN; +import static org.objectweb.asm.Opcodes.SWAP; import static org.objectweb.asm.Opcodes.V1_8; import static org.objectweb.asm.Type.getDescriptor; import static org.objectweb.asm.Type.getInternalName; @@ -35,20 +39,12 @@ import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.function.IntFunction; import java.util.regex.Pattern; import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.eclipse.microprofile.config.spi.Converter; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -58,9 +54,11 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import io.smallrye.config.ConfigMapping.NamingStrategy; import io.smallrye.config.ConfigMappingInterface.CollectionProperty; import io.smallrye.config.ConfigMappingInterface.LeafProperty; import io.smallrye.config.ConfigMappingInterface.MapProperty; +import io.smallrye.config.ConfigMappingInterface.MayBeOptionalProperty; import io.smallrye.config.ConfigMappingInterface.PrimitiveProperty; import io.smallrye.config.ConfigMappingInterface.Property; @@ -77,27 +75,22 @@ public class ConfigMappingGenerator { } private static final String I_CLASS = getInternalName(Class.class); - private static final String I_COLLECTIONS = getInternalName(Collections.class); private static final String I_CONFIGURATION_OBJECT = getInternalName(ConfigMappingObject.class); - private static final String I_CONVERTER = getInternalName(Converter.class); - private static final String I_MAP = getInternalName(Map.class); - private static final String I_COLLECTION = getInternalName(Collection.class); - private static final String I_LIST = getInternalName(List.class); - private static final String I_INT_FUNCTION = getInternalName(IntFunction.class); private static final String I_MAPPING_CONTEXT = getInternalName(ConfigMappingContext.class); + private static final String I_OBJECT_CREATOR = getInternalName(ConfigMappingContext.ObjectCreator.class); private static final String I_OBJECT = getInternalName(Object.class); - private static final String I_OPTIONAL = getInternalName(Optional.class); private static final String I_RUNTIME_EXCEPTION = getInternalName(RuntimeException.class); - private static final String I_SMALLRYE_CONFIG = getInternalName(SmallRyeConfig.class); private static final String I_STRING_BUILDER = getInternalName(StringBuilder.class); - private static final String I_INTEGER = getInternalName(Integer.class); private static final String I_STRING = getInternalName(String.class); + private static final String I_NAMING_STRATEGY = getInternalName(NamingStrategy.class); + private static final String I_SET = getInternalName(Set.class); private static final String I_FIELD = getInternalName(Field.class); private static final int V_THIS = 0; private static final int V_MAPPING_CONTEXT = 1; private static final int V_STRING_BUILDER = 2; private static final int V_LENGTH = 3; + private static final int V_NAMING_STRATEGY = 4; /** * Generates the backing implementation of an interface annotated with the {@link ConfigMapping} annotation. @@ -109,7 +102,7 @@ static byte[] generate(final ConfigMappingInterface mapping) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = usefulDebugInfo ? new Debugging.ClassVisitorImpl(writer) : writer; - visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, mapping.getClassInternalName(), null, I_OBJECT, + visitor.visit(V1_8, ACC_PUBLIC, mapping.getClassInternalName(), null, I_OBJECT, new String[] { I_CONFIGURATION_OBJECT, getInternalName(mapping.getInterfaceType()) @@ -117,71 +110,68 @@ static byte[] generate(final ConfigMappingInterface mapping) { visitor.visitSource(null, null); // No Args Constructor - To use for proxies - MethodVisitor noArgsCtor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); - noArgsCtor.visitVarInsn(Opcodes.ALOAD, V_THIS); - noArgsCtor.visitMethodInsn(Opcodes.INVOKESPECIAL, I_OBJECT, "", "()V", false); + MethodVisitor noArgsCtor = visitor.visitMethod(ACC_PUBLIC, "", "()V", null, null); + noArgsCtor.visitVarInsn(ALOAD, V_THIS); + noArgsCtor.visitMethodInsn(INVOKESPECIAL, I_OBJECT, "", "()V", false); noArgsCtor.visitInsn(RETURN); noArgsCtor.visitEnd(); noArgsCtor.visitMaxs(0, 0); - MethodVisitor ctor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); - ctor.visitParameter("context", Opcodes.ACC_FINAL); + MethodVisitor ctor = visitor.visitMethod(ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); + ctor.visitParameter("context", ACC_FINAL); Label ctorStart = new Label(); Label ctorEnd = new Label(); ctor.visitLabel(ctorStart); // stack: - - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); + ctor.visitVarInsn(ALOAD, V_THIS); // stack: this - ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, I_OBJECT, "", "()V", false); + ctor.visitMethodInsn(INVOKESPECIAL, I_OBJECT, "", "()V", false); // stack: - - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); + ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); // stack: ctxt - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getStringBuilder", "()L" + I_STRING_BUILDER + ';', + ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getStringBuilder", "()L" + I_STRING_BUILDER + ';', false); // stack: sb - ctor.visitInsn(Opcodes.DUP); + ctor.visitInsn(DUP); // stack: sb sb Label ctorSbStart = new Label(); ctor.visitLabel(ctorSbStart); - ctor.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); + ctor.visitVarInsn(ASTORE, V_STRING_BUILDER); // stack: sb - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "length", "()I", false); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "length", "()I", false); // stack: len Label ctorLenStart = new Label(); ctor.visitLabel(ctorLenStart); - ctor.visitVarInsn(Opcodes.ISTORE, V_LENGTH); - // stack: - - MethodVisitor fio = visitor.visitMethod(Opcodes.ACC_PUBLIC, "fillInOptionals", "(L" + I_MAPPING_CONTEXT + ";)V", null, - null); - fio.visitParameter("context", Opcodes.ACC_FINAL); - Label fioStart = new Label(); - Label fioEnd = new Label(); - fio.visitLabel(fioStart); - // stack: - - fio.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - // stack: ctxt - fio.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getStringBuilder", "()L" + I_STRING_BUILDER + ';', - false); - // stack: sb - fio.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); + ctor.visitVarInsn(ISTORE, V_LENGTH); + + Label ctorNsStart = new Label(); + ctor.visitLabel(ctorNsStart); + ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); + + if (mapping.hasNamingStrategy()) { + ctor.visitFieldInsn(GETSTATIC, I_NAMING_STRATEGY, mapping.getNamingStrategy().name(), + "L" + I_NAMING_STRATEGY + ";"); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "applyNamingStrategy", + "(L" + I_NAMING_STRATEGY + ";)L" + I_NAMING_STRATEGY + ";", false); + ctor.visitVarInsn(ASTORE, V_NAMING_STRATEGY); + // stack: - - addProperties(visitor, ctor, fio, new HashSet<>(), mapping, mapping.getClassInternalName()); + addProperties(visitor, ctor, new HashSet<>(), mapping, mapping.getClassInternalName()); // stack: - if (mapping.getToStringMethod().generate()) { addToString(visitor, mapping); } // stack: - - fio.visitInsn(Opcodes.RETURN); - fio.visitLabel(fioEnd); - fio.visitLocalVariable("mc", 'L' + I_MAPPING_CONTEXT + ';', null, fioStart, fioEnd, V_MAPPING_CONTEXT); - fio.visitEnd(); - fio.visitMaxs(0, 0); - // stack: - ctor.visitInsn(RETURN); ctor.visitLabel(ctorEnd); ctor.visitLocalVariable("mc", 'L' + I_MAPPING_CONTEXT + ';', null, ctorStart, ctorEnd, V_MAPPING_CONTEXT); ctor.visitLocalVariable("sb", 'L' + I_STRING_BUILDER + ';', null, ctorSbStart, ctorEnd, V_STRING_BUILDER); ctor.visitLocalVariable("len", "I", null, ctorLenStart, ctorEnd, V_LENGTH); + ctor.visitLocalVariable("ns", "Lio/smallrye/config/ConfigMapping$NamingStrategy;", null, ctorNsStart, ctorEnd, + V_NAMING_STRATEGY); ctor.visitEnd(); ctor.visitMaxs(0, 0); visitor.visitEnd(); @@ -218,8 +208,8 @@ static byte[] generate(final Class classType, final String interfaceName) { { AnnotationVisitor av = writer.visitAnnotation("L" + getInternalName(ConfigMapping.class) + ";", true); - av.visitEnum("namingStrategy", "L" + getInternalName(ConfigMapping.NamingStrategy.class) + ";", - ConfigMapping.NamingStrategy.VERBATIM.toString()); + av.visitEnum("namingStrategy", "L" + getInternalName(NamingStrategy.class) + ";", + NamingStrategy.VERBATIM.toString()); if (classType.isAnnotationPresent(ConfigProperties.class)) { av.visit("prefix", classType.getAnnotation(ConfigProperties.class).prefix()); @@ -379,7 +369,6 @@ static byte[] generate(final Class classType, final String interfaceName) { private static void addProperties( final ClassVisitor cv, final MethodVisitor ctor, - final MethodVisitor fio, final Set visited, final ConfigMappingInterface mapping, final String className) { @@ -387,587 +376,299 @@ private static void addProperties( for (Property property : mapping.getProperties()) { Method method = property.getMethod(); String memberName = method.getName(); + + // skip super members with overrides if (!visited.add(memberName)) { - // duplicated property continue; } - // the field + + // Field Declaration String fieldType = getInternalName(method.getReturnType()); String fieldDesc = getDescriptor(method.getReturnType()); - cv.visitField(Opcodes.ACC_PRIVATE, memberName, fieldDesc, null, null); + cv.visitField(ACC_PRIVATE, memberName, fieldDesc, null, null); - // now process the property - final Property realProperty; - final boolean optional = property.isOptional(); - if (optional) { - realProperty = property.asOptional().getNestedProperty(); - } else { - realProperty = property; + // Getter + MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, memberName, "()" + fieldDesc, null, null); + mv.visitVarInsn(ALOAD, V_THIS); + mv.visitFieldInsn(GETFIELD, className, memberName, fieldDesc); + mv.visitInsn(getReturnInstruction(property)); + mv.visitEnd(); + mv.visitMaxs(0, 0); + + if (property.isDefaultMethod()) { + continue; } - // now handle each possible type - if (property.isCollection() || realProperty.isCollection() && optional) { - CollectionProperty collectionProperty = realProperty.asCollection(); + // Constructor field init + Label _try = new Label(); + Label _catch = new Label(); + Label _continue = new Label(); + ctor.visitTryCatchBlock(_try, _catch, _catch, I_RUNTIME_EXCEPTION); + + appendPropertyName(ctor, property); + ctor.visitVarInsn(ALOAD, V_THIS); + ctor.visitTypeInsn(NEW, I_OBJECT_CREATOR); + ctor.visitInsn(DUP); + ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); + ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "toString", "()L" + I_STRING + ';', false); + ctor.visitMethodInsn(INVOKESPECIAL, I_OBJECT_CREATOR, "", "(L" + I_MAPPING_CONTEXT + ";L" + I_STRING + ";)V", + false); - ctor.visitVarInsn(ALOAD, V_THIS); - // append property name - boolean restoreLength = appendPropertyName(ctor, property); - - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConfig", "()L" + I_SMALLRYE_CONFIG + ';', false); - ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "toString", "()L" + I_STRING + ';', false); - // stack config key - - // For Both Group and Optional Group - if (realProperty.asCollection().getElement().isGroup() || realProperty.asCollection().getElement().isMap()) { - // get properties indexes - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getIndexedPropertiesIndexes", - "(L" + I_STRING + ";)L" + I_LIST + ';', false); - ctor.visitVarInsn(ASTORE, 4); - - // Retrieve Collection to init. - ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createCollectionFactory", - "(L" + I_CLASS + ";)L" + I_INT_FUNCTION + ';', false); - ctor.visitVarInsn(ALOAD, 4); - ctor.visitMethodInsn(INVOKEINTERFACE, I_LIST, "size", "()I", true); - ctor.visitMethodInsn(INVOKEINTERFACE, I_INT_FUNCTION, "apply", "(I)L" + I_OBJECT + ";", true); - // We do it in a separate var so we can either PUT directly or wrap it in Optional - ctor.visitVarInsn(ASTORE, 5); - - // TODO - Try to optimize loads / stores / debug - // Iterate - // i = 0 - ctor.visitInsn(ICONST_0); - ctor.visitVarInsn(ISTORE, 6); - // list size - ctor.visitVarInsn(ALOAD, 4); - ctor.visitMethodInsn(INVOKEINTERFACE, I_LIST, "size", "()I", true); - ctor.visitVarInsn(ISTORE, 7); - - Label iter = new Label(); - ctor.visitLabel(iter); - // i - ctor.visitVarInsn(ILOAD, 6); - // size - ctor.visitVarInsn(ILOAD, 7); - Label each = new Label(); - ctor.visitJumpInsn(IF_ICMPGE, each); - - // list to iterate - ctor.visitVarInsn(ALOAD, 4); - // i - ctor.visitVarInsn(ILOAD, 6); - // get property index - ctor.visitMethodInsn(INVOKEINTERFACE, I_LIST, "get", "(I)L" + I_OBJECT + ";", true); - ctor.visitTypeInsn(CHECKCAST, I_INTEGER); - ctor.visitVarInsn(ASTORE, 8); - - // current sb length - ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "length", "()I", false); - ctor.visitVarInsn(ISTORE, 9); - - // construct collection index - ctor.visitTypeInsn(NEW, I_STRING_BUILDER); - ctor.visitInsn(DUP); - ctor.visitMethodInsn(INVOKESPECIAL, I_STRING_BUILDER, "", "()V", false); - ctor.visitLdcInsn("["); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitVarInsn(ALOAD, 8); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_OBJECT + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitLdcInsn("]"); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "toString", "()L" + I_STRING + ";", - false); - ctor.visitVarInsn(ASTORE, 10); - - // append collection index - ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); - ctor.visitVarInsn(ALOAD, 10); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitInsn(POP); - - if (collectionProperty.getElement().isGroup()) { - // create group - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(collectionProperty.getElement().asGroup().getGroupType().getInterfaceType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "constructGroup", - "(L" + I_CLASS + ";)L" + I_OBJECT + ';', false); - ctor.visitVarInsn(ASTORE, 11); - } else if (collectionProperty.getElement().isMap()) { - // create empty map - ctor.visitTypeInsn(NEW, getInternalName(HashMap.class)); - ctor.visitInsn(DUP); - ctor.visitMethodInsn(INVOKESPECIAL, getInternalName(HashMap.class), "", "()V", false); - ctor.visitVarInsn(ASTORE, 11); - } + // try + ctor.visitLabel(_try); - // add to collection - ctor.visitVarInsn(ALOAD, 5); - ctor.visitTypeInsn(CHECKCAST, I_COLLECTION); - ctor.visitVarInsn(ALOAD, 11); - ctor.visitMethodInsn(INVOKEINTERFACE, I_COLLECTION, "add", "(L" + I_OBJECT + ";)Z", true); - ctor.visitInsn(POP); - - // register indexed enclosing element - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(Type.getType(mapping.getInterfaceType())); - ctor.visitTypeInsn(NEW, I_STRING_BUILDER); - ctor.visitInsn(DUP); - ctor.visitMethodInsn(INVOKESPECIAL, I_STRING_BUILDER, "", "()V", false); - ctor.visitLdcInsn(memberName); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitVarInsn(ALOAD, 10); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "toString", "()L" + I_STRING + ";", false); - ctor.visitVarInsn(ALOAD, V_THIS); - ctor.visitVarInsn(ALOAD, 11); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "registerEnclosedField", - "(L" + I_CLASS + ";L" + I_STRING + ";L" + I_OBJECT + ";L" + I_OBJECT + ";)V", - false); - - // reset sb without index - ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); - ctor.visitVarInsn(ILOAD, 9); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "setLength", "(I)V", false); - - // i ++ - ctor.visitIincInsn(6, 1); - ctor.visitJumpInsn(GOTO, iter); - ctor.visitLabel(each); - - // set field value - if (optional) { - ctor.visitVarInsn(ILOAD, 7); - Label optionalEmpty = new Label(); - // If indexed properties are empty, then we couldn't find any element, so Optional.empty. - ctor.visitJumpInsn(IFNE, optionalEmpty); - ctor.visitVarInsn(ALOAD, V_THIS); - ctor.visitMethodInsn(INVOKESTATIC, I_OPTIONAL, "empty", "()L" + I_OPTIONAL + ";", false); - ctor.visitFieldInsn(PUTFIELD, className, memberName, fieldDesc); - Label optionalOf = new Label(); - // Else wrap the Collection in Optional - ctor.visitJumpInsn(GOTO, optionalOf); - ctor.visitLabel(optionalEmpty); - ctor.visitVarInsn(ALOAD, V_THIS); - ctor.visitVarInsn(ALOAD, 5); - - ctor.visitMethodInsn(INVOKESTATIC, I_OPTIONAL, "of", "(L" + I_OBJECT + ";)L" + I_OPTIONAL + ';', false); - ctor.visitFieldInsn(PUTFIELD, className, memberName, fieldDesc); - ctor.visitLabel(optionalOf); - ctor.visitInsn(Opcodes.POP); - } else { - ctor.visitVarInsn(ALOAD, 5); - ctor.visitFieldInsn(PUTFIELD, className, memberName, fieldDesc); - } + generateProperty(ctor, property); - } else if (optional) { - LeafProperty collectionElementProperty = collectionProperty.getElement().asLeaf(); - if (collectionElementProperty.hasConvertWith()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(collectionElementProperty.getConvertWith())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConverterInstance", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ';', false); - ctor.visitLdcInsn(getType(realProperty.asCollection().getCollectionRawType())); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createCollectionFactory", - "(L" + I_CLASS + ";)L" + I_INT_FUNCTION + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getOptionalValues", - "(L" + I_STRING + ";L" + I_CONVERTER + ";L" + I_INT_FUNCTION + ";)L" + I_OPTIONAL + ';', false); - } else { - ctor.visitLdcInsn(getType(collectionElementProperty.getValueRawType())); - ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createCollectionFactory", - "(L" + I_CLASS + ";)L" + I_INT_FUNCTION + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getOptionalValues", - "(L" + I_STRING + ";L" + I_CLASS + ";L" + I_INT_FUNCTION + ";)L" + I_OPTIONAL + ';', false); - } - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "get", "()L" + I_OBJECT + ";", false); + if (property.isPrimitive()) { + PrimitiveProperty primitive = property.asPrimitive(); + ctor.visitTypeInsn(CHECKCAST, getInternalName(primitive.getBoxType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, getInternalName(primitive.getBoxType()), primitive.getUnboxMethodName(), + primitive.getUnboxMethodDescriptor(), false); + } else { + ctor.visitTypeInsn(CHECKCAST, fieldType); + } + ctor.visitFieldInsn(PUTFIELD, className, memberName, fieldDesc); + ctor.visitJumpInsn(GOTO, _continue); - if (restoreLength) { - restoreLength(ctor); - } - } else { - Label _try = new Label(); - Label _catch = new Label(); - Label _continue = new Label(); - ctor.visitLabel(_try); - - LeafProperty collectionElementProperty = collectionProperty.getElement().asLeaf(); - if (collectionElementProperty.hasConvertWith()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(collectionElementProperty.getConvertWith())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConverterInstance", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ';', false); - ctor.visitLdcInsn(getType(realProperty.asCollection().getCollectionRawType())); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createCollectionFactory", - "(L" + I_CLASS + ";)L" + I_INT_FUNCTION + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValues", - "(L" + I_STRING + ";L" + I_CONVERTER + ";L" + I_INT_FUNCTION + ";)L" + I_COLLECTION + ';', - false); - } else { - ctor.visitLdcInsn(getType(collectionElementProperty.getValueRawType())); - ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createCollectionFactory", - "(L" + I_CLASS + ";)L" + I_INT_FUNCTION + ";", false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValues", - "(L" + I_STRING + ";L" + I_CLASS + ";L" + I_INT_FUNCTION + ";)L" + I_COLLECTION + ';', false); - } - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - - ctor.visitJumpInsn(Opcodes.GOTO, _continue); - ctor.visitLabel(_catch); - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - ctor.visitInsn(Opcodes.SWAP); - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "reportProblem", - "(L" + I_RUNTIME_EXCEPTION + ";)V", false); - ctor.visitLabel(_continue); - if (restoreLength) { - restoreLength(ctor); - } - ctor.visitTryCatchBlock(_try, _catch, _catch, I_RUNTIME_EXCEPTION); - } + // catch + ctor.visitLabel(_catch); + ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); + ctor.visitInsn(SWAP); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "reportProblem", "(L" + I_RUNTIME_EXCEPTION + ";)V", false); + ctor.visitJumpInsn(GOTO, _continue); - // reset stringbuilder - if (restoreLength) { - restoreLength(ctor); - } + ctor.visitLabel(_continue); - } else if (property.isMap()) { - // stack: - - MapProperty mapProperty = property.asMap(); - if (mapProperty.getValueProperty().isGroup() && mapProperty.hasDefaultValue()) { - appendPropertyName(ctor, property); - ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); - ctor.visitLdcInsn(".*"); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ';', false); - ctor.visitInsn(POP); - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(mapProperty.getValueProperty().asGroup().getGroupType().getInterfaceType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "constructGroup", - "(L" + I_CLASS + ";)L" + I_OBJECT + ';', false); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createMapWithDefault", - "(L" + I_OBJECT + ";)L" + I_MAP + ";", false); - restoreLength(ctor); - } else if (mapProperty.getValueProperty().isLeaf() && mapProperty.hasDefaultValue()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConfig", "()L" + I_SMALLRYE_CONFIG + ';', false); - ctor.visitLdcInsn(getType(mapProperty.getValueProperty().asLeaf().getValueRawType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "requireConverter", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ";", false); - ctor.visitLdcInsn(mapProperty.getDefaultValue()); - ctor.visitMethodInsn(INVOKEINTERFACE, I_CONVERTER, "convert", "(L" + I_STRING + ";)L" + I_OBJECT + ";", - true); - ctor.visitMethodInsn(INVOKESTATIC, I_MAPPING_CONTEXT, "createMapWithDefault", - "(L" + I_OBJECT + ";)L" + I_MAP + ";", false); - } else { - ctor.visitTypeInsn(NEW, getInternalName(HashMap.class)); - ctor.visitInsn(DUP); - ctor.visitMethodInsn(INVOKESPECIAL, getInternalName(HashMap.class), "", "()V", false); - } + restoreLength(ctor); + } - // stack: map - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: map this - ctor.visitInsn(Opcodes.SWAP); - // stack: this map - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); + // We don't know the order in the constructor and the default method may require call to other + // properties that may not be initialized yet, so we add them last + for (Property property : mapping.getProperties()) { + Method method = property.getMethod(); + String memberName = method.getName(); + String fieldDesc = getDescriptor(method.getReturnType()); - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(Type.getType(mapping.getInterfaceType())); - ctor.visitLdcInsn(memberName); + if (property.isDefaultMethod()) { ctor.visitVarInsn(ALOAD, V_THIS); + Method defaultMethod = property.asDefaultMethod().getDefaultMethod(); ctor.visitVarInsn(ALOAD, V_THIS); - ctor.visitFieldInsn(GETFIELD, className, memberName, fieldDesc); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "registerEnclosedField", - "(L" + I_CLASS + ";L" + I_STRING + ";L" + I_OBJECT + ";L" + I_OBJECT + ";)V", - false); + ctor.visitMethodInsn(INVOKESTATIC, getInternalName(defaultMethod.getDeclaringClass()), defaultMethod.getName(), + "(" + getType(mapping.getInterfaceType()) + ")" + fieldDesc, false); + ctor.visitFieldInsn(PUTFIELD, className, memberName, fieldDesc); + } + } - // stack: - - // then sweep it up - // stack: - - fio.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - // stack: ctxt - fio.visitLdcInsn(getType(mapping.getInterfaceType())); - // stack: ctxt iface - fio.visitLdcInsn(memberName); - // stack: ctxt iface name - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: ctxt iface name this - fio.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getEnclosedField", - "(L" + I_CLASS + ";L" + I_STRING + ";L" + I_OBJECT + ";)L" + I_OBJECT + ';', false); - // stack: obj? - fio.visitInsn(Opcodes.DUP); - Label _continue = new Label(); - Label _done = new Label(); - // stack: obj? obj? - fio.visitJumpInsn(Opcodes.IFNULL, _continue); - // stack: obj - fio.visitTypeInsn(Opcodes.CHECKCAST, I_MAP); - // stack: map - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: map this - fio.visitInsn(Opcodes.SWAP); - // stack: this map - fio.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - // stack: - - fio.visitJumpInsn(Opcodes.GOTO, _done); - fio.visitLabel(_continue); - // stack: null - fio.visitInsn(Opcodes.POP); - // stack: - - fio.visitLabel(_done); - } else if (property.isGroup()) { - // stack: - - boolean restoreLength = appendPropertyName(ctor, property); - // stack: - - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - // stack: ctxt - ctor.visitLdcInsn(getType(realProperty.asGroup().getGroupType().getInterfaceType())); - // stack: ctxt clazz - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "constructGroup", - "(L" + I_CLASS + ";)L" + I_OBJECT + ';', false); - // stack: nested - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: nested this - ctor.visitInsn(Opcodes.SWAP); - // stack: this nested - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - // register the group - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(Type.getType(mapping.getInterfaceType())); - ctor.visitLdcInsn(memberName); - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - ctor.visitFieldInsn(GETFIELD, className, memberName, fieldDesc); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "registerEnclosedField", - "(L" + I_CLASS + ";L" + I_STRING + ";L" + I_OBJECT + ";L" + I_OBJECT + ";)V", - false); - // stack: - - if (restoreLength) { - restoreLength(ctor); + for (ConfigMappingInterface superType : mapping.getSuperTypes()) { + addProperties(cv, ctor, visited, superType, className); + } + } + + private static void generateProperty(final MethodVisitor ctor, final Property property) { + if (property.isLeaf() || property.isPrimitive() || property.isLeaf() && property.isOptional()) { + Class rawType = property.isLeaf() ? property.asLeaf().getValueRawType() : property.asPrimitive().getBoxType(); + ctor.visitLdcInsn(Type.getType(rawType)); + if (property.hasConvertWith() || property.isLeaf() && property.asLeaf().hasConvertWith()) { + ctor.visitLdcInsn(getType( + property.isLeaf() ? property.asLeaf().getConvertWith() : property.asPrimitive().getConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + if (property.isOptional()) { + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "optionalValue", + "(L" + I_CLASS + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } else { + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "value", + "(L" + I_CLASS + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } + } else if (property.isGroup()) { + ctor.visitLdcInsn(getType(property.asGroup().getGroupType().getInterfaceType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "group", "(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", + false); + } else if (property.isMap()) { + MapProperty mapProperty = property.asMap(); + Property valueProperty = mapProperty.getValueProperty(); + if (valueProperty.isLeaf()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyRawType())); + if (mapProperty.hasKeyConvertWith()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); } - } else if (property.isLeaf() || property.isPrimitive() || property.isOptional() && property.isLeaf()) { - // stack: - - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: this - boolean restoreLength = appendPropertyName(ctor, property); - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConfig", "()L" + I_SMALLRYE_CONFIG + ';', + LeafProperty leafProperty = valueProperty.asLeaf(); + ctor.visitLdcInsn(getType(leafProperty.getValueRawType())); + if (leafProperty.hasConvertWith()) { + ctor.visitLdcInsn(getType(leafProperty.getConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + if (mapProperty.hasDefaultValue() && mapProperty.getDefaultValue() != null) { + ctor.visitLdcInsn(mapProperty.getDefaultValue()); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS + + ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false); + } else if (valueProperty.isGroup()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyRawType())); + if (mapProperty.hasKeyConvertWith()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + if (mapProperty.hasKeyUnnamed()) { + ctor.visitLdcInsn(mapProperty.getKeyUnnamed()); + } else { + ctor.visitInsn(ACONST_NULL); + } + if (mapProperty.hasDefaultValue()) { + ctor.visitLdcInsn(getType(valueProperty.asGroup().getGroupType().getInterfaceType())); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "map", + "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); - // stack: this config - ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "toString", "()L" + I_STRING + ';', false); - // stack: this config key - Label _try = new Label(); - Label _catch = new Label(); - Label _continue = new Label(); - ctor.visitLabel(_try); - if (property.isOptional()) { - LeafProperty leafProperty = property.asOptional().getNestedProperty().asLeaf(); - if (leafProperty.hasConvertWith()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(leafProperty.getConvertWith())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConverterInstance", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ';', false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getOptionalValue", - "(L" + I_STRING + ";L" + I_CONVERTER + ";)L" + I_OPTIONAL + ';', false); - } else { - ctor.visitLdcInsn(getType(leafProperty.getValueRawType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getOptionalValue", - "(L" + I_STRING + ";L" + I_CLASS + ";)L" + I_OPTIONAL + ';', false); - } - ctor.visitTypeInsn(Opcodes.CHECKCAST, fieldType); - } else if (property.isLeaf()) { - LeafProperty leafProperty = property.asLeaf(); - if (leafProperty.hasConvertWith()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(leafProperty.getConvertWith())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConverterInstance", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ';', false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValue", - "(L" + I_STRING + ";L" + I_CONVERTER + ";)L" + I_OBJECT + ';', false); - } else { - ctor.visitLdcInsn(getType(leafProperty.getValueRawType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValue", - "(L" + I_STRING + ";L" + I_CLASS + ";)L" + I_OBJECT + ';', false); - } - ctor.visitTypeInsn(Opcodes.CHECKCAST, fieldType); - } else if (property.isPrimitive()) { - PrimitiveProperty primitiveProperty = property.asPrimitive(); - if (primitiveProperty.hasConvertWith()) { - ctor.visitVarInsn(ALOAD, V_MAPPING_CONTEXT); - ctor.visitLdcInsn(getType(primitiveProperty.getConvertWith())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConverterInstance", - "(L" + I_CLASS + ";)L" + I_CONVERTER + ';', false); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValue", - "(L" + I_STRING + ";L" + I_CONVERTER + ";)L" + I_OBJECT + ';', false); + ctor.visitLdcInsn(getType(valueProperty.asGroup().getGroupType().getInterfaceType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "lazyGroup", + "(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } else if (valueProperty.isCollection() && valueProperty.asCollection().getElement().isLeaf()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyRawType())); + if (mapProperty.hasKeyConvertWith()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + LeafProperty leafProperty = mapProperty.getValueProperty().asCollection().getElement().asLeaf(); + ctor.visitLdcInsn(getType(leafProperty.getValueRawType())); + if (leafProperty.hasConvertWith()) { + ctor.visitLdcInsn(getType(leafProperty.getConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitLdcInsn(getType(mapProperty.getValueProperty().asCollection().getCollectionRawType())); + if (mapProperty.hasDefaultValue()) { + ctor.visitLdcInsn(mapProperty.getDefaultValue()); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS + + ";L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false); + } else { + unwrapProperty(ctor, property); + } + } else if (property.isCollection()) { + CollectionProperty collectionProperty = property.asCollection(); + if (collectionProperty.getElement().isLeaf()) { + ctor.visitLdcInsn(getType(collectionProperty.getElement().asLeaf().getValueRawType())); + if (collectionProperty.getElement().hasConvertWith()) { + ctor.visitLdcInsn(getType(collectionProperty.getElement().asLeaf().getConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", + "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } else { + unwrapProperty(ctor, property); + } + } else if (property.isOptional()) { + final MayBeOptionalProperty nestedProperty = property.asOptional().getNestedProperty(); + if (nestedProperty.isGroup()) { + ctor.visitLdcInsn(getType(nestedProperty.asGroup().getGroupType().getInterfaceType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "optionalGroup", + "(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } else if (nestedProperty.isCollection()) { + CollectionProperty collectionProperty = nestedProperty.asCollection(); + if (collectionProperty.getElement().isLeaf()) { + ctor.visitLdcInsn(getType(collectionProperty.getElement().asLeaf().getValueRawType())); + if (collectionProperty.getElement().hasConvertWith()) { + ctor.visitLdcInsn(getType(collectionProperty.getElement().asLeaf().getConvertWith())); } else { - ctor.visitLdcInsn(getType(primitiveProperty.getBoxType())); - ctor.visitMethodInsn(INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValue", - "(L" + I_STRING + ";L" + I_CLASS + ";)L" + I_OBJECT + ';', false); + ctor.visitInsn(ACONST_NULL); } - String boxType = getInternalName(primitiveProperty.getBoxType()); - ctor.visitTypeInsn(Opcodes.CHECKCAST, boxType); - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxType, primitiveProperty.getUnboxMethodName(), - primitiveProperty.getUnboxMethodDescriptor(), false); - } - // stack: this value - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - // stack: - - ctor.visitJumpInsn(Opcodes.GOTO, _continue); - ctor.visitLabel(_catch); - // stack: exception - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - // stack: exception ctxt - ctor.visitInsn(Opcodes.SWAP); - // stack: ctxt exception - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "reportProblem", - "(L" + I_RUNTIME_EXCEPTION + ";)V", false); - // stack: - - ctor.visitLabel(_continue); - if (restoreLength) { - restoreLength(ctor); + ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "optionalValues", + "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + } else { + ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "optionalCollection", + "(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false); + generateProperty(ctor, collectionProperty.getElement()); } - // add the try/catch - ctor.visitTryCatchBlock(_try, _catch, _catch, I_RUNTIME_EXCEPTION); - } else if (property.isOptional()) { - // stack: - - ctor.visitMethodInsn(Opcodes.INVOKESTATIC, I_OPTIONAL, "empty", "()L" + I_OPTIONAL + ";", false); - // stack: empty - ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: empty this - ctor.visitInsn(Opcodes.SWAP); - // stack: this empty - ctor.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - - // also generate a sweep-up stub - // stack: - - fio.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - // stack: ctxt - fio.visitLdcInsn(getType(mapping.getInterfaceType())); - // stack: ctxt iface - fio.visitLdcInsn(memberName); - // stack: ctxt iface name - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: ctxt iface name this - fio.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getEnclosedField", - "(L" + I_CLASS + ";L" + I_STRING + ";L" + I_OBJECT + ";)L" + I_OBJECT + ';', false); - // stack: obj? - fio.visitInsn(Opcodes.DUP); - Label _continue = new Label(); - Label _done = new Label(); - // stack: obj? obj? - fio.visitJumpInsn(Opcodes.IFNULL, _continue); - // stack: obj - fio.visitMethodInsn(Opcodes.INVOKESTATIC, I_OPTIONAL, "of", "(L" + I_OBJECT + ";)L" + I_OPTIONAL + ';', false); - // stack: opt - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: opt this - fio.visitInsn(Opcodes.SWAP); - // stack: this opt - fio.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); - // stack: - - fio.visitJumpInsn(Opcodes.GOTO, _done); - fio.visitLabel(_continue); - // stack: null - fio.visitInsn(Opcodes.POP); - // stack: - - fio.visitLabel(_done); - } else if (property.isDefaultMethod()) { - // Call default methods in fillInOptionals. - // We don't know the order in the constructor and the default method may require call to other - // properties that may not be initialized yet. - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - Method defaultMethod = property.asDefaultMethod().getDefaultMethod(); - fio.visitVarInsn(Opcodes.ALOAD, V_THIS); - fio.visitMethodInsn(INVOKESTATIC, getInternalName(defaultMethod.getDeclaringClass()), defaultMethod.getName(), - "(" + getType(mapping.getInterfaceType()) + ")" + fieldDesc, false); - fio.visitFieldInsn(Opcodes.PUTFIELD, className, memberName, fieldDesc); + } else { + throw new UnsupportedOperationException(); } - - // the accessor method implementation - MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, memberName, "()" + fieldDesc, null, null); - // stack: - - mv.visitVarInsn(Opcodes.ALOAD, V_THIS); - // stack: this - mv.visitFieldInsn(GETFIELD, className, memberName, fieldDesc); - // stack: obj - mv.visitInsn(getReturnInstruction(property)); - - mv.visitEnd(); - mv.visitMaxs(0, 0); - // end loop + } else { + throw new UnsupportedOperationException(); } - // subtype overrides supertype - for (ConfigMappingInterface superType : mapping.getSuperTypes()) { - addProperties(cv, ctor, fio, visited, superType, className); + } + + private static void unwrapProperty(final MethodVisitor ctor, final Property property) { + if (property.isMap()) { + MapProperty mapProperty = property.asMap(); + ctor.visitLdcInsn(getType(mapProperty.getKeyRawType())); + if (mapProperty.hasKeyConvertWith()) { + ctor.visitLdcInsn(getType(mapProperty.getKeyConvertWith())); + } else { + ctor.visitInsn(ACONST_NULL); + } + if (mapProperty.hasKeyUnnamed()) { + ctor.visitLdcInsn(mapProperty.getKeyUnnamed()); + } else { + ctor.visitInsn(ACONST_NULL); + } + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "map", + "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false); + generateProperty(ctor, mapProperty.getValueProperty()); + } else if (property.isCollection()) { + CollectionProperty collectionProperty = property.asCollection(); + ctor.visitLdcInsn(getType(collectionProperty.getCollectionRawType())); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "collection", "(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", + false); + generateProperty(ctor, collectionProperty.getElement()); + } else { + throw new UnsupportedOperationException(); } } - private static boolean appendPropertyName(final MethodVisitor ctor, final Property property) { + private static void appendPropertyName(final MethodVisitor ctor, final Property property) { if (property.isParentPropertyName()) { - return false; + return; } - // stack: - - Label _continue = new Label(); - ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); - - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "length", "()I", false); - // if length != 0 (mean that a prefix exists and not the empty prefix) - ctor.visitJumpInsn(Opcodes.IFEQ, _continue); + Label _continue = new Label(); + ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "length", "()I", false); + ctor.visitJumpInsn(IFEQ, _continue); - ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); - // stack: sb + ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); ctor.visitLdcInsn('.'); - // stack: sb '.' - ctor.visitInsn(Opcodes.I2C); - // stack: sb '.' - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "append", "(C)L" + I_STRING_BUILDER + ';', false); - - ctor.visitInsn(Opcodes.POP); - + ctor.visitInsn(I2C); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", "(C)L" + I_STRING_BUILDER + ';', false); + ctor.visitInsn(POP); ctor.visitLabel(_continue); - ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); - + ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); if (property.hasPropertyName()) { ctor.visitLdcInsn(property.getPropertyName()); } else { - ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); - + ctor.visitVarInsn(ALOAD, V_NAMING_STRATEGY); ctor.visitLdcInsn(property.getPropertyName()); - - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, - "applyNamingStrategy", "(L" + I_STRING + ";)L" + I_STRING + ";", false); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_NAMING_STRATEGY, "apply", "(L" + I_STRING + ";)L" + I_STRING + ";", false); } - // stack: sb name - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "append", - "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ';', false); - // stack: sb - ctor.visitInsn(Opcodes.POP); - // stack: - - return true; + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "append", "(L" + I_STRING + ";)L" + I_STRING_BUILDER + ';', + false); + ctor.visitInsn(POP); } private static void restoreLength(final MethodVisitor ctor) { - // stack: - - ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); - // stack: sb - ctor.visitVarInsn(Opcodes.ILOAD, V_LENGTH); - // stack: sb length - ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "setLength", "(I)V", false); - // stack: - + ctor.visitVarInsn(ALOAD, V_STRING_BUILDER); + ctor.visitVarInsn(ILOAD, V_LENGTH); + ctor.visitMethodInsn(INVOKEVIRTUAL, I_STRING_BUILDER, "setLength", "(I)V", false); } private static int getReturnInstruction(Property property) { diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index 91c7a6c54..7275ca9e7 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -17,13 +17,12 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import org.eclipse.microprofile.config.spi.Converter; import io.smallrye.common.constraint.Assert; +import io.smallrye.config.ConfigMapping.NamingStrategy; import io.smallrye.config._private.ConfigMessages; -import io.smallrye.config.common.utils.StringUtil; /** * Information about a configuration interface. @@ -42,7 +41,6 @@ protected ConfigMappingInterface computeValue(final Class type) { private final ConfigMappingInterface[] superTypes; private final Property[] properties; private final Map propertiesByName; - private final NamingStrategy namingStrategy; private final ToStringMethod toStringMethod; ConfigMappingInterface(final Class interfaceType, final ConfigMappingInterface[] superTypes, @@ -68,7 +66,6 @@ protected ConfigMappingInterface computeValue(final Class type) { this.properties = filteredProperties.toArray(new Property[0]); this.propertiesByName = propertiesByName; - this.namingStrategy = getNamingStrategy(interfaceType); this.toStringMethod = toStringMethod; } @@ -152,8 +149,13 @@ Property getProperty(final String name) { return propertiesByName.get(name); } + public boolean hasNamingStrategy() { + return interfaceType.getAnnotation(ConfigMapping.class) != null; + } + public NamingStrategy getNamingStrategy() { - return namingStrategy; + ConfigMapping configMapping = interfaceType.getAnnotation(ConfigMapping.class); + return configMapping != null ? configMapping.namingStrategy() : NamingStrategy.KEBAB_CASE; } ToStringMethod getToStringMethod() { @@ -198,6 +200,10 @@ public String getPropertyName() { return hasPropertyName() && !propertyName.isEmpty() ? propertyName : method.getName(); } + public String getPropertyName(final NamingStrategy namingStrategy) { + return hasPropertyName() ? getPropertyName() : namingStrategy.apply(getPropertyName()); + } + public String getMemberName() { return method.getName(); } @@ -214,6 +220,14 @@ public boolean isParentPropertyName() { return hasPropertyName() && propertyName.isEmpty(); } + public boolean hasDefaultValue() { + return false; + } + + public String getDefaultValue() { + return null; + } + public boolean isPrimitive() { return false; } @@ -472,6 +486,21 @@ public boolean isLeaf() { return nestedProperty.isLeaf(); } + @Override + public LeafProperty asLeaf() { + return isLeaf() ? nestedProperty.asLeaf() : super.asLeaf(); + } + + @Override + public boolean hasDefaultValue() { + return isLeaf() && nestedProperty.asLeaf().hasDefaultValue(); + } + + @Override + public String getDefaultValue() { + return hasDefaultValue() ? nestedProperty.asLeaf().getDefaultValue() : null; + } + public MayBeOptionalProperty getNestedProperty() { return nestedProperty; } @@ -498,6 +527,14 @@ public boolean isGroup() { public GroupProperty asGroup() { return this; } + + public boolean hasNamingStrategy() { + return groupType.getInterfaceType().isAnnotationPresent(ConfigMapping.class); + } + + public NamingStrategy getNamingStrategy() { + return groupType.getNamingStrategy(); + } } public static final class LeafProperty extends MayBeOptionalProperty { @@ -586,7 +623,7 @@ public Class getKeyRawType() { } public String getKeyUnnamed() { - return "".equals(keyUnnamed) ? null : keyUnnamed; + return keyUnnamed; } public boolean hasKeyUnnamed() { @@ -1018,52 +1055,4 @@ static Class rawTypeOf(final Type type) { throw ConfigMessages.msg.noRawType(type); } } - - private static NamingStrategy getNamingStrategy(final Class interfaceType) { - final ConfigMapping configMapping = interfaceType.getAnnotation(ConfigMapping.class); - if (configMapping != null) { - switch (configMapping.namingStrategy()) { - case VERBATIM: - return VERBATIM_NAMING_STRATEGY; - case KEBAB_CASE: - return KEBAB_CASE_NAMING_STRATEGY; - case SNAKE_CASE: - return SNAKE_CASE_NAMING_STRATEGY; - } - } - - return DEFAULT_NAMING_STRATEGY; - } - - private static final NamingStrategy DEFAULT_NAMING_STRATEGY = new KebabNamingStrategy(); - private static final NamingStrategy VERBATIM_NAMING_STRATEGY = new VerbatimNamingStrategy(); - private static final NamingStrategy KEBAB_CASE_NAMING_STRATEGY = new KebabNamingStrategy(); - private static final NamingStrategy SNAKE_CASE_NAMING_STRATEGY = new SnakeNamingStrategy(); - - public interface NamingStrategy extends Function { - default boolean isDefault() { - return this.equals(DEFAULT_NAMING_STRATEGY); - } - } - - static class VerbatimNamingStrategy implements NamingStrategy { - @Override - public String apply(final String s) { - return s; - } - } - - static class KebabNamingStrategy implements NamingStrategy { - @Override - public String apply(final String s) { - return StringUtil.skewer(s, '-'); - } - } - - static class SnakeNamingStrategy implements NamingStrategy { - @Override - public String apply(final String s) { - return StringUtil.skewer(s, '_'); - } - } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java index f887934a0..e79d21336 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java @@ -84,11 +84,24 @@ static T configMappingObject(Class interfaceType, ConfigMappingContext co @SuppressWarnings("unchecked") public static Class getImplementationClass(Class type) { - final ConfigMappingMetadata mappingMetadata = ConfigMappingInterface.getConfigurationInterface(type); - if (mappingMetadata == null) { - throw ConfigMessages.msg.classIsNotAMapping(type); + try { + Class implementationClass = type.getClassLoader().loadClass(type.getName() + type.getName().hashCode() + "Impl"); + if (type.isAssignableFrom(implementationClass)) { + return (Class) implementationClass; + } + + ConfigMappingMetadata mappingMetadata = ConfigMappingInterface.getConfigurationInterface(type); + if (mappingMetadata == null) { + throw ConfigMessages.msg.classIsNotAMapping(type); + } + return (Class) loadClass(type, mappingMetadata); + } catch (ClassNotFoundException e) { + ConfigMappingMetadata mappingMetadata = ConfigMappingInterface.getConfigurationInterface(type); + if (mappingMetadata == null) { + throw ConfigMessages.msg.classIsNotAMapping(type); + } + return (Class) loadClass(type, mappingMetadata); } - return (Class) loadClass(type, mappingMetadata); } static Class loadClass(final Class parent, final ConfigMappingMetadata configMappingMetadata) { @@ -96,7 +109,7 @@ static Class loadClass(final Class parent, final ConfigMappingMetadata con synchronized (getClassLoaderLock(configMappingMetadata.getClassName())) { // Check if the interface implementation was already loaded. If not we will load it. try { - final Class klass = parent.getClassLoader().loadClass(configMappingMetadata.getClassName()); + Class klass = parent.getClassLoader().loadClass(configMappingMetadata.getClassName()); // Check if this is the right classloader class. If not we will load it. if (parent.isAssignableFrom(klass)) { return klass; diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java new file mode 100644 index 000000000..01e3e1e65 --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java @@ -0,0 +1,51 @@ +package io.smallrye.config; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class ConfigMappingNames { + private final Map names; + + public ConfigMappingNames(final Map>> names) { + this.names = new HashMap<>(names.size()); + for (Map.Entry>> entry : names.entrySet()) { + this.names.put(entry.getKey(), new Names(entry.getValue())); + } + } + + Set get(String mapping, String name) { + return names.get(mapping).get(name); + } + + private static class Names { + private final Map> names; + private final Map> anys; + + Names(Map> names) { + this.names = new HashMap<>(); + this.anys = new HashMap<>(); + for (Map.Entry> entry : names.entrySet()) { + if (entry.getKey().indexOf('*') == -1) { + this.names.put(new PropertyName(entry.getKey()), toMappingNameSet(entry.getValue())); + } else { + this.anys.put(new PropertyName(entry.getKey()), toMappingNameSet(entry.getValue())); + } + } + } + + Set get(String name) { + PropertyName mappingName = new PropertyName(name); + return names.getOrDefault(mappingName, anys.get(mappingName)); + } + + private static Set toMappingNameSet(Set names) { + Set mappingNames = new HashSet<>(names.size()); + for (String name : names) { + mappingNames.add(new PropertyName(name)); + } + return mappingNames; + } + } +} diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java index daa710847..184c6864d 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java @@ -4,5 +4,4 @@ * An interface implemented internally by configuration object implementations. */ public interface ConfigMappingObject { - void fillInOptionals(ConfigMappingContext context); } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index d700c5f5e..0ed6f25f9 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -1,40 +1,27 @@ package io.smallrye.config; -import static io.smallrye.config.ConfigMappingContext.createCollectionFactory; -import static io.smallrye.config.ConfigMappingInterface.GroupProperty; -import static io.smallrye.config.ConfigMappingInterface.LeafProperty; -import static io.smallrye.config.ConfigMappingInterface.MapProperty; -import static io.smallrye.config.ConfigMappingInterface.PrimitiveProperty; -import static io.smallrye.config.ConfigMappingInterface.Property; -import static io.smallrye.config.ConfigMappingLoader.getConfigMapping; import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass; +import static io.smallrye.config.ConfigMappings.getDefaults; +import static io.smallrye.config.ConfigMappings.getNames; +import static io.smallrye.config.ConfigMappings.getProperties; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN; -import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; -import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted; -import static java.lang.Integer.parseInt; import java.io.Serializable; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.IntFunction; +import java.util.function.Function; +import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.Converter; import io.smallrye.common.constraint.Assert; -import io.smallrye.common.function.Functions; -import io.smallrye.config.ConfigMappingInterface.CollectionProperty; -import io.smallrye.config.ConfigMappingInterface.NamingStrategy; -import io.smallrye.config._private.ConfigMessages; -import io.smallrye.config.common.utils.StringUtil; +import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix; /** * @@ -42,1128 +29,59 @@ final class ConfigMappingProvider implements Serializable { private static final long serialVersionUID = 3977667610888849912L; - /** - * The do-nothing action is used when the matched property is eager. - */ - private static final BiConsumer DO_NOTHING = Functions.discardingBiConsumer(); - private static final KeyMap> IGNORE_EVERYTHING; - - static { - KeyMap> map = new KeyMap<>(); - map.putRootValue(DO_NOTHING); - //noinspection CollectionAddedToSelf - map.putAny(map); - IGNORE_EVERYTHING = map; - } - private final Map>> roots; - private final KeyMap> matchActions; - private final Map properties; - private final Map defaultValues; + private final Set keys; + private final Map>> names; + private final List ignoredPaths; private final boolean validateUnknown; ConfigMappingProvider(final Builder builder) { this.roots = new HashMap<>(builder.roots); - this.matchActions = new KeyMap<>(); - this.properties = new HashMap<>(); - this.defaultValues = new HashMap<>(); + this.keys = builder.keys; + this.names = builder.names; + this.ignoredPaths = builder.ignoredPaths; this.validateUnknown = builder.validateUnknown; - - final ArrayDeque currentPath = new ArrayDeque<>(); - for (Map.Entry>> entry : roots.entrySet()) { - NameIterator rootNi = new NameIterator(entry.getKey()); - while (rootNi.hasNext()) { - final String nextSegment = rootNi.getNextSegment(); - if (!nextSegment.isEmpty()) { - currentPath.add(nextSegment); - } - rootNi.next(); - } - List> roots = entry.getValue(); - for (Class root : roots) { - // construct the lazy match actions for each group - BiFunction ef = new GetRootAction(root, - entry.getKey()); - ConfigMappingInterface mapping = getConfigMapping(root); - processEagerGroup(currentPath, matchActions, defaultValues, mapping.getNamingStrategy(), mapping, ef); - } - currentPath.clear(); - } - for (String[] ignoredPath : builder.ignored) { - int len = ignoredPath.length; - KeyMap> found; - if (ignoredPath[len - 1].equals("**")) { - found = matchActions.findOrAdd(ignoredPath, 0, len - 1); - found.putRootValue(DO_NOTHING); - ignoreRecursively(found); - } else { - found = matchActions.findOrAdd(ignoredPath); - found.putRootValue(DO_NOTHING); - } - } - } - - static void ignoreRecursively(KeyMap> root) { - if (root.getRootValue() == null) { - root.putRootValue(DO_NOTHING); - } - - if (root.getAny() == null) { - root.putAny(IGNORE_EVERYTHING); - } else { - var any = root.getAny(); - if (root != any) { - ignoreRecursively(any); - } - } - - for (var value : root.values()) { - ignoreRecursively(value); - } - } - - static final class ConsumeOneAndThen implements BiConsumer { - private final BiConsumer delegate; - - ConsumeOneAndThen(final BiConsumer delegate) { - this.delegate = delegate; - } - - public void accept(final ConfigMappingContext context, final NameIterator nameIterator) { - nameIterator.previous(); - delegate.accept(context, nameIterator); - nameIterator.next(); - } - } - - static final class ConsumeOneAndThenFn implements BiFunction { - private final BiFunction delegate; - - ConsumeOneAndThenFn(final BiFunction delegate) { - this.delegate = delegate; - } - - public T apply(final ConfigMappingContext context, final NameIterator nameIterator) { - nameIterator.previous(); - T result = delegate.apply(context, nameIterator); - nameIterator.next(); - return result; - } - } - - private void processEagerGroup( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final NamingStrategy namingStrategy, - final ConfigMappingInterface group, - final BiFunction getEnclosingFunction) { - - // Register super types first. The main mapping will override methods from the super types - int sc = group.getSuperTypeCount(); - for (int i = 0; i < sc; i++) { - processEagerGroup(currentPath, matchActions, defaultValues, namingStrategy, group.getSuperType(i), - getEnclosingFunction); - } - - Class type = group.getInterfaceType(); - for (int i = 0; i < group.getPropertyCount(); i++) { - Property property = group.getProperty(i); - ArrayDeque propertyPath = new ArrayDeque<>(currentPath); - // process by property type - if (!property.isParentPropertyName()) { - NameIterator ni = new NameIterator(property.hasPropertyName() ? property.getPropertyName() - : propertyName(property, group, namingStrategy)); - while (ni.hasNext()) { - propertyPath.add(ni.getNextSegment()); - ni.next(); - } - } - processProperty(propertyPath, matchActions, defaultValues, namingStrategy, group, getEnclosingFunction, type, - property); - } - } - - private void processProperty( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final NamingStrategy namingStrategy, - final ConfigMappingInterface group, - final BiFunction getEnclosingFunction, - final Class type, - final Property property) { - - if (property.isOptional()) { - // switch to lazy mode - Property nestedProperty = property.asOptional().getNestedProperty(); - processOptionalProperty(currentPath, matchActions, defaultValues, namingStrategy, group, getEnclosingFunction, type, - nestedProperty); - } else if (property.isGroup()) { - GroupProperty nestedGroup = property.asGroup(); - GetOrCreateEnclosingGroupInGroup enclosingFunction = new GetOrCreateEnclosingGroupInGroup(getEnclosingFunction, - nestedGroup.getGroupType().getInterfaceType(), - nestedGroup.getMemberName(), - currentPath, - nestedGroup.getGroupType().getNamingStrategy(), - group.getInterfaceType(), - namingStrategy); - processEagerGroup(currentPath, matchActions, defaultValues, namingStrategy, property.asGroup().getGroupType(), - enclosingFunction); - } else if (property.isPrimitive()) { - // already processed eagerly - PrimitiveProperty primitiveProperty = property.asPrimitive(); - if (primitiveProperty.hasDefaultValue()) { - addDefault(currentPath, primitiveProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), primitiveProperty.getDefaultValue()); - } - } - addAction(currentPath, property, DO_NOTHING); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addAction(inlineCollectionPath(currentPath), property, DO_NOTHING); - } - } else if (property.isLeaf()) { - // already processed eagerly - LeafProperty leafProperty = property.asLeaf(); - if (leafProperty.hasDefaultValue()) { - addDefault(currentPath, leafProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), leafProperty.getDefaultValue()); - } - } - // ignore with no error message - addAction(currentPath, property, DO_NOTHING); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addAction(inlineCollectionPath(currentPath), property, DO_NOTHING); - } - } else if (property.isMap()) { - // the enclosure of the map is this group - processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), getEnclosingFunction, - namingStrategy, group); - } else if (property.isCollection()) { - CollectionProperty collectionProperty = property.asCollection(); - currentPath.addLast(currentPath.removeLast() + "[*]"); - processProperty(currentPath, matchActions, defaultValues, namingStrategy, group, getEnclosingFunction, type, - collectionProperty.getElement()); - } - } - - private void processOptionalProperty( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final NamingStrategy namingStrategy, - final ConfigMappingInterface group, - final BiFunction getEnclosingFunction, - final Class type, - final Property property) { - - if (property.isGroup()) { - GroupProperty nestedGroup = property.asGroup(); - // on match, always create the outermost group, which recursively creates inner groups - GetOrCreateEnclosingGroupInGroup enclosingFunction = new GetOrCreateEnclosingGroupInGroup(getEnclosingFunction, - nestedGroup.getGroupType().getInterfaceType(), - nestedGroup.getMemberName(), - currentPath, - nestedGroup.getGroupType().getNamingStrategy(), - group.getInterfaceType(), - namingStrategy); - processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy, nestedGroup.getGroupType(), - enclosingFunction); - } else if (property.isLeaf()) { - LeafProperty leafProperty = property.asLeaf(); - if (leafProperty.hasDefaultValue()) { - addDefault(currentPath, leafProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), leafProperty.getDefaultValue()); - } - } - addAction(currentPath, property, DO_NOTHING); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addAction(inlineCollectionPath(currentPath), property, DO_NOTHING); - } - } else if (property.isCollection()) { - CollectionProperty collectionProperty = property.asCollection(); - currentPath.addLast(currentPath.removeLast() + "[*]"); - processProperty(currentPath, matchActions, defaultValues, namingStrategy, group, getEnclosingFunction, type, - collectionProperty.getElement()); - } - } - - private void processLazyGroupInGroup( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final NamingStrategy namingStrategy, - final ConfigMappingInterface group, - final BiConsumer matchAction) { - int pc = group.getPropertyCount(); - int pathLen = currentPath.size(); - for (int i = 0; i < pc; i++) { - Property property = group.getProperty(i); - if (!property.isParentPropertyName()) { - NameIterator ni = new NameIterator(property.hasPropertyName() ? property.getPropertyName() - : propertyName(property, group, namingStrategy)); - while (ni.hasNext()) { - currentPath.add(ni.getNextSegment()); - ni.next(); - } - } - boolean optional = property.isOptional(); - processLazyPropertyInGroup(currentPath, matchActions, defaultValues, matchAction, namingStrategy, group, optional, - property); - while (currentPath.size() > pathLen) { - currentPath.removeLast(); - } - } - int sc = group.getSuperTypeCount(); - for (int i = 0; i < sc; i++) { - processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy, group.getSuperType(i), - matchAction); - } - } - - private void processLazyPropertyInGroup( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final BiConsumer matchAction, - final NamingStrategy namingStrategy, - final ConfigMappingInterface group, - final boolean optional, - final Property property) { - - if (property.isGroup() || optional && property.asOptional().getNestedProperty().isGroup()) { - BiFunction delegate = property.isParentPropertyName() - ? new GetNestedEnclosing(matchAction) - : new ConsumeOneAndThenFn<>(new GetNestedEnclosing(matchAction)); - GroupProperty nestedGroup = property.isGroup() ? property.asGroup() - : property.asOptional().getNestedProperty().asGroup(); - GetOrCreateEnclosingGroupInGroup enclosingFunction = new GetOrCreateEnclosingGroupInGroup(delegate, - nestedGroup.getGroupType().getInterfaceType(), - nestedGroup.getMemberName(), - currentPath, - nestedGroup.getGroupType().getNamingStrategy(), - group.getInterfaceType(), - namingStrategy); - processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy, nestedGroup.getGroupType(), - enclosingFunction); - } else if (property.isGroup()) { - GroupProperty nestedGroup = property.asGroup(); - BiFunction delegate = property.isParentPropertyName() - ? new GetNestedEnclosing(matchAction) - : new ConsumeOneAndThenFn<>(new GetNestedEnclosing(matchAction)); - GetOrCreateEnclosingGroupInGroup enclosingFunction = new GetOrCreateEnclosingGroupInGroup(delegate, - nestedGroup.getGroupType().getInterfaceType(), - nestedGroup.getMemberName(), - currentPath, - nestedGroup.getGroupType().getNamingStrategy(), - group.getInterfaceType(), - namingStrategy); - processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy, nestedGroup.getGroupType(), - enclosingFunction); - } else if (property.isLeaf() || property.isPrimitive() - || optional && property.asOptional().getNestedProperty().isLeaf()) { - BiConsumer actualAction = property.isParentPropertyName() ? matchAction - : new ConsumeOneAndThen(matchAction); - addAction(currentPath, property, actualAction); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addAction(inlineCollectionPath(currentPath), property, actualAction); - } - if (property.isPrimitive()) { - PrimitiveProperty primitiveProperty = property.asPrimitive(); - if (primitiveProperty.hasDefaultValue()) { - addDefault(currentPath, primitiveProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), primitiveProperty.getDefaultValue()); - } - } - } else if (property.isLeaf() && optional) { - LeafProperty leafProperty = property.asOptional().getNestedProperty().asLeaf(); - if (leafProperty.hasDefaultValue()) { - addDefault(currentPath, leafProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), leafProperty.getDefaultValue()); - } - } - } else { - LeafProperty leafProperty = property.asLeaf(); - if (leafProperty.hasDefaultValue()) { - addDefault(currentPath, leafProperty.getDefaultValue()); - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addDefault(inlineCollectionPath(currentPath), leafProperty.getDefaultValue()); - } - } - } - } else if (property.isMap()) { - GetNestedEnclosing nestedMatchAction = new GetNestedEnclosing(matchAction); - processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), nestedMatchAction, namingStrategy, - group); - } else if (property.isCollection() || optional && property.asOptional().getNestedProperty().isCollection()) { - CollectionProperty collectionProperty = optional ? property.asOptional().getNestedProperty().asCollection() - : property.asCollection(); - currentPath.addLast(currentPath.removeLast() + "[*]"); - processLazyPropertyInGroup(currentPath, matchActions, defaultValues, matchAction, namingStrategy, group, false, - collectionProperty.getElement()); - } - } - - private void processLazyMapInGroup( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final MapProperty mapProperty, - final BiFunction getEnclosingGroup, - final NamingStrategy namingStrategy, - final ConfigMappingInterface enclosingGroup) { - - BiFunction delegate = mapProperty.isParentPropertyName() - ? getEnclosingGroup - : new ConsumeOneAndThenFn<>(getEnclosingGroup); - GetOrCreateEnclosingMapInGroup getEnclosingMap = new GetOrCreateEnclosingMapInGroup(delegate, - mapProperty.getMemberName(), currentPath, enclosingGroup.getInterfaceType(), - enclosingGroup.getNamingStrategy()); - processLazyMap(currentPath, matchActions, defaultValues, mapProperty, getEnclosingMap, namingStrategy, enclosingGroup); - } - - private void processLazyMap( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final MapProperty property, - final BiFunction> getEnclosingMap, - final NamingStrategy namingStrategy, - final ConfigMappingInterface enclosingGroup) { - - Property valueProperty = property.getValueProperty(); - ArrayDeque unnamedPath = new ArrayDeque<>(currentPath); - currentPath.addLast("*"); - processLazyMapValue(currentPath, matchActions, defaultValues, property, valueProperty, false, getEnclosingMap, - namingStrategy, enclosingGroup); - if (property.hasKeyUnnamed()) { - processLazyMapValue(unnamedPath, matchActions, defaultValues, property, valueProperty, true, getEnclosingMap, - namingStrategy, enclosingGroup); - } - } - - private void processLazyMapValue( - final ArrayDeque currentPath, - final KeyMap> matchActions, - final Map defaultValues, - final MapProperty mapProperty, - final Property mapValueProperty, - final boolean keyUnnamed, - final BiFunction> getEnclosingMap, - final NamingStrategy namingStrategy, - final ConfigMappingInterface enclosingGroup) { - - if (mapValueProperty.isLeaf()) { - LeafProperty leafProperty = mapValueProperty.asLeaf(); - String mapPath = String.join(".", currentPath); - - CreateValueInMap matchAction = new CreateValueInMap( - getEnclosingMap, - mapPath, - mapProperty.getKeyRawType(), mapProperty.hasKeyConvertWith() ? mapProperty.getKeyConvertWith() : null, - leafProperty.getValueRawType(), - leafProperty.getConvertWith(), - mapProperty.getValueProperty().isCollection(), - mapProperty.getValueProperty().isCollection() - ? mapProperty.getValueProperty().asCollection().getCollectionRawType() - : null); - - addAction(currentPath, mapProperty, matchAction); - - // action to match all segments of a key after the map path - KeyMap> mapAction = matchActions.find(mapPath); - if (mapAction != null) { - mapAction.putAny(matchActions.find(mapPath)); - } - // collections may also be represented without [] so we need to register both paths - if (isCollection(currentPath)) { - addAction(inlineCollectionPath(currentPath), leafProperty, DO_NOTHING); - } - } else if (mapValueProperty.isMap()) { - GetOrCreateEnclosingMapInMap getNestedEnclosingMap = new GetOrCreateEnclosingMapInMap( - getEnclosingMap, - mapProperty.getKeyRawType(), - keyUnnamed, - mapProperty.getKeyUnnamed(), - mapProperty.hasKeyConvertWith() ? mapProperty.getKeyConvertWith() : null); - processLazyMap(currentPath, matchActions, defaultValues, mapValueProperty.asMap(), getNestedEnclosingMap, - namingStrategy, enclosingGroup); - } else if (mapValueProperty.isGroup()) { - GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap( - getEnclosingMap, - currentPath, - mapProperty.getKeyRawType(), - keyUnnamed, - mapProperty.getKeyUnnamed(), - mapProperty.hasKeyConvertWith() ? mapProperty.getKeyConvertWith() : null, - mapProperty.getValueProperty().isCollection() - ? mapProperty.getValueProperty().asCollection().getCollectionRawType() - : null, - mapProperty.getValueProperty().isCollection(), - mapValueProperty.asGroup().getGroupType().getInterfaceType(), - mapValueProperty.asGroup().getGroupType().getNamingStrategy(), - enclosingGroup.getInterfaceType(), - enclosingGroup.getNamingStrategy()); - processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy, - mapValueProperty.asGroup().getGroupType(), ef); - } else if (mapValueProperty.isCollection()) { - CollectionProperty collectionProperty = mapValueProperty.asCollection(); - Property element = collectionProperty.getElement(); - if (!element.hasConvertWith() && !keyUnnamed && !element.isLeaf()) { - currentPath.addLast(currentPath.removeLast() + "[*]"); - } - processLazyMapValue(currentPath, matchActions, defaultValues, mapProperty, element, keyUnnamed, getEnclosingMap, - namingStrategy, enclosingGroup); - } - } - - private void addAction( - final ArrayDeque currentPath, - final Property property, - final BiConsumer action) { - KeyMap> current = matchActions.findOrAdd(currentPath); - Property previous = properties.put(String.join(".", currentPath), property); - if (current.hasRootValue() && current.getRootValue() != action && previous != null && !previous.equals(property)) { - throw ConfigMessages.msg.ambiguousMapping(String.join(".", currentPath), property.getMemberName(), - previous.getMemberName()); - } - current.putRootValue(action); - } - - private static boolean isCollection(final ArrayDeque currentPath) { - return !currentPath.isEmpty() && currentPath.getLast().endsWith("[*]"); - } - - private static ArrayDeque inlineCollectionPath(final ArrayDeque currentPath) { - ArrayDeque inlineCollectionPath = new ArrayDeque<>(currentPath); - String last = inlineCollectionPath.removeLast(); - inlineCollectionPath.addLast(last.substring(0, last.length() - 3)); - return inlineCollectionPath; - } - - // This will add the property index (if exists) to the name - private static String indexName(final String name, final String groupPath, final NameIterator nameIterator) { - String group = new NameIterator(groupPath, true).getPreviousSegment(); - String property = nameIterator.getAllPreviousSegments(); - int start = property.lastIndexOf(normalizeIfIndexed(group)); - if (start != -1) { - int i = start + normalizeIfIndexed(group).length(); - if (i < property.length() && property.charAt(i) == '[') { - for (;;) { - if (property.charAt(i) == ']') { - try { - int index = parseInt( - property.substring(start + normalizeIfIndexed(group).length() + 1, i)); - return name + "[" + index + "]"; - } catch (NumberFormatException e) { - //NOOP - } - break; - } else if (i < property.length() - 1) { - i++; - } else { - break; - } - } - } - } - return name; - } - - private static NameIterator mapPath(final String mapPath, final NameIterator propertyName) { - int segments = 0; - NameIterator countSegments = new NameIterator(mapPath); - while (countSegments.hasNext()) { - segments++; - countSegments.next(); - } - - // We don't want the key; keys only exist when the map ends with '*'; else it is an unnamed key - if (mapPath.endsWith("*") || mapPath.endsWith("*[*]")) { - segments = segments - 1; - } - - NameIterator propertyMap = new NameIterator(propertyName.getName()); - propertyMap.next(segments); - return propertyMap; - } - - private static String propertyName(final Property property, final ConfigMappingInterface group, - final NamingStrategy namingStrategy) { - return namingStrategy(namingStrategy, group.getNamingStrategy()).apply(property.getPropertyName()); - } - - private static NamingStrategy namingStrategy(NamingStrategy enclosing, NamingStrategy enclosed) { - // if enclosed element is using default, then use the enclosing naming strategy - if (enclosed.isDefault()) { - return enclosing; - } else { - return enclosed; - } - } - - static class GetRootAction implements BiFunction { - private final Class root; - private final String rootPath; - - GetRootAction(final Class root, final String rootPath) { - this.root = root; - this.rootPath = rootPath; - } - - @Override - public ConfigMappingObject apply(final ConfigMappingContext mc, final NameIterator ni) { - return mc.getRoot(root, rootPath); - } - } - - static class GetOrCreateEnclosingGroupInGroup - implements BiFunction, - BiConsumer { - private final BiFunction delegate; - private final Class groupType; - private final String groupName; - private final String groupPath; - private final int groupDepth; - private final NamingStrategy groupNaming; - private final Class enclosingGroupType; - private final NamingStrategy enclosingGroupNaming; - - public GetOrCreateEnclosingGroupInGroup( - final BiFunction delegate, - final Class groupType, - final String groupName, - final ArrayDeque groupPath, - final NamingStrategy groupNaming, - final Class enclosingGroupType, - final NamingStrategy enclosingGroupNaming) { - - this.delegate = delegate; - this.groupType = groupType; - this.groupName = groupName; - this.groupPath = String.join(".", groupPath); - this.groupDepth = groupPath.size(); - this.groupNaming = groupNaming; - this.enclosingGroupType = enclosingGroupType; - this.enclosingGroupNaming = enclosingGroupNaming; - } - - @Override - public ConfigMappingObject apply(final ConfigMappingContext context, final NameIterator ni) { - ConfigMappingObject ourEnclosing = delegate.apply(context, ni); - String key = indexName(groupName, groupPath, ni); - ConfigMappingObject val = (ConfigMappingObject) context.getEnclosedField(enclosingGroupType, key, ourEnclosing); - context.applyNamingStrategy(namingStrategy(enclosingGroupNaming, groupNaming)); - if (val == null) { - NameIterator groupNi = new NameIterator(ni.getName()); - groupNi.next(groupDepth); - StringBuilder sb = context.getStringBuilder(); - sb.replace(0, sb.length(), groupNi.getAllPreviousSegments()); - val = (ConfigMappingObject) context.constructGroup(groupType); - context.registerEnclosedField(enclosingGroupType, key, ourEnclosing, val); - } - return val; - } - - @Override - public void accept(final ConfigMappingContext context, final NameIterator nameIterator) { - apply(context, nameIterator); - } - } - - static class GetOrCreateEnclosingGroupInMap implements BiFunction, - BiConsumer { - private final BiFunction> delegate; - - private final String mapPath; - private final Class mapKeyRawType; - private final boolean mapKeyUnnamed; - private final String mapKeyUnnamedKey; - private final Class> mapKeyConverter; - private final Class mapValueRawType; - private final boolean mapValueCollection; - private final Class groupType; - private final NamingStrategy groupNaming; - private final Class enclosingGroupType; - private final NamingStrategy enclosingGroupNaming; - - public GetOrCreateEnclosingGroupInMap( - final BiFunction> delegate, - final ArrayDeque mapPath, - final Class mapKeyRawType, - final boolean mapKeyUnnamed, - final String mapKeyUnnamedKey, - final Class> mapKeyConverter, - final Class mapValueRawType, - final boolean mapValueCollection, - final Class groupType, - final NamingStrategy groupNaming, - final Class enclosingGroupType, - final NamingStrategy enclosingGroupNaming) { - - this.delegate = delegate; - this.mapPath = String.join(".", mapPath); - this.mapKeyRawType = mapKeyRawType; - this.mapKeyUnnamed = mapKeyUnnamed; - this.mapKeyUnnamedKey = mapKeyUnnamedKey; - this.mapKeyConverter = mapKeyConverter; - this.mapValueRawType = mapValueRawType; - this.mapValueCollection = mapValueCollection; - this.groupType = groupType; - this.groupNaming = groupNaming; - this.enclosingGroupType = enclosingGroupType; - this.enclosingGroupNaming = enclosingGroupNaming; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public ConfigMappingObject apply(final ConfigMappingContext context, final NameIterator ni) { - NameIterator niMapPath = mapPath(mapPath, ni); - MapKey mapKey = mapKey(context, ni, niMapPath); - Map ourEnclosing = delegate.apply(context, niMapPath); - ConfigMappingObject val = (ConfigMappingObject) context.getEnclosedField(enclosingGroupType, mapKey.getKey(), - ourEnclosing); - if (val == null) { - StringBuilder sb = context.getStringBuilder(); - sb.replace(0, sb.length(), mapKeyUnnamed ? niMapPath.getAllPreviousSegments() - : niMapPath.getAllPreviousSegmentsWith(mapKey.getKey())); - context.applyNamingStrategy(namingStrategy(enclosingGroupNaming, groupNaming)); - val = (ConfigMappingObject) context.constructGroup(groupType); - context.registerEnclosedField(enclosingGroupType, mapKey.getKey(), ourEnclosing, val); - if (mapValueCollection) { - Collection collection = (Collection) ourEnclosing.get(mapKey.getConvertedKey()); - if (collection == null) { - // Create the Collection in the Map does not have it - IntFunction> collectionFactory = createCollectionFactory(mapValueRawType); - // Get all the available indexes - List indexes = mapKeyUnnamed ? List.of(0) - : context.getConfig() - .getIndexedPropertiesIndexes(niMapPath.getAllPreviousSegmentsWith(mapKey.getNameKey())); - collection = collectionFactory.apply(indexes.size()); - // Initialize all expected elements in the list - if (collection instanceof List) { - for (Integer index : indexes) { - ((List) collection).add(index, null); - } - } - ((Map) ourEnclosing).put(mapKey.getConvertedKey(), collection); - } - - if (collection instanceof List) { - // We don't know the order in which the properties will be processed, so we set it manually - ((List) collection).set(mapKey.getIndex(), val); - } else { - collection.add(val); - } - } else { - ((Map) ourEnclosing).put(mapKey.getConvertedKey(), val); - } - } - return val; - } - - @Override - public void accept(final ConfigMappingContext context, final NameIterator ni) { - apply(context, ni); - } - - private MapKey mapKey(final ConfigMappingContext context, final NameIterator ni, final NameIterator mapPath) { - if (mapKeyUnnamed && mapKeyUnnamedKey == null) { - return new MapKey(null, null, null, 0); - } - - String rawKey = mapKeyUnnamed ? mapKeyUnnamedKey : mapPath.getNextSegment(); - mapPath.next(); - String pathKey = mapPath.getAllPreviousSegments(); - mapPath.previous(); - Converter converterKey; - if (mapKeyConverter != null) { - converterKey = context.getConverterInstance(mapKeyConverter); - } else { - converterKey = context.getConfig().requireConverter(mapKeyRawType); - } - - // This will be the key to use to store the value in the map - String nameKey = normalizeIfIndexed(rawKey); - Object convertedKey = converterKey.convert(rawKey); - if (convertedKey.equals(rawKey)) { - convertedKey = nameKey; - } - - int index = -1; - if (mapValueCollection) { - index = mapKeyUnnamed ? 0 : getIndex(rawKey); - } - - // NameIterator#getNextSegment() returns the name without quotes. We need add them if they exist for lookups to work properly - if (pathKey.charAt(pathKey.length() - 1 - rawKey.length() + nameKey.length()) == '"' - && pathKey.charAt(pathKey.length() - 1 - rawKey.length() - 1) == '"') { - nameKey = "\"" + nameKey + "\""; - rawKey = mapValueCollection ? nameKey + "[" + index + "]" : nameKey; - } - - if (!mapKeyUnnamed && (index >= 0 ? rawKey : nameKey).equals(mapKeyUnnamedKey)) { - throw ConfigMessages.msg.explicitNameInUnnamed(ni.getName(), rawKey); - } - - return new MapKey(rawKey, nameKey, convertedKey, index); - } - - static class MapKey { - private final String rawKey; - private final String nameKey; - private final Object convertedKey; - private final int index; - - public MapKey(final String rawKey, final String nameKey, final Object convertedKey, final int index) { - this.rawKey = rawKey; - this.nameKey = nameKey; - this.convertedKey = convertedKey; - this.index = index; - } - - public String getKey() { - return index >= 0 ? rawKey : nameKey; - } - - public String getNameKey() { - return nameKey; - } - - public Object getConvertedKey() { - return convertedKey; - } - - public int getIndex() { - return index; - } - } - } - - static class GetOrCreateEnclosingMapInGroup implements BiFunction>, - BiConsumer { - private final BiFunction delegate; - private final String mapName; - private final String mapPath; - private final Class enclosingGroupType; - private final NamingStrategy enclosingGroupNaming; - - public GetOrCreateEnclosingMapInGroup( - final BiFunction delegate, - final String mapName, - final ArrayDeque mapPath, - final Class enclosingGroupType, - final NamingStrategy enclosingGroupNaming) { - - this.delegate = delegate; - this.mapName = mapName; - this.mapPath = String.join(".", mapPath); - this.enclosingGroupType = enclosingGroupType; - this.enclosingGroupNaming = enclosingGroupNaming; - } - - @Override - public Map apply(final ConfigMappingContext context, final NameIterator ni) { - ConfigMappingObject ourEnclosing = delegate.apply(context, ni); - String key = indexName(mapName, mapPath, ni); - Map val = (Map) context.getEnclosedField(enclosingGroupType, key, ourEnclosing); - context.applyNamingStrategy(enclosingGroupNaming); - if (val == null) { - // map is not yet constructed - val = new HashMap<>(); - context.registerEnclosedField(enclosingGroupType, key, ourEnclosing, val); - } - return val; - } - - @Override - public void accept(final ConfigMappingContext context, final NameIterator ni) { - apply(context, ni); - } - } - - static class CreateValueInMap implements BiConsumer { - private final BiFunction> delegate; - private final String mapPath; - private final Class mapKeyRawType; - private final Class> mapKeyConverter; - private final Class mapValueRawType; - private final Class> mapValueConverter; - private final boolean mapValueCollection; - private final Class mapValueCollectionRawType; - - public CreateValueInMap( - final BiFunction> delegate, - final String mapPath, - final Class mapKeyRawType, - final Class> mapKeyConverter, - final Class mapValueRawType, - final Class> mapValueConverter, - final boolean mapValueCollection, - final Class mapValueCollectionRawType) { - - this.delegate = delegate; - this.mapPath = mapPath; - this.mapKeyRawType = mapKeyRawType; - this.mapKeyConverter = mapKeyConverter; - this.mapValueRawType = mapValueRawType; - this.mapValueConverter = mapValueConverter; - this.mapValueCollection = mapValueCollection; - this.mapValueCollectionRawType = mapValueCollectionRawType; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void accept(final ConfigMappingContext context, final NameIterator ni) { - // Place the cursor at the map path - NameIterator niMapPath = mapPath(mapPath, ni); - Map map = delegate.apply(context, niMapPath); - - String rawMapKey; - String configKey; - boolean indexed = isIndexed(ni.getPreviousSegment()); - if (indexed && ni.hasPrevious()) { - rawMapKey = normalizeIfIndexed(niMapPath.getName().substring(niMapPath.getPosition() + 1)); - configKey = niMapPath.getAllPreviousSegmentsWith(rawMapKey); - } else { - rawMapKey = niMapPath.getName().substring(niMapPath.getPosition() + 1); - configKey = ni.getAllPreviousSegments(); - } - - // Remove quotes if exists - if (rawMapKey.length() > 1 && rawMapKey.charAt(0) == '"' && rawMapKey.charAt(rawMapKey.length() - 1) == '"') { - rawMapKey = rawMapKey.substring(1, rawMapKey.length() - 1); - } - - Converter keyConv; - SmallRyeConfig config = context.getConfig(); - if (mapKeyConverter != null) { - keyConv = context.getConverterInstance(mapKeyConverter); - } else { - keyConv = config.requireConverter(mapKeyRawType); - } - Converter valueConv; - if (mapValueConverter != null) { - valueConv = context.getConverterInstance(mapValueConverter); - } else { - valueConv = config.requireConverter(mapValueRawType); - } - - if (mapValueCollection && mapValueConverter == null) { - IntFunction collectionFactory = createCollectionFactory(mapValueCollectionRawType); - ((Map) map).put(keyConv.convert(rawMapKey), config.getValues(configKey, valueConv, collectionFactory)); - } else { - ((Map) map).put(keyConv.convert(rawMapKey), config.getValue(configKey, valueConv)); - } - } - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - static class GetOrCreateEnclosingMapInMap implements BiFunction>, - BiConsumer { - private final BiFunction> delegate; - private final Class mapKeyRawType; - private final boolean mapKeyUnnamed; - private final String mapKeyUnnamedKey; - private final Class> mapKeyConverter; - - public GetOrCreateEnclosingMapInMap( - final BiFunction> delegate, - final Class mapKeyRawType, - final boolean mapKeyUnnamed, - final String mapKeyUnnamedKey, - final Class> mapKeyConverter) { - - this.delegate = delegate; - this.mapKeyRawType = mapKeyRawType; - this.mapKeyUnnamed = mapKeyUnnamed; - this.mapKeyUnnamedKey = mapKeyUnnamedKey; - this.mapKeyConverter = mapKeyConverter; - } - - @Override - public Map apply(final ConfigMappingContext context, final NameIterator ni) { - if (!mapKeyUnnamed) { - ni.previous(); - } - Map enclosingMap = delegate.apply(context, ni); - if (!mapKeyUnnamed) { - ni.next(); - } - String rawMapKey = mapKeyUnnamed ? mapKeyUnnamedKey : ni.getPreviousSegment(); - Converter keyConv; - SmallRyeConfig config = context.getConfig(); - if (mapKeyConverter != null) { - keyConv = context.getConverterInstance(mapKeyConverter); - } else { - keyConv = config.requireConverter(mapKeyRawType); - } - Object key = rawMapKey != null ? keyConv.convert(rawMapKey) : null; - return (Map) ((Map) enclosingMap).computeIfAbsent(key, map -> new HashMap<>()); - } - - @Override - public void accept(final ConfigMappingContext context, final NameIterator ni) { - apply(context, ni); - } - } - - // To recursively create Optional nested groups - static class GetNestedEnclosing implements BiFunction { - private final BiConsumer matchAction; - - public GetNestedEnclosing(final BiConsumer matchAction) { - this.matchAction = matchAction; - } - - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public ConfigMappingObject apply(final ConfigMappingContext configMappingContext, final NameIterator nameIterator) { - if (matchAction instanceof BiFunction) { - return (ConfigMappingObject) ((BiFunction) matchAction).apply(configMappingContext, nameIterator); - } - return null; - } } public static Builder builder() { return new Builder(); } - KeyMap> getMatchActions() { - return matchActions; - } - - Map getProperties() { - return properties; - } - - Map getDefaultValues() { - return defaultValues; - } - - private void addDefault(ArrayDeque path, String value) { - defaultValues.put(String.join(".", path), value); - } - - ConfigMappingContext mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { - // We need to set defaults from mappings here, because in a CDI environment mappings are added on an existent Config instance - ConfigSource configSource = config.getDefaultValues(); - if (configSource instanceof DefaultValuesConfigSource) { - DefaultValuesConfigSource defaultValuesConfigSource = (DefaultValuesConfigSource) configSource; - defaultValuesConfigSource.addDefaults(defaultValues); - } - matchPropertiesWithEnv(config, roots.keySet(), getProperties().keySet()); - return SecretKeys.doUnlocked(() -> mapConfigurationInternal(config)); - } - - private ConfigMappingContext mapConfigurationInternal(SmallRyeConfig config) throws ConfigValidationException { - Assert.checkNotNullParam("config", config); - ConfigMappingContext context = new ConfigMappingContext(config); - + Map, Map> mapConfiguration(final SmallRyeConfig config) + throws ConfigValidationException { if (roots.isEmpty()) { - return context; + return Collections.emptyMap(); } - // eagerly populate roots - for (Map.Entry>> entry : roots.entrySet()) { - String path = entry.getKey(); - List> roots = entry.getValue(); - for (Class root : roots) { - StringBuilder sb = context.getStringBuilder(); - sb.replace(0, sb.length(), path); - ConfigMappingObject group = (ConfigMappingObject) context.constructRoot(root); - context.registerRoot(root, path, group); - } - } + // Register additional dissabiguation property names comparing mapped keys and env names + matchPropertiesWithEnv(config, roots.keySet(), keys); - // lazily sweep - for (String name : filterPropertiesInRoots(config.getPropertyNames(), roots.keySet())) { - NameIterator ni = new NameIterator(name); - BiConsumer action = matchActions.findRootValue(ni); - if (action != null) { - action.accept(context, ni); - } else { - context.unknownProperty(name); + // Perform the config mapping + ConfigMappingContext context = SecretKeys.doUnlocked(new Supplier() { + @Override + public ConfigMappingContext get() { + return new ConfigMappingContext(config, roots, names); } - } + }); - boolean validateUnknown = config.getOptionalValue(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, boolean.class) - .orElse(this.validateUnknown); - if (validateUnknown) { - context.reportUnknown(); + if (config.getOptionalValue(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, boolean.class).orElse(this.validateUnknown)) { + context.reportUnknown(ignoredPaths); } List problems = context.getProblems(); if (!problems.isEmpty()) { throw new ConfigValidationException(problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS)); } - context.fillInOptionals(); - return context; + return context.getRootsMap(); } - /** - * Filters the full list of properties names in Config to only the property names that can match any of the - * prefixes (namespaces) registered in mappings. - * - * @param properties the available property names in Config. - * @param roots the registered mapping roots. - * - * @return the property names that match to at least one root. - */ - private static Iterable filterPropertiesInRoots(final Iterable properties, final Set roots) { - if (roots.isEmpty()) { - return properties; - } - - // Will match everything, so no point in filtering - if (roots.contains("")) { - return properties; - } - - List matchedProperties = new ArrayList<>(); - for (String property : properties) { - for (String root : roots) { - if (isPropertyInRoot(property, root)) { - matchedProperties.add(property); - break; - } - } - } - return matchedProperties; - } - - private static void matchPropertiesWithEnv(final SmallRyeConfig config, final Set roots, + private static void matchPropertiesWithEnv( + final SmallRyeConfig config, + final Set roots, final Set mappedProperties) { // TODO - We shouldn't be mutating the EnvSource. - // We should do the calculation when creating the EnvSource, but right mappings and sources are not well integrated. - - // Collect properties from all sources except Env - List configuredProperties = new ArrayList<>(); - for (ConfigSource configSource : config.getConfigSources()) { - if (!(configSource instanceof EnvConfigSource)) { - Set propertyNames = configSource.getPropertyNames(); - if (propertyNames != null) { - configuredProperties.addAll(propertyNames); - } - } - } + // We should do the calculation when creating the EnvSource, but right now mappings and sources are not well integrated. // Check Env properties StringBuilder sb = new StringBuilder(); @@ -1204,42 +122,9 @@ private static void matchPropertiesWithEnv(final SmallRyeConfig config, final Se } } } - - // Match configured properties with Env with the same semantic meaning and use that one - for (String configuredProperty : configuredProperties) { - Set envNames = envConfigSource.getPropertyNames(); - if (envConfigSource.hasPropertyName(configuredProperty)) { - if (!envNames.contains(configuredProperty)) { - // this may be expensive, but it shouldn't happend that often - envNames.remove(toLowerCaseAndDotted(replaceNonAlphanumericByUnderscores(configuredProperty))); - envNames.add(configuredProperty); - } - } - } } } - private static boolean isPropertyInRoot(final String property, final String root) { - if (property.equals(root)) { - return true; - } - - // if property is less than the root no way to match - if (property.length() <= root.length()) { - return false; - } - - // foo.bar - // foo.bar."baz" - // foo.bar[0] - char c = property.charAt(root.length()); - if ((c == '.') || c == '[') { - return property.startsWith(root); - } - - return false; - } - /** * Matches if a dotted Environment property name is part of a registered root. * @@ -1337,79 +222,65 @@ private static List indexOfDashes(final String mappedProperty, final St return dashesPosition; } - private static String normalizeIfIndexed(final String propertyName) { - int indexStart = propertyName.indexOf("["); - int indexEnd = propertyName.indexOf("]"); - if (indexStart != -1 && indexEnd != -1) { - String index = propertyName.substring(indexStart + 1, indexEnd); - if (index.equals("*")) { - return propertyName.substring(0, indexStart); - } - try { - Integer.parseInt(index); - return propertyName.substring(0, indexStart); - } catch (NumberFormatException e) { - return propertyName; - } - } - return propertyName; - } - - private static boolean isIndexed(final String propertyName) { - int indexStart = propertyName.indexOf('['); - int indexEnd = propertyName.indexOf(']'); - if (indexStart != -1 && indexEnd != -1) { - int indexLength = indexEnd - (indexStart + 1); - if (indexLength == 1 && ((CharSequence) propertyName).charAt(indexStart + 1) == '*') { - return true; - } - return StringUtil.isNumeric(propertyName, indexStart + 1, indexEnd); - } - return false; - } - - private static int getIndex(final String propertyName) { - int indexStart = propertyName.indexOf('['); - int indexEnd = propertyName.indexOf(']'); - if (indexStart != -1 && indexEnd != -1) { - try { - return Integer.parseInt(propertyName.substring(indexStart + 1, indexEnd)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(); - } - } - throw new IllegalArgumentException(); - } - - public static final class Builder { - final Set> types = new HashSet<>(); - final Map>> roots = new HashMap<>(); - final List ignored = new ArrayList<>(); + static final class Builder { + Set> types = new HashSet<>(); + Map>> roots = new HashMap<>(); + Set keys = new HashSet<>(); + Map>> names = new HashMap<>(); + List ignoredPaths = new ArrayList<>(); boolean validateUnknown = true; + SmallRyeConfigBuilder configBuilder = null; Builder() { } - public Builder addRoot(String path, Class type) { - Assert.checkNotNullParam("path", path); + Builder addRoot(String prefix, Class type) { + Assert.checkNotNullParam("path", prefix); Assert.checkNotNullParam("type", type); types.add(type); - roots.computeIfAbsent(path, k -> new ArrayList<>(4)).add(getConfigMappingClass(type)); + roots.computeIfAbsent(prefix, k -> new ArrayList<>(4)).add(getConfigMappingClass(type)); + return this; + } + + Builder keys(Set keys) { + Assert.checkNotNullParam("keys", keys); + this.keys.addAll(keys); + return this; + } + + Builder names(Map>> names) { + Assert.checkNotNullParam("names", names); + for (Map.Entry>> entry : names.entrySet()) { + Map> groupNames = this.names.computeIfAbsent(entry.getKey(), + new Function>>() { + @Override + public Map> apply( + final String s) { + return new HashMap<>(); + } + }); + groupNames.putAll(entry.getValue()); + } return this; } - public Builder addIgnored(String path) { - Assert.checkNotNullParam("path", path); - ignored.add(path.split("\\.")); + Builder ignoredPath(String ignoredPath) { + Assert.checkNotNullParam("ignoredPath", ignoredPath); + ignoredPaths.add(ignoredPath.split("\\.")); return this; } - public Builder validateUnknown(boolean validateUnknown) { + Builder validateUnknown(boolean validateUnknown) { this.validateUnknown = validateUnknown; return this; } - public ConfigMappingProvider build() { + Builder registerDefaults(SmallRyeConfigBuilder configBuilder) { + this.configBuilder = configBuilder; + return this; + } + + ConfigMappingProvider build() { // We don't validate for MP ConfigProperties, so if all classes are MP ConfigProperties disable validation. boolean allConfigurationProperties = true; for (Class type : types) { @@ -1420,7 +291,37 @@ public ConfigMappingProvider build() { } if (allConfigurationProperties) { - this.validateUnknown = false; + validateUnknown = false; + } + + if (keys.isEmpty()) { + for (Map.Entry>> entry : roots.entrySet()) { + for (Class root : entry.getValue()) { + ConfigClassWithPrefix configClass = configClassWithPrefix(root, entry.getKey()); + keys(getProperties(configClass).get(configClass.getKlass()).get(configClass.getPrefix()).keySet()); + } + } + } + + if (names.isEmpty()) { + for (Map.Entry>> entry : roots.entrySet()) { + for (Class root : entry.getValue()) { + ConfigClassWithPrefix configClass = configClassWithPrefix(root, entry.getKey()); + names(getNames(configClass)); + } + } + } + + if (configBuilder != null) { + Map defaultValues = configBuilder.getDefaultValues(); + for (Map.Entry>> entry : roots.entrySet()) { + for (Class root : entry.getValue()) { + for (Map.Entry defaultEntry : getDefaults(configClassWithPrefix(root, entry.getKey())) + .entrySet()) { + defaultValues.putIfAbsent(defaultEntry.getKey(), defaultEntry.getValue()); + } + } + } } return new ConfigMappingProvider(this); diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappings.java b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java index 55e9e9b8a..f274c52de 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappings.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java @@ -1,34 +1,27 @@ package io.smallrye.config; import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN; import static java.lang.Boolean.TRUE; import java.io.Serializable; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.eclipse.microprofile.config.inject.ConfigProperties; +import java.util.function.Function; +import io.smallrye.config.ConfigMapping.NamingStrategy; +import io.smallrye.config.ConfigMappingInterface.CollectionProperty; +import io.smallrye.config.ConfigMappingInterface.GroupProperty; +import io.smallrye.config.ConfigMappingInterface.LeafProperty; import io.smallrye.config.ConfigMappingInterface.Property; -import io.smallrye.config._private.ConfigMessages; public final class ConfigMappings implements Serializable { private static final long serialVersionUID = -7790784345796818526L; - private final ConfigValidator configValidator; - private final ConcurrentMap, Map> mappings; - - ConfigMappings(final ConfigValidator configValidator) { - this.configValidator = configValidator; - this.mappings = new ConcurrentHashMap<>(); - } - public static void registerConfigMappings(final SmallRyeConfig config, final Set configClasses) throws ConfigValidationException { if (!configClasses.isEmpty()) { @@ -45,86 +38,247 @@ public static void registerConfigProperties(final SmallRyeConfig config, final S } } - public static Map getProperties(final ConfigClassWithPrefix configClass) { - ConfigMappingProvider provider = ConfigMappingProvider.builder() - .validateUnknown(false) - .addRoot(configClass.getPrefix(), configClass.getKlass()) - .build(); + public static Map, Map>> getProperties(final ConfigClassWithPrefix configClass) { + Map, Map>> properties = new HashMap<>(); + Function path = new Function<>() { + @Override + public String apply(final String path) { + return configClass.getPrefix().isEmpty() && !path.isEmpty() ? path.substring(1) + : configClass.getPrefix() + path; + } + }; + ConfigMappingInterface configMapping = ConfigMappingLoader.getConfigMapping(configClass.getKlass()); + for (ConfigMappingInterface superType : configMapping.getSuperTypes()) { + getProperties(new GroupProperty(null, null, superType), configMapping.getNamingStrategy(), path, properties); + } + getProperties(new GroupProperty(null, null, configMapping), configMapping.getNamingStrategy(), path, properties); + return properties; + } - return provider.getProperties(); + public static Map>> getNames(final ConfigClassWithPrefix configClass) { + Map>> names = new HashMap<>(); + Map, Map>> properties = getProperties(configClass); + for (Map.Entry, Map>> entry : properties.entrySet()) { + Map> groups = new HashMap<>(); + for (Map.Entry> group : entry.getValue().entrySet()) { + groups.put(group.getKey(), group.getValue().keySet()); + } + names.put(entry.getKey().getName(), groups); + } + return names; } - public static Set mappedProperties(final ConfigClassWithPrefix configClass, final Set properties) { - ConfigMappingProvider provider = ConfigMappingProvider.builder() - .validateUnknown(false) - .addRoot(configClass.getPrefix(), configClass.getKlass()) - .build(); + public static Set getKeys(final ConfigClassWithPrefix configClass) { + return getProperties(configClass).get(configClass.getKlass()).get(configClass.getPrefix()).keySet(); + } + + public static Map getDefaults(final ConfigClassWithPrefix configClass) { + Function path = new Function<>() { + @Override + public String apply(final String path) { + return configClass.getPrefix().isEmpty() && !path.isEmpty() ? path.substring(1) + : configClass.getPrefix() + path; + } + }; + ConfigMappingInterface configMapping = ConfigMappingLoader.getConfigMapping(configClass.getKlass()); + Map defaults = new HashMap<>(); + + for (ConfigMappingInterface superType : configMapping.getSuperTypes()) { + Map, Map>> properties = new HashMap<>(); + getProperties(new GroupProperty(null, null, superType), configMapping.getNamingStrategy(), path, properties); + for (Map.Entry, Map>> mappingEntry : properties.entrySet()) { + for (Map.Entry> prefixEntry : mappingEntry.getValue().entrySet()) { + for (Map.Entry propertyEntry : prefixEntry.getValue().entrySet()) { + if (propertyEntry.getValue().hasDefaultValue()) { + defaults.put(propertyEntry.getKey(), propertyEntry.getValue().getDefaultValue()); + } + } + } + } + } + Map, Map>> properties = getProperties(configClass); + for (Map.Entry entry : properties.get(configClass.getKlass()).get(configClass.getPrefix()) + .entrySet()) { + if (entry.getValue().hasDefaultValue()) { + defaults.put(entry.getKey(), entry.getValue().getDefaultValue()); + } + } + return defaults; + } + + public static Set mappedProperties(final ConfigClassWithPrefix configClass, final Set properties) { + Set names = new ConfigMappingNames(getNames(configClass)) + .get(configClass.getKlass().getName(), configClass.getPrefix()); Set mappedProperties = new HashSet<>(); for (String property : properties) { - if (provider.getMatchActions().findRootValue(new NameIterator(property)) != null) { + if (names.contains(new PropertyName(property))) { mappedProperties.add(property); } } return mappedProperties; } - static void mapConfiguration( - final SmallRyeConfig config, - final ConfigMappingProvider.Builder builder) - throws ConfigValidationException { - mapConfiguration(config, builder, new HashSet<>()); - } - - static void mapConfiguration( + private static void mapConfiguration( final SmallRyeConfig config, final ConfigMappingProvider.Builder builder, final Set configClasses) throws ConfigValidationException { + DefaultValuesConfigSource defaultValues = (DefaultValuesConfigSource) config.getDefaultValues(); for (ConfigClassWithPrefix configClass : configClasses) { builder.addRoot(configClass.getPrefix(), configClass.getKlass()); + defaultValues.addDefaults( + getDefaults(configClassWithPrefix(getConfigMappingClass(configClass.getKlass()), configClass.getPrefix()))); } - ConfigMappingProvider mappingProvider = builder.build(); - mapConfiguration(config, mappingProvider); + config.getMappings().putAll(builder.build().mapConfiguration(config)); } - static void mapConfiguration(SmallRyeConfig config, ConfigMappingProvider mappingProvider) { - ConfigMappingContext mappingContext = mappingProvider.mapConfiguration(config); - config.getConfigMappings().mappings.putAll(mappingContext.getRootsMap()); - } + private static void getProperties( + final GroupProperty groupProperty, + final NamingStrategy namingStrategy, + final Function path, + final Map, Map>> properties) { - T getConfigMapping(Class type) { - final String prefix = Optional.ofNullable(type.getAnnotation(ConfigMapping.class)) - .map(ConfigMapping::prefix) - .orElseGet(() -> Optional.ofNullable(type.getAnnotation(ConfigProperties.class)).map(ConfigProperties::prefix) - .orElse("")); + ConfigMappingInterface groupType = groupProperty.getGroupType(); + Map groupProperties = properties + .computeIfAbsent(groupType.getInterfaceType(), group -> new HashMap<>()) + .computeIfAbsent(path.apply(""), s -> new HashMap<>()); - return getConfigMapping(type, prefix); + getProperties(groupProperty, namingStrategy, path, properties, groupProperties); } - T getConfigMapping(Class type, String prefix) { - if (prefix == null) { - return getConfigMapping(type); - } - - final Map mappingsForType = mappings.get(getConfigMappingClass(type)); - if (mappingsForType == null) { - throw ConfigMessages.msg.mappingNotFound(type.getName()); - } + private static void getProperties( + final GroupProperty groupProperty, + final NamingStrategy namingStrategy, + final Function path, + final Map, Map>> properties, + final Map groupProperties) { - final ConfigMappingObject configMappingObject = mappingsForType.get(prefix); - if (configMappingObject == null) { - throw ConfigMessages.msg.mappingPrefixNotFound(type.getName(), prefix); + for (Property property : groupProperty.getGroupType().getProperties()) { + getProperty(property, namingStrategy, path, properties, groupProperties); } + } - Object value = configMappingObject; - if (configMappingObject instanceof ConfigMappingClassMapper) { - value = ((ConfigMappingClassMapper) configMappingObject).map(); + private static void getProperty( + final Property property, + final NamingStrategy namingStrategy, + final Function path, + final Map, Map>> properties, + final Map groupProperties) { + + if (property.isLeaf()) { + groupProperties.put( + path.apply(property.isParentPropertyName() ? "" : "." + property.getPropertyName(namingStrategy)), + property); + } else if (property.isPrimitive()) { + groupProperties.put( + path.apply(property.isParentPropertyName() ? "" : "." + property.getPropertyName(namingStrategy)), + property); + } else if (property.isGroup()) { + GroupProperty groupProperty = property.asGroup(); + NamingStrategy groupNamingStrategy = groupProperty.hasNamingStrategy() ? groupProperty.getNamingStrategy() + : namingStrategy; + Function groupPath = new Function<>() { + @Override + public String apply(final String name) { + return property.isParentPropertyName() ? path.apply("") + name + : path.apply("." + property.getPropertyName(namingStrategy)) + name; + } + }; + getProperties(groupProperty, groupNamingStrategy, groupPath, properties); + getProperties(groupProperty, groupNamingStrategy, groupPath, properties, groupProperties); + } else if (property.isMap()) { + ConfigMappingInterface.MapProperty mapProperty = property.asMap(); + if (mapProperty.getValueProperty().isLeaf()) { + groupProperties.put(property.isParentPropertyName() ? path.apply(".*") + : path.apply("." + property.getPropertyName(namingStrategy) + ".*"), mapProperty); + if (mapProperty.hasKeyUnnamed()) { + groupProperties.put(property.isParentPropertyName() ? path.apply("") + : path.apply("." + property.getPropertyName(namingStrategy)), mapProperty); + } + } else if (mapProperty.getValueProperty().isGroup()) { + GroupProperty groupProperty = mapProperty.getValueProperty().asGroup(); + NamingStrategy groupNamingStrategy = groupProperty.hasNamingStrategy() ? groupProperty.getNamingStrategy() + : namingStrategy; + Function groupPath = new Function<>() { + @Override + public String apply(final String name) { + return property.isParentPropertyName() ? path.apply(".*") + name + : path.apply("." + mapProperty.getPropertyName(namingStrategy) + ".*") + name; + } + }; + getProperties(groupProperty, groupNamingStrategy, groupPath, properties); + getProperties(groupProperty, groupNamingStrategy, groupPath, properties, groupProperties); + if (mapProperty.hasKeyUnnamed()) { + Function unnamedGroupPath = new Function<>() { + @Override + public String apply(final String name) { + return property.isParentPropertyName() ? path.apply(name) + : path.apply("." + mapProperty.getPropertyName(namingStrategy)) + name; + } + }; + getProperties(groupProperty, groupNamingStrategy, unnamedGroupPath, properties); + getProperties(groupProperty, groupNamingStrategy, unnamedGroupPath, properties, groupProperties); + } + } else if (mapProperty.getValueProperty().isCollection()) { + CollectionProperty collectionProperty = mapProperty.getValueProperty().asCollection(); + Property element = collectionProperty.getElement(); + if (element.isLeaf()) { + LeafProperty leafProperty = new LeafProperty(element.getMethod(), element.getPropertyName(), + element.asLeaf().getValueType(), element.asLeaf().getConvertWith(), null); + getProperty(leafProperty, namingStrategy, new Function() { + @Override + public String apply(final String name) { + return path.apply(name + ".*"); + } + }, properties, groupProperties); + } + getProperty(element, namingStrategy, new Function() { + @Override + public String apply(final String name) { + return path.apply(name + ".*[*]"); + } + }, properties, groupProperties); + if (mapProperty.hasKeyUnnamed()) { + getProperty(element, namingStrategy, new Function() { + @Override + public String apply(final String name) { + return path.apply(name + "[*]"); + } + }, properties, groupProperties); + } + } else if (mapProperty.getValueProperty().isMap()) { + getProperty(mapProperty.getValueProperty(), namingStrategy, + new Function() { + @Override + public String apply(final String name) { + return path.apply(name + ".*"); + } + }, properties, groupProperties); + if (mapProperty.hasKeyUnnamed()) { + getProperty(mapProperty.getValueProperty(), namingStrategy, + new Function() { + @Override + public String apply(final String name) { + return path.apply(name); + } + }, properties, groupProperties); + } + } + } else if (property.isCollection()) { + CollectionProperty collectionProperty = property.asCollection(); + if (collectionProperty.getElement().isLeaf()) { + getProperty(collectionProperty.getElement(), namingStrategy, path, properties, groupProperties); + } + getProperty(collectionProperty.getElement(), namingStrategy, new Function() { + @Override + public String apply(final String name) { + return path.apply(name.endsWith(".*") ? name.substring(0, name.length() - 2) + "[*].*" : name + "[*]"); + } + }, properties, groupProperties); + } else if (property.isOptional()) { + getProperty(property.asOptional().getNestedProperty(), namingStrategy, path, properties, groupProperties); } - - configValidator.validateMapping(type, prefix, value); - - return type.cast(value); } static String getPrefix(Class type) { diff --git a/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java b/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java index bd5f5645c..b9c14cdf0 100644 --- a/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java @@ -11,7 +11,7 @@ public final class DefaultValuesConfigSource extends AbstractConfigSource { private final Map properties; - private final KeyMap wildcards; + private final Map wildcards; public DefaultValuesConfigSource(final Map properties) { this(properties, "DefaultValuesConfigSource", Integer.MIN_VALUE); @@ -20,7 +20,7 @@ public DefaultValuesConfigSource(final Map properties) { public DefaultValuesConfigSource(final Map properties, final String name, final int ordinal) { super(name, ordinal); this.properties = new HashMap<>(); - this.wildcards = new KeyMap<>(); + this.wildcards = new HashMap<>(); addDefaults(properties); } @@ -30,20 +30,20 @@ public Set getPropertyNames() { } public String getValue(final String propertyName) { - String value = properties.get(propertyName); - return value == null && !wildcards.isEmpty() ? wildcards.findRootValue(propertyName) : value; + return properties.getOrDefault(propertyName, wildcards.get(new PropertyName(propertyName))); } void addDefaults(final Map properties) { for (Map.Entry entry : properties.entrySet()) { - if (entry.getKey().indexOf('*') == -1) { - this.properties.putIfAbsent(entry.getKey(), entry.getValue()); - } else { - KeyMap key = this.wildcards.findOrAdd(entry.getKey()); - if (!key.hasRootValue()) { - key.putRootValue(entry.getValue()); - } - } + addDefault(entry.getKey(), entry.getValue()); + } + } + + void addDefault(final String name, final String value) { + if (name.indexOf('*') == -1) { + this.properties.putIfAbsent(name, value); + } else { + this.wildcards.put(new PropertyName(name), value); } } } diff --git a/implementation/src/main/java/io/smallrye/config/PropertyName.java b/implementation/src/main/java/io/smallrye/config/PropertyName.java new file mode 100644 index 000000000..e65d7f67f --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/PropertyName.java @@ -0,0 +1,107 @@ +package io.smallrye.config; + +import static io.smallrye.config.common.utils.StringUtil.isNumeric; + +public class PropertyName { + private final String name; + + public PropertyName(final String name) { + this.name = name; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PropertyName that = (PropertyName) o; + return equals(this.name, that.name) || equals(that.name, this.name); + } + + static boolean equals(final String name, final String other) { + //noinspection StringEquality + if (name == other) { + return true; + } + + char n; + char o; + + int matchPosition = name.length() - 1; + for (int i = other.length() - 1; i >= 0; i--) { + if (matchPosition == -1) { + return false; + } + + o = other.charAt(i); + n = name.charAt(matchPosition); + + if (n == '*') { + if (o == ']') { + return false; + } else if (o == '"') { + int beginQuote = other.lastIndexOf('"', i - 1); + if (beginQuote != -1 && beginQuote != 0 && other.charAt(beginQuote - 1) == '.') { + i = beginQuote; + } + } else { + int previousDot = other.lastIndexOf('.', i); + if (previousDot != -1) { + i = previousDot + 1; + } else { + i = 0; + } + } + } else if (n == ']' && o == ']') { + if (name.length() >= 3 && other.length() >= 3 + && name.charAt(matchPosition - 1) == '*' && name.charAt(matchPosition - 2) == '[' + && other.charAt(i - 1) == '*' && other.charAt(i - 2) == '[') { + matchPosition = matchPosition - 2; + i = i - 1; + continue; + } else { + int beginIndexed = other.lastIndexOf('[', i); + if (beginIndexed != -1) { + int range = i - beginIndexed - 1; + if (isNumeric(other, beginIndexed + range, i)) { + matchPosition = matchPosition - 3; + i = i - range - 1; + continue; + } + } + } + return false; + } else if (o != n) { + return false; + } + matchPosition--; + } + return matchPosition <= 0; + } + + @Override + public int hashCode() { + int h = 0; + int length = name.length(); + boolean quotesOpen = false; + for (int i = 0; i < length; i++) { + char c = name.charAt(i); + if (quotesOpen) { + if (c == '"') { + quotesOpen = false; + } + continue; + } else if (c == '"') { + quotesOpen = true; + continue; + } else if (c != '.' && c != '[' && c != ']') { + continue; + } + h = 31 * h + c; + } + return h; + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index cb44bb1de..532c1a272 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -15,10 +15,13 @@ */ package io.smallrye.config; +import static io.smallrye.config.ConfigMappingLoader.getConfigMappingClass; import static io.smallrye.config.ConfigSourceInterceptor.EMPTY; import static io.smallrye.config.Converters.newCollectionConverter; import static io.smallrye.config.Converters.newMapConverter; import static io.smallrye.config.Converters.newOptionalConverter; +import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores; +import static io.smallrye.config.common.utils.StringUtil.toLowerCaseAndDotted; import static io.smallrye.config.common.utils.StringUtil.unindexed; import static io.smallrye.config.common.utils.StringUtil.unquoted; @@ -44,6 +47,7 @@ import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.Converter; @@ -70,12 +74,16 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final ConfigMappings mappings; + private final ConfigValidator configValidator; + private final Map, Map> mappings; SmallRyeConfig(SmallRyeConfigBuilder builder) { + // This needs to be executed before everything else to make sure that defaults from mappings are available to all sources + ConfigMappingProvider mappingProvider = builder.getMappingsBuilder().build(); this.configSources = new ConfigSources(builder); this.converters = buildConverters(builder); - this.mappings = new ConfigMappings(builder.getValidator()); + this.configValidator = builder.getValidator(); + this.mappings = new ConcurrentHashMap<>(mappingProvider.mapConfiguration(this)); } private Map> buildConverters(final SmallRyeConfigBuilder builder) { @@ -135,14 +143,13 @@ public > C getIndexedValues(String name, Converter public List getIndexedProperties(final String property) { List indexedProperties = new ArrayList<>(); for (String propertyName : this.getPropertyNames()) { - if (propertyName.length() > property.length() && propertyName.startsWith(property)) { + if (propertyName.startsWith(property) && propertyName.length() > property.length()) { int indexStart = property.length(); if (propertyName.charAt(indexStart) == '[') { - int indexEnd = propertyName.indexOf(']', indexStart - 1); - if (indexEnd != -1) { - if (StringUtil.isNumeric(propertyName, indexStart + 1, indexEnd)) { - indexedProperties.add(propertyName); - } + int indexEnd = propertyName.indexOf(']', indexStart); + if (indexEnd != -1 && propertyName.charAt(propertyName.length() - 1) != '.' + && StringUtil.isNumeric(propertyName, indexStart + 1, indexEnd)) { + indexedProperties.add(propertyName); } } } @@ -155,10 +162,11 @@ public List getIndexedPropertiesIndexes(final String property) { Set indexes = new HashSet<>(); for (String propertyName : this.getPropertyNames()) { if (propertyName.startsWith(property) && propertyName.length() > property.length()) { - int indexStart = propertyName.indexOf('[', property.length()); - int indexEnd = propertyName.indexOf(']', indexStart); - if (indexStart != -1 && indexEnd != -1) { - if (StringUtil.isNumeric(propertyName, indexStart + 1, indexEnd)) { + int indexStart = property.length(); + if (propertyName.charAt(indexStart) == '[') { + int indexEnd = propertyName.indexOf(']', indexStart); + if (indexEnd != -1 && propertyName.charAt(propertyName.length() - 1) != '.' + && StringUtil.isNumeric(propertyName, indexStart + 1, indexEnd)) { indexes.add(Integer.parseInt(propertyName.substring(indexStart + 1, indexEnd))); } } @@ -173,16 +181,20 @@ public List getIndexedPropertiesIndexes(final String property) { * Return the content of the direct sub properties as the requested type of Map. * * @param name The configuration property name - * @param kClass the type into which the keys should be converted - * @param vClass the type into which the values should be converted + * @param keyClass the type into which the keys should be converted + * @param valueClass the type into which the values should be converted * @param the key type * @param the value type * @return the resolved property value as an instance of the requested Map (not {@code null}) * @throws IllegalArgumentException if a key or a value cannot be converted to the specified types * @throws NoSuchElementException if no direct sub properties could be found. */ - public Map getValues(String name, Class kClass, Class vClass) { - return getValues(name, requireConverter(kClass), requireConverter(vClass)); + public Map getValues(String name, Class keyClass, Class valueClass) { + return getValues(name, requireConverter(keyClass), requireConverter(valueClass)); + } + + public Map getValues(String name, Converter keyConverter, Converter valueConverter) { + return getValues(name, keyConverter, valueConverter, HashMap::new); } /** @@ -197,7 +209,11 @@ public Map getValues(String name, Class kClass, Class vClass) * @throws IllegalArgumentException if a key or a value cannot be converted to the specified types * @throws NoSuchElementException if no direct sub properties could be found. */ - public Map getValues(String name, Converter keyConverter, Converter valueConverter) { + public Map getValues( + String name, + Converter keyConverter, + Converter valueConverter, + IntFunction> mapFactory) { try { return getValue(name, newMapConverter(keyConverter, valueConverter)); } catch (NoSuchElementException e) { @@ -206,7 +222,7 @@ public Map getValues(String name, Converter keyConverter, Conver throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name)); } - Map map = new HashMap<>(mapKeys.size()); + Map map = mapFactory.apply(mapKeys.size()); for (Map.Entry entry : mapKeys.entrySet()) { map.put(convertValue(ConfigValue.builder().withName(entry.getKey()).withValue(entry.getKey()).build(), keyConverter), getValue(entry.getValue(), valueConverter)); @@ -215,13 +231,20 @@ public Map getValues(String name, Converter keyConverter, Conver } } - public > Map getValues(String name, Class kClass, Class vClass, + public > Map getValues( + String name, + Class keyClass, + Class valueClass, IntFunction collectionFactory) { - return getValues(name, requireConverter(kClass), requireConverter(vClass), collectionFactory); + return getValues(name, requireConverter(keyClass), requireConverter(valueClass), HashMap::new, collectionFactory); } - public > Map getValues(String name, Converter keyConverter, - Converter valueConverter, IntFunction collectionFactory) { + public > Map getValues( + String name, + Converter keyConverter, + Converter valueConverter, + IntFunction> mapFactory, + IntFunction collectionFactory) { try { return getValue(name, newMapConverter(keyConverter, newCollectionConverter(valueConverter, collectionFactory))); } catch (NoSuchElementException e) { @@ -230,7 +253,7 @@ public > Map getValues(String name, Converte throw new NoSuchElementException(ConfigMessages.msg.propertyNotFound(name)); } - Map map = new HashMap<>(mapCollectionKeys.size()); + Map map = mapFactory.apply(mapCollectionKeys.size()); for (Map.Entry entry : mapCollectionKeys.entrySet()) { map.put(convertValue(ConfigValue.builder().withName(entry.getKey()).withValue(entry.getKey()).build(), keyConverter), getValues(entry.getValue(), valueConverter, collectionFactory)); @@ -242,9 +265,10 @@ public > Map getValues(String name, Converte public Map getMapKeys(final String name) { Map mapKeys = new HashMap<>(); for (String propertyName : getPropertyNames()) { - if (propertyName.length() > name.length() && propertyName.charAt(name.length()) == '.' + if (propertyName.length() > name.length() + 1 + && (name.length() == 0 || propertyName.charAt(name.length()) == '.') && propertyName.startsWith(name)) { - String key = unquoted(unindexed(propertyName), name.length() + 1); + String key = unquoted(unindexed(propertyName), name.length() == 0 ? 0 : name.length() + 1); mapKeys.put(key, unindexed(propertyName)); } } @@ -422,16 +446,24 @@ public > Optional getIndexedOptionalValues(String * Return the content of the direct sub properties as the requested type of Map. * * @param name The configuration property name - * @param kClass the type into which the keys should be converted - * @param vClass the type into which the values should be converted + * @param keyClass the type into which the keys should be converted + * @param valueClass the type into which the values should be converted * @param the key type * @param the value type * @return the resolved property value as an instance of the requested Map (not {@code null}) * @throws IllegalArgumentException if a key or a value cannot be converted to the specified types */ - public Optional> getOptionalValues(String name, Class kClass, Class vClass) { - Optional> optionalValue = getOptionalValue(name, - newMapConverter(requireConverter(kClass), requireConverter(vClass))); + public Optional> getOptionalValues(String name, Class keyClass, Class valueClass) { + return getOptionalValues(name, requireConverter(keyClass), requireConverter(valueClass)); + } + + public Optional> getOptionalValues(String name, Converter keyConverter, Converter valueConverter) { + return getOptionalValues(name, keyConverter, valueConverter, HashMap::new); + } + + public Optional> getOptionalValues(String name, Converter keyConverter, Converter valueConverter, + IntFunction> mapFactory) { + Optional> optionalValue = getOptionalValue(name, newMapConverter(keyConverter, valueConverter)); if (optionalValue.isPresent()) { return optionalValue; } @@ -440,13 +472,26 @@ public Optional> getOptionalValues(String name, Class kClass if (mapKeys.isEmpty()) { return Optional.empty(); } - return Optional.of(getValues(name, kClass, vClass)); + return Optional.of(getValues(name, keyConverter, valueConverter, mapFactory)); } - public > Optional> getOptionalValues(String name, Class kClass, Class vClass, + public > Optional> getOptionalValues( + String name, + Class keyClass, + Class valueClass, + IntFunction collectionFactory) { + return getOptionalValues(name, requireConverter(keyClass), requireConverter(valueClass), HashMap::new, + collectionFactory); + } + + public > Optional> getOptionalValues( + String name, + Converter keyConverter, + Converter valueConverter, + IntFunction> mapFactory, IntFunction collectionFactory) { Optional> optionalValue = getOptionalValue(name, - newMapConverter(requireConverter(kClass), newCollectionConverter(requireConverter(vClass), collectionFactory))); + newMapConverter(keyConverter, newCollectionConverter(valueConverter, collectionFactory))); if (optionalValue.isPresent()) { return optionalValue; } @@ -455,19 +500,48 @@ public > Optional> getOptionalValues(Str if (mapKeys.isEmpty()) { return Optional.empty(); } - return Optional.of(getValues(name, kClass, vClass, collectionFactory)); + return Optional.of(getValues(name, keyConverter, valueConverter, mapFactory, collectionFactory)); } - public ConfigMappings getConfigMappings() { + Map, Map> getMappings() { return mappings; } public T getConfigMapping(Class type) { - return mappings.getConfigMapping(type); + String prefix; + if (type.isInterface()) { + ConfigMapping configMapping = type.getAnnotation(ConfigMapping.class); + prefix = configMapping != null ? configMapping.prefix() : ""; + } else { + ConfigProperties configProperties = type.getAnnotation(ConfigProperties.class); + prefix = configProperties != null ? configProperties.prefix() : ""; + } + return getConfigMapping(type, prefix); } public T getConfigMapping(Class type, String prefix) { - return mappings.getConfigMapping(type, prefix); + if (prefix == null) { + return getConfigMapping(type); + } + + Map mappingsForType = mappings.get(getConfigMappingClass(type)); + if (mappingsForType == null) { + throw ConfigMessages.msg.mappingNotFound(type.getName()); + } + + ConfigMappingObject configMappingObject = mappingsForType.get(prefix); + if (configMappingObject == null) { + throw ConfigMessages.msg.mappingPrefixNotFound(type.getName(), prefix); + } + + Object value = configMappingObject; + if (configMappingObject instanceof ConfigMappingClassMapper) { + value = ((ConfigMappingClassMapper) configMappingObject).map(); + } + + configValidator.validateMapping(type, prefix, value); + + return type.cast(value); } /** @@ -774,13 +848,41 @@ private static List mapLateSources( private static List getSources(final List sourceWithPriorities) { List configSources = new ArrayList<>(); + + List envSources = new ArrayList<>(); + List configuredProperties = new ArrayList<>(); + for (ConfigSourceWithPriority configSourceWithPriority : sourceWithPriorities) { ConfigSource source = configSourceWithPriority.getSource(); configSources.add(source); if (ConfigLogging.log.isDebugEnabled()) { ConfigLogging.log.loadedConfigSource(source.getName(), source.getOrdinal()); } + + if (source instanceof EnvConfigSource) { + envSources.add((EnvConfigSource) source); + } else { + Set propertyNames = source.getPropertyNames(); + if (propertyNames != null) { + configuredProperties.addAll(propertyNames); + } + } } + + // Match configured properties with Env with the same semantic meaning and use that one + for (EnvConfigSource envSource : envSources) { + for (String configuredProperty : configuredProperties) { + Set envNames = envSource.getPropertyNames(); + if (envSource.hasPropertyName(configuredProperty)) { + if (!envNames.contains(configuredProperty)) { + // this may be expensive, but it shouldn't happend that often + envNames.remove(toLowerCaseAndDotted(replaceNonAlphanumericByUnderscores(configuredProperty))); + envNames.add(configuredProperty); + } + } + } + } + return Collections.unmodifiableList(configSources); } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index f06d52a5c..f95f4ab80 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -82,6 +82,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private boolean addDiscoveredValidator = false; public SmallRyeConfigBuilder() { + withMappingDefaults(true); } public SmallRyeConfigBuilder addDiscoveredCustomizers() { @@ -175,13 +176,17 @@ ConfigValidator discoverValidator() { return ConfigValidator.EMPTY; } + ConfigMappingProvider.Builder getMappingsBuilder() { + return mappingsBuilder; + } + @Override public SmallRyeConfigBuilder addDefaultSources() { addDefaultSources = true; return this; } - protected List getDefaultSources() { + List getDefaultSources() { List defaultSources = new ArrayList<>(); defaultSources.add(new EnvConfigSource()); @@ -531,7 +536,7 @@ public SmallRyeConfigBuilder withMapping(Class klass, String prefix) { } public SmallRyeConfigBuilder withMappingIgnore(String path) { - mappingsBuilder.addIgnored(path); + mappingsBuilder.ignoredPath(path); return this; } @@ -541,6 +546,21 @@ public SmallRyeConfigBuilder withValidateUnknown(boolean validateUnknown) { return this; } + public SmallRyeConfigBuilder withMappingDefaults(boolean mappingDefaults) { + mappingsBuilder.registerDefaults(mappingDefaults ? this : null); + return this; + } + + public SmallRyeConfigBuilder withMappingNames(final Map>> names) { + mappingsBuilder.names(names); + return this; + } + + public SmallRyeConfigBuilder withMappingKeys(final Set keys) { + mappingsBuilder.keys(keys); + return this; + } + public SmallRyeConfigBuilder withValidator(ConfigValidator validator) { this.validator = validator; return this; @@ -697,13 +717,7 @@ public SmallRyeConfig build() { .sorted(Comparator.comparingInt(SmallRyeConfigBuilderCustomizer::priority)) .forEach(customizer -> customizer.configBuilder(SmallRyeConfigBuilder.this)); - ConfigMappingProvider mappingProvider = mappingsBuilder.build(); - for (Map.Entry entry : mappingProvider.getDefaultValues().entrySet()) { - defaultValues.putIfAbsent(entry.getKey(), entry.getValue()); - } - SmallRyeConfig config = new SmallRyeConfig(this); - ConfigMappings.mapConfiguration(config, mappingProvider); - return config; + return new SmallRyeConfig(this); } static class ConverterWithPriority { diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingClassTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingClassTest.java index a32867edf..8b2423502 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingClassTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingClassTest.java @@ -15,36 +15,36 @@ class ConfigMappingClassTest { @Test void toClass() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerClass.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerClass.class) .withDefaultValue("host", "localhost") .withDefaultValue("port", "8080") .build(); - final ServerClass server = config.getConfigMapping(ServerClass.class); + ServerClass server = config.getConfigMapping(ServerClass.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @Test void privateFields() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerPrivate.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerPrivate.class) .withDefaultValue("host", "localhost") .withDefaultValue("port", "8080") .build(); - final ServerPrivate server = config.getConfigMapping(ServerPrivate.class); + ServerPrivate server = config.getConfigMapping(ServerPrivate.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @Test void optionals() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerOptional.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerOptional.class) .withDefaultValue("host", "localhost") .withDefaultValue("port", "8080") .build(); - final ServerOptional server = config.getConfigMapping(ServerOptional.class); + ServerOptional server = config.getConfigMapping(ServerOptional.class); assertTrue(server.getHost().isPresent()); assertEquals("localhost", server.getHost().get()); assertTrue(server.getPort().isPresent()); @@ -53,38 +53,38 @@ void optionals() { @Test void names() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerNames.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerNames.class) .withDefaultValue("h", "localhost") .withDefaultValue("p", "8080") .build(); - final ServerNames server = config.getConfigMapping(ServerNames.class); + ServerNames server = config.getConfigMapping(ServerNames.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @Test void defaults() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerDefaults.class).build(); - final ServerDefaults server = config.getConfigMapping(ServerDefaults.class); + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerDefaults.class).build(); + ServerDefaults server = config.getConfigMapping(ServerDefaults.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @Test void converters() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerConverters.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerConverters.class) .withConverters(new Converter[] { new ServerPortConverter() }).build(); - final ServerConverters server = config.getConfigMapping(ServerConverters.class); + ServerConverters server = config.getConfigMapping(ServerConverters.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort().getPort()); } @Test void initialized() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerInitialized.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerInitialized.class) .withDefaultValue("host", "localhost") .build(); - final ServerInitialized server = config.getConfigMapping(ServerInitialized.class); + ServerInitialized server = config.getConfigMapping(ServerInitialized.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @@ -96,10 +96,10 @@ void initializedDefault() { @Test void mpConfig20() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerMPConfig20.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerMPConfig20.class) .withDefaultValue("name", "localhost") .build(); - final ServerMPConfig20 server = config.getConfigMapping(ServerMPConfig20.class); + ServerMPConfig20 server = config.getConfigMapping(ServerMPConfig20.class); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @@ -115,12 +115,12 @@ void empty() { @Test void camelCase() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerCamelCase.class) + SmallRyeConfig config = new SmallRyeConfigBuilder().withMapping(ServerCamelCase.class) .withDefaultValue("theHost", "localhost") .withDefaultValue("thePort", "8080") .build(); - final ServerCamelCase server = config.getConfigMapping(ServerCamelCase.class); + ServerCamelCase server = config.getConfigMapping(ServerCamelCase.class); assertEquals("localhost", server.getTheHost()); assertEquals(8080, server.getThePort()); } @@ -213,7 +213,7 @@ public ServerPort getPort() { static class ServerPort { private int port; - public ServerPort(final String port) { + public ServerPort(String port) { this.port = Integer.parseInt(port); } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java index 5adbfba9a..8e0c13dde 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java @@ -1,14 +1,13 @@ package io.smallrye.config; -import static io.smallrye.config.Converters.newCollectionConverter; import static io.smallrye.config.KeyValuesConfigSource.config; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -217,7 +216,7 @@ interface App { } @Test - void mappingCollectionsOptionals() throws Exception { + void mappingCollectionsOptionals() { SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerCollectionsOptionals.class, "server") .withSources(config( @@ -650,110 +649,290 @@ void mapWithListsAndParentName() { assertEquals("root", mapping.aliases().get("admin").alias().get(1)); } - @ConfigMapping(prefix = "maps") - public interface NestedMaps { - Map> values(); + @ConfigMapping(prefix = "map") + interface MapIndexedAndPlain { + @WithParentName + Map> map(); + } + + @Test + void mapIndexedAndPlain() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(MapIndexedAndPlain.class, "map") + .withSources(config( + "map.one[0]", "one", "map.one[1]", "1", + "map.two", "two,2")) + .build(); - Map>> roles(); + MapIndexedAndPlain mapping = config.getConfigMapping(MapIndexedAndPlain.class); - Map>> aliases(); + assertEquals("one", mapping.map().get("one").get(0)); + assertEquals("1", mapping.map().get("one").get(1)); + assertEquals("two", mapping.map().get("two").get(0)); + } - interface Aliases { - String name(); + @Test + void simpleMap() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(SimpleMap.class, "map") + .withSources(config("map.one", "value")) + .withSources(config("map.key-converter.key", "value")) + .withSources(config("map.value-converter.one", "something")) + .withSources(config("map.defaults.one", "value")) + .withSources(config("map.defaults-value-converter.one", "something")) + .build(); + + SimpleMap mapping = config.getConfigMapping(SimpleMap.class); + + assertEquals("value", mapping.map().get("one")); + assertNull(mapping.map().get("any")); + assertEquals("value", mapping.keyConverter().get("one")); + assertNull(mapping.keyConverter().get("any")); + assertEquals("value", mapping.valueConverter().get("one")); + assertNull(mapping.valueConverter().get("any")); + assertEquals("value", mapping.defaults().get("one")); + assertEquals("any", mapping.defaults().get("any")); + assertEquals("value", mapping.defaultsValueConverter().get("one")); + assertEquals("value", mapping.defaultsValueConverter().get("any")); + assertTrue(mapping.defaultsOnly().isEmpty()); + assertEquals("any", mapping.defaultsOnly().get("any")); + } + + @ConfigMapping(prefix = "map") + interface SimpleMap { + @WithParentName + Map map(); + + Map<@WithConverter(KeyConverter.class) String, String> keyConverter(); + + Map valueConverter(); + + @WithDefault("any") + Map defaults(); + + @WithDefault("any") + Map defaultsValueConverter(); + + @WithDefault("any") + Map defaultsOnly(); + + class KeyConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "one"; + } + } + + class ValueConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "value"; + } } } @Test void nestedMaps() { SmallRyeConfig config = new SmallRyeConfigBuilder() - .withMapping(NestedMaps.class) .withSources(config( - "maps.values.key1.nested-key1", "value")) + "nested.simple.one", "one")) + .withSources(config( + "nested.map.one.two", "one-two", + "nested.map.1.2", "1-2", + "nested.map.one.two.three", "one-two-three", + "nested.map.\"1.one\".\"2.two\"", "1.one-2.two")) .withSources(config( - "maps.roles.user.crud[0]", "read", - "maps.roles.user.crud[1]", "write")) + "nested.key-converter.\"1.one\".\"2.two\"", "1.one-2.two")) .withSources(config( - "maps.aliases.user.system[0].name", "username", - "maps.aliases.user.system[1].name", "login")) + "nested.key-implicit.1.1", "1-1", + "nested.key-implicit.1.\"2\"", "1-2")) + .withSources(config( + "nested.value-converter.one.one", "one-one", + "nested.value-converter.one.\"two\"", "one-two")) + .withSources(config( + "nested.defaults.one.one", "one-one", + "nested.defaults.one.\"two\"", "one-two")) + .withSources(config( + "nested.triple.one.two.one", "one-two-one", + "nested.triple.one.two.two", "one-two-two", + "nested.triple.one.two.three", "one-two-three", + "nested.triple.1.2.3", "1-2-3", + "nested.triple.\"1.one\".\"2.two\".\"3.three\"", "1.one-2.two-3.three")) + .withMapping(NestedMaps.class) .build(); - NestedMaps configMapping = config.getConfigMapping(NestedMaps.class); + NestedMaps mapping = config.getConfigMapping(NestedMaps.class); + + assertEquals("one", mapping.simple().get("one")); + + assertEquals("one-two", mapping.map().get("one").get("two")); + assertEquals("1-2", mapping.map().get("1").get("2")); + assertEquals("one-two-three", mapping.map().get("one").get("two.three")); + assertEquals("1.one-2.two", mapping.map().get("1.one").get("2.two")); + + assertEquals("1.one-2.two", mapping.keyConverter().get("one").get("one")); - assertEquals("value", configMapping.values().get("key1").get("nested-key1")); - assertEquals("read", configMapping.roles().get("user").get("crud").get(0)); - assertEquals("write", configMapping.roles().get("user").get("crud").get(1)); - assertEquals("username", configMapping.aliases().get("user").get("system").get(0).name()); - assertEquals("login", configMapping.aliases().get("user").get("system").get(1).name()); + assertEquals("1-1", mapping.keyImplicit().get(1).get(1)); + assertEquals("1-2", mapping.keyImplicit().get(1).get(2)); + + assertEquals("value", mapping.valueConverter().get("one").get("one")); + assertEquals("value", mapping.valueConverter().get("one").get("two")); + + assertEquals("one-one", mapping.defaults().get("one").get("one")); + assertEquals("one-two", mapping.defaults().get("one").get("two")); + assertEquals("any", mapping.defaults().get("one").get("three")); + // TODO - Add defaults for middle maps? + //assertEquals("any", mapping.defaults().get("any").get("any")); + + assertEquals("one-two-one", mapping.triple().get("one").get("two").get("one")); + assertEquals("one-two-two", mapping.triple().get("one").get("two").get("two")); + assertEquals("one-two-three", mapping.triple().get("one").get("two").get("three")); + assertEquals("1-2-3", mapping.triple().get("1").get("2").get("3")); + assertEquals("1.one-2.two-3.three", mapping.triple().get("1.one").get("2.two").get("3.three")); + + // TODO - Assert keys that do not complete the Map nesting levels } - @ConfigMapping(prefix = "map") - interface MapOfListWithConverter { - Map<@WithConverter(KeyConverter.class) String, @WithConverter(ListConverter.class) List> list(); + @ConfigMapping(prefix = "nested") + public interface NestedMaps { + Map simple(); - Map<@WithConverter(KeyConverter.class) String, Nested> nested(); + Map> map(); - interface Nested { - String value(); - } + Map<@WithConverter(KeyConverter.class) String, Map<@WithConverter(KeyConverter.class) String, String>> keyConverter(); + + Map> keyImplicit(); + + Map> valueConverter(); + + @WithDefault("any") + Map> defaults(); + + Map>> triple(); class KeyConverter implements Converter { @Override public String convert(final String value) throws IllegalArgumentException, NullPointerException { - if (value.equals("one")) { - return "1"; - } else if (value.equals("two")) { - return "2"; - } else { - throw new IllegalArgumentException(); - } + return "one"; } } - class ListConverter implements Converter> { - static final Converter> DELEGATE = newCollectionConverter(value -> value, ArrayList::new); - + class ValueConverter implements Converter { @Override - public List convert(final String value) throws IllegalArgumentException, NullPointerException { - return DELEGATE.convert(value); + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "value"; } } } @Test - void mapWithKeyAndListConverters() { + void simpleMapList() { SmallRyeConfig config = new SmallRyeConfigBuilder() - .withMapping(MapOfListWithConverter.class, "map") - .withSources(config("map.list.one", "one,1", "map.list.two", "two,2")) - .withSources(config("map.nested.one.value", "1234")) + .withMapping(SimpleMapList.class, "map") + .withSources(config("map.one[0]", "value")) + .withSources(config("map.key-converter.key[0]", "value")) + .withSources(config("map.value-converter.one[0]", "something")) + .withSources(config("map.defaults.one[0]", "value")) + .withSources(config("map.defaults-value-converter.one[0]", "something")) .build(); - MapOfListWithConverter mapping = config.getConfigMapping(MapOfListWithConverter.class); - assertEquals("one", mapping.list().get("1").get(0)); - assertEquals("1", mapping.list().get("1").get(1)); - assertEquals("two", mapping.list().get("2").get(0)); - assertEquals("2", mapping.list().get("2").get(1)); - assertEquals("1234", mapping.nested().get("1").value()); + SimpleMapList mapping = config.getConfigMapping(SimpleMapList.class); + + assertEquals("value", mapping.map().get("one").get(0)); + assertNull(mapping.map().get("any")); + assertEquals("value", mapping.keyConverter().get("one").get(0)); + assertNull(mapping.keyConverter().get("any")); + assertEquals("value", mapping.valueConverter().get("one").get(0)); + assertNull(mapping.valueConverter().get("any")); + assertEquals("value", mapping.defaults().get("one").get(0)); + assertEquals("any", mapping.defaults().get("any").get(0)); + assertEquals("something", mapping.defaults().get("any").get(1)); + assertEquals("value", mapping.defaultsValueConverter().get("one").get(0)); + assertEquals("value", mapping.defaultsValueConverter().get("any").get(0)); + assertTrue(mapping.defaultsOnly().isEmpty()); + assertEquals("any", mapping.defaultsOnly().get("any").get(0)); + assertEquals("something", mapping.defaultsOnly().get("any").get(1)); } @ConfigMapping(prefix = "map") - interface MapIndexedAndPlain { + interface SimpleMapList { @WithParentName Map> map(); + + Map<@WithConverter(KeyConverter.class) String, List> keyConverter(); + + Map> valueConverter(); + + @WithDefault("any,something") + Map> defaults(); + + @WithDefault("any") + Map> defaultsValueConverter(); + + @WithDefault("any,something") + Map> defaultsOnly(); + + class KeyConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "one"; + } + } + + class ValueConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "value"; + } + } } @Test - void mapIndexedAndPlain() { + void nestedMapsLists() { SmallRyeConfig config = new SmallRyeConfigBuilder() - .withMapping(MapIndexedAndPlain.class, "map") .withSources(config( - "map.one[0]", "one", "map.one[1]", "1", - "map.two", "two,2")) + "nested.simple.one[1]", "one[1]", + "nested.simple.one[0]", "one[0]")) + .withSources(config( + "nested.map.one[1].two", "one[1]-two", + "nested.map.one[0].two", "one[0]-two")) + .withSources(config( + "nested.key-converter.1[0].two", "one[0]-two")) + .withSources(config( + "nested.mixed.one[0].one[0].one", "one[0].one[0].one", + "nested.mixed.one[0].one[0].two", "one[0].one[0].two")) + .withMapping(NestedMapsLists.class) .build(); - MapIndexedAndPlain mapping = config.getConfigMapping(MapIndexedAndPlain.class); + NestedMapsLists mapping = config.getConfigMapping(NestedMapsLists.class); - assertEquals("one", mapping.map().get("one").get(0)); - assertEquals("1", mapping.map().get("one").get(1)); - assertEquals("two", mapping.map().get("two").get(0)); + assertEquals("one[0]", mapping.simple().get("one").get(0)); + assertEquals("one[1]", mapping.simple().get("one").get(1)); + + assertEquals("one[0]-two", mapping.map().get("one").get(0).get("two")); + assertEquals("one[1]-two", mapping.map().get("one").get(1).get("two")); + + assertEquals("one[0]-two", mapping.keyConverter().get("one").get(0).get("two")); + + assertEquals("one[0].one[0].one", mapping.mixed().get("one").get(0).get("one").get(0).get("one")); + assertEquals("one[0].one[0].two", mapping.mixed().get("one").get(0).get("one").get(0).get("two")); + } + + @ConfigMapping(prefix = "nested") + interface NestedMapsLists { + Map> simple(); + + Map>> map(); + + Map<@WithConverter(KeyConverter.class) String, List>> keyConverter(); + + Map>>>> mixed(); + + class KeyConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + return "one"; + } + } } } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingDefaultsTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingDefaultsTest.java new file mode 100644 index 000000000..ab9e0372b --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingDefaultsTest.java @@ -0,0 +1,604 @@ +package io.smallrye.config; + +import static io.smallrye.config.ConfigMappings.getDefaults; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; +import static io.smallrye.config.KeyValuesConfigSource.config; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; + +import org.junit.jupiter.api.Test; + +import io.smallrye.config.ConfigMappingDefaultsTest.DataSourcesJdbcBuildTimeConfig.DataSourceJdbcOuterNamedBuildTimeConfig; + +public class ConfigMappingDefaultsTest { + @Test + void defaults() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "defaults.list-nested[0].value", "value", + "defaults.parent.list-nested[0].value", "value")) + .withMapping(Defaults.class) + .build(); + + Defaults mapping = config.getConfigMapping(Defaults.class); + + assertEquals("value", mapping.value()); + assertEquals(10, mapping.primitive()); + assertTrue(mapping.optional().isPresent()); + assertEquals("value", mapping.optional().get()); + assertTrue(mapping.optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.list()); + assertEquals("value", mapping.map().get("default")); + + assertEquals("value", mapping.nested().value()); + assertEquals(10, mapping.nested().primitive()); + assertTrue(mapping.nested().optional().isPresent()); + assertEquals("value", mapping.nested().optional().get()); + assertTrue(mapping.nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.nested().list()); + assertEquals("value", mapping.nested().map().get("default")); + + assertTrue(mapping.optionalNested().isPresent()); + assertEquals("value", mapping.optionalNested().get().value()); + assertEquals(10, mapping.optionalNested().get().primitive()); + assertTrue(mapping.optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.optionalNested().get().optional().get()); + assertTrue(mapping.optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.optionalNested().get().list()); + assertEquals("value", mapping.optionalNested().get().map().get("default")); + + assertEquals("value", mapping.listNested().get(0).value()); + assertEquals(10, mapping.listNested().get(0).primitive()); + assertTrue(mapping.listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.listNested().get(0).optional().get()); + assertTrue(mapping.listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.listNested().get(0).list()); + assertEquals("value", mapping.listNested().get(0).map().get("default")); + + assertEquals("value", mapping.mapNested().get("default").value()); + assertEquals(10, mapping.mapNested().get("default").primitive()); + assertTrue(mapping.mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.mapNested().get("default").optional().get()); + assertTrue(mapping.mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.mapNested().get("default").list()); + assertEquals("value", mapping.mapNested().get("default").map().get("default")); + + assertEquals("value", mapping.parent().child().nested().value()); + assertEquals(10, mapping.parent().child().nested().primitive()); + assertTrue(mapping.parent().child().nested().optional().isPresent()); + assertEquals("value", mapping.parent().child().nested().optional().get()); + assertTrue(mapping.parent().child().nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().nested().list()); + assertEquals("value", mapping.parent().child().nested().map().get("default")); + + assertTrue(mapping.parent().child().optionalNested().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().value()); + assertEquals(10, mapping.parent().child().optionalNested().get().primitive()); + assertTrue(mapping.parent().child().optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().optional().get()); + assertTrue(mapping.parent().child().optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().optionalNested().get().list()); + assertEquals("value", mapping.parent().child().optionalNested().get().map().get("default")); + + assertEquals("value", mapping.parent().child().listNested().get(0).value()); + assertEquals(10, mapping.parent().child().listNested().get(0).primitive()); + assertTrue(mapping.parent().child().listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.parent().child().listNested().get(0).optional().get()); + assertTrue(mapping.parent().child().listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().listNested().get(0).list()); + assertEquals("value", mapping.parent().child().listNested().get(0).map().get("default")); + + assertEquals("value", mapping.parent().child().mapNested().get("default").value()); + assertEquals(10, mapping.parent().child().mapNested().get("default").primitive()); + assertTrue(mapping.parent().child().mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.parent().child().mapNested().get("default").optional().get()); + assertTrue(mapping.parent().child().mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().mapNested().get("default").list()); + assertEquals("value", mapping.parent().child().mapNested().get("default").map().get("default")); + } + + @Test + void emptyPrefix() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "list-nested[0].value", "value", + "parent.list-nested[0].value", "value")) + .withMapping(Defaults.class, "") + .build(); + + Defaults mapping = config.getConfigMapping(Defaults.class, ""); + + assertEquals("value", mapping.value()); + assertEquals(10, mapping.primitive()); + assertTrue(mapping.optional().isPresent()); + assertEquals("value", mapping.optional().get()); + assertTrue(mapping.optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.list()); + assertEquals("value", mapping.map().get("default")); + + assertEquals("value", mapping.nested().value()); + assertEquals(10, mapping.nested().primitive()); + assertTrue(mapping.nested().optional().isPresent()); + assertEquals("value", mapping.nested().optional().get()); + assertTrue(mapping.nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.nested().list()); + assertEquals("value", mapping.nested().map().get("default")); + + assertTrue(mapping.optionalNested().isPresent()); + assertEquals("value", mapping.optionalNested().get().value()); + assertEquals(10, mapping.optionalNested().get().primitive()); + assertTrue(mapping.optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.optionalNested().get().optional().get()); + assertTrue(mapping.optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.optionalNested().get().list()); + assertEquals("value", mapping.optionalNested().get().map().get("default")); + + assertEquals("value", mapping.listNested().get(0).value()); + assertEquals(10, mapping.listNested().get(0).primitive()); + assertTrue(mapping.listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.listNested().get(0).optional().get()); + assertTrue(mapping.listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.listNested().get(0).list()); + assertEquals("value", mapping.listNested().get(0).map().get("default")); + + assertEquals("value", mapping.mapNested().get("default").value()); + assertEquals(10, mapping.mapNested().get("default").primitive()); + assertTrue(mapping.mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.mapNested().get("default").optional().get()); + assertTrue(mapping.mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.mapNested().get("default").list()); + assertEquals("value", mapping.mapNested().get("default").map().get("default")); + + assertEquals("value", mapping.parent().child().nested().value()); + assertEquals(10, mapping.parent().child().nested().primitive()); + assertTrue(mapping.parent().child().nested().optional().isPresent()); + assertEquals("value", mapping.parent().child().nested().optional().get()); + assertTrue(mapping.parent().child().nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().nested().list()); + assertEquals("value", mapping.parent().child().nested().map().get("default")); + + assertTrue(mapping.parent().child().optionalNested().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().value()); + assertEquals(10, mapping.parent().child().optionalNested().get().primitive()); + assertTrue(mapping.parent().child().optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().optional().get()); + assertTrue(mapping.parent().child().optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().optionalNested().get().list()); + assertEquals("value", mapping.parent().child().optionalNested().get().map().get("default")); + + assertEquals("value", mapping.parent().child().listNested().get(0).value()); + assertEquals(10, mapping.parent().child().listNested().get(0).primitive()); + assertTrue(mapping.parent().child().listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.parent().child().listNested().get(0).optional().get()); + assertTrue(mapping.parent().child().listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().listNested().get(0).list()); + assertEquals("value", mapping.parent().child().listNested().get(0).map().get("default")); + + assertEquals("value", mapping.parent().child().mapNested().get("default").value()); + assertEquals(10, mapping.parent().child().mapNested().get("default").primitive()); + assertTrue(mapping.parent().child().mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.parent().child().mapNested().get("default").optional().get()); + assertTrue(mapping.parent().child().mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().mapNested().get("default").list()); + assertEquals("value", mapping.parent().child().mapNested().get("default").map().get("default")); + } + + @ConfigMapping(prefix = "defaults") + interface Defaults { + @WithDefault("value") + String value(); + + @WithDefault("10") + int primitive(); + + @WithDefault("value") + Optional optional(); + + @WithDefault("10") + OptionalInt optionalPrimitive(); + + @WithDefault("one,two") + List list(); + + @WithDefault("value") + Map map(); + + Nested nested(); + + Optional optionalNested(); + + List listNested(); + + @WithDefaults + Map mapNested(); + + Parent parent(); + + interface Nested { + @WithDefault("value") + String value(); + + @WithDefault("10") + int primitive(); + + @WithDefault("value") + Optional optional(); + + @WithDefault("10") + OptionalInt optionalPrimitive(); + + @WithDefault("one,two") + List list(); + + @WithDefault("value") + Map map(); + } + + interface Parent { + @WithParentName + Child child(); + } + + interface Child { + Nested nested(); + + Optional optionalNested(); + + List listNested(); + + @WithDefaults + Map mapNested(); + } + } + + @Test + void defaultsNamingStrategy() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "defaults.listNested[0].value", "value", + "defaults.parent.listNested[0].value", "value")) + .withMapping(DefaultsWithNamingStrategy.class) + .build(); + + DefaultsWithNamingStrategy mapping = config.getConfigMapping(DefaultsWithNamingStrategy.class); + + assertEquals("value", mapping.value()); + assertEquals(10, mapping.primitive()); + assertTrue(mapping.optional().isPresent()); + assertEquals("value", mapping.optional().get()); + assertTrue(mapping.optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.list()); + assertEquals("value", mapping.map().get("default")); + + assertEquals("value", mapping.nested().value()); + assertEquals(10, mapping.nested().primitive()); + assertTrue(mapping.nested().optional().isPresent()); + assertEquals("value", mapping.nested().optional().get()); + assertTrue(mapping.nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.nested().list()); + assertEquals("value", mapping.nested().map().get("default")); + + assertTrue(mapping.optionalNested().isPresent()); + assertEquals("value", mapping.optionalNested().get().value()); + assertEquals(10, mapping.optionalNested().get().primitive()); + assertTrue(mapping.optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.optionalNested().get().optional().get()); + assertTrue(mapping.optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.optionalNested().get().list()); + assertEquals("value", mapping.optionalNested().get().map().get("default")); + + assertEquals("value", mapping.listNested().get(0).value()); + assertEquals(10, mapping.listNested().get(0).primitive()); + assertTrue(mapping.listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.listNested().get(0).optional().get()); + assertTrue(mapping.listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.listNested().get(0).list()); + assertEquals("value", mapping.listNested().get(0).map().get("default")); + + assertEquals("value", mapping.mapNested().get("default").value()); + assertEquals(10, mapping.mapNested().get("default").primitive()); + assertTrue(mapping.mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.mapNested().get("default").optional().get()); + assertTrue(mapping.mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.mapNested().get("default").list()); + assertEquals("value", mapping.mapNested().get("default").map().get("default")); + + assertEquals("value", mapping.parent().child().nested().value()); + assertEquals(10, mapping.parent().child().nested().primitive()); + assertTrue(mapping.parent().child().nested().optional().isPresent()); + assertEquals("value", mapping.parent().child().nested().optional().get()); + assertTrue(mapping.parent().child().nested().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().nested().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().nested().list()); + assertEquals("value", mapping.parent().child().nested().map().get("default")); + + assertTrue(mapping.parent().child().optionalNested().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().value()); + assertEquals(10, mapping.parent().child().optionalNested().get().primitive()); + assertTrue(mapping.parent().child().optionalNested().get().optional().isPresent()); + assertEquals("value", mapping.parent().child().optionalNested().get().optional().get()); + assertTrue(mapping.parent().child().optionalNested().get().optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().optionalNested().get().optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().optionalNested().get().list()); + assertEquals("value", mapping.parent().child().optionalNested().get().map().get("default")); + + assertEquals("value", mapping.parent().child().listNested().get(0).value()); + assertEquals(10, mapping.parent().child().listNested().get(0).primitive()); + assertTrue(mapping.parent().child().listNested().get(0).optional().isPresent()); + assertEquals("value", mapping.parent().child().listNested().get(0).optional().get()); + assertTrue(mapping.parent().child().listNested().get(0).optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().listNested().get(0).optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().listNested().get(0).list()); + assertEquals("value", mapping.parent().child().listNested().get(0).map().get("default")); + + assertEquals("value", mapping.parent().child().mapNested().get("default").value()); + assertEquals(10, mapping.parent().child().mapNested().get("default").primitive()); + assertTrue(mapping.parent().child().mapNested().get("default").optional().isPresent()); + assertEquals("value", mapping.parent().child().mapNested().get("default").optional().get()); + assertTrue(mapping.parent().child().mapNested().get("default").optionalPrimitive().isPresent()); + assertEquals(10, mapping.parent().child().mapNested().get("default").optionalPrimitive().getAsInt()); + assertIterableEquals(List.of("one", "two"), mapping.parent().child().mapNested().get("default").list()); + assertEquals("value", mapping.parent().child().mapNested().get("default").map().get("default")); + } + + @ConfigMapping(prefix = "defaults", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM) + interface DefaultsWithNamingStrategy { + @WithDefault("value") + String value(); + + @WithDefault("10") + int primitive(); + + @WithDefault("value") + Optional optional(); + + @WithDefault("10") + OptionalInt optionalPrimitive(); + + @WithDefault("one,two") + List list(); + + @WithDefault("value") + Map map(); + + Nested nested(); + + Optional optionalNested(); + + List listNested(); + + @WithDefaults + Map mapNested(); + + Parent parent(); + + interface Nested { + @WithDefault("value") + String value(); + + @WithDefault("10") + int primitive(); + + @WithDefault("value") + Optional optional(); + + @WithDefault("10") + OptionalInt optionalPrimitive(); + + @WithDefault("one,two") + List list(); + + @WithDefault("value") + Map map(); + } + + interface Parent { + @WithParentName + Child child(); + } + + interface Child { + Nested nested(); + + Optional optionalNested(); + + List listNested(); + + @WithDefaults + Map mapNested(); + } + } + + @Test + void defaultsParentName() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(DefaultsParentName.class) + .build(); + + // TODO - Complete + } + + @ConfigMapping(prefix = "defaults") + interface DefaultsParentName { + @WithParentName + @WithDefault("value") + String value(); + } + + @Test + void moreDefaults() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(DataSourcesJdbcBuildTimeConfig.class) + .withMapping(DataSourcesJdbcRuntimeConfig.class) + .build(); + + DataSourcesJdbcBuildTimeConfig mapping = config.getConfigMapping(DataSourcesJdbcBuildTimeConfig.class); + DataSourceJdbcOuterNamedBuildTimeConfig mapDefault = mapping.dataSources().get(""); + assertNotNull(mapDefault); + assertTrue(mapDefault.jdbc().enabled()); + assertFalse(mapDefault.jdbc().tracing()); + assertFalse(mapDefault.jdbc().telemetry()); + } + + @ConfigMapping(prefix = "quarkus.datasource") + public interface DataSourcesJdbcBuildTimeConfig { + + @WithParentName + @WithDefaults + @WithUnnamedKey("") + Map dataSources(); + + interface DataSourceJdbcOuterNamedBuildTimeConfig { + DataSourceJdbcBuildTimeConfig jdbc(); + } + + interface DataSourceJdbcBuildTimeConfig { + @WithParentName + @WithDefault("true") + boolean enabled(); + + Optional driver(); + + Optional enableMetrics(); + + @WithDefault("false") + boolean tracing(); + + @WithDefault("false") + boolean telemetry(); + } + } + + @ConfigMapping(prefix = "quarkus.datasource") + public interface DataSourcesJdbcRuntimeConfig { + + DataSourceJdbcRuntimeConfig jdbc(); + + @WithParentName + @WithDefaults + Map namedDataSources(); + + interface DataSourceJdbcOuterNamedRuntimeConfig { + DataSourceJdbcRuntimeConfig jdbc(); + } + + interface DataSourceJdbcRuntimeConfig { + @WithDefault("0") + int minSize(); + + @WithDefault("20") + int maxSize(); + + @WithDefault("2M") + String backgroundValidationInterval(); + + @WithDefault("5S") + Optional acquisitionTimeout(); + + @WithDefault("5M") + String idleRemovalInterval(); + + @WithDefault("false") + boolean extendedLeakReport(); + + @WithDefault("false") + boolean flushOnClose(); + + @WithDefault("true") + boolean detectStatementLeaks(); + + @WithDefault("true") + boolean poolingEnabled(); + + DataSourceJdbcTracingRuntimeConfig tracing(); + + interface DataSourceJdbcTracingRuntimeConfig { + @WithDefault("false") + boolean traceWithActiveSpanOnly(); + } + } + } + + @Test + void parentDefaults() { + Map defaults = getDefaults(configClassWithPrefix(ExtendsBase.class)); + assertEquals(2, defaults.size()); + assertEquals("default", defaults.get("base.base")); + assertEquals("default", defaults.get("base.my-prop")); + } + + public interface Base { + @WithDefault("default") + String base(); + } + + @ConfigMapping(prefix = "base") + public interface ExtendsBase extends Base { + @WithDefault("default") + String myProp(); + } + + @Test + void mapDefaults() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(MapDefaults.class) + .build(); + + MapDefaults mapping = config.getConfigMapping(MapDefaults.class); + assertIterableEquals(List.of("foo", "bar"), mapping.map().get("any")); + + config = new SmallRyeConfigBuilder() + .withSources(config("map.defaults.any", "one,two")) + .withMapping(MapDefaults.class) + .build(); + + mapping = config.getConfigMapping(MapDefaults.class); + assertIterableEquals(List.of("one", "two"), mapping.map().get("any")); + + config = new SmallRyeConfigBuilder() + .withSources(config("map.defaults.any[0]", "one", "map.defaults.any[1]", "two")) + .withMapping(MapDefaults.class) + .build(); + + mapping = config.getConfigMapping(MapDefaults.class); + assertIterableEquals(List.of("one", "two"), mapping.map().get("any")); + } + + @ConfigMapping(prefix = "map.defaults") + interface MapDefaults { + @WithParentName + @WithDefault("foo,bar") + Map> map(); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java index e5dc7e925..4d7a271f7 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingInterfaceTest.java @@ -1,5 +1,6 @@ package io.smallrye.config; +import static io.smallrye.config.ConfigMapping.NamingStrategy.VERBATIM; import static io.smallrye.config.ConfigMappingInterfaceTest.HyphenatedEnumMapping.HyphenatedEnum.VALUE_ONE; import static io.smallrye.config.ConfigMappingInterfaceTest.MapKeyEnum.ClientId.NAF; import static io.smallrye.config.ConfigMappingInterfaceTest.MapKeyEnum.ClientId.SOS_DAH; @@ -10,6 +11,7 @@ import static java.util.stream.StreamSupport.stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -20,6 +22,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -41,38 +44,38 @@ class ConfigMappingInterfaceTest { @Test void configMapping() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Server.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")).build(); - final Server configProperties = config.getConfigMapping(Server.class, "server"); + Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @Test void noConfigMapping() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("server.host", "localhost", "server.port", "8080")).build(); - final NoSuchElementException exception = assertThrows(NoSuchElementException.class, + NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class, "server")); assertEquals("SRCFG00027: Could not find a mapping for " + Server.class.getName(), exception.getMessage()); } @Test void unregisteredConfigMapping() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("host", "localhost", "port", "8080")).build(); - final NoSuchElementException exception = assertThrows(NoSuchElementException.class, + NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class)); assertEquals("SRCFG00027: Could not find a mapping for " + Server.class.getName(), exception.getMessage()); } @Test void unregistedPrefix() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Server.class) .withSources(config("host", "localhost", "port", "8080")).build(); - final NoSuchElementException exception = assertThrows(NoSuchElementException.class, + NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class, "server")); assertEquals("SRCFG00028: Could not find a mapping for " + Server.class.getName() + " with prefix server", exception.getMessage()); @@ -80,10 +83,10 @@ void unregistedPrefix() { @Test void noPrefix() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Server.class) .withSources(config("host", "localhost", "port", "8080")).build(); - final Server configProperties = config.getConfigMapping(Server.class); + Server configProperties = config.getConfigMapping(Server.class); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -96,11 +99,11 @@ void unknownConfigElement() { @Test void ignorePropertiesInUnregisteredRoots() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Server.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) .build(); - final Server configProperties = config.getConfigMapping(Server.class, "server"); + Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -115,22 +118,22 @@ void ignoreSomeProperties() { "client.port", "8080", "client.name", "konoha")) .build(); - final Server server = config.getConfigMapping(Server.class, "server"); + Server server = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); - final Client client = config.getConfigMapping(Client.class, "client"); + Client client = config.getConfigMapping(Client.class, "client"); assertEquals("localhost", client.host()); assertEquals(8080, client.port()); } @Test void ignoreProperties() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .withMapping(Server.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")).build(); - final Server configProperties = config.getConfigMapping(Server.class, "server"); + Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -140,7 +143,7 @@ void validateUnknown() { assertThrows(ConfigValidationException.class, () -> new SmallRyeConfigBuilder().addDefaultSources().withMapping(Server.class).build()); - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .withMapping(Server.class) .withMapping(Server.class, "server") @@ -148,22 +151,19 @@ void validateUnknown() { .withSources(config("server.host", "localhost", "server.port", "8080", "host", "localhost", "port", "8080")) .build(); - final Server configProperties = config.getConfigMapping(Server.class); + Server configProperties = config.getConfigMapping(Server.class); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @Test void splitRoots() { - SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + .withMapping(SplitRootServerHostAndPort.class, "server") + .withMapping(SplitRootServerName.class, "server") .build(); - ConfigMappings.mapConfiguration( - config, ConfigMappingProvider.builder() - .addRoot("server", SplitRootServerHostAndPort.class) - .addRoot("server", SplitRootServerName.class)); - SplitRootServerHostAndPort server = config.getConfigMapping(SplitRootServerHostAndPort.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); @@ -174,25 +174,25 @@ void splitRoots() { @Test void splitRootsInConfig() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .withMapping(SplitRootServerHostAndPort.class, "server") .withMapping(SplitRootServerName.class, "server") .build(); - final SplitRootServerHostAndPort server = config.getConfigMapping(SplitRootServerHostAndPort.class, "server"); + SplitRootServerHostAndPort server = config.getConfigMapping(SplitRootServerHostAndPort.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); } @Test void subGroups() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .withMapping(ServerSub.class, "server") .build(); - final ServerSub server = config.getConfigMapping(ServerSub.class, "server"); + ServerSub server = config.getConfigMapping(ServerSub.class, "server"); assertEquals("localhost", server.subHostAndPort().host()); assertEquals(8080, server.subHostAndPort().port()); assertEquals("konoha", server.subName().name()); @@ -200,22 +200,17 @@ void subGroups() { @Test void types() { - final Map typesConfig = new HashMap() { - { - put("int", "9"); - put("long", "9999999999"); - put("float", "99.9"); - put("double", "99.99"); - put("char", "c"); - put("boolean", "true"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "int", "9", + "long", "9999999999", + "float", "99.9", + "double", "99.99", + "char", "c", + "boolean", "true")) .withMapping(SomeTypes.class) .build(); - final SomeTypes types = config.getConfigMapping(SomeTypes.class); + SomeTypes types = config.getConfigMapping(SomeTypes.class); assertEquals(9, types.intPrimitive()); assertEquals(9, types.intWrapper()); @@ -233,23 +228,18 @@ void types() { @Test void optionals() { - final Map typesConfig = new HashMap() { - { - put("server.host", "localhost"); - put("server.port", "8080"); - put("optional", "optional"); - put("optional.int", "9"); - put("info.name", "server"); - put("info.login", "login"); - put("info.password", "password"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "server.host", "localhost", + "server.port", "8080", + "optional", "optional", + "optional.int", "9", + "info.name", "server", + "info.login", "login", + "info.password", "password")) .withMapping(Optionals.class) .build(); - final Optionals optionals = config.getConfigMapping(Optionals.class); + Optionals optionals = config.getConfigMapping(Optionals.class); assertTrue(optionals.server().isPresent()); assertEquals("localhost", optionals.server().get().host()); @@ -260,7 +250,7 @@ void optionals() { assertTrue(optionals.optionalInt().isPresent()); assertEquals(9, optionals.optionalInt().getAsInt()); - assertFalse(optionals.info().isEmpty()); + assertEquals(1, optionals.info().size()); assertEquals("server", optionals.info().get("info").name()); assertTrue(optionals.info().get("info").login().isPresent()); assertEquals("login", optionals.info().get("info").login().get()); @@ -270,18 +260,11 @@ void optionals() { @Test void collectionTypes() { - final Map typesConfig = new HashMap() { - { - put("strings", "foo,bar"); - put("ints", "1,2,3"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("strings", "foo,bar", "ints", "1,2,3")) .withMapping(CollectionTypes.class) .build(); - final CollectionTypes types = config.getConfigMapping(CollectionTypes.class); + CollectionTypes types = config.getConfigMapping(CollectionTypes.class); assertEquals(Stream.of("foo", "bar").collect(toList()), types.listStrings()); assertEquals(Stream.of(1, 2, 3).collect(toList()), types.listInts()); @@ -289,24 +272,17 @@ void collectionTypes() { @Test void maps() { - final Map typesConfig = new HashMap() { - { - put("server.host", "localhost"); - put("server.port", "8080"); - - put("server.server.host", "localhost-server"); - put("server.server.port", "8080"); - - put("server.group.server.host", "localhost-group"); - put("server.group.server.port", "8080"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "server.host", "localhost", + "server.port", "8080", + "server.server.host", "localhost-server", + "server.server.port", "8080", + "server.group.server.host", "localhost-group", + "server.group.server.port", "8080")) .withMapping(Maps.class) .build(); - final Maps maps = config.getConfigMapping(Maps.class); + Maps maps = config.getConfigMapping(Maps.class); assertEquals("localhost", maps.server().get("host")); assertEquals(8080, Integer.valueOf(maps.server().get("port"))); @@ -320,24 +296,17 @@ void maps() { @Test void mapsEmptyPrefix() { - final Map typesConfig = new HashMap<>() { - { - put("host", "localhost"); - put("port", "8080"); - - put("server.host", "localhost"); - put("server.port", "8080"); - - put("group.server.host", "localhost"); - put("group.server.port", "8080"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "host", "localhost", + "port", "8080", + "server.host", "localhost", + "server.port", "8080", + "group.server.host", "localhost", + "group.server.port", "8080")) .withMapping(Maps.class, "") .build(); - final Maps maps = config.getConfigMapping(Maps.class, ""); + Maps maps = config.getConfigMapping(Maps.class, ""); assertEquals("localhost", maps.server().get("host")); assertEquals(8080, Integer.valueOf(maps.server().get("port"))); @@ -349,29 +318,13 @@ void mapsEmptyPrefix() { assertEquals(8080, maps.groupParentName().get("server").port()); } - @Test - void defaults() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withMapping(Defaults.class) - .build(); - final Defaults defaults = config.getConfigMapping(Defaults.class); - - assertEquals("foo", defaults.foo()); - assertEquals("bar", defaults.bar()); - assertEquals("foo", config.getRawValue("foo")); - - List propertyNames = stream(config.getPropertyNames().spliterator(), false).collect(toList()); - assertTrue(propertyNames.contains("foo")); - assertTrue(propertyNames.contains("bar")); - } - @Test void converters() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("foo", "notbar")) .withMapping(Converters.class) .build(); - final Converters converters = config.getConfigMapping(Converters.class); + Converters converters = config.getConfigMapping(Converters.class); assertEquals("bar", converters.foo()); assertTrue(converters.bprim()); @@ -385,23 +338,18 @@ void converters() { @Test void mix() { - final Map typesConfig = new HashMap() { - { - put("server.host", "localhost"); - put("server.port", "8080"); - put("server.name", "server"); - put("client.host", "clienthost"); - put("client.port", "80"); - put("client.name", "client"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "server.host", "localhost", + "server.port", "8080", + "server.name", "server", + "client.host", "clienthost", + "client.port", "80", + "client.name", "client")) .withMapping(ComplexSample.class) .build(); - final ComplexSample sample = config.getConfigMapping(ComplexSample.class); + ComplexSample sample = config.getConfigMapping(ComplexSample.class); assertEquals("localhost", sample.server().subHostAndPort().host()); assertEquals(8080, sample.server().subHostAndPort().port()); assertTrue(sample.client().isPresent()); @@ -411,19 +359,19 @@ void mix() { @Test void noDynamicValues() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Server.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")) .withSources(new MapBackedConfigSource("test", new HashMap<>(), Integer.MAX_VALUE) { private int counter = 1; @Override - public String getValue(final String propertyName) { + public String getValue(String propertyName) { return counter++ + ""; } }).build(); - final Server server = config.getConfigMapping(Server.class, "server"); + Server server = config.getConfigMapping(Server.class, "server"); assertNotEquals(config.getRawValue("server.port"), config.getRawValue("server.port")); assertEquals(server.port(), server.port()); @@ -431,10 +379,10 @@ public String getValue(final String propertyName) { @Test void mapClass() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerClass.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")).build(); - final ServerClass server = config.getConfigMapping(ServerClass.class, "server"); + ServerClass server = config.getConfigMapping(ServerClass.class, "server"); assertEquals("localhost", server.getHost()); assertEquals(8080, server.getPort()); } @@ -443,35 +391,35 @@ static class ServerClass { String host; int port; - public String getHost() { + String getHost() { return host; } - public int getPort() { + int getPort() { return port; } } @Test void configMappingAnnotation() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerAnnotated.class, "server") .withMapping(ServerAnnotated.class, "cloud") .withSources( config("server.host", "localhost", "server.port", "8080", "cloud.host", "cloud", "cloud.port", "9090")) .build(); - final ServerAnnotated server = config.getConfigMapping(ServerAnnotated.class, "server"); + ServerAnnotated server = config.getConfigMapping(ServerAnnotated.class, "server"); assertNotNull(server); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); - final ServerAnnotated cloud = config.getConfigMapping(ServerAnnotated.class); + ServerAnnotated cloud = config.getConfigMapping(ServerAnnotated.class); assertNotNull(cloud); assertEquals("cloud", cloud.host()); assertEquals(9090, cloud.port()); - final ServerAnnotated cloudNull = config.getConfigMapping(ServerAnnotated.class, null); + ServerAnnotated cloudNull = config.getConfigMapping(ServerAnnotated.class, null); assertNotNull(cloudNull); assertEquals("cloud", cloudNull.host()); assertEquals(9090, cloudNull.port()); @@ -479,12 +427,12 @@ void configMappingAnnotation() { @Test void prefixFromAnnotation() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerAnnotated.class) .withSources(config("cloud.host", "cloud", "cloud.port", "9090")) .build(); - final ServerAnnotated cloud = config.getConfigMapping(ServerAnnotated.class); + ServerAnnotated cloud = config.getConfigMapping(ServerAnnotated.class); assertNotNull(cloud); assertEquals("cloud", cloud.host()); assertEquals(9090, cloud.port()); @@ -492,12 +440,12 @@ void prefixFromAnnotation() { @Test void superTypes() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerChild.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")) .build(); - final ServerChild server = config.getConfigMapping(ServerChild.class, "server"); + ServerChild server = config.getConfigMapping(ServerChild.class, "server"); assertNotNull(server); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); @@ -505,12 +453,12 @@ void superTypes() { @Test void configValue() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerConfigValue.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")) .build(); - final ServerConfigValue server = config.getConfigMapping(ServerConfigValue.class, "server"); + ServerConfigValue server = config.getConfigMapping(ServerConfigValue.class, "server"); assertNotNull(server); assertEquals("localhost", server.host().getValue()); assertEquals(8080, Integer.valueOf(server.port().getValue())); @@ -565,7 +513,7 @@ interface ServerSubName { String name(); } - public interface SomeTypes { + interface SomeTypes { @WithName("int") int intPrimitive(); @@ -603,7 +551,7 @@ public interface SomeTypes { Boolean booleanWrapper(); } - public interface Optionals { + interface Optionals { Optional server(); Optional optional(); @@ -623,7 +571,7 @@ interface Info { } } - public interface CollectionTypes { + interface CollectionTypes { @WithName("strings") List listStrings(); @@ -632,7 +580,7 @@ public interface CollectionTypes { } @ConfigMapping(prefix = "server") - public interface Maps { + interface Maps { @WithParentName Map server(); @@ -642,14 +590,6 @@ public interface Maps { Map groupParentName(); } - public interface Defaults { - @WithDefault("foo") - String foo(); - - @WithDefault("bar") - String bar(); - } - public interface ComplexSample { ServerSub server(); @@ -739,34 +679,34 @@ public Double convert(String value) throws IllegalArgumentException, NullPointer } @ConfigMapping(prefix = "cloud") - public interface ServerAnnotated { + interface ServerAnnotated { String host(); int port(); } - public interface ServerParent { + interface ServerParent { String host(); } - public interface ServerChild extends ServerParent { + interface ServerChild extends ServerParent { int port(); } @ConfigMapping(prefix = "server") - public interface ServerConfigValue { + interface ServerConfigValue { ConfigValue host(); ConfigValue port(); } @ConfigMapping(prefix = "empty") - public interface Empty { + interface Empty { } @ConfigMapping(prefix = "server") - public interface MapsInGroup { + interface MapsInGroup { Info info(); interface Info { @@ -784,20 +724,15 @@ interface Data { @Test void mapsInGroup() { - final Map typesConfig = new HashMap() { - { - put("server.info.name", "naruto"); - put("server.info.values.name", "naruto"); - put("server.info.data.first.name", "naruto"); - } - }; - - final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(config(typesConfig)) + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "server.info.name", "naruto", + "server.info.values.name", "naruto", + "server.info.data.first.name", "naruto")) .withMapping(MapsInGroup.class) .build(); - final MapsInGroup mapping = config.getConfigMapping(MapsInGroup.class); + MapsInGroup mapping = config.getConfigMapping(MapsInGroup.class); assertNotNull(mapping); assertEquals("naruto", mapping.info().name()); assertEquals("naruto", mapping.info().values().get("name")); @@ -805,20 +740,20 @@ void mapsInGroup() { } @ConfigMapping(prefix = "server") - public interface ServerPrefix { + interface ServerPrefix { String host(); int port(); } @ConfigMapping(prefix = "server") - public interface ServerNamePrefix { + interface ServerNamePrefix { String host(); } @Test void prefixPropertyInRoot() { - final SmallRyeConfig config = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(ServerPrefix.class, "server") .withMapping(ServerPrefix.class, "cloud.server") .withMapping(ServerNamePrefix.class, "server") @@ -872,7 +807,7 @@ void prefixPropertyInRootUnknown() { } @ConfigMapping(prefix = "mapping.server.env") - public interface ServerMapEnv { + interface ServerMapEnv { @WithParentName Map info(); @@ -912,7 +847,7 @@ void mapEnv() { } @ConfigMapping(prefix = "server") - public interface ServerOptionalWithName { + interface ServerOptionalWithName { @WithName("a_server") Optional aServer(); @@ -940,7 +875,7 @@ void optionalWithName() { } @ConfigMapping(prefix = "server") - public interface ServerHierarchy extends Server { + interface ServerHierarchy extends Server { @WithParentName Named named(); @@ -973,7 +908,7 @@ void hierarchy() { } @ConfigMapping(prefix = "server") - public interface ServerExpandDefaults { + interface ServerExpandDefaults { @WithDefault("localhost") String host(); @@ -1123,7 +1058,7 @@ void path() { } @ConfigMapping(prefix = "map") - public interface DottedKeyInMap { + interface DottedKeyInMap { @WithParentName Map map(); } @@ -1142,7 +1077,7 @@ void properties() { // From https://github.com/quarkusio/quarkus/issues/20728 @ConfigMapping(prefix = "clients") - public interface BugsConfiguration { + interface BugsConfiguration { @WithParentName Map clients(); @@ -1193,7 +1128,7 @@ void mapWithMultipleGroupsAndSameMethodNames() { } @ConfigMapping(prefix = "defaults") - public interface DefaultMethods { + interface DefaultMethods { default String host() { return "localhost"; } @@ -1214,7 +1149,7 @@ void defaultMethods() { } @ConfigMapping(prefix = "defaults") - public interface DefaultKotlinMethods { + interface DefaultKotlinMethods { String host(); @WithDefault("8080") @@ -1265,7 +1200,7 @@ void defaultKotlinMethods() { } @ConfigMapping(prefix = "clients", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM) - public interface MapKeyEnum { + interface MapKeyEnum { enum ClientId { SOS_DAH, NAF @@ -1354,16 +1289,16 @@ class Range { private final Integer min; private final Integer max; - public Range(final Integer min, final Integer max) { + Range(final Integer min, final Integer max) { this.min = min; this.max = max; } - public Integer getMin() { + Integer getMin() { return min; } - public Integer getMax() { + Integer getMax() { return max; } } @@ -1395,7 +1330,7 @@ void listElementConverter() { } @ConfigMapping(prefix = "my-app.rest-config.my-client") - public interface MyRestClientConfig { + interface MyRestClientConfig { @WithParentName Optional client(); @@ -1587,9 +1522,11 @@ void nestedOptionalAndMaps() { SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(NestedOptionalMapGroup.class) .withSources(config("optional-map.enable", "true")) - .withSources(config("optional-map.map.filter.default.enable", "false", + .withSources(config( + "optional-map.map.filter.default.enable", "false", "optional-map.map.filter.get-jokes-uni.enable", "true")) - .withSources(config("optional-map.map.client.reaction-api.enable", "true", + .withSources(config( + "optional-map.map.client.reaction-api.enable", "true", "optional-map.map.client.setup-api.enable", "true")) .build(); @@ -1711,31 +1648,6 @@ interface Nested { } } - @Test - void ignorePartial() { - SmallRyeConfig config = new SmallRyeConfigBuilder() - .addDefaultInterceptors() - .withMapping(IgnorePartial.class) - .withSources(config("path.key", "value")) - .withSources(config("path.main.key", "value")) - .withSources(config("path.ignore.key", "")) - .withMappingIgnore("path.ignore.**") - .build(); - - IgnorePartial mapping = config.getConfigMapping(IgnorePartial.class); - - assertEquals("value", mapping.common().get("key")); - assertEquals("value", mapping.main().get("key")); - } - - @ConfigMapping(prefix = "path") - interface IgnorePartial { - @WithParentName - Map common(); - - Map main(); - } - @Test void withNameDotted() { SmallRyeConfig config = new SmallRyeConfigBuilder() @@ -1801,34 +1713,6 @@ interface Dotted { } } - @Test - void mapKeyLevels() { - SmallRyeConfig config = new SmallRyeConfigBuilder() - .addDefaultInterceptors() - .withMapping(MapKeyLevels.class) - .withSources(config("map.1.two.value", "value")) - .withSources(config("map.one.2.3.value", "value")) - .build(); - - MapKeyLevels mapping = config.getConfigMapping(MapKeyLevels.class); - - assertEquals("value", mapping.two().get(1L).get("two").value()); - assertEquals("value", mapping.three().get("one").get(2).get(3L).value()); - } - - @ConfigMapping(prefix = "map") - public interface MapKeyLevels { - @WithParentName - Map> two(); - - @WithParentName - Map>> three(); - - interface Value { - String value(); - } - } - @Test void optionalWithConverter() { SmallRyeConfig config = new SmallRyeConfigBuilder() @@ -1992,7 +1876,7 @@ void unnamedMapKeys() { "unnamed.triple-map.value", "unnamed", "unnamed.triple-map.one.three.value", "unnamed-2-3", "unnamed.triple-map.one.two.three.value", "triple", - "unnamed.map-list.value", "unnamed", + "unnamed.map-list[0].value", "unnamed", "unnamed.map-list.one[0].value", "one-0", "unnamed.map-list.one[1].value", "one-1", "unnamed.map-list.\"3.three\"[0].value", "3.three-0", @@ -2044,14 +1928,17 @@ interface Parent { @Test void explicitUnnamedMapKeys() { - SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder() + SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() .withMapping(UnnamedExplicitMapKeys.class) .withSources(config( "unnamed.map.value", "value", - "unnamed.map.unnamed.value", "explicit")); + "unnamed.map.unnamed.value", "explicit")) + .build(); - assertThrows(IllegalArgumentException.class, builder::build); + UnnamedExplicitMapKeys mapping = config.getConfigMapping(UnnamedExplicitMapKeys.class); + assertEquals(1, mapping.map().size()); + assertEquals("explicit", mapping.map().get("unnamed").value()); } @ConfigMapping(prefix = "unnamed") @@ -2066,17 +1953,19 @@ interface Nested { @Test void ambiguousMapping() { - assertThrows(IllegalStateException.class, - () -> new SmallRyeConfigBuilder().withMapping(AmbiguousMapping.class).build()); + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("ambiguous.value", "value")) + .withMapping(AmbiguousMapping.class).build(); + + AmbiguousMapping mapping = config.getConfigMapping(AmbiguousMapping.class); + assertEquals("value", mapping.value()); + assertEquals("value", mapping.nested().get(null).value()); } @ConfigMapping(prefix = "ambiguous") interface AmbiguousMapping { - // match `ambiguous.value` String value(); - // match `ambiguous.value` - // match `ambiguous.*.value` @WithParentName @WithUnnamedKey Map nested(); @@ -2218,7 +2107,6 @@ void mapDefaultsWithParentName() { SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultInterceptors() .withMapping(MapDefaultsWithParentName.class) - .withSources(config("map.nested.value", "value")) .build(); MapDefaultsWithParentName mapping = config.getConfigMapping(MapDefaultsWithParentName.class); @@ -2430,4 +2318,145 @@ interface InvalidKeys { List list(); } + + @Test + void mapWithParentName() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("value", "value", "key.value", "value")) + .withMapping(MapWithParentName.class) + .build(); + + MapWithParentName mapping = config.getConfigMapping(MapWithParentName.class); + + assertEquals("value", mapping.value()); + assertEquals("value", mapping.map().get("key").value()); + } + + @ConfigMapping + interface MapWithParentName { + String value(); + + @WithParentName + Map map(); + + interface Nested { + String value(); + } + } + + @Test + void superOptionals() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "markets.config.default.timezone", "Europe/Berlin", + "markets.config.default.activeFrom", "2099-12-31T08:00", + "markets.config.default.disabledFrom", "2099-12-31T08:00", + "markets.config.de.timezone", "Europe/Berlin", + "markets.config.de.activeFrom", "2099-12-31T08:00", + "markets.config.de.disabledFrom", "2099-12-31T08:00")) + .withMapping(MarketConfigChild.class) + .build(); + + MarketConfigChild mapping = config.getConfigMapping(MarketConfigChild.class); + + assertEquals(2, mapping.config().size()); + + MarketConfig.MarketConfiguration germanMarket = mapping.config().get("de"); + assertTrue(germanMarket.activeFrom().isPresent()); + assertTrue(germanMarket.disabledFrom().isPresent()); + assertTrue(germanMarket.timezone().isPresent()); + assertTrue(germanMarket.partners().isEmpty()); + } + + interface MarketConfig { + Map config(); + + interface MarketConfiguration { + Optional activeFrom(); + + Optional disabledFrom(); + + Optional timezone(); + + Optional> partners(); + } + } + + @ConfigMapping(prefix = "markets", namingStrategy = VERBATIM) + public interface MarketConfigChild extends MarketConfig { + + } + + @Test + void embeddedMapping() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "example.foo", "foo", + "example.config.map.a", "a", + "example.config.map.b", "b", + "example.config.foo", "bar", + "example.config.list", "foo,bar", + "example.config.nested.nested-name", "nested")) + .withMapping(Embedded.class) + .withMapping(Embeddable.class) + .build(); + + Embedded embedded = config.getConfigMapping(Embedded.class); + assertEquals("bar", embedded.foo()); + assertEquals("a", embedded.map().get("a")); + assertEquals("b", embedded.map().get("b")); + assertIterableEquals(List.of("foo", "bar"), embedded.list()); + assertEquals("nested", embedded.nested().nestedName()); + + Embeddable embeddable = config.getConfigMapping(Embeddable.class); + assertEquals("foo", embeddable.foo()); + assertEquals(embedded.map().get("a"), embeddable.config().map().get("a")); + assertEquals(embedded.map().get("b"), embeddable.config().map().get("b")); + assertIterableEquals(embedded.list(), embeddable.config().list()); + assertEquals(embedded.nested().nestedName(), embeddable.config().nested().nestedName()); + } + + @ConfigMapping(prefix = "example.config") + interface Embedded { + String foo(); + + Map map(); + + List list(); + + NestedConfig nested(); + + interface NestedConfig { + String nestedName(); + } + } + + @ConfigMapping(prefix = "example") + interface Embeddable { + String foo(); + + Embedded config(); + } + + @Test + void nestedMaps() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("auth.policy.shared1.roles.root", "admin,user")) + .withMapping(AuthRuntimeConfig.class) + .build(); + + AuthRuntimeConfig mapping = config.getConfigMapping(AuthRuntimeConfig.class); + + assertIterableEquals(List.of("admin", "user"), mapping.rolePolicy().get("shared1").roles().get("root")); + } + + @ConfigMapping(prefix = "auth") + interface AuthRuntimeConfig { + @WithName("policy") + Map rolePolicy(); + + interface PolicyConfig { + Map> roles(); + } + } } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingNamingStrategyTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingNamingStrategyTest.java index fb162c3e9..f85428155 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingNamingStrategyTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingNamingStrategyTest.java @@ -1,5 +1,8 @@ package io.smallrye.config; +import static io.smallrye.config.ConfigMapping.NamingStrategy.KEBAB_CASE; +import static io.smallrye.config.ConfigMapping.NamingStrategy.SNAKE_CASE; +import static io.smallrye.config.ConfigMapping.NamingStrategy.VERBATIM; import static io.smallrye.config.KeyValuesConfigSource.config; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -35,8 +38,8 @@ void namingStrategy() { assertTrue(snake.log().enabled()); } - @ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM) - public interface ServerVerbatimNamingStrategy { + @ConfigMapping(prefix = "server", namingStrategy = VERBATIM) + interface ServerVerbatimNamingStrategy { String theHost(); int thePort(); @@ -48,8 +51,8 @@ interface Log { } } - @ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.SNAKE_CASE) - public interface ServerSnakeNamingStrategy { + @ConfigMapping(prefix = "server", namingStrategy = SNAKE_CASE) + interface ServerSnakeNamingStrategy { String theHost(); int thePort(); @@ -100,8 +103,8 @@ void composedNamingStrategy() { assertEquals("log", kebab.theLog().logAppenders().get(0).logName()); } - @ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.SNAKE_CASE) - public interface ServerComposedSnakeNaming { + @ConfigMapping(prefix = "server", namingStrategy = SNAKE_CASE) + interface ServerComposedSnakeNaming { String theHost(); int thePort(); @@ -109,8 +112,8 @@ public interface ServerComposedSnakeNaming { LogInheritedNaming theLog(); } - @ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM) - public interface ServerComposedVerbatimNaming { + @ConfigMapping(prefix = "server", namingStrategy = VERBATIM) + interface ServerComposedVerbatimNaming { String theHost(); int thePort(); @@ -123,7 +126,7 @@ public interface ServerComposedVerbatimNaming { } @ConfigMapping(prefix = "server") - public interface ServerComposedKebabNaming { + interface ServerComposedKebabNaming { String theHost(); int thePort(); @@ -131,7 +134,7 @@ public interface ServerComposedKebabNaming { LogInheritedNaming theLog(); } - public interface LogInheritedNaming { + interface LogInheritedNaming { boolean isEnabled(); List logAppenders(); @@ -159,12 +162,12 @@ void interfaceAndClass() { } @ConfigProperties(prefix = "server") - public static class ConfigPropertiesNamingVerbatim { + static class ConfigPropertiesNamingVerbatim { String theHost; } @ConfigMapping(prefix = "server") - public interface ConfigMappingNamingKebab { + interface ConfigMappingNamingKebab { String theHost(); @WithParentName @@ -176,8 +179,8 @@ interface Form { } // From https://github.com/quarkusio/quarkus/issues/21407 - @ConfigMapping(prefix = "bugs", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM) - public interface NamingStrategyVerbatimOptionalGroup { + @ConfigMapping(prefix = "bugs", namingStrategy = VERBATIM) + interface NamingStrategyVerbatimOptionalGroup { @WithParentName Map bugs(); @@ -197,7 +200,7 @@ interface Properties { String scriptSelector(); } - @ConfigMapping(namingStrategy = ConfigMapping.NamingStrategy.KEBAB_CASE) + @ConfigMapping(namingStrategy = KEBAB_CASE) interface OverrideNamingStrategyProperties { String feed(); @@ -232,4 +235,52 @@ void namingStrategyVerbatimOptionalGroup() { assertEquals("36936471", mapping.bugs().get("KEY1").override().get().customerId()); assertEquals("RoadRunner_Task1_AAR01", mapping.bugs().get("KEY1").override().get().scriptSelector()); } + + @Test + void namingStrategyDefaults() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(NamingStrategyDefaults.class) + .build(); + + NamingStrategyDefaults mapping = config.getConfigMapping(NamingStrategyDefaults.class); + + assertEquals("value", mapping.verbatimDefault()); + assertEquals("value", mapping.kebabDefaults().kebabDefault()); + assertEquals("value", mapping.snakeDefaults().snakeDefault()); + assertEquals("value", mapping.verbatimDefaults().verbatimDefault()); + + assertEquals("value", config.getRawValue("defaults.verbatimDefault")); + assertEquals("value", config.getRawValue("defaults.kebabDefaults.kebab-default")); + assertEquals("value", config.getRawValue("defaults.snakeDefaults.snake_default")); + assertEquals("value", config.getRawValue("defaults.verbatimDefaults.verbatimDefault")); + } + + @ConfigMapping(prefix = "defaults", namingStrategy = VERBATIM) + interface NamingStrategyDefaults { + @WithDefault("value") + String verbatimDefault(); + + KebabDefaults kebabDefaults(); + + SnakeDefaults snakeDefaults(); + + VerbatimDefaults verbatimDefaults(); + + @ConfigMapping(namingStrategy = KEBAB_CASE) + interface KebabDefaults { + @WithDefault("value") + String kebabDefault(); + } + + @ConfigMapping(namingStrategy = SNAKE_CASE) + interface SnakeDefaults { + @WithDefault("value") + String snakeDefault(); + } + + interface VerbatimDefaults { + @WithDefault("value") + String verbatimDefault(); + } + } } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingsTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingsTest.java index 6f8de00ea..2b2e5f99a 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingsTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingsTest.java @@ -15,8 +15,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.microprofile.config.inject.ConfigProperties; import org.eclipse.microprofile.config.spi.Converter; @@ -93,6 +91,7 @@ void registerProperties() { @Test void validate() { SmallRyeConfig config = new SmallRyeConfigBuilder() + .withConverter(Version.class, 100, new VersionConverter()) .withSources(config("server.host", "localhost", "server.port", "8080", "server.unmapped", "unmapped")) .build(); @@ -112,6 +111,7 @@ void validateWithBuilderOrConfig() { SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("server.host", "localhost", "server.port", "8080", "server.unmapped", "unmapped")) .withMapping(ServerClass.class, "server") + .withConverter(Version.class, 100, new VersionConverter()) .withValidateUnknown(true) .withDefaultValue(SmallRyeConfig.SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, "false") .build(); @@ -127,6 +127,7 @@ void validateDisableOnConfigProperties() { SmallRyeConfig config = new SmallRyeConfigBuilder() .withSources(config("server.host", "localhost", "server.port", "8080", "server.unmapped", "unmapped")) .withMapping(ServerClass.class, "server") + .withConverter(Version.class, 100, new VersionConverter()) .build(); ServerClass server = config.getConfigMapping(ServerClass.class); @@ -279,22 +280,19 @@ interface Nested { @Test void properties() { - Map properties = ConfigMappings.getProperties(configClassWithPrefix(MappedProperties.class)); + ConfigMappings.ConfigClassWithPrefix configClass = configClassWithPrefix(MappedProperties.class); + Map properties = ConfigMappings.getProperties(configClass).get(configClass.getKlass()) + .get(configClass.getPrefix()); assertEquals(3, properties.size()); } @Test void mappedProperties() { - Set mappedProperties = mappedProperties(MappedProperties.class, "mapped.value", "mapped.nested.value", - "mapped.collection[0].value", "mapped.unknown"); + Set mappedProperties = ConfigMappings.mappedProperties(configClassWithPrefix(MappedProperties.class), + Set.of("mapped.value", "mapped.nested.value", "mapped.collection[0].value", "mapped.unknown")); assertEquals(3, mappedProperties.size()); assertTrue(mappedProperties.contains("mapped.value")); assertTrue(mappedProperties.contains("mapped.nested.value")); assertTrue(mappedProperties.contains("mapped.collection[0].value")); } - - private static Set mappedProperties(final Class mappingClass, final String... properties) { - return ConfigMappings.mappedProperties(configClassWithPrefix(mappingClass), - Stream.of(properties).collect(Collectors.toSet())); - } } diff --git a/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java b/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java new file mode 100644 index 000000000..9c13c5848 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java @@ -0,0 +1,604 @@ +package io.smallrye.config; + +import static io.smallrye.config.ConfigMappings.getNames; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; +import static io.smallrye.config.KeyValuesConfigSource.config; +import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +public class ObjectCreatorTest { + @Test + void objectCreator() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "unnamed.value", "unnamed", + "unnamed.key.value", "value")) + .withSources(config( + "list-map[0].key", "value", + "list-map[0].another", "another")) + .withSources(config( + "list-group[0].value", "value")) + .withSources(config( + "map-group.key.value", "value")) + .withSources(config( + "optional-group.value", "value")) + .withSources(config( + "group.value", "value")) + .withSources(config( + "value", "value")) + .withSources(config( + "optional-value", "value")) + .withSources(config( + "optional-list", "value")) + .withSources(config( + "optional-list-group[0].value", "value")) + .build(); + + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>(), + getNames(configClassWithPrefix(ObjectCreator.class))); + ObjectCreator mapping = new ObjectCreatorImpl(context); + + assertEquals(2, mapping.unnamed().size()); + assertEquals("unnamed", mapping.unnamed().get("unnamed").value()); + assertEquals("value", mapping.unnamed().get("key").value()); + + assertEquals("value", mapping.listMap().get(0).get("key")); + assertEquals("another", mapping.listMap().get(0).get("another")); + + assertEquals("value", mapping.listGroup().get(0).value()); + + assertEquals("value", mapping.mapGroup().get("key").value()); + + assertTrue(mapping.optionalGroup().isPresent()); + assertEquals("value", mapping.optionalGroup().get().value()); + assertTrue(mapping.optionalGroupMissing().isEmpty()); + assertEquals("value", mapping.group().value()); + + assertEquals("value", mapping.value()); + assertTrue(mapping.optionalValue().isPresent()); + assertEquals("value", mapping.optionalValue().get()); + assertTrue(mapping.optionalList().isPresent()); + assertEquals("value", mapping.optionalList().get().get(0)); + assertTrue(mapping.optionalListGroup().isPresent()); + assertEquals("value", mapping.optionalListGroup().get().get(0).value()); + assertTrue(mapping.optionalListGroupMissing().isEmpty()); + + assertTrue(context.getProblems().isEmpty()); + } + + @ConfigMapping + interface ObjectCreator { + @WithUnnamedKey + Map unnamed(); + + List> listMap(); + + List listGroup(); + + Map mapGroup(); + + Optional optionalGroup(); + + Optional optionalGroupMissing(); + + Nested group(); + + String value(); + + Optional optionalValue(); + + Optional> optionalList(); + + Optional> optionalListGroup(); + + Optional> optionalListGroupMissing(); + + interface Nested { + String value(); + } + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + static class ObjectCreatorImpl implements ObjectCreator { + Map unnamed; + List> listMap; + List listGroup; + Map mapGroup; + Optional optionalGroup; + Optional optionalGroupMissing; + Nested group; + String value; + Optional optionalValue; + Optional> optionalList; + Optional> optionalListGroup; + Optional> optionalListGroupMissing; + + @SuppressWarnings("unchecked") + public ObjectCreatorImpl(ConfigMappingContext context) { + StringBuilder sb = context.getStringBuilder(); + int length = sb.length(); + ConfigMapping.NamingStrategy ns = ConfigMapping.NamingStrategy.KEBAB_CASE; + + sb.append(ns.apply("unnamed")); + ConfigMappingContext.ObjectCreator> unnamed = context.new ObjectCreator>( + sb.toString()) + .map(String.class, null, "unnamed") + .lazyGroup(Nested.class); + this.unnamed = unnamed.get(); + sb.setLength(length); + + sb.append(ns.apply("list-map")); + ConfigMappingContext.ObjectCreator>> listMap = context.new ObjectCreator>>( + sb.toString()).collection(List.class).values(String.class, null, String.class, null, null); + this.listMap = listMap.get(); + sb.setLength(length); + + sb.append(ns.apply("list-group")); + ConfigMappingContext.ObjectCreator> listGroup = context.new ObjectCreator>(sb.toString()) + .collection(List.class).group(Nested.class); + this.listGroup = listGroup.get(); + sb.setLength(length); + + sb.append(ns.apply("map-group")); + ConfigMappingContext.ObjectCreator> mapGroup = context.new ObjectCreator>( + sb.toString()).map(String.class, null).lazyGroup(Nested.class); + this.mapGroup = mapGroup.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-group")); + ConfigMappingContext.ObjectCreator> optionalGroup = context.new ObjectCreator>( + sb.toString()).optionalGroup(Nested.class); + this.optionalGroup = optionalGroup.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-group-missing")); + ConfigMappingContext.ObjectCreator> optionalGroupMissing = context.new ObjectCreator>( + sb.toString()).optionalGroup(Nested.class); + this.optionalGroupMissing = optionalGroupMissing.get(); + sb.setLength(length); + + sb.append(ns.apply("group")); + ConfigMappingContext.ObjectCreator group = context.new ObjectCreator(sb.toString()) + .group(Nested.class); + this.group = group.get(); + sb.setLength(length); + + sb.append(ns.apply("value")); + ConfigMappingContext.ObjectCreator value = context.new ObjectCreator(sb.toString()) + .value(String.class, null); + this.value = value.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-value")); + ConfigMappingContext.ObjectCreator> optionalValue = context.new ObjectCreator>( + sb.toString()).optionalValue(String.class, null); + this.optionalValue = optionalValue.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-value")); + ConfigMappingContext.ObjectCreator>> optionalList = context.new ObjectCreator>>( + sb.toString()).optionalValues(String.class, null, List.class); + this.optionalList = optionalList.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-list-group")); + ConfigMappingContext.ObjectCreator>> optionalListGroup = context.new ObjectCreator>>( + sb.toString()).optionalCollection(List.class).group(Nested.class); + this.optionalListGroup = optionalListGroup.get(); + sb.setLength(length); + + sb.append(ns.apply("optional-list-group-missing")); + ConfigMappingContext.ObjectCreator>> optionalListGroupMissing = context.new ObjectCreator>>( + sb.toString()).optionalCollection(List.class).group(Nested.class); + this.optionalListGroupMissing = optionalListGroupMissing.get(); + sb.setLength(length); + } + + @Override + public Map unnamed() { + return unnamed; + } + + @Override + public List> listMap() { + return listMap; + } + + @Override + public List listGroup() { + return listGroup; + } + + @Override + public Map mapGroup() { + return mapGroup; + } + + @Override + public Optional optionalGroup() { + return optionalGroup; + } + + @Override + public Optional optionalGroupMissing() { + return optionalGroupMissing; + } + + @Override + public Nested group() { + return group; + } + + @Override + public String value() { + return value; + } + + @Override + public Optional optionalValue() { + return optionalValue; + } + + @Override + public Optional> optionalList() { + return optionalList; + } + + @Override + public Optional> optionalListGroup() { + return optionalListGroup; + } + + @Override + public Optional> optionalListGroupMissing() { + return optionalListGroupMissing; + } + } + + @Test + void optionalGroup() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "optional.value", "value")) + .build(); + + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>(), + getNames(configClassWithPrefix(OptionalGroup.class))); + OptionalGroup mapping = new OptionalGroupImpl(context); + + assertTrue(mapping.optional().isPresent()); + assertTrue(mapping.empty().isEmpty()); + + assertTrue(context.getProblems().isEmpty()); + } + + @ConfigMapping + interface OptionalGroup { + Optional optional(); + + Optional empty(); + + interface Nested { + String value(); + } + } + + static class OptionalGroupImpl implements OptionalGroup { + Optional optional; + Optional empty; + + public OptionalGroupImpl(ConfigMappingContext context) { + StringBuilder sb = context.getStringBuilder(); + int length = sb.length(); + ConfigMapping.NamingStrategy ns = ConfigMapping.NamingStrategy.KEBAB_CASE; + + sb.append(ns.apply("optional")); + ConfigMappingContext.ObjectCreator> optional = context.new ObjectCreator>( + sb.toString()).optionalGroup(Nested.class); + this.optional = optional.get(); + sb.setLength(length); + + sb.append(ns.apply("empty")); + ConfigMappingContext.ObjectCreator> empty = context.new ObjectCreator>( + sb.toString()).optionalGroup(Nested.class); + this.empty = empty.get(); + sb.setLength(length); + } + + @Override + public Optional optional() { + return optional; + } + + @Override + public Optional empty() { + return empty; + } + } + + @Test + void unnamedKeys() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(UnnamedKeys.class) + .withSources(config( + "unnamed.value", "unnamed", + "unnamed.key.value", "value")) + .build(); + + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>(), + getNames(configClassWithPrefix(UnnamedKeys.class))); + context.getStringBuilder().append("unnamed"); + + UnnamedKeys mapping = new UnnamedKeysImpl(context); + assertEquals("unnamed", mapping.map().get(null).value()); + assertEquals("value", mapping.map().get("key").value()); + + mapping = config.getConfigMapping(UnnamedKeys.class); + assertEquals("unnamed", mapping.map().get(null).value()); + assertEquals("value", mapping.map().get("key").value()); + + assertTrue(context.getProblems().isEmpty()); + } + + @ConfigMapping(prefix = "unnamed") + interface UnnamedKeys { + @WithUnnamedKey + @WithParentName + Map map(); + + interface Nested { + String value(); + } + } + + static class UnnamedKeysImpl implements UnnamedKeys { + Map map; + + public UnnamedKeysImpl(ConfigMappingContext context) { + StringBuilder sb = context.getStringBuilder(); + int length = sb.length(); + + ConfigMappingContext.ObjectCreator> map = context.new ObjectCreator>( + sb.toString()) + .map(String.class, null, "") + .lazyGroup(Nested.class); + this.map = map.get(); + sb.setLength(length); + } + + @Override + public Map map() { + return map; + } + } + + @Test + void mapDefaults() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(MapDefaults.class) + .withSources(config( + "map.defaults.one", "value")) + .withSources(config( + "map.defaults-nested.one.value", "value")) + .withSources(config( + "map.defaults-list.one[0].value", "value")) + .build(); + + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>(), + getNames(configClassWithPrefix(MapDefaults.class))); + context.getStringBuilder().append("map."); + MapDefaults mapping = new MapDefaultsImpl(context); + + assertEquals("value", mapping.defaults().get("one")); + assertEquals("default", mapping.defaults().get("default")); + assertEquals("default", mapping.defaults().get("something")); + + assertEquals("value", mapping.defaultsNested().get("one").value()); + assertEquals("default", mapping.defaultsNested().get("default").value()); + assertEquals("another", mapping.defaultsNested().get("default").another().another()); + + assertEquals("value", mapping.defaultsList().get("one").get(0).value()); + + assertTrue(context.getProblems().isEmpty()); + } + + @ConfigMapping(prefix = "map") + interface MapDefaults { + @WithDefault("default") + Map defaults(); + + @WithDefaults + Map defaultsNested(); + + @WithDefaults + Map> defaultsList(); + + interface Nested { + @WithDefault("default") + String value(); + + AnotherNested another(); + + interface AnotherNested { + @WithDefault("another") + String another(); + } + } + } + + @SuppressWarnings("unchecked") + static class MapDefaultsImpl implements MapDefaults { + Map defaults; + Map defaultsNested; + Map> defaultsList; + + public MapDefaultsImpl(ConfigMappingContext context) { + StringBuilder sb = context.getStringBuilder(); + int length = sb.length(); + ConfigMapping.NamingStrategy ns = ConfigMapping.NamingStrategy.KEBAB_CASE; + + sb.append(ns.apply("defaults")); + ConfigMappingContext.ObjectCreator> defaults = context.new ObjectCreator>( + sb.toString()) + .values(String.class, null, String.class, null, "default"); + this.defaults = defaults.get(); + sb.setLength(length); + + sb.append(ns.apply("defaults-nested")); + ConfigMappingContext.ObjectCreator> defaultsNested = context.new ObjectCreator>( + sb.toString()) + .map(String.class, null, null, new Supplier() { + @Override + public Nested get() { + sb.append(".*"); + Nested nested = context.constructGroup(Nested.class); + sb.setLength(length); + return nested; + } + }) + .lazyGroup(Nested.class); + this.defaultsNested = defaultsNested.get(); + sb.setLength(length); + + sb.append(ns.apply("defaults-list")); + ConfigMappingContext.ObjectCreator>> defaultsList = context.new ObjectCreator>>( + sb.toString()) + .map(String.class, null) + .collection(List.class) + .lazyGroup(Nested.class); + this.defaultsList = defaultsList.get(); + sb.setLength(length); + } + + @Override + public Map defaults() { + return defaults; + } + + @Override + public Map defaultsNested() { + return defaultsNested; + } + + @Override + public Map> defaultsList() { + return defaultsList; + } + } + + @Test + void namingStrategy() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "naming.nested_value.value", "value")) + .build(); + + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>(), + getNames(configClassWithPrefix(Naming.class))); + context.getStringBuilder().append("naming."); + Naming naming = new NamingImpl(context); + + assertEquals("value", naming.nestedValue().value()); + + assertTrue(context.getProblems().isEmpty()); + } + + @ConfigMapping(prefix = "naming", namingStrategy = ConfigMapping.NamingStrategy.SNAKE_CASE) + interface Naming { + Nested nestedValue(); + + interface Nested { + String value(); + } + } + + static class NamingImpl implements Naming { + Nested nestedValue; + + public NamingImpl(ConfigMappingContext context) { + ConfigMapping.NamingStrategy ns = ConfigMapping.NamingStrategy.SNAKE_CASE; + StringBuilder sb = context.getStringBuilder(); + int length = sb.length(); + + sb.append(ns.apply("nestedValue")); + ConfigMappingContext.ObjectCreator nestedValue = context.new ObjectCreator(sb.toString()) + .group(Nested.class); + this.nestedValue = nestedValue.get(); + sb.setLength(length); + } + + @Override + public Nested nestedValue() { + return nestedValue; + } + } + + @Test + void splitRootsRequiredGroup() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config(SMALLRYE_CONFIG_MAPPING_VALIDATE_UNKNOWN, "false")) + .withDefaultValue("nested.nested.something", "something") + .withMapping(SplitRootsRequiredGroup.class) + .build(); + + SplitRootsRequiredGroup mapping = config.getConfigMapping(SplitRootsRequiredGroup.class); + assertTrue(mapping.nested().isEmpty()); + } + + @ConfigMapping + interface SplitRootsRequiredGroup { + + Optional nested(); + + interface NestedOptional { + @WithName("x") + Optional nestedOpt(); + } + + interface Nested { + String value(); + } + } + + @Test + void hierarchy() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config( + "base.nested.base", "value", + "base.nested.value", "value")) + .withMapping(ExtendsBase.class) + .build(); + + ExtendsBase mapping = config.getConfigMapping(ExtendsBase.class); + + assertTrue(mapping.nested().isPresent()); + assertEquals("value", mapping.nested().get().base()); + assertEquals("value", mapping.nested().get().value()); + } + + public interface Base { + Optional nested(); + + interface NestedBase { + String base(); + } + + interface Nested extends NestedBase { + String value(); + } + } + + @ConfigMapping(prefix = "base") + public interface ExtendsBase extends Base { + + } +} diff --git a/implementation/src/test/java/io/smallrye/config/PropertyNameTest.java b/implementation/src/test/java/io/smallrye/config/PropertyNameTest.java new file mode 100644 index 000000000..73ad0d8f4 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/PropertyNameTest.java @@ -0,0 +1,52 @@ +package io.smallrye.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +class PropertyNameTest { + @Test + void mappingNameEquals() { + assertEquals(new PropertyName(new String("foo")), new PropertyName(new String("foo"))); + assertEquals(new PropertyName(new String("foo.bar")), new PropertyName(new String("foo.bar"))); + assertEquals(new PropertyName("foo.*"), new PropertyName("foo.bar")); + assertEquals(new PropertyName(new String("foo.*")), new PropertyName(new String("foo.*"))); + assertEquals(new PropertyName("*"), new PropertyName("foo")); + assertEquals(new PropertyName("foo"), new PropertyName("*")); + assertEquals(new PropertyName("foo.*.bar"), new PropertyName("foo.bar.bar")); + assertEquals(new PropertyName("foo.bar.bar"), new PropertyName("foo.*.bar")); + assertEquals(new PropertyName("foo.*.bar"), new PropertyName("foo.\"bar\".bar")); + assertEquals(new PropertyName("foo.\"bar\".bar"), new PropertyName("foo.*.bar")); + assertEquals(new PropertyName("foo.*.bar"), new PropertyName("foo.\"bar-baz\".bar")); + assertEquals(new PropertyName("foo.\"bar-baz\".bar"), new PropertyName("foo.*.bar")); + assertNotEquals(new PropertyName("foo.*.bar"), new PropertyName("foo.bar.baz")); + assertNotEquals(new PropertyName("foo.bar.baz"), new PropertyName("foo.*.bar")); + assertEquals(new PropertyName(new String("foo.bar[*]")), new PropertyName(new String("foo.bar[*]"))); + assertEquals(new PropertyName("foo.bar[*]"), new PropertyName("foo.bar[0]")); + assertEquals(new PropertyName("foo.bar[0]"), new PropertyName("foo.bar[*]")); + assertEquals(new PropertyName("foo.*[*]"), new PropertyName("foo.bar[0]")); + assertEquals(new PropertyName("foo.bar[0]"), new PropertyName("foo.*[*]")); + assertEquals(new PropertyName("foo.*[*]"), new PropertyName("foo.baz[1]")); + assertEquals(new PropertyName("foo.baz[1]"), new PropertyName("foo.*[*]")); + assertNotEquals(new PropertyName("foo.*[*]"), new PropertyName("foo.baz[x]")); + assertNotEquals(new PropertyName("foo.baz[x]"), new PropertyName("foo.*[*]")); + assertEquals(new PropertyName("foo.*[*].bar[*]"), new PropertyName("foo.baz[0].bar[0]")); + assertEquals(new PropertyName(new String("foo.*[*].bar[*]")), new PropertyName(new String("foo.*[*].bar[*]"))); + assertEquals(new PropertyName("foo.baz[0].bar[0]"), new PropertyName("foo.*[*].bar[*]")); + assertEquals(new PropertyName(new String("foo.baz[0].bar[0]")), new PropertyName(new String("foo.baz[0].bar[0]"))); + assertNotEquals(new PropertyName("foo.bar.baz[*]").hashCode(), new PropertyName("foo.bar.*").hashCode()); + assertNotEquals(new PropertyName("foo.bar.baz[*]"), new PropertyName("foo.bar.*")); + + assertEquals(new PropertyName("foo").hashCode(), new PropertyName("foo").hashCode()); + assertEquals(new PropertyName("foo.bar").hashCode(), new PropertyName("foo.bar").hashCode()); + assertEquals(new PropertyName("foo.*").hashCode(), new PropertyName("foo.bar").hashCode()); + assertEquals(new PropertyName("foo.*.bar").hashCode(), new PropertyName("foo.bar.bar").hashCode()); + assertEquals(new PropertyName("foo.*.bar").hashCode(), new PropertyName("foo.\"bar\".bar").hashCode()); + assertEquals(new PropertyName(new String("foo.\"bar\".bar")).hashCode(), + new PropertyName(new String("foo.\"bar\".bar")).hashCode()); + assertEquals(new PropertyName("foo.*.bar").hashCode(), new PropertyName("foo.\"bar-baz\".bar").hashCode()); + assertEquals(new PropertyName(new String("foo.\"bar-baz\".bar")).hashCode(), + new PropertyName(new String("foo.\"bar-baz\".bar")).hashCode()); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java index 158ec6930..cb0ae3889 100644 --- a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java @@ -1,5 +1,6 @@ package io.smallrye.config; +import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix; import static io.smallrye.config.KeyValuesConfigSource.config; import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; import static java.util.Collections.singletonMap; @@ -272,8 +273,7 @@ public Iterator iterateNames(final ConfigSourceInterceptorContext contex hierarchyCandidates.add("child." + name.substring(7)); } } - names.addAll(ConfigMappings.mappedProperties( - ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix(Child.class), hierarchyCandidates)); + names.addAll(ConfigMappings.mappedProperties(configClassWithPrefix(Child.class), hierarchyCandidates)); return names.iterator(); } }; diff --git a/implementation/src/test/java/io/smallrye/config/SmallRyeConfigTest.java b/implementation/src/test/java/io/smallrye/config/SmallRyeConfigTest.java index 35a71f48c..0801559c0 100644 --- a/implementation/src/test/java/io/smallrye/config/SmallRyeConfigTest.java +++ b/implementation/src/test/java/io/smallrye/config/SmallRyeConfigTest.java @@ -62,7 +62,7 @@ void getOptionalValuesConverter() { SmallRyeConfig config = new SmallRyeConfigBuilder().withSources(config("my.list", "1,2,3,4")).build(); Optional> values = config.getOptionalValues("my.list", config.getConverter(Integer.class).get(), - ArrayList::new); + (IntFunction>) value -> new ArrayList<>()); assertTrue(values.isPresent()); assertEquals(Arrays.asList(1, 2, 3, 4), values.get()); } diff --git a/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigMappingTest.java b/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigMappingTest.java index 41e352371..3e1882aea 100644 --- a/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigMappingTest.java +++ b/sources/yaml/src/test/java/io/smallrye/config/source/yaml/YamlConfigMappingTest.java @@ -1,19 +1,24 @@ package io.smallrye.config.source.yaml; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; +import org.eclipse.microprofile.config.spi.Converter; import org.junit.jupiter.api.Test; import io.smallrye.config.ConfigMapping; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; import io.smallrye.config.WithParentName; @@ -514,4 +519,269 @@ interface Nested { String value(); } } + + @Test + void unmapped() { + SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(new YamlConfigSource("yaml", + "http:\n" + + " server:\n" + + " name: server\n" + + " alias: server\n" + + " host: localhost\n" + + " port: 8080\n" + + " timeout: 60s\n" + + " io-threads: 200\n" + + " bytes: dummy\n" + + "\n" + + " form:\n" + + " login-page: login.html\n" + + " error-page: error.html\n" + + " landing-page: index.html\n" + + " positions:\n" + + " - 10\n" + + " - 20\n" + + "\n" + + " ssl:\n" + + " port: 8443\n" + + " certificate: certificate\n" + + "\n" + + " cors:\n" + + " origins:\n" + + " - host: some-server\n" + + " port: 9000\n" + + " - host: another-server\n" + + " port: 8000\n" + + " methods:\n" + + " - GET\n" + + " - POST\n" + + "\n" + + " log:\n" + + " period: P1D\n" + + " days: 10\n" + + "\n" + + "cloud:\n" + + " host: localhost\n" + + " port: 8080\n" + + " timeout: 60s\n" + + " io-threads: 200\n" + + "\n" + + " form:\n" + + " login-page: login.html\n" + + " error-page: error.html\n" + + " landing-page: index.html\n" + + "\n" + + " ssl:\n" + + " port: 8443\n" + + " certificate: certificate\n" + + "\n" + + " cors:\n" + + " origins:\n" + + " - host: some-server\n" + + " port: 9000\n" + + " - host: localhost\n" + + " port: 1\n" + + " methods:\n" + + " - GET\n" + + " - POST\n" + + "\n" + + " proxy:\n" + + " enable: true\n" + + " timeout: 20\n" + + "\n" + + " log:\n" + + " period: P1D\n" + + " days: 20\n" + + "\n" + + " info:\n" + + " name: Bond\n" + + " code: 007\n" + + " alias:\n" + + " - James\n" + + " admins:\n" + + " root:\n" + + " -\n" + + " username: root\n" + + " -\n" + + " username: super\n" + + " firewall:\n" + + " accepted:\n" + + " - 127.0.0.1\n" + + " - 8.8.8")) + .withMapping(Server.class) + .withMapping(Cloud.class) + .build(); + + Server server = config.getConfigMapping(Server.class); + assertTrue(server.name().isPresent()); + assertEquals("server", server.name().get()); + assertTrue(server.alias().isPresent()); + assertEquals("server", server.alias().get()); + assertEquals("localhost", server.host()); + assertEquals(8080, server.port()); + assertEquals("60s", server.timeout()); + assertEquals(200, server.threads()); + assertArrayEquals(new Server.ByteArrayConverter().convert("dummy"), server.bytes()); + assertEquals("login.html", server.form().get("form").loginPage()); + assertEquals("error.html", server.form().get("form").errorPage()); + assertEquals("index.html", server.form().get("form").landingPage()); + assertIterableEquals(List.of(10, 20), server.form().get("form").positions()); + assertTrue(server.ssl().isPresent()); + assertEquals(8443, server.ssl().get().port()); + assertEquals("certificate", server.ssl().get().certificate()); + assertIterableEquals(List.of("TLSv1.3", "TLSv1.2"), server.ssl().get().protocols()); + assertTrue(server.cors().isPresent()); + assertEquals("some-server", server.cors().get().origins().get(0).host()); + assertEquals(9000, server.cors().get().origins().get(0).port()); + assertEquals("another-server", server.cors().get().origins().get(1).host()); + assertEquals(8000, server.cors().get().origins().get(1).port()); + assertEquals("GET", server.cors().get().methods().get(0)); + assertEquals("POST", server.cors().get().methods().get(1)); + assertFalse(server.log().enabled()); + assertEquals(".log", server.log().suffix()); + assertTrue(server.log().rotate()); + assertEquals("P1D", server.log().period()); + assertEquals(10, server.log().days()); + assertEquals(Server.Log.Pattern.COMMON.name(), server.log().pattern()); + assertTrue(server.info().name().isEmpty()); + assertTrue(server.info().code().isEmpty()); + assertTrue(server.info().alias().isEmpty()); + assertTrue(server.info().admins().isEmpty()); + assertTrue(server.info().firewall().isEmpty()); + + Cloud cloud = config.getConfigMapping(Cloud.class); + assertTrue(cloud.server().ssl().isPresent()); + assertEquals(8443, cloud.server().ssl().get().port()); + assertEquals("certificate", cloud.server().ssl().get().certificate()); + } + + @ConfigMapping(prefix = "cloud") + public interface Cloud { + @WithParentName + Server server(); + } + + public interface Named { + Optional name(); + } + + public interface Alias extends Named { + Optional alias(); + } + + @ConfigMapping(prefix = "http.server") + public interface Server extends Alias { + String host(); + + int port(); + + String timeout(); + + @WithName("io-threads") + int threads(); + + @WithConverter(ByteArrayConverter.class) + byte[] bytes(); + + @WithParentName + Map form(); + + Optional ssl(); + + Optional proxy(); + + Optional cors(); + + Log log(); + + Info info(); + + interface Form { + String loginPage(); + + String errorPage(); + + String landingPage(); + + Optional cookie(); + + @WithDefault("1") + List positions(); + } + + interface Proxy { + boolean enable(); + + int timeout(); + } + + interface Log { + @WithDefault("false") + boolean enabled(); + + @WithDefault(".log") + String suffix(); + + @WithDefault("true") + boolean rotate(); + + @WithDefault("COMMON") + String pattern(); + + String period(); + + int days(); + + enum Pattern { + COMMON, + SHORT, + COMBINED, + LONG; + } + } + + interface Cors { + List origins(); + + List methods(); + + interface Origin { + String host(); + + int port(); + } + } + + interface Info { + Optional name(); + + OptionalInt code(); + + Optional> alias(); + + Map> admins(); + + Map> firewall(); + + interface Admin { + String username(); + } + } + + class ByteArrayConverter implements Converter { + @Override + public byte[] convert(String value) throws IllegalArgumentException, NullPointerException { + return value.getBytes(StandardCharsets.UTF_8); + } + } + } + + public interface Ssl { + int port(); + + String certificate(); + + @WithDefault("TLSv1.3,TLSv1.2") + List protocols(); + } } diff --git a/validator/src/main/java/io/smallrye/config/validator/BeanValidationConfigValidator.java b/validator/src/main/java/io/smallrye/config/validator/BeanValidationConfigValidator.java index b9fb46c65..836f75503 100644 --- a/validator/src/main/java/io/smallrye/config/validator/BeanValidationConfigValidator.java +++ b/validator/src/main/java/io/smallrye/config/validator/BeanValidationConfigValidator.java @@ -14,10 +14,10 @@ import jakarta.validation.Path; import jakarta.validation.Validator; +import io.smallrye.config.ConfigMapping.NamingStrategy; import io.smallrye.config.ConfigMappingInterface; import io.smallrye.config.ConfigMappingInterface.CollectionProperty; import io.smallrye.config.ConfigMappingInterface.MapProperty; -import io.smallrye.config.ConfigMappingInterface.NamingStrategy; import io.smallrye.config.ConfigMappingInterface.Property; import io.smallrye.config.ConfigValidationException; import io.smallrye.config.ConfigValidationException.Problem;