From 9aa768be0cc5f976c5b44d1883c9d993acb07322 Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Mon, 11 May 2020 10:09:30 -0500 Subject: [PATCH 01/42] Configuration mapper --- .../config/ConfigSerializationTest.java | 4 +- .../ProfileConfigSourceInterceptorTest.java | 10 +- mapper/pom.xml | 48 + .../smallrye/config/mapper/CompareWith.java | 23 + .../smallrye/config/mapper/ConfigMapping.java | 690 +++++++++++ .../config/mapper/ConfigurationInterface.java | 1040 +++++++++++++++++ .../config/mapper/ConfigurationObject.java | 8 + .../ConfigurationValidationException.java | 57 + .../smallrye/config/mapper/ConvertWith.java | 24 + .../io/smallrye/config/mapper/Debugging.java | 152 +++ .../io/smallrye/config/mapper/Default.java | 17 + .../mapper/DefaultValuesConfigSource.java | 30 + .../io/smallrye/config/mapper/KeyMap.java | 250 ++++ .../config/mapper/MappingContext.java | 226 ++++ .../io/smallrye/config/mapper/Matching.java | 22 + .../smallrye/config/mapper/MaxExclusive.java | 22 + .../smallrye/config/mapper/MaxInclusive.java | 22 + .../smallrye/config/mapper/MinExclusive.java | 22 + .../smallrye/config/mapper/MinInclusive.java | 22 + .../smallrye/config/mapper/NameIterator.java | 348 ++++++ .../io/smallrye/config/mapper/WithName.java | 22 + .../config/mapper/WithParentName.java | 16 + .../smallrye/config/mapper/MapperTests.java | 185 +++ pom.xml | 8 + 24 files changed, 3261 insertions(+), 7 deletions(-) create mode 100644 mapper/pom.xml create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/Debugging.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/Default.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/Matching.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/WithName.java create mode 100644 mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java create mode 100644 mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java b/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java index 858af9d28..b5efa2d33 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java @@ -23,7 +23,7 @@ public void serialize() { .withSources(ConfigValueConfigSourceWrapper.wrap(KeyValuesConfigSource.config("my.prop", "1"))) .build(); - assertEquals("1", config.getConfigValue("my.prop").getValue()); + assertEquals("1", config.getRawConfigValue("my.prop").getValue()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream)) { @@ -41,6 +41,6 @@ public void serialize() { } SmallRyeConfig serialized = (SmallRyeConfig) readObject; - assertEquals("1", serialized.getConfigValue("my.prop").getValue()); + assertEquals("1", serialized.getRawConfigValue("my.prop").getValue()); } } diff --git a/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java index efdd81862..992ce2b15 100644 --- a/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java @@ -24,8 +24,8 @@ public void profile() { assertEquals("2", config.getValue("my.prop", String.class)); - assertEquals("my.prop", config.getConfigValue("my.prop").getName()); - assertEquals("my.prop", config.getConfigValue("%prof.my.prop").getName()); + assertEquals("my.prop", config.getRawConfigValue("my.prop").getName()); + assertEquals("my.prop", config.getRawConfigValue("%prof.my.prop").getName()); } @Test @@ -169,8 +169,8 @@ public void propertyNames() { final SmallRyeConfig config = (SmallRyeConfig) buildConfig("my.prop", "1", "%prof.my.prop", "2", "%prof.prof.only", "1", SMALLRYE_PROFILE, "prof"); - assertEquals("2", config.getConfigValue("my.prop").getValue()); - assertEquals("1", config.getConfigValue("prof.only").getValue()); + assertEquals("2", config.getRawConfigValue("my.prop").getValue()); + assertEquals("1", config.getRawConfigValue("prof.only").getValue()); final List properties = StreamSupport.stream(config.getPropertyNames().spliterator(), false).collect(toList()); assertFalse(properties.contains("%prof.my.prop")); @@ -185,7 +185,7 @@ public void profileName() { .withProfile("prof") .build(); - assertEquals("2", config.getConfigValue("my.prop").getValue()); + assertEquals("2", config.getRawConfigValue("my.prop").getValue()); } private static Config buildConfig(String... keyValues) { diff --git a/mapper/pom.xml b/mapper/pom.xml new file mode 100644 index 000000000..ce0e358ab --- /dev/null +++ b/mapper/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + io.smallrye.config + smallrye-config-parent + 1.8.1-SNAPSHOT + + + smallrye-config-mapper + + SmallRye: Configuration Mapper + A configuration-to-object mapping utility + + + + org.ow2.asm + asm + + + io.smallrye.config + smallrye-config + + + io.smallrye.common + smallrye-common-constraint + + + junit + junit + test + + + + + + + maven-surefire-plugin + + false + + + + + diff --git a/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java b/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java new file mode 100644 index 000000000..f9466c0c7 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java @@ -0,0 +1,23 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Comparator; + +/** + * Specify the comparator to use to compare the annotated type for range. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface CompareWith { + /** + * The comparator class to use. + * + * @return the comparator class + */ + Class> value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java new file mode 100644 index 000000000..e41db6953 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -0,0 +1,690 @@ +package io.smallrye.config.mapper; + +import static io.smallrye.config.mapper.ConfigurationInterface.GroupProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.LeafProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.MapProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.MayBeOptionalProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.PrimitiveProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.Property; +import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; + +import java.lang.reflect.Method; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +import org.eclipse.microprofile.config.spi.Converter; + +import io.smallrye.common.constraint.Assert; +import io.smallrye.common.function.Functions; +import io.smallrye.config.ConfigValue; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * + */ +public final class ConfigMapping { + /** + * 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 { + final KeyMap> map = new KeyMap<>(); + map.putAny(map); + map.putRootValue(DO_NOTHING); + IGNORE_EVERYTHING = map; + } + + private final Map> roots; + private final KeyMap> matchActions; + private final KeyMap defaultValues; + + ConfigMapping(final Builder builder) { + roots = new HashMap<>(builder.roots); + final ArrayDeque currentPath = new ArrayDeque<>(); + KeyMap> matchActions = new KeyMap<>(); + KeyMap defaultValues = new KeyMap<>(); + for (Map.Entry> entry : roots.entrySet()) { + NameIterator rootNi = new NameIterator(entry.getKey()); + while (rootNi.hasNext()) { + currentPath.add(rootNi.getNextSegment()); + rootNi.next(); + } + List roots = entry.getValue(); + for (ConfigurationInterface root : roots) { + // construct the lazy match actions for each group + BiFunction ef = new GetRootAction(root, entry); + processEagerGroup(currentPath, matchActions, defaultValues, root, 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); + found.putAny(IGNORE_EVERYTHING); + } else { + found = matchActions.findOrAdd(ignoredPath); + found.putRootValue(DO_NOTHING); + } + } + this.matchActions = matchActions; + this.defaultValues = defaultValues; + } + + static String skewer(Method method) { + return skewer(method.getName()); + } + + static String skewer(String camelHumps) { + return skewer(camelHumps, 0, camelHumps.length(), new StringBuilder()); + } + + static String skewer(String camelHumps, int start, int end, StringBuilder b) { + assert !camelHumps.isEmpty() : "Method seems to have an empty name"; + int cp = camelHumps.codePointAt(start); + b.appendCodePoint(Character.toLowerCase(cp)); + start += Character.charCount(cp); + if (start == end) { + // a lonely character at the end of the string + return b.toString(); + } + if (Character.isUpperCase(cp)) { + // all-uppercase words need one code point of lookahead + int nextCp = camelHumps.codePointAt(start); + if (Character.isUpperCase(nextCp)) { + // it's some kind of `WORD` + for (;;) { + b.appendCodePoint(Character.toLowerCase(cp)); + start += Character.charCount(cp); + cp = nextCp; + if (start == end) { + return b.toString(); + } + nextCp = camelHumps.codePointAt(start); + // combine non-letters in with this name + if (Character.isLowerCase(nextCp)) { + b.append('-'); + return skewer(camelHumps, start, end, b); + } + } + // unreachable + } else { + // it was the start of a `Word`; continue until we hit the end or an uppercase. + b.appendCodePoint(nextCp); + start += Character.charCount(nextCp); + for (;;) { + if (start == end) { + return b.toString(); + } + cp = camelHumps.codePointAt(start); + // combine non-letters in with this name + if (Character.isUpperCase(cp)) { + b.append('-'); + return skewer(camelHumps, start, end, b); + } + b.appendCodePoint(cp); + start += Character.charCount(cp); + } + // unreachable + } + // unreachable + } else { + // it's some kind of `word` + for (;;) { + cp = camelHumps.codePointAt(start); + // combine non-letters in with this name + if (Character.isUpperCase(cp)) { + b.append('-'); + return skewer(camelHumps, start, end, b); + } + b.appendCodePoint(cp); + start += Character.charCount(cp); + if (start == end) { + return b.toString(); + } + } + // unreachable + } + // unreachable + } + + static final class ConsumeOneAndThen implements BiConsumer { + private final BiConsumer delegate; + + ConsumeOneAndThen(final BiConsumer delegate) { + this.delegate = delegate; + } + + public void accept(final MappingContext 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 MappingContext 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 KeyMap defaultValues, + final ConfigurationInterface group, + final BiFunction getEnclosingFunction) { + Class type = group.getInterfaceType(); + int pc = group.getPropertyCount(); + int pathLen = currentPath.size(); + HashSet usedProperties = new HashSet<>(); + for (int i = 0; i < pc; i++) { + Property property = group.getProperty(i); + String memberName = property.getMethod().getName(); + if (usedProperties.add(memberName)) { + // process by property type + if (!property.isParentPropertyName()) { + String propertyName = property.hasPropertyName() ? property.getPropertyName() + : skewer(property.getMethod()); + NameIterator ni = new NameIterator(propertyName); + while (ni.hasNext()) { + currentPath.add(ni.getNextSegment()); + ni.next(); + } + } + if (property.isOptional()) { + // switch to lazy mode + MayBeOptionalProperty nestedProperty = property.asOptional().getNestedProperty(); + if (nestedProperty.isGroup()) { + GroupProperty nestedGroup = nestedProperty.asGroup(); + // on match, always create the outermost group, which recursively creates inner groups + GetOrCreateEnclosingGroupInGroup matchAction = new GetOrCreateEnclosingGroupInGroup( + getEnclosingFunction, group, nestedGroup); + GetFieldOfEnclosing ef = new GetFieldOfEnclosing( + nestedGroup.isParentPropertyName() ? getEnclosingFunction + : new ConsumeOneAndThenFn<>(getEnclosingFunction), + type, memberName); + processLazyGroupInGroup(currentPath, matchActions, defaultValues, nestedGroup, ef, matchAction, + new HashSet<>()); + } else if (nestedProperty.isLeaf()) { + LeafProperty leafProperty = nestedProperty.asLeaf(); + if (leafProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue()); + } + matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING); + } + } else if (property.isGroup()) { + + processEagerGroup(currentPath, matchActions, defaultValues, property.asGroup().getGroupType(), + new GetOrCreateEnclosingGroupInGroup(getEnclosingFunction, group, property.asGroup())); + } else if (property.isPrimitive()) { + // already processed eagerly + PrimitiveProperty primitiveProperty = property.asPrimitive(); + if (primitiveProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(primitiveProperty.getDefaultValue()); + } + matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING); + } else if (property.isLeaf()) { + // already processed eagerly + LeafProperty leafProperty = property.asLeaf(); + if (leafProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue()); + } + // ignore with no error message + matchActions.findOrAdd(currentPath).putRootValue(DO_NOTHING); + } else if (property.isMap()) { + // the enclosure of the map is this group + processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), getEnclosingFunction, + group); + } + while (currentPath.size() > pathLen) { + currentPath.removeLast(); + } + } + } + int sc = group.getSuperTypeCount(); + for (int i = 0; i < sc; i++) { + processEagerGroup(currentPath, matchActions, defaultValues, group.getSuperType(i), getEnclosingFunction); + } + } + + private void processLazyGroupInGroup(ArrayDeque currentPath, + KeyMap> matchActions, + KeyMap defaultValues, + GroupProperty groupProperty, + BiFunction getEnclosingFunction, + BiConsumer matchAction, HashSet usedProperties) { + ConfigurationInterface group = groupProperty.getGroupType(); + int pc = group.getPropertyCount(); + int pathLen = currentPath.size(); + for (int i = 0; i < pc; i++) { + Property property = group.getProperty(i); + if (!property.isParentPropertyName()) { + String propertyName = property.hasPropertyName() ? property.getPropertyName() + : skewer(property.getMethod()); + NameIterator ni = new NameIterator(propertyName); + while (ni.hasNext()) { + currentPath.add(ni.getNextSegment()); + ni.next(); + } + } + if (usedProperties.add(property.getMethod().getName())) { + boolean optional = property.isOptional(); + if (optional && property.asOptional().getNestedProperty().isGroup()) { + GroupProperty nestedGroup = property.asOptional().getNestedProperty().asGroup(); + GetOrCreateEnclosingGroupInGroup nestedMatchAction = new GetOrCreateEnclosingGroupInGroup( + property.isParentPropertyName() ? getEnclosingFunction + : new ConsumeOneAndThenFn<>(getEnclosingFunction), + group, nestedGroup); + processLazyGroupInGroup(currentPath, matchActions, defaultValues, nestedGroup, nestedMatchAction, + nestedMatchAction, new HashSet<>()); + } else if (property.isGroup()) { + GroupProperty asGroup = property.asGroup(); + GetOrCreateEnclosingGroupInGroup nestedEnclosingFunction = new GetOrCreateEnclosingGroupInGroup( + property.isParentPropertyName() ? getEnclosingFunction + : new ConsumeOneAndThenFn<>(getEnclosingFunction), + group, asGroup); + BiConsumer nestedMatchAction; + nestedMatchAction = matchAction; + if (!property.isParentPropertyName()) { + nestedMatchAction = new ConsumeOneAndThen(nestedMatchAction); + } + processLazyGroupInGroup(currentPath, matchActions, defaultValues, asGroup, nestedEnclosingFunction, + nestedMatchAction, usedProperties); + } else if (property.isLeaf() || property.isPrimitive() + || optional && property.asOptional().getNestedProperty().isLeaf()) { + BiConsumer actualAction; + if (!property.isParentPropertyName()) { + actualAction = new ConsumeOneAndThen(matchAction); + } else { + actualAction = matchAction; + } + matchActions.findOrAdd(currentPath).putRootValue(actualAction); + if (property.isPrimitive()) { + PrimitiveProperty primitiveProperty = property.asPrimitive(); + if (primitiveProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(primitiveProperty.getDefaultValue()); + } + } else if (property.isLeaf()) { + LeafProperty leafProperty = property.asLeaf(); + if (leafProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue()); + } + } else { + LeafProperty leafProperty = property.asOptional().getNestedProperty().asLeaf(); + if (leafProperty.hasDefaultValue()) { + defaultValues.findOrAdd(currentPath).putRootValue(leafProperty.getDefaultValue()); + } + } + } else if (property.isMap()) { + processLazyMapInGroup(currentPath, matchActions, defaultValues, property.asMap(), getEnclosingFunction, + group); + } + } + while (currentPath.size() > pathLen) { + currentPath.removeLast(); + } + } + int sc = group.getSuperTypeCount(); + for (int i = 0; i < sc; i++) { + processLazyGroupInGroup(currentPath, matchActions, defaultValues, groupProperty, getEnclosingFunction, + matchAction, usedProperties); + } + } + + private void processLazyMapInGroup(final ArrayDeque currentPath, + final KeyMap> matchActions, final KeyMap defaultValues, + final MapProperty property, BiFunction getEnclosingGroup, + ConfigurationInterface enclosingGroup) { + GetOrCreateEnclosingMapInGroup getEnclosingMap = new GetOrCreateEnclosingMapInGroup(getEnclosingGroup, enclosingGroup, + property); + processLazyMap(currentPath, matchActions, defaultValues, property, getEnclosingMap, enclosingGroup); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void processLazyMap(final ArrayDeque currentPath, + final KeyMap> matchActions, final KeyMap defaultValues, + final MapProperty property, BiFunction> getEnclosingMap, + ConfigurationInterface enclosingGroup) { + Property valueProperty = property.getValueProperty(); + Class> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null; + Class keyRawType = property.getKeyRawType(); + + currentPath.addLast("*"); + if (valueProperty.isLeaf()) { + LeafProperty leafProperty = valueProperty.asLeaf(); + Class> valConvertWith = leafProperty.getConvertWith(); + Class valueRawType = leafProperty.getValueRawType(); + + matchActions.find(currentPath).putRootValue((mc, ni) -> { + StringBuilder sb = mc.getStringBuilder(); + sb.setLength(0); + ni.previous(); + sb.append(ni.getAllPreviousSegments()); + String configKey = sb.toString(); + Map map = getEnclosingMap.apply(mc, ni); + ni.next(); + String rawMapKey = ni.getPreviousSegment(); + Converter keyConv; + SmallRyeConfig config = mc.getConfig(); + if (keyConvertWith != null) { + keyConv = mc.getConverterInstance(keyConvertWith); + } else { + keyConv = config.getConverter(keyRawType); + } + Object key = keyConv.convert(rawMapKey); + Converter valueConv; + if (valConvertWith != null) { + valueConv = mc.getConverterInstance(valConvertWith); + } else { + valueConv = config.getConverter(valueRawType); + } + ((Map) map).put(key, config.getValue(configKey, valueConv)); + }); + } else if (valueProperty.isMap()) { + processLazyMap(currentPath, matchActions, defaultValues, valueProperty.asMap(), (mc, ni) -> { + ni.previous(); + Map enclosingMap = getEnclosingMap.apply(mc, ni); + ni.next(); + String rawMapKey = ni.getPreviousSegment(); + Converter keyConv; + SmallRyeConfig config = mc.getConfig(); + if (keyConvertWith != null) { + keyConv = mc.getConverterInstance(keyConvertWith); + } else { + keyConv = config.getConverter(keyRawType); + } + Object key = keyConv.convert(rawMapKey); + return (Map) ((Map) enclosingMap).computeIfAbsent(key, x -> new HashMap<>()); + }, enclosingGroup); + } else { + assert valueProperty.isGroup(); + final GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap(getEnclosingMap, property, + enclosingGroup, valueProperty.asGroup()); + processLazyGroupInGroup(currentPath, matchActions, defaultValues, valueProperty.asGroup(), + ef, ef, new HashSet<>()); + } + currentPath.removeLast(); + } + + static class GetRootAction implements BiFunction { + private final ConfigurationInterface root; + private final Map.Entry> entry; + + GetRootAction(final ConfigurationInterface root, final Map.Entry> entry) { + this.root = root; + this.entry = entry; + } + + public ConfigurationObject apply(final MappingContext mc, final NameIterator ni) { + return mc + .getRoot(root.getInterfaceType(), entry.getKey()); + } + } + + static class GetOrCreateEnclosingGroupInGroup implements BiFunction, + BiConsumer { + private final BiFunction delegate; + private final ConfigurationInterface enclosingGroup; + private final GroupProperty enclosedGroup; + + GetOrCreateEnclosingGroupInGroup(final BiFunction delegate, + final ConfigurationInterface enclosingGroup, final GroupProperty enclosedGroup) { + this.delegate = delegate; + this.enclosingGroup = enclosingGroup; + this.enclosedGroup = enclosedGroup; + } + + public ConfigurationObject apply(final MappingContext context, final NameIterator ni) { + ConfigurationObject ourEnclosing = delegate.apply(context, ni); + Class enclosingType = enclosingGroup.getInterfaceType(); + String methodName = enclosedGroup.getMethod().getName(); + ConfigurationObject val = (ConfigurationObject) context.getEnclosedField(enclosingType, methodName, ourEnclosing); + if (val == null) { + // it must be an optional group + StringBuilder sb = context.getStringBuilder(); + sb.replace(0, sb.length(), ni.getAllPreviousSegments()); + val = (ConfigurationObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType()); + context.registerEnclosedField(enclosingType, methodName, ourEnclosing, val); + } + return val; + } + + public void accept(final MappingContext context, final NameIterator nameIterator) { + apply(context, nameIterator); + } + } + + static class GetOrCreateEnclosingGroupInMap implements BiFunction, + BiConsumer { + final BiFunction> getEnclosingMap; + final MapProperty enclosingMap; + final ConfigurationInterface enclosingGroup; + private final GroupProperty enclosedGroup; + + GetOrCreateEnclosingGroupInMap(final BiFunction> getEnclosingMap, + final MapProperty enclosingMap, final ConfigurationInterface enclosingGroup, + final GroupProperty enclosedGroup) { + this.getEnclosingMap = getEnclosingMap; + this.enclosingMap = enclosingMap; + this.enclosingGroup = enclosingGroup; + this.enclosedGroup = enclosedGroup; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ConfigurationObject apply(final MappingContext context, final NameIterator ni) { + ni.previous(); + Map ourEnclosing = getEnclosingMap.apply(context, ni); + ni.next(); + String mapKey = ni.getPreviousSegment(); + Converter keyConverter = context.getKeyConverter(enclosingGroup.getInterfaceType(), + enclosingMap.getMethod().getName(), enclosingMap.getLevels() - 1); + ConfigurationObject val = (ConfigurationObject) ourEnclosing.get(mapKey); + if (val == null) { + StringBuilder sb = context.getStringBuilder(); + sb.replace(0, sb.length(), ni.getAllPreviousSegments()); + Object convertedKey = keyConverter.convert(mapKey); + ((Map) ourEnclosing).put(convertedKey, + val = (ConfigurationObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType())); + } + return val; + } + + public void accept(final MappingContext context, final NameIterator ni) { + apply(context, ni); + } + } + + static class GetOrCreateEnclosingMapInGroup implements BiFunction>, + BiConsumer { + final BiFunction getEnclosingGroup; + final ConfigurationInterface enclosingGroup; + final MapProperty property; + + GetOrCreateEnclosingMapInGroup(final BiFunction getEnclosingGroup, + final ConfigurationInterface enclosingGroup, final MapProperty property) { + this.getEnclosingGroup = getEnclosingGroup; + this.enclosingGroup = enclosingGroup; + this.property = property; + } + + public Map apply(final MappingContext context, final NameIterator ni) { + boolean consumeName = !property.isParentPropertyName(); + if (consumeName) + ni.previous(); + ConfigurationObject ourEnclosing = getEnclosingGroup.apply(context, ni); + if (consumeName) + ni.next(); + Class enclosingType = enclosingGroup.getInterfaceType(); + String methodName = property.getMethod().getName(); + Map val = (Map) context.getEnclosedField(enclosingType, methodName, ourEnclosing); + if (val == null) { + // map is not yet constructed + val = new HashMap<>(); + context.registerEnclosedField(enclosingType, methodName, ourEnclosing, val); + } + return val; + } + + public void accept(final MappingContext context, final NameIterator ni) { + apply(context, ni); + } + } + + static class GetFieldOfEnclosing implements BiFunction { + private final BiFunction getEnclosingFunction; + private final Class type; + private final String memberName; + + GetFieldOfEnclosing(final BiFunction getEnclosingFunction, + final Class type, final String memberName) { + this.getEnclosingFunction = getEnclosingFunction; + this.type = type; + this.memberName = memberName; + } + + public ConfigurationObject apply(final MappingContext mc, final NameIterator ni) { + ConfigurationObject outer = getEnclosingFunction.apply(mc, ni); + // eagerly populated groups will always exist + return (ConfigurationObject) mc.getEnclosedField(type, memberName, outer); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + static Map getOrCreateEnclosingMapInMap(MappingContext context, NameIterator ni, + BiFunction> getEnclosingMap, ConfigurationInterface enclosingGroup, + MapProperty property) { + ni.previous(); + Map ourEnclosing = getEnclosingMap.apply(context, ni); + String mapKey = ni.getNextSegment(); + Converter keyConverter = context.getKeyConverter(enclosingGroup.getInterfaceType(), property.getMethod().getName(), + property.getLevels() - 1); + Object realKey = keyConverter.convert(mapKey); + Map map = (Map) ourEnclosing.get(realKey); + if (map == null) { + map = new HashMap<>(); + ((Map) ourEnclosing).put(realKey, map); + } + ni.next(); + return map; + } + + public static Builder builder() { + return new Builder(); + } + + public B registerDefaultValues(B builder) { + Assert.checkNotNullParam("builder", builder); + builder.withSources(new DefaultValuesConfigSource(defaultValues)); + return builder; + } + + public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValidationException { + Assert.checkNotNullParam("config", config); + final MappingContext context = new MappingContext(config); + // eagerly populate roots + for (Map.Entry> entry : roots.entrySet()) { + String path = entry.getKey(); + List roots = entry.getValue(); + for (ConfigurationInterface root : roots) { + StringBuilder sb = context.getStringBuilder(); + sb.replace(0, sb.length(), path); + Class type = root.getInterfaceType(); + ConfigurationObject group = (ConfigurationObject) context.constructGroup(type); + context.registerRoot(type, path, group); + } + } + // lazily sweep + for (String name : config.getPropertyNames()) { + // may be null + ConfigValue configValue = config.getRawConfigValue(name); + NameIterator ni = new NameIterator(name); + BiConsumer action = matchActions.findRootValue(ni); + if (action != null) { + // ni is positioned at the end of the string + action.accept(context, ni); + } else if (configValue != null) { + context.unknownConfigElement(configValue); + } + } + ArrayList problems = context.getProblems(); + if (!problems.isEmpty()) { + throw new ConfigurationValidationException( + problems.toArray(ConfigurationValidationException.Problem.NO_PROBLEMS)); + } + context.fillInOptionals(); + return new Result(context.getRootsMap()); + } + + public static final class Builder { + SmallRyeConfig config; + final Map> roots = new HashMap<>(); + final List ignored = new ArrayList<>(); + + Builder() { + } + + public SmallRyeConfig getConfig() { + return config; + } + + public Builder setConfig(final SmallRyeConfig config) { + this.config = config; + return this; + } + + public Builder addRoot(String path, Class type) { + Assert.checkNotNullParam("type", type); + return addRoot(path, getConfigurationInterface(type)); + } + + public Builder addRoot(String path, ConfigurationInterface info) { + Assert.checkNotNullParam("path", path); + Assert.checkNotNullParam("info", info); + roots.computeIfAbsent(path, k -> new ArrayList<>(4)).add(info); + return this; + } + + public Builder addIgnored(String... patternSegments) { + Assert.checkNotNullParam("patternSegments", patternSegments); + ignored.add(patternSegments); + return this; + } + + public ConfigMapping build() { + return new ConfigMapping(this); + } + } + + public static final class Result { + private final Map, Map> rootsMap; + + Result(final Map, Map> rootsMap) { + this.rootsMap = rootsMap; + } + + public T getConfigRoot(String path, Class type) { + return type.cast(rootsMap.getOrDefault(type, Collections.emptyMap()).get(path)); + } + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java new file mode 100644 index 000000000..41dd7e24f --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -0,0 +1,1040 @@ +package io.smallrye.config.mapper; + +import static io.smallrye.config.mapper.ConfigMapping.skewer; +import static org.objectweb.asm.Type.getDescriptor; +import static org.objectweb.asm.Type.getInternalName; +import static org.objectweb.asm.Type.getType; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.microprofile.config.spi.Converter; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.smallrye.common.constraint.Assert; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.inject.InjectionMessages; +import sun.misc.Unsafe; + +/** + * Information about a configuration interface. + */ +public final class ConfigurationInterface { + static final boolean usefulDebugInfo = true; + static final ConfigurationInterface[] NO_TYPES = new ConfigurationInterface[0]; + static final Property[] NO_PROPERTIES = new Property[0]; + static final ClassValue cv = new ClassValue() { + protected ConfigurationInterface computeValue(final Class type) { + return createConfigurationInterface(type); + } + }; + static final Unsafe unsafe; + + static { + unsafe = AccessController.doPrivileged(new PrivilegedAction() { + public Unsafe run() { + try { + final Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + return (Unsafe) field.get(null); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } catch (NoSuchFieldException e) { + throw new NoSuchFieldError(e.getMessage()); + } + } + }); + } + + private final Class interfaceType; + private final ConfigurationInterface[] superTypes; + private final Property[] properties; + private final Class implClass; + private final Constructor constructor; + private final Map propertiesByName; + + ConfigurationInterface(final Class interfaceType, final ConfigurationInterface[] superTypes, + final Property[] properties) { + this.interfaceType = interfaceType; + this.superTypes = superTypes; + this.properties = properties; + implClass = createConfigurationObjectClass().asSubclass(ConfigurationObject.class); + try { + constructor = implClass.getDeclaredConstructor(MappingContext.class); + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + final Map propertiesByName = new HashMap<>(); + for (Property property : properties) { + propertiesByName.put(property.getMethod().getName(), property); + } + this.propertiesByName = propertiesByName; + } + + /** + * Get the configuration interface information for the given interface class. This information is cached. + * + * @param interfaceType the interface type (must not be {@code null}) + * @return the configuration interface, or {@code null} if the type does not appear to be a configuration interface + */ + public static ConfigurationInterface getConfigurationInterface(Class interfaceType) { + Assert.checkNotNullParam("interfaceType", interfaceType); + return cv.get(interfaceType); + } + + /** + * Get the configuration interface type. + * + * @return the configuration interface type + */ + public Class getInterfaceType() { + return interfaceType; + } + + /** + * Get the number of supertypes which define configuration properties. Implemented interfaces which do not + * define any configuration properties and whose supertypes in turn do not define any configuration properties + * are not counted. + * + * @return the number of supertypes + */ + public int getSuperTypeCount() { + return superTypes.length; + } + + /** + * Get the supertype at the given index, which must be greater than or equal to zero and less than the value returned + * by {@link #getSuperTypeCount()}. + * + * @param index the index + * @return the supertype definition + * @throws IndexOutOfBoundsException if {@code index} is invalid + */ + public ConfigurationInterface getSuperType(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= superTypes.length) + throw new IndexOutOfBoundsException(); + return superTypes[index]; + } + + /** + * Get the number of properties defined on this type (excluding supertypes). + * + * @return the number of properties + */ + public int getPropertyCount() { + return properties.length; + } + + /** + * Get the property definition at the given index, which must be greater than or equal to zero and less than the + * value returned by {@link #getPropertyCount()}. + * + * @param index the index + * @return the property definition + * @throws IndexOutOfBoundsException if {@code index} is invalid + */ + public Property getProperty(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= properties.length) + throw new IndexOutOfBoundsException(); + return properties[index]; + } + + public Property getProperty(final String name) { + return propertiesByName.get(name); + } + + Constructor getConstructor() { + return constructor; + } + + public static abstract class Property { + private final Method method; + private final String propertyName; + + Property(final Method method, final String propertyName) { + this.method = method; + this.propertyName = propertyName; + } + + public Method getMethod() { + return method; + } + + public String getPropertyName() { + return Assert.checkNotEmptyParam("propertyName", Assert.checkNotNullParam("propertyName", propertyName)); + } + + public boolean hasPropertyName() { + return propertyName != null; + } + + public boolean isParentPropertyName() { + return hasPropertyName() && propertyName.isEmpty(); + } + + public boolean isPrimitive() { + return false; + } + + public boolean isOptional() { + return false; + } + + public boolean isGroup() { + return false; + } + + public boolean isLeaf() { + return false; + } + + public boolean isMap() { + return false; + } + + public boolean isMayBeOptional() { + return false; + } + + public PrimitiveProperty asPrimitive() { + throw new ClassCastException(); + } + + public OptionalProperty asOptional() { + throw new ClassCastException(); + } + + public GroupProperty asGroup() { + throw new ClassCastException(); + } + + public LeafProperty asLeaf() { + throw new ClassCastException(); + } + + public MapProperty asMap() { + throw new ClassCastException(); + } + + public MayBeOptionalProperty asMayBeOptional() { + throw new ClassCastException(); + } + } + + public static abstract class MayBeOptionalProperty extends Property { + MayBeOptionalProperty(final Method method, final String propertyName) { + super(method, propertyName); + } + + @Override + public boolean isMayBeOptional() { + return true; + } + + @Override + public MayBeOptionalProperty asMayBeOptional() { + return this; + } + } + + public static final class PrimitiveProperty extends Property { + private static final Map, Class> boxTypes; + private static final Map, String> unboxMethodName; + private static final Map, String> unboxMethodDesc; + + static { + Map, Class> map = new HashMap<>(); + map.put(byte.class, Byte.class); + map.put(short.class, Short.class); + map.put(int.class, Integer.class); + map.put(long.class, Long.class); + + map.put(float.class, Float.class); + map.put(double.class, Double.class); + + map.put(boolean.class, Boolean.class); + + map.put(char.class, Character.class); + boxTypes = map; + Map, String> nameMap = new HashMap<>(); + nameMap.put(byte.class, "byteValue"); + nameMap.put(short.class, "shortValue"); + nameMap.put(int.class, "intValue"); + nameMap.put(long.class, "longValue"); + + nameMap.put(float.class, "floatValue"); + nameMap.put(double.class, "doubleValue"); + + nameMap.put(boolean.class, "booleanValue"); + + nameMap.put(char.class, "charValue"); + unboxMethodName = nameMap; + nameMap = new HashMap<>(); + nameMap.put(byte.class, "()B"); + nameMap.put(short.class, "()S"); + nameMap.put(int.class, "()I"); + nameMap.put(long.class, "()J"); + + nameMap.put(float.class, "()F"); + nameMap.put(double.class, "()D"); + + nameMap.put(boolean.class, "()Z"); + + nameMap.put(char.class, "()C"); + unboxMethodDesc = nameMap; + nameMap = new HashMap<>(); + nameMap.put(byte.class, "B"); + nameMap.put(short.class, "S"); + nameMap.put(int.class, "I"); + nameMap.put(long.class, "J"); + + nameMap.put(float.class, "F"); + nameMap.put(double.class, "D"); + + nameMap.put(boolean.class, "Z"); + + nameMap.put(char.class, "C"); + } + + private final Class primitiveType; + private final Class> convertWith; + private final String defaultValue; + + PrimitiveProperty(final Method method, final String propertyName, final Class primitiveType, + final Class> convertWith, final String defaultValue) { + super(method, propertyName); + this.primitiveType = primitiveType; + this.convertWith = convertWith; + this.defaultValue = defaultValue; + } + + public Class getPrimitiveType() { + return primitiveType; + } + + public Class getBoxType() { + return boxTypes.get(primitiveType); + } + + public Class> getConvertWith() { + return Assert.checkNotNullParam("convertWith", convertWith); + } + + public boolean hasConvertWith() { + return convertWith != null; + } + + public String getDefaultValue() { + return Assert.checkNotNullParam("defaultValue", defaultValue); + } + + public boolean hasDefaultValue() { + return defaultValue != null; + } + + @Override + public boolean isPrimitive() { + return true; + } + + @Override + public PrimitiveProperty asPrimitive() { + return this; + } + + String getUnboxMethodName() { + return unboxMethodName.get(primitiveType); + } + + String getUnboxMethodDescriptor() { + return unboxMethodDesc.get(primitiveType); + } + + int getReturnInstruction() { + if (primitiveType == float.class) { + return Opcodes.FRETURN; + } else if (primitiveType == double.class) { + return Opcodes.DRETURN; + } else if (primitiveType == long.class) { + return Opcodes.LRETURN; + } else { + return Opcodes.IRETURN; + } + } + } + + public static final class OptionalProperty extends Property { + private final MayBeOptionalProperty nestedProperty; + + OptionalProperty(final Method method, final String propertyName, final MayBeOptionalProperty nestedProperty) { + super(method, propertyName); + this.nestedProperty = nestedProperty; + } + + @Override + public boolean isOptional() { + return true; + } + + @Override + public OptionalProperty asOptional() { + return this; + } + + public MayBeOptionalProperty getNestedProperty() { + return nestedProperty; + } + } + + public static final class GroupProperty extends MayBeOptionalProperty { + private final ConfigurationInterface groupType; + + GroupProperty(final Method method, final String propertyName, final ConfigurationInterface groupType) { + super(method, propertyName); + this.groupType = groupType; + } + + public ConfigurationInterface getGroupType() { + return groupType; + } + + @Override + public boolean isGroup() { + return true; + } + + @Override + public GroupProperty asGroup() { + return this; + } + } + + public static final class LeafProperty extends MayBeOptionalProperty { + private final Type valueType; + private final Class> convertWith; + private final Class rawType; + private final String defaultValue; + + LeafProperty(final Method method, final String propertyName, final Type valueType, + final Class> convertWith, final String defaultValue) { + super(method, propertyName); + this.valueType = valueType; + this.convertWith = convertWith; + rawType = rawTypeOf(valueType); + this.defaultValue = defaultValue; + } + + public Type getValueType() { + return valueType; + } + + public Class> getConvertWith() { + return Assert.checkNotNullParam("convertWith", convertWith); + } + + public boolean hasConvertWith() { + return convertWith != null; + } + + public String getDefaultValue() { + return Assert.checkNotNullParam("defaultValue", defaultValue); + } + + public boolean hasDefaultValue() { + return defaultValue != null; + } + + public Class getValueRawType() { + return rawType; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public LeafProperty asLeaf() { + return this; + } + } + + public static final class MapProperty extends Property { + private final Type keyType; + private final Class> keyConvertWith; + private final Property valueProperty; + + MapProperty(final Method method, final String propertyName, final Type keyType, + final Class> keyConvertWith, final Property valueProperty) { + super(method, propertyName); + this.keyType = keyType; + this.keyConvertWith = keyConvertWith; + this.valueProperty = valueProperty; + } + + public Type getKeyType() { + return keyType; + } + + public Class getKeyRawType() { + return rawTypeOf(keyType); + } + + public Class> getKeyConvertWith() { + return Assert.checkNotNullParam("keyConvertWith", keyConvertWith); + } + + public boolean hasKeyConvertWith() { + return keyConvertWith != null; + } + + public Property getValueProperty() { + return valueProperty; + } + + @Override + public boolean isMap() { + return true; + } + + @Override + public MapProperty asMap() { + return this; + } + + public int getLevels() { + if (valueProperty.isMap()) { + return valueProperty.asMap().getLevels() + 1; + } else { + return 1; + } + } + } + + static ConfigurationInterface createConfigurationInterface(Class interfaceType) { + if (!interfaceType.isInterface() || interfaceType.getTypeParameters().length != 0) { + return null; + } + // first, find any supertypes + ConfigurationInterface[] superTypes = getSuperTypes(interfaceType.getInterfaces(), 0, 0); + // now find any properties + Property[] properties = getProperties(interfaceType.getDeclaredMethods(), 0, 0); + // is it anything? + if (superTypes.length == 0 && properties.length == 0) { + // no + return null; + } else { + // it is a proper configuration interface + return new ConfigurationInterface(interfaceType, superTypes, properties); + } + } + + 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(ConfigurationObject.class); + private static final String I_CONVERTER = getInternalName(Converter.class); + private static final String I_MAP = getInternalName(Map.class); + private static final String I_MAPPING_CONTEXT = getInternalName(MappingContext.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_STRING = getInternalName(String.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 void addProperties(ClassVisitor cv, final String className, MethodVisitor ctor, MethodVisitor fio, + Set visited) { + for (Property property : properties) { + Method method = property.getMethod(); + String memberName = method.getName(); + if (!visited.add(memberName)) { + // duplicated property + continue; + } + // the field + String fieldType = getInternalName(method.getReturnType()); + String fieldDesc = getDescriptor(method.getReturnType()); + cv.visitField(Opcodes.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; + } + + // now handle each possible type + if (property.isMap()) { + // stack: - + ctor.visitMethodInsn(Opcodes.INVOKESTATIC, I_COLLECTIONS, "emptyMap", "()L" + I_MAP + ';', false); + // 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); + // stack: - + // then sweep it up + // stack: - + fio.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); + // stack: ctxt + fio.visitLdcInsn(getType(interfaceType)); + // 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, getDescriptor(Map.class)); + // 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, memberName); + // 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); + // stack: - + if (restoreLength) { + restoreLength(ctor); + } + } else if (property.isLeaf() || property.isPrimitive() || property.isOptional() && property.isLeaf()) { + // stack: - + ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); + // stack: this + boolean restoreLength = appendPropertyName(ctor, property, memberName); + ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getConfig", "()L" + I_SMALLRYE_CONFIG + ';', + 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 + // get the converter to use + ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); + // public Converter getValueConverter(Class enclosingType, String field) { + ctor.visitLdcInsn(getType(getInterfaceType())); + ctor.visitLdcInsn(memberName); + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getValueConverter", + "(L" + I_CLASS + ";L" + I_STRING + ";)L" + I_CONVERTER + ';', false); + // stack: this config key converter + Label _try = new Label(); + Label _catch = new Label(); + Label _continue = new Label(); + ctor.visitLabel(_try); + if (property.isOptional()) { + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getOptionalValue", + "(L" + I_STRING + ";L" + I_CONVERTER + ";)L" + I_OPTIONAL + ';', false); + } else { + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_SMALLRYE_CONFIG, "getValue", + "(L" + I_STRING + ";L" + I_CONVERTER + ";)L" + I_OBJECT + ';', false); + } + // stack: this value + if (property.isPrimitive()) { + PrimitiveProperty prim = property.asPrimitive(); + // unbox it + // stack: this box + String boxType = getInternalName(prim.getBoxType()); + ctor.visitTypeInsn(Opcodes.CHECKCAST, boxType); + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxType, prim.getUnboxMethodName(), + prim.getUnboxMethodDescriptor(), false); + // stack: this value + } else if (!property.isOptional()) { + assert property.isLeaf(); + ctor.visitTypeInsn(Opcodes.CHECKCAST, fieldType); + } + // 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); + } + // 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(interfaceType)); + // 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); + } + + // 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(Opcodes.GETFIELD, className, memberName, fieldDesc); + // stack: obj + if (property.isPrimitive()) { + mv.visitInsn(property.asPrimitive().getReturnInstruction()); + } else { + mv.visitInsn(Opcodes.ARETURN); + } + mv.visitEnd(); + mv.visitMaxs(0, 0); + // end loop + } + // subtype overrides supertype + for (ConfigurationInterface superType : superTypes) { + superType.addProperties(cv, className, ctor, fio, visited); + } + } + + private boolean appendPropertyName(final MethodVisitor ctor, final Property property, final String memberName) { + if (property.isParentPropertyName()) { + return false; + } + // stack: - + ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); + // stack: sb + ctor.visitLdcInsn(Character.valueOf('.')); + // stack: sb '.' + ctor.visitInsn(Opcodes.I2C); + // stack: sb '.' + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_STRING_BUILDER, "append", "(C)L" + I_STRING_BUILDER + ';', false); + // stack: sb + if (property.hasPropertyName()) { + ctor.visitLdcInsn(property.getPropertyName()); + } else { + ctor.visitLdcInsn(skewer(memberName)); + } + // 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; + } + + private 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: - + } + + Class createConfigurationObjectClass() { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + String internalName = getInternalName(interfaceType); + String className = usefulDebugInfo ? getInternalName(getClass()) : internalName; // ignored by the VM, probably? + ClassVisitor visitor = usefulDebugInfo ? new Debugging.ClassVisitorImpl(writer) : writer; + visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, I_OBJECT, + new String[] { I_CONFIGURATION_OBJECT, internalName }); + visitor.visitSource(null, null); + MethodVisitor ctor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); + ctor.visitParameter("context", Opcodes.ACC_FINAL); + Label ctorStart = new Label(); + Label ctorEnd = new Label(); + ctor.visitLabel(ctorStart); + // stack: - + ctor.visitVarInsn(Opcodes.ALOAD, V_THIS); + // stack: this + ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, I_OBJECT, "", "()V", false); + // stack: - + ctor.visitVarInsn(Opcodes.ALOAD, V_MAPPING_CONTEXT); + // stack: ctxt + ctor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, I_MAPPING_CONTEXT, "getStringBuilder", "()L" + I_STRING_BUILDER + ';', + false); + // stack: sb + ctor.visitInsn(Opcodes.DUP); + // stack: sb sb + Label ctorSbStart = new Label(); + ctor.visitLabel(ctorSbStart); + ctor.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); + // stack: sb + ctor.visitMethodInsn(Opcodes.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); + // stack: - + addProperties(visitor, className, ctor, fio, new HashSet<>()); + // 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(Opcodes.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.visitEnd(); + ctor.visitMaxs(0, 0); + visitor.visitEnd(); + + // todo: MR JAR/JDKSpecific + return unsafe.defineAnonymousClass(usefulDebugInfo ? getClass() : interfaceType, writer.toByteArray(), null); + } + + private static ConfigurationInterface[] getSuperTypes(Class[] interfaces, int si, int ti) { + if (si == interfaces.length) { + if (ti == 0) { + return NO_TYPES; + } else { + return new ConfigurationInterface[ti]; + } + } + Class item = interfaces[si]; + ConfigurationInterface ci = getConfigurationInterface(item); + if (ci != null) { + ConfigurationInterface[] array = getSuperTypes(interfaces, si + 1, ti + 1); + array[ti] = ci; + return array; + } else { + return getSuperTypes(interfaces, si + 1, ti); + } + } + + private static Property[] getProperties(Method[] methods, int si, int ti) { + if (si == methods.length) { + if (ti == 0) { + return NO_PROPERTIES; + } else { + return new Property[ti]; + } + } + Method method = methods[si]; + int mods = method.getModifiers(); + if (!Modifier.isPublic(mods) || Modifier.isStatic(mods) || !Modifier.isAbstract(mods)) { + return getProperties(methods, si + 1, ti); + } + if (method.getParameterCount() > 0) { + throw new IllegalArgumentException("Configuration methods cannot accept parameters"); + } + if (method.getReturnType() == void.class) { + throw new IllegalArgumentException("Void config methods are not allowed"); + } + Property p = getPropertyDef(method, method.getGenericReturnType()); + Property[] array = getProperties(methods, si + 1, ti + 1); + array[ti] = p; + return array; + } + + private static Property getPropertyDef(Method method, Type type) { + // now figure out what kind it is + Class> convertWith = getConvertWith(type); + String propertyName = getPropertyName(method); + Class rawType = rawTypeOf(type); + if (rawType.isPrimitive()) { + // primitive! + Default annotation = method.getAnnotation(Default.class); + return new PrimitiveProperty(method, propertyName, rawType, convertWith, + annotation == null ? null : annotation.value()); + } + if (convertWith == null) { + if (rawType == Optional.class) { + // optional is special: it can contain a leaf or a group, but not a map (unless it has @ConvertWith) + Property nested = getPropertyDef(method, typeOfParameter(type, 0)); + if (nested.isMayBeOptional()) { + return new OptionalProperty(method, propertyName, nested.asMayBeOptional()); + } + throw new IllegalArgumentException("Property type " + type + " cannot be optional"); + } + if (rawType == Map.class) { + // it's a map... + Type keyType = typeOfParameter(type, 0); + Class> keyConvertWith = getConvertWith(keyType); + Type valueType = typeOfParameter(type, 1); + return new MapProperty(method, propertyName, keyType, keyConvertWith, getPropertyDef(method, valueType)); + } + ConfigurationInterface configurationInterface = getConfigurationInterface(rawType); + if (configurationInterface != null) { + // it's a group + return new GroupProperty(method, propertyName, configurationInterface); + } + // fall out (leaf) + } + // otherwise it's a leaf + Default annotation = method.getAnnotation(Default.class); + return new LeafProperty(method, propertyName, type, convertWith, annotation == null ? null : annotation.value()); + } + + private static Class> getConvertWith(final Type type) { + if (type instanceof AnnotatedType) { + ConvertWith annotation = ((AnnotatedType) type).getAnnotation(ConvertWith.class); + if (annotation != null) { + return annotation.value(); + } else { + return null; + } + } else { + return null; + } + } + + private static String getPropertyName(final AnnotatedElement element) { + boolean useParent = element.getAnnotation(WithParentName.class) != null; + WithName annotation = element.getAnnotation(WithName.class); + if (annotation != null) { + if (useParent) { + throw new IllegalArgumentException("Cannot specify both @ParentConfigName and @ConfigName"); + } + String name = annotation.value(); + if (!name.isEmpty()) { + // already interned, effectively + return name; + } + // else invalid name + throw new IllegalArgumentException("Property name is empty"); + } else if (useParent) { + return ""; + } else { + return null; + } + } + + static Type typeOfParameter(final Type type, final int index) { + if (type instanceof ParameterizedType) { + return ((ParameterizedType) type).getActualTypeArguments()[index]; + } else { + return null; + } + } + + static Class rawTypeOf(final Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return rawTypeOf(((ParameterizedType) type).getRawType()); + } else if (type instanceof GenericArrayType) { + return Array.newInstance(rawTypeOf(((GenericArrayType) type).getGenericComponentType()), 0).getClass(); + } else if (type instanceof WildcardType) { + Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + if (upperBounds != null) { + return rawTypeOf(upperBounds[0]); + } else { + return Object.class; + } + } else { + throw InjectionMessages.msg.noRawType(type); + } + } + +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java new file mode 100644 index 000000000..a0c26db49 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java @@ -0,0 +1,8 @@ +package io.smallrye.config.mapper; + +/** + * An interface implemented internally by configuration object implementations. + */ +public interface ConfigurationObject { + void fillInOptionals(MappingContext context); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java new file mode 100644 index 000000000..9e9897c8f --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java @@ -0,0 +1,57 @@ +package io.smallrye.config.mapper; + +import io.smallrye.common.constraint.Assert; + +/** + * + */ +public class ConfigurationValidationException extends Exception { + private static final long serialVersionUID = -2637730579475070264L; + + private final Problem[] problems; + + /** + * Constructs a new {@code ConfigurationValidationException} instance. + * + * @param problems the reported problems + */ + public ConfigurationValidationException(final Problem[] problems) { + super(list("Configuration validation failed", problems)); + this.problems = problems; + } + + private static String list(String msg, Problem[] problems) { + StringBuilder b = new StringBuilder(); + b.append(msg).append(':'); + for (int i = 0; i < problems.length; i++) { + Problem problem = problems[i]; + Assert.checkNotNullArrayParam("problems", i, problem); + b.append(System.lineSeparator()); + b.append("\t"); + b.append(problem.getMessage()); + } + return b.toString(); + } + + public int getProblemCount() { + return problems.length; + } + + public Problem getProblem(int index) { + return problems[index]; + } + + public static final class Problem { + public static final Problem[] NO_PROBLEMS = new Problem[0]; + + private final String message; + + public Problem(final String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java b/mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java new file mode 100644 index 000000000..25be97a84 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java @@ -0,0 +1,24 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * Specify the converter to use to convert the annotated type. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface ConvertWith { + /** + * The converter class to use. + * + * @return the converter class + */ + Class> value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Debugging.java b/mapper/src/main/java/io/smallrye/config/mapper/Debugging.java new file mode 100644 index 000000000..a5299ebe0 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/Debugging.java @@ -0,0 +1,152 @@ +package io.smallrye.config.mapper; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * + */ +final class Debugging { + static StackTraceElement getCaller() { + return new Throwable().getStackTrace()[2]; + } + + static final class MethodVisitorImpl extends MethodVisitor { + + MethodVisitorImpl(final int api) { + super(api); + } + + MethodVisitorImpl(final int api, final MethodVisitor methodVisitor) { + super(api, methodVisitor); + } + + public void visitInsn(final int opcode) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitInsn(opcode); + } + + public void visitIntInsn(final int opcode, final int operand) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitIntInsn(opcode, operand); + } + + public void visitVarInsn(final int opcode, final int var) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitVarInsn(opcode, var); + } + + public void visitTypeInsn(final int opcode, final String type) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitTypeInsn(opcode, type); + } + + public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMethodInsn(opcode, owner, name, descriptor); + } + + public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, + final boolean isInterface) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + public void visitJumpInsn(final int opcode, final Label label) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitJumpInsn(opcode, label); + } + + public void visitLdcInsn(final Object value) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitLdcInsn(value); + } + + public void visitIincInsn(final int var, final int increment) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitIincInsn(var, increment); + } + + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + static final class ClassVisitorImpl extends ClassVisitor { + + final String sourceFile; + + ClassVisitorImpl(final int api) { + super(api); + sourceFile = getCaller().getFileName(); + } + + ClassVisitorImpl(final ClassWriter cw) { + super(Opcodes.ASM7, cw); + sourceFile = getCaller().getFileName(); + } + + public void visitSource(final String source, final String debug) { + super.visitSource(sourceFile, debug); + } + + public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, + final String[] exceptions) { + return new MethodVisitorImpl(api, super.visitMethod(access, name, descriptor, signature, exceptions)); + } + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Default.java b/mapper/src/main/java/io/smallrye/config/mapper/Default.java new file mode 100644 index 000000000..e81418d9c --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/Default.java @@ -0,0 +1,17 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the default value of a property. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Default { + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java b/mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java new file mode 100644 index 000000000..ccf46f69b --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java @@ -0,0 +1,30 @@ +package io.smallrye.config.mapper; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +final class DefaultValuesConfigSource implements ConfigSource { + private final KeyMap defaultValues; + + DefaultValuesConfigSource(final KeyMap defaultValues) { + this.defaultValues = defaultValues; + } + + public Map getProperties() { + return Collections.emptyMap(); + } + + public String getValue(final String s) { + return defaultValues.findRootValue(s); + } + + public String getName() { + return "Default values"; + } + + public int getOrdinal() { + return Integer.MIN_VALUE; + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java b/mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java new file mode 100644 index 000000000..bab82443a --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java @@ -0,0 +1,250 @@ +package io.smallrye.config.mapper; + +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.smallrye.common.function.Functions; + +/** + * A multi-level key map. + */ +public final class KeyMap extends HashMap> { + private static final Object NO_VALUE = new Object(); + + private static final long serialVersionUID = 3584966224369608557L; + + private KeyMap any; + @SuppressWarnings("unchecked") + private V rootValue = (V) NO_VALUE; + + public KeyMap(final int initialCapacity, final float loadFactor) { + super(initialCapacity, loadFactor); + } + + public KeyMap(final int initialCapacity) { + super(initialCapacity); + } + + public KeyMap() { + } + + public KeyMap get(final String key, int offs, int len) { + return get(key.substring(offs, offs + len)); + } + + public KeyMap getAny() { + return any; + } + + public KeyMap getOrCreateAny() { + KeyMap any = this.any; + if (any == null) { + any = this.any = new KeyMap<>(); + } + return any; + } + + public KeyMap putAny(KeyMap any) { + KeyMap oldAny = this.any; + this.any = any; + return oldAny; + } + + public KeyMap putAnyIfAbsent(KeyMap any) { + KeyMap oldAny = this.any; + if (oldAny == null) { + this.any = any; + return null; + } else { + return oldAny; + } + } + + public boolean hasRootValue() { + return rootValue != NO_VALUE; + } + + public V getRootValue() { + return getRootValueOrDefault(null); + } + + public V getRootValueOrDefault(final V defaultVal) { + V rootValue = this.rootValue; + return rootValue == NO_VALUE ? defaultVal : rootValue; + } + + public V getOrComputeRootValue(final Supplier supplier) { + V rootValue = this.rootValue; + if (rootValue == NO_VALUE) { + this.rootValue = rootValue = supplier.get(); + } + return rootValue; + } + + @SuppressWarnings("unchecked") + public V removeRootValue() { + V rootValue = this.rootValue; + if (rootValue != NO_VALUE) { + this.rootValue = (V) NO_VALUE; + } + return rootValue; + } + + public V putRootValue(final V rootValue) { + V old = this.rootValue; + this.rootValue = rootValue; + return old == NO_VALUE ? null : old; + } + + public KeyMap find(final String path) { + return find(new NameIterator(path)); + } + + public KeyMap find(final NameIterator ni) { + if (!ni.hasNext()) { + return this; + } + String seg = ni.getNextSegment(); + ni.next(); + KeyMap next = getOrDefault(seg, any); + return next == null ? null : next.find(ni); + } + + public KeyMap find(final Iterator iter) { + if (!iter.hasNext()) { + return this; + } + String seg = iter.next(); + KeyMap next = seg.equals("*") ? any : getOrDefault(seg, any); + return next == null ? null : next.find(iter); + } + + public KeyMap find(final Iterable i) { + return find(i.iterator()); + } + + public KeyMap findOrAdd(final String path) { + return findOrAdd(new NameIterator(path)); + } + + public KeyMap findOrAdd(final NameIterator ni) { + if (!ni.hasNext()) { + return this; + } + String seg = ni.getNextSegment(); + ni.next(); + try { + return computeIfAbsent(seg, k -> new KeyMap<>()).findOrAdd(ni); + } finally { + ni.previous(); + } + } + + public KeyMap findOrAdd(final Iterator iter) { + if (!iter.hasNext()) { + return this; + } + String seg = iter.next(); + KeyMap next = seg.equals("*") ? getOrCreateAny() : computeIfAbsent(seg, k -> new KeyMap<>()); + return next.findOrAdd(iter); + } + + public KeyMap findOrAdd(final Iterable i) { + return findOrAdd(i.iterator()); + } + + public KeyMap findOrAdd(final String... keys) { + return findOrAdd(keys, 0, keys.length); + } + + public KeyMap findOrAdd(final String[] keys, int off, int len) { + String seg = keys[off]; + KeyMap next = seg.equals("*") ? getOrCreateAny() : computeIfAbsent(seg, k -> new KeyMap<>()); + return next.findOrAdd(keys, off + 1, len - 1); + } + + public V findRootValue(final String path) { + return findRootValue(new NameIterator(path)); + } + + public V findRootValue(final NameIterator ni) { + KeyMap result = find(ni); + return result == null ? null : result.getRootValue(); + } + + public boolean hasRootValue(final String path) { + return hasRootValue(new NameIterator(path)); + } + + public boolean hasRootValue(final NameIterator ni) { + KeyMap result = find(ni); + return result != null && result.hasRootValue(); + } + + public KeyMap map(P param, BiFunction conversion) { + return map(param, conversion, new IdentityHashMap<>()); + } + + public KeyMap map(Function conversion) { + return map(conversion, Functions.functionBiFunction()); + } + + KeyMap map(P param, BiFunction conversion, IdentityHashMap, KeyMap> cached) { + if (cached.containsKey(this)) { + return cached.get(this); + } + KeyMap newMap = new KeyMap<>(size()); + cached.put(this, newMap); + Set>> entries = entrySet(); + for (Entry> entry : entries) { + newMap.put(entry.getKey(), entry.getValue().map(param, conversion, cached)); + } + KeyMap any = getAny(); + if (any != null) { + newMap.putAny(any.map(param, conversion, cached)); + } + if (hasRootValue()) { + newMap.putRootValue(conversion.apply(param, getRootValue())); + } + return newMap; + } + + public StringBuilder toString(StringBuilder b) { + b.append("KeyMap("); + V rootValue = this.rootValue; + if (rootValue == NO_VALUE) { + b.append("no value"); + } else { + b.append("value=").append(rootValue); + } + b.append(") {"); + final Iterator>> iterator = entrySet().iterator(); + KeyMap any = this.any; + if (iterator.hasNext()) { + Entry> entry = iterator.next(); + b.append(entry.getKey()).append("=>").append(entry.getValue()); + while (iterator.hasNext()) { + entry = iterator.next(); + b.append(',').append(entry.getKey()).append("=>").append(entry.getValue()); + } + if (any != null) { + b.append(',').append("(any)=>").append(any); + } + } else { + if (any != null) { + b.append("(any)=>").append(any); + } + } + b.append('}'); + return b; + } + + public String toString() { + return toString(new StringBuilder()).toString(); + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java b/mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java new file mode 100644 index 000000000..fc1a336da --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java @@ -0,0 +1,226 @@ +package io.smallrye.config.mapper; + +import static io.smallrye.config.mapper.ConfigurationInterface.LeafProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.MapProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.PrimitiveProperty; +import static io.smallrye.config.mapper.ConfigurationInterface.Property; +import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; +import static io.smallrye.config.mapper.ConfigurationInterface.rawTypeOf; +import static io.smallrye.config.mapper.ConfigurationInterface.typeOfParameter; +import static io.smallrye.config.mapper.ConfigurationValidationException.Problem; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +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.Set; + +import org.eclipse.microprofile.config.spi.Converter; + +import io.smallrye.config.ConfigValue; +import io.smallrye.config.Converters; +import io.smallrye.config.SmallRyeConfig; + +/** + * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration + * mapping has completed. + */ +public final class MappingContext { + + private final Map, Map>> enclosedThings = new IdentityHashMap<>(); + private final Map, Map> roots = new IdentityHashMap<>(); + private final Map, Map>> convertersByTypeAndField = new IdentityHashMap<>(); + private final List, Map>>> keyConvertersByDegreeTypeAndField = new ArrayList<>(); + private final Map, Converter> converterInstances = new IdentityHashMap<>(); + private final List allInstances = new ArrayList<>(); + private final SmallRyeConfig config; + private final StringBuilder stringBuilder = new StringBuilder(); + private final ArrayList problems = new ArrayList<>(); + + MappingContext(final SmallRyeConfig config) { + this.config = config; + } + + public ConfigurationObject getRoot(Class rootType, String rootPath) { + return roots.getOrDefault(rootType, Collections.emptyMap()).get(rootPath); + } + + public void registerRoot(Class rootType, String rootPath, ConfigurationObject root) { + roots.computeIfAbsent(rootType, x -> new HashMap<>()).put(rootPath, root); + } + + public Object getEnclosedField(Class enclosingType, String key, Object enclosingObject) { + return enclosedThings + .getOrDefault(enclosingType, Collections.emptyMap()) + .getOrDefault(key, Collections.emptyMap()) + .get(enclosingObject); + } + + 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); + } + + public T constructGroup(Class interfaceType) { + Constructor constructor = getConfigurationInterface(interfaceType).getConstructor(); + ConfigurationObject instance; + try { + instance = constructor.newInstance(this); + } catch (InstantiationException e) { + throw new InstantiationError(e.getMessage()); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } + allInstances.add(instance); + return interfaceType.cast(instance); + } + + @SuppressWarnings("unchecked") + public Converter getValueConverter(Class enclosingType, String field) { + return (Converter) convertersByTypeAndField + .computeIfAbsent(enclosingType, x -> new HashMap<>()) + .computeIfAbsent(field, x -> { + ConfigurationInterface ci = getConfigurationInterface(enclosingType); + Property property = ci.getProperty(field); + boolean optional = property.isOptional(); + if (property.isLeaf() || optional && property.asOptional().getNestedProperty().isLeaf()) { + LeafProperty leafProperty = optional ? property.asOptional().getNestedProperty().asLeaf() + : property.asLeaf(); + if (leafProperty.hasConvertWith()) { + Class> convertWith = leafProperty.getConvertWith(); + // todo: generics + return getConverterInstance(convertWith); + } else { + // todo: replace with generic converter lookup + Class valueRawType = leafProperty.getValueRawType(); + if (valueRawType == List.class) { + return Converters.newCollectionConverter( + config.getConverter(rawTypeOf(typeOfParameter(leafProperty.getValueType(), 0))), + ArrayList::new); + } else if (valueRawType == Set.class) { + return Converters.newCollectionConverter( + config.getConverter(rawTypeOf(typeOfParameter(leafProperty.getValueType(), 0))), + HashSet::new); + } else { + return config.getConverter(valueRawType); + } + } + } else if (property.isPrimitive()) { + PrimitiveProperty primitiveProperty = property.asPrimitive(); + if (primitiveProperty.hasConvertWith()) { + return getConverterInstance(primitiveProperty.getConvertWith()); + } else { + return config.getConverter(primitiveProperty.getBoxType()); + } + } else { + throw new IllegalStateException(); + } + }); + } + + @SuppressWarnings("unchecked") + public Converter getKeyConverter(Class enclosingType, String field, int degree) { + List, Map>>> list = this.keyConvertersByDegreeTypeAndField; + while (list.size() <= degree) { + list.add(new IdentityHashMap<>()); + } + Map, Map>> map = list.get(degree); + return (Converter) map + .computeIfAbsent(enclosingType, x -> new HashMap<>()) + .computeIfAbsent(field, x -> { + ConfigurationInterface ci = getConfigurationInterface(enclosingType); + MapProperty property = ci.getProperty(field).asMap(); + while (degree + 1 > property.getLevels()) { + property = property.getValueProperty().asMap(); + } + if (property.hasKeyConvertWith()) { + return getConverterInstance(property.getKeyConvertWith()); + } else { + // todo: replace with generic converter lookup + Class valueRawType = property.getKeyRawType(); + if (valueRawType == List.class) { + return Converters.newCollectionConverter( + config.getConverter(rawTypeOf(typeOfParameter(property.getKeyType(), 0))), ArrayList::new); + } else if (valueRawType == Set.class) { + return Converters.newCollectionConverter( + config.getConverter(rawTypeOf(typeOfParameter(property.getKeyType(), 0))), HashSet::new); + } else { + return config.getConverter(valueRawType); + } + } + }); + } + + @SuppressWarnings("unchecked") + public Converter getConverterInstance(Class> converterType) { + return (Converter) converterInstances.computeIfAbsent(converterType, t -> { + try { + return (Converter) t.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw new InstantiationError(e.getMessage()); + } catch (IllegalAccessException e) { + throw new IllegalAccessError(e.getMessage()); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t2) { + throw new UndeclaredThrowableException(t2); + } + } catch (NoSuchMethodException e) { + throw new NoSuchMethodError(e.getMessage()); + } + }); + } + + public NoSuchElementException noSuchElement(Class type) { + return new NoSuchElementException("A required configuration group of type " + type.getName() + " was not provided"); + } + + public void unknownConfigElement(final ConfigValue configValue) { + } + + void fillInOptionals() { + for (ConfigurationObject instance : allInstances) { + instance.fillInOptionals(this); + } + } + + public SmallRyeConfig getConfig() { + return config; + } + + public StringBuilder getStringBuilder() { + return stringBuilder; + } + + public void reportProblem(RuntimeException problem) { + problems.add(new Problem(problem.toString())); + } + + ArrayList getProblems() { + return problems; + } + + Map, Map> getRootsMap() { + return roots; + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Matching.java b/mapper/src/main/java/io/smallrye/config/mapper/Matching.java new file mode 100644 index 000000000..5d749b4d6 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/Matching.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the regular expression pattern that must be matched for this property to be considered valid. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface Matching { + /** + * The pattern. + * + * @return the pattern + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java new file mode 100644 index 000000000..7c76db856 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the maximum value (exclusive) for this property. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface MaxExclusive { + /** + * The value. + * + * @return the value + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java new file mode 100644 index 000000000..9d332eb49 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the maximum value (inclusive) for this property. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface MaxInclusive { + /** + * The value. + * + * @return the value + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java new file mode 100644 index 000000000..204b85561 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the minimum value (exclusive) for this property. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface MinExclusive { + /** + * The value. + * + * @return the value + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java new file mode 100644 index 000000000..dea5905ec --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specify the minimum value (inclusive) for this property. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE_USE) +public @interface MinInclusive { + /** + * The value. + * + * @return the value + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java b/mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java new file mode 100644 index 000000000..0238d58d6 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java @@ -0,0 +1,348 @@ +package io.smallrye.config.mapper; + +import java.util.NoSuchElementException; + +import io.smallrye.common.constraint.Assert; + +/** + * An iterator for property name strings. + */ +public final class NameIterator { + /** + * Configuration key maximum allowed length. + */ + public static final int MAX_LENGTH = 2048; + private static final int POS_MASK = 0x0FFF; + private static final int POS_BITS = 12; + private static final int SE_SHIFT = 32 - POS_BITS; + + private final String name; + private int pos; + + public NameIterator(final String name) { + this(name, false); + } + + public NameIterator(final String name, final boolean startAtEnd) { + this(name, startAtEnd ? name.length() : -1); + } + + public NameIterator(final String name, final int pos) { + Assert.checkNotNullParam("name", name); + if (name.length() > MAX_LENGTH) + throw new IllegalArgumentException("Name is too long"); + Assert.checkMinimumParameter("pos", -1, pos); + Assert.checkMaximumParameter("pos", name.length(), pos); + if (pos != -1 && pos != name.length() && name.charAt(pos) != '.') + throw new IllegalArgumentException("Position is not located at a delimiter"); + this.name = name; + this.pos = pos; + } + + public void goToEnd() { + this.pos = name.length(); + } + + public void goToStart() { + this.pos = -1; + } + + /** + * Get the cursor position. It will be {@code -1} if the cursor is at the beginning of the string, or {@code name.length()} + * if it is at the end. + * + * @return the cursor position + */ + public int getPosition() { + return pos; + } + + /* + * next-iteration DFA + * ## on EOI + * I → ## on '.' + * I → Q ## on '"' + * Q → I ## on '"' + * Q → QBS ## on '\' + * QBS → Q ## on any + * I → BS ## on '\' + * BS → I ## on any + */ + private static final int FS_INITIAL = 0; + private static final int FS_QUOTE = 1; + private static final int FS_BACKSLASH = 2; + private static final int FS_QUOTE_BACKSLASH = 3; + + /* + * Iteration cookie format + * + * Bit: 14...12 11 ... 0 + * ┌───────┬────────────┐ + * │ state │ position │ + * │ │ (signed) │ + * └───────┴────────────┘ + */ + + /** + * Create a new iteration cookie at the current position. + * + * @return the new cookie + */ + private int initIteration() { + return this.pos & POS_MASK; + } + + private int cookieOf(int state, int pos) { + return state << POS_BITS | pos & POS_MASK; + } + + private int getPosition(int cookie) { + return (cookie & POS_MASK) << SE_SHIFT >> SE_SHIFT; + } + + private int getState(int cookie) { + return cookie >> POS_BITS; + } + + /** + * Move to the next position. + * + * @param cookie the original cookie value + * @return the new cookie value + */ + private int nextPos(int cookie) { + int pos = getPosition(cookie); + if (isEndOfString(cookie)) { + throw new NoSuchElementException(); + } + int state = getState(cookie); + int ch; + for (;;) { + pos++; + if (pos == name.length()) { + return cookieOf(state, pos); + } + ch = name.charAt(pos); + if (state == FS_INITIAL) { + if (ch == '.') { + return cookieOf(state, pos); + } else if (ch == '"') { + state = FS_QUOTE; + } else if (ch == '\\') { + state = FS_BACKSLASH; + } else { + return cookieOf(state, pos); + } + } else if (state == FS_QUOTE) { + if (ch == '"') { + state = FS_INITIAL; + } else if (ch == '\\') { + state = FS_QUOTE_BACKSLASH; + } else { + return cookieOf(state, pos); + } + } else if (state == FS_BACKSLASH) { + state = FS_INITIAL; + return cookieOf(state, pos); + } else { + assert state == FS_QUOTE_BACKSLASH; + state = FS_QUOTE; + return cookieOf(state, pos); + } + } + } + + private int prevPos(int cookie) { + int pos = getPosition(cookie); + if (isStartOfString(cookie)) { + throw new NoSuchElementException(); + } + int state = getState(cookie); + int ch; + for (;;) { + pos--; + if (pos == -1) { + return cookieOf(state, pos); + } + ch = name.charAt(pos); + if (state == FS_INITIAL) { + if (pos >= 1 && name.charAt(pos - 1) == '\\') { + // always accept as-is + return cookieOf(state, pos); + } else if (ch == '.') { + return cookieOf(state, pos); + } else if (ch == '"') { + state = FS_QUOTE; + } else if (ch == '\\') { + // skip + } else { + // regular char + return cookieOf(state, pos); + } + } else if (state == FS_QUOTE) { + if (pos >= 1 && name.charAt(pos - 1) == '\\') { + // always accept as-is + return cookieOf(state, pos); + } else if (ch == '"') { + state = FS_INITIAL; + } else if (ch == '\\') { + // skip + } else { + return cookieOf(state, pos); + } + } else { + throw Assert.unreachableCode(); + } + } + } + + private boolean isSegmentDelimiter(int cookie) { + return isStartOfString(cookie) || isEndOfString(cookie) || getState(cookie) == FS_INITIAL && charAt(cookie) == '.'; + } + + private boolean isEndOfString(int cookie) { + return getPosition(cookie) == name.length(); + } + + private boolean isStartOfString(int cookie) { + return getPosition(cookie) == -1; + } + + private int charAt(int cookie) { + return name.charAt(getPosition(cookie)); + } + + public int getPreviousStart() { + int cookie = initIteration(); + do { + cookie = prevPos(cookie); + } while (!isSegmentDelimiter(cookie)); + return getPosition(cookie) + 1; + } + + public int getNextEnd() { + int cookie = initIteration(); + do { + cookie = nextPos(cookie); + } while (!isSegmentDelimiter(cookie)); + return getPosition(cookie); + } + + public boolean nextSegmentEquals(String other) { + return nextSegmentEquals(other, 0, other.length()); + } + + public boolean nextSegmentEquals(String other, int offs, int len) { + int cookie = initIteration(); + int strPos = 0; + for (;;) { + cookie = nextPos(cookie); + if (isSegmentDelimiter(cookie)) { + return strPos == len; + } + if (strPos == len) { + return false; + } + if (other.charAt(offs + strPos) != charAt(cookie)) { + return false; + } + strPos++; + } + } + + public String getNextSegment() { + final StringBuilder b = new StringBuilder(); + int cookie = initIteration(); + for (;;) { + cookie = nextPos(cookie); + if (isSegmentDelimiter(cookie)) { + return b.toString(); + } + b.append((char) charAt(cookie)); + } + } + + public boolean previousSegmentEquals(String other) { + return previousSegmentEquals(other, 0, other.length()); + } + + public boolean previousSegmentEquals(final String other, final int offs, final int len) { + int cookie = initIteration(); + int strPos = len; + for (;;) { + strPos--; + cookie = prevPos(cookie); + if (isSegmentDelimiter(cookie)) { + return strPos == -1; + } + if (strPos == -1) { + return false; + } + if (other.charAt(offs + strPos) != charAt(cookie)) { + return false; + } + } + } + + public String getPreviousSegment() { + final StringBuilder b = new StringBuilder(); + int cookie = initIteration(); + for (;;) { + cookie = prevPos(cookie); + if (isSegmentDelimiter(cookie)) { + return b.reverse().toString(); + } + b.append((char) charAt(cookie)); + } + } + + public String getAllPreviousSegments() { + final int pos = getPosition(); + if (pos == -1) { + return ""; + } + return name.substring(0, pos); + } + + public String getAllPreviousSegmentsWith(String suffix) { + final int pos = getPosition(); + if (pos == -1) { + return suffix; + } + return name.substring(0, pos) + "." + suffix; + } + + public boolean hasNext() { + return pos < name.length(); + } + + public boolean hasPrevious() { + return pos > -1; + } + + public void next() { + pos = getNextEnd(); + } + + public void previous() { + pos = getPreviousStart() - 1; + } + + public String getName() { + return name; + } + + public String toString() { + if (pos == -1) { + return "*" + name; + } else if (pos == name.length()) { + return name + "*"; + } else { + return name.substring(0, pos) + '*' + name.substring(pos + 1); + } + } + + public void appendTo(final StringBuilder sb) { + sb.append(getAllPreviousSegments()); + } +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/WithName.java b/mapper/src/main/java/io/smallrye/config/mapper/WithName.java new file mode 100644 index 000000000..82d265a6d --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/WithName.java @@ -0,0 +1,22 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The name of the configuration property or group. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface WithName { + /** + * The name of the property or group. Must not be empty. + * + * @return the name + */ + String value(); +} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java b/mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java new file mode 100644 index 000000000..40e855f28 --- /dev/null +++ b/mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java @@ -0,0 +1,16 @@ +package io.smallrye.config.mapper; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Use the parent's configuration name. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface WithParentName { +} diff --git a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java new file mode 100644 index 000000000..6737cca20 --- /dev/null +++ b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java @@ -0,0 +1,185 @@ +package io.smallrye.config.mapper; + +import static io.smallrye.config.mapper.ConfigMapping.*; +import static java.util.Collections.singletonMap; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.Test; + +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.common.MapBackedConfigSource; + +/** + * + */ +public class MapperTests { + + public interface Basic { + String helloWorld(); + + @Default("this is the default") + String helloWorldWithDefault(); + } + + @Test + public void testBasic() throws ConfigurationValidationException { + Builder mb = builder(); + mb.addRoot("test", Basic.class); + ConfigMapping mapping = mb.build(); + SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); + mapping.registerDefaultValues(cb); + SmallRyeConfig config = cb.build(); + try { + mapping.mapConfiguration(config); + } catch (ConfigurationValidationException e) { + assertEquals(1, e.getProblemCount()); + } + + cb = new SmallRyeConfigBuilder(); + cb.withSources(configSource(singletonMap("test.hello-world", "here I am!"))); + mapping.registerDefaultValues(cb); + config = cb.build(); + Result result = mapping.mapConfiguration(config); + Basic basic = result.getConfigRoot("test", Basic.class); + assertEquals("here I am!", basic.helloWorld()); + assertEquals("this is the default", basic.helloWorldWithDefault()); + } + + public interface SomeTypes { + int intProp(); + + boolean boolProp(); + + float floatProp(); + + double doubleProp(); + + long longProp(); + + char charProp(); + + Integer boxedIntProp(); + + List intListProp(); + + int[] intArrayProp(); + + OptionalInt optionalIntProp(); + } + + @Test + public void testTypes() throws ConfigurationValidationException { + Builder mb = builder(); + mb.addRoot("test.level2", SomeTypes.class); + ConfigMapping mapping = mb.build(); + SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); + mapping.registerDefaultValues(cb); + cb.withSources(configSource( + singletonMap("test.level2.int-prop", "1234"), + singletonMap("test.level2.bool-prop", "true"), + singletonMap("test.level2.float-prop", "12.25"), // choose an exact number for sanity + singletonMap("test.level2.double-prop", "555.5"), + singletonMap("test.level2.long-prop", "4000111222333"), + singletonMap("test.level2.char-prop", "X"), + singletonMap("test.level2.boxed-int-prop", "4096"), + singletonMap("test.level2.int-list-prop", "6,22,77"), + singletonMap("test.level2.int-array-prop", "10,9,8,7,3,2,1,0"), + singletonMap("test.level2.optional-int-prop", "555"))); + SmallRyeConfig config = cb.build(); + Result result = mapping.mapConfiguration(config); + SomeTypes someTypes = result.getConfigRoot("test.level2", SomeTypes.class); + assertEquals(1234, someTypes.intProp()); + assertTrue(someTypes.boolProp()); + assertEquals(12.25f, someTypes.floatProp(), 0f); + assertEquals(555.5, someTypes.doubleProp(), 0.0); + assertEquals(4000111222333L, someTypes.longProp()); + assertEquals('X', someTypes.charProp()); + assertEquals(Integer.valueOf(4096), someTypes.boxedIntProp()); + assertEquals(Arrays.asList(Integer.valueOf(6), Integer.valueOf(22), Integer.valueOf(77)), someTypes.intListProp()); + assertArrayEquals(new int[] { 10, 9, 8, 7, 3, 2, 1, 0 }, someTypes.intArrayProp()); + assertTrue(someTypes.optionalIntProp().isPresent()); + assertEquals(555, someTypes.optionalIntProp().getAsInt()); + } + + public interface WithSubGroups { + Optional optBasic1(); + + Optional optBasic2(); + + Basic reqBasic(); + } + + @Test + public void testWithSubGroups() throws ConfigurationValidationException { + Builder mb = builder(); + mb.addRoot("test", WithSubGroups.class); + ConfigMapping mapping = mb.build(); + SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); + mapping.registerDefaultValues(cb); + cb.withSources(configSource( + singletonMap("test.req-basic.hello-world", "surprise"), + singletonMap("test.req-basic.hello-world-with-default", "non-default"), + singletonMap("test.opt-basic2.hello-world", "present!"))); + SmallRyeConfig config = cb.build(); + Result result = mapping.mapConfiguration(config); + WithSubGroups root = result.getConfigRoot("test", WithSubGroups.class); + assertEquals("surprise", root.reqBasic().helloWorld()); + assertEquals("non-default", root.reqBasic().helloWorldWithDefault()); + assertFalse(root.optBasic1().isPresent()); + assertTrue(root.optBasic2().isPresent()); + assertEquals("present!", root.optBasic2().get().helloWorld()); + assertEquals("this is the default", root.optBasic2().get().helloWorldWithDefault()); + } + + public interface WithMaps { + Map theMap(); + } + + @Test + public void testWithMaps() throws ConfigurationValidationException { + Builder mb = builder(); + mb.addRoot("test", WithMaps.class); + ConfigMapping mapping = mb.build(); + SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); + mapping.registerDefaultValues(cb); + cb.withSources(configSource( + singletonMap("test.the-map.foo.req-basic.hello-world", "surprise"), + singletonMap("test.the-map.foo.req-basic.hello-world-with-default", "non-default"), + singletonMap("test.the-map.foo.opt-basic2.hello-world", "present!"))); + SmallRyeConfig config = cb.build(); + Result result = mapping.mapConfiguration(config); + WithMaps root = result.getConfigRoot("test", WithMaps.class); + assertEquals("surprise", root.theMap().get("foo").reqBasic().helloWorld()); + assertEquals("non-default", root.theMap().get("foo").reqBasic().helloWorldWithDefault()); + assertFalse(root.theMap().get("foo").optBasic1().isPresent()); + assertTrue(root.theMap().get("foo").optBasic2().isPresent()); + assertEquals("present!", root.theMap().get("foo").optBasic2().get().helloWorld()); + assertEquals("this is the default", root.theMap().get("foo").optBasic2().get().helloWorldWithDefault()); + } + + ///////////////////////////////////////////////////////////////////////////////////////// + + @SuppressWarnings("serial") + @SafeVarargs + public static ConfigSource configSource(Map... maps) { + int length = maps.length; + Map map = new HashMap<>(length); + for (Map subMap : maps) { + map.putAll(subMap); + } + return new MapBackedConfigSource("Test config source", map) { + }; + } +} diff --git a/pom.xml b/pom.xml index 3b22e0d48..c7ab041e8 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ http://smallrye.io + 7.0 1.4 1.2.0 @@ -67,6 +68,7 @@ common implementation + mapper sources/hocon sources/file-system sources/yaml @@ -116,6 +118,12 @@ + + org.ow2.asm + asm + ${version.asm} + + io.smallrye.common From f699816734cd4c596a34baa3de51e712077956d8 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 16 Jun 2020 10:47:47 +0100 Subject: [PATCH 02/42] Fix compilation issues after master merge. --- .../io/smallrye/config/ConfigSerializationTest.java | 4 ++-- .../config/ProfileConfigSourceInterceptorTest.java | 10 +++++----- mapper/pom.xml | 2 +- .../java/io/smallrye/config/mapper/ConfigMapping.java | 2 +- .../java/io/smallrye/config/mapper/MapperTests.java | 6 +++++- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java b/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java index b5efa2d33..858af9d28 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigSerializationTest.java @@ -23,7 +23,7 @@ public void serialize() { .withSources(ConfigValueConfigSourceWrapper.wrap(KeyValuesConfigSource.config("my.prop", "1"))) .build(); - assertEquals("1", config.getRawConfigValue("my.prop").getValue()); + assertEquals("1", config.getConfigValue("my.prop").getValue()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream)) { @@ -41,6 +41,6 @@ public void serialize() { } SmallRyeConfig serialized = (SmallRyeConfig) readObject; - assertEquals("1", serialized.getRawConfigValue("my.prop").getValue()); + assertEquals("1", serialized.getConfigValue("my.prop").getValue()); } } diff --git a/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java index 992ce2b15..efdd81862 100644 --- a/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/ProfileConfigSourceInterceptorTest.java @@ -24,8 +24,8 @@ public void profile() { assertEquals("2", config.getValue("my.prop", String.class)); - assertEquals("my.prop", config.getRawConfigValue("my.prop").getName()); - assertEquals("my.prop", config.getRawConfigValue("%prof.my.prop").getName()); + assertEquals("my.prop", config.getConfigValue("my.prop").getName()); + assertEquals("my.prop", config.getConfigValue("%prof.my.prop").getName()); } @Test @@ -169,8 +169,8 @@ public void propertyNames() { final SmallRyeConfig config = (SmallRyeConfig) buildConfig("my.prop", "1", "%prof.my.prop", "2", "%prof.prof.only", "1", SMALLRYE_PROFILE, "prof"); - assertEquals("2", config.getRawConfigValue("my.prop").getValue()); - assertEquals("1", config.getRawConfigValue("prof.only").getValue()); + assertEquals("2", config.getConfigValue("my.prop").getValue()); + assertEquals("1", config.getConfigValue("prof.only").getValue()); final List properties = StreamSupport.stream(config.getPropertyNames().spliterator(), false).collect(toList()); assertFalse(properties.contains("%prof.my.prop")); @@ -185,7 +185,7 @@ public void profileName() { .withProfile("prof") .build(); - assertEquals("2", config.getRawConfigValue("my.prop").getValue()); + assertEquals("2", config.getConfigValue("my.prop").getValue()); } private static Config buildConfig(String... keyValues) { diff --git a/mapper/pom.xml b/mapper/pom.xml index ce0e358ab..297ac767e 100644 --- a/mapper/pom.xml +++ b/mapper/pom.xml @@ -7,7 +7,7 @@ io.smallrye.config smallrye-config-parent - 1.8.1-SNAPSHOT + 1.8.2-SNAPSHOT smallrye-config-mapper diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index e41db6953..3d4c0ebbf 100644 --- a/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -617,7 +617,7 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida // lazily sweep for (String name : config.getPropertyNames()) { // may be null - ConfigValue configValue = config.getRawConfigValue(name); + ConfigValue configValue = config.getConfigValue(name); NameIterator ni = new NameIterator(name); BiConsumer action = matchActions.findRootValue(ni); if (action != null) { diff --git a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java index 6737cca20..128fdb465 100644 --- a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java +++ b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java @@ -31,6 +31,8 @@ public interface Basic { @Default("this is the default") String helloWorldWithDefault(); + + Integer port(); } @Test @@ -44,17 +46,19 @@ public void testBasic() throws ConfigurationValidationException { try { mapping.mapConfiguration(config); } catch (ConfigurationValidationException e) { - assertEquals(1, e.getProblemCount()); + assertEquals(2, e.getProblemCount()); } cb = new SmallRyeConfigBuilder(); cb.withSources(configSource(singletonMap("test.hello-world", "here I am!"))); + cb.withSources(configSource(singletonMap("test.port", "8080"))); mapping.registerDefaultValues(cb); config = cb.build(); Result result = mapping.mapConfiguration(config); Basic basic = result.getConfigRoot("test", Basic.class); assertEquals("here I am!", basic.helloWorld()); assertEquals("this is the default", basic.helloWorldWithDefault()); + assertEquals(8080, basic.port().intValue()); } public interface SomeTypes { From db7304e380fb8f959f25e091903f813fc3b166ce Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 16 Jun 2020 20:59:57 +0100 Subject: [PATCH 03/42] Move ConfigMapping classes to integrate with SmallRyeConfig. --- implementation/pom.xml | 5 +++ .../io/smallrye/config/SmallRyeConfig.java | 22 ++++++++++++ .../smallrye/config/mapper/ConfigMapping.java | 0 .../config/mapper/ConfigurationInterface.java | 0 .../config/mapper/ConfigurationObject.java | 0 .../ConfigurationValidationException.java | 0 .../smallrye/config/mapper/ConvertWith.java | 0 .../io/smallrye/config/mapper/Debugging.java | 0 .../io/smallrye/config/mapper/Default.java | 0 .../mapper/DefaultValuesConfigSource.java | 0 .../io/smallrye/config/mapper/KeyMap.java | 0 .../config/mapper/MappingContext.java | 0 .../smallrye/config/mapper/NameIterator.java | 0 .../io/smallrye/config/mapper/WithName.java | 0 .../config/mapper/WithParentName.java | 0 .../config/mapper/ConfigMappingTest.java | 35 +++++++++++++++++++ 16 files changed, 62 insertions(+) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/ConfigMapping.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/ConvertWith.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/Debugging.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/Default.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/KeyMap.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/MappingContext.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/NameIterator.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/WithName.java (100%) rename {mapper => implementation}/src/main/java/io/smallrye/config/mapper/WithParentName.java (100%) create mode 100644 implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java diff --git a/implementation/pom.xml b/implementation/pom.xml index 154ddf3ce..22a2de3a4 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -75,6 +75,11 @@ jboss-logging-processor + + org.ow2.asm + asm + + org.junit.jupiter junit-jupiter diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 44bff1143..629b82bcd 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -45,6 +45,8 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; +import io.smallrye.config.mapper.ConfigMapping; +import io.smallrye.config.mapper.ConfigurationValidationException; import io.smallrye.config.common.MapBackedConfigSource; /** @@ -211,6 +213,26 @@ public > Optional getOptionalValues(String name, C return getOptionalValue(name, Converters.newCollectionConverter(converter, collectionFactory)); } + @Experimental("TODO") + public T getConfigProperties(Class klass) { + return getConfigProperties(klass, ""); + } + + @Experimental("TODO") + public T getConfigProperties(Class klass, String prefix) { + final ConfigMapping configMapping = ConfigMapping.builder() + .addRoot(prefix, klass) + .build(); + + try { + final ConfigMapping.Result result = configMapping.mapConfiguration(this); + return result.getConfigRoot(prefix, klass); + } catch (ConfigurationValidationException e) { + e.printStackTrace(); + throw new IllegalArgumentException(e.getCause()); + } + } + @Override public Iterable getPropertyNames() { final HashSet names = new HashSet<>(); diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/ConfigMapping.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java b/implementation/src/main/java/io/smallrye/config/mapper/ConvertWith.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/ConvertWith.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConvertWith.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Debugging.java b/implementation/src/main/java/io/smallrye/config/mapper/Debugging.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/Debugging.java rename to implementation/src/main/java/io/smallrye/config/mapper/Debugging.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Default.java b/implementation/src/main/java/io/smallrye/config/mapper/Default.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/Default.java rename to implementation/src/main/java/io/smallrye/config/mapper/Default.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java b/implementation/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java rename to implementation/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java b/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/KeyMap.java rename to implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/MappingContext.java rename to implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java b/implementation/src/main/java/io/smallrye/config/mapper/NameIterator.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/NameIterator.java rename to implementation/src/main/java/io/smallrye/config/mapper/NameIterator.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/WithName.java b/implementation/src/main/java/io/smallrye/config/mapper/WithName.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/WithName.java rename to implementation/src/main/java/io/smallrye/config/mapper/WithName.java diff --git a/mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java b/implementation/src/main/java/io/smallrye/config/mapper/WithParentName.java similarity index 100% rename from mapper/src/main/java/io/smallrye/config/mapper/WithParentName.java rename to implementation/src/main/java/io/smallrye/config/mapper/WithParentName.java diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java new file mode 100644 index 000000000..c170dabf7 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -0,0 +1,35 @@ +package io.smallrye.config.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +import io.smallrye.config.KeyValuesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +public class ConfigMappingTest { + @Test + void configMapping() { + final SmallRyeConfig config = buildConfig("server.host", "localhost", "server.port", "8080"); + final ConfigsInterface configProperties = config.getConfigProperties(ConfigsInterface.class, "server"); + //assertEquals("localhost", configProperties.getHost()); + assertEquals("localhost", configProperties.host()); + //assertEquals(8080, configProperties.getPort()); + assertEquals(8080, configProperties.port()); + } + + interface ConfigsInterface { + String host(); + + int port(); + } + + private static SmallRyeConfig buildConfig(String... keyValues) { + return new SmallRyeConfigBuilder() + .addDefaultSources() + .addDefaultInterceptors() + .withSources(KeyValuesConfigSource.config(keyValues)) + .build(); + } +} From c7ab4ced71bf4b4b17021cbe364f7f68e51103fb Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 17 Jun 2020 12:15:28 +0100 Subject: [PATCH 04/42] Remove mapper project, but kept code if we need to retrieve additional things. --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index c7ab041e8..fdcaa5c02 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,6 @@ common implementation - mapper sources/hocon sources/file-system sources/yaml From d7075e8a5f226a540e2738b6c2c2af2d70527f84 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 17 Jun 2020 12:19:55 +0100 Subject: [PATCH 05/42] Ignore properties validation for unregistered roots. --- .../io/smallrye/config/SmallRyeConfig.java | 1 - .../smallrye/config/mapper/ConfigMapping.java | 23 +++++++--- .../config/mapper/MappingContext.java | 4 +- .../config/mapper/ConfigMappingTest.java | 45 ++++++++++++++----- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 629b82bcd..06f2d7fb8 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -228,7 +228,6 @@ public T getConfigProperties(Class klass, String prefix) { final ConfigMapping.Result result = configMapping.mapConfiguration(this); return result.getConfigRoot(prefix, klass); } catch (ConfigurationValidationException e) { - e.printStackTrace(); throw new IllegalArgumentException(e.getCause()); } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index 3d4c0ebbf..d9aff3364 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -16,6 +16,7 @@ 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; @@ -23,7 +24,6 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.common.function.Functions; -import io.smallrye.config.ConfigValue; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; @@ -616,15 +616,18 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida } // lazily sweep for (String name : config.getPropertyNames()) { - // may be null - ConfigValue configValue = config.getConfigValue(name); + // filter properties in root + if (!isPropertyInRoot(name)) { + break; + } + NameIterator ni = new NameIterator(name); BiConsumer action = matchActions.findRootValue(ni); if (action != null) { // ni is positioned at the end of the string action.accept(context, ni); - } else if (configValue != null) { - context.unknownConfigElement(configValue); + } else { + context.unknownConfigElement(name); } } ArrayList problems = context.getProblems(); @@ -636,6 +639,16 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida return new Result(context.getRootsMap()); } + private boolean isPropertyInRoot(String propertyName) { + final Set registeredRoots = roots.keySet(); + for (String registeredRoot : registeredRoots) { + if (propertyName.startsWith(registeredRoot)) { + return true; + } + } + return false; + } + public static final class Builder { SmallRyeConfig config; final Map> roots = new HashMap<>(); diff --git a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java index fc1a336da..d56940d9c 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java @@ -24,7 +24,6 @@ import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.ConfigValue; import io.smallrye.config.Converters; import io.smallrye.config.SmallRyeConfig; @@ -195,7 +194,8 @@ public NoSuchElementException noSuchElement(Class type) { return new NoSuchElementException("A required configuration group of type " + type.getName() + " was not provided"); } - public void unknownConfigElement(final ConfigValue configValue) { + public void unknownConfigElement(final String propertyName) { + problems.add(new Problem(propertyName + " does not map to any root")); } void fillInOptionals() { diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index c170dabf7..2348ffe7b 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -1,6 +1,7 @@ package io.smallrye.config.mapper; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; @@ -11,25 +12,45 @@ public class ConfigMappingTest { @Test void configMapping() { - final SmallRyeConfig config = buildConfig("server.host", "localhost", "server.port", "8080"); - final ConfigsInterface configProperties = config.getConfigProperties(ConfigsInterface.class, "server"); - //assertEquals("localhost", configProperties.getHost()); + final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( + KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); + final Configs configProperties = config.getConfigProperties(Configs.class, "server"); assertEquals("localhost", configProperties.host()); - //assertEquals(8080, configProperties.getPort()); assertEquals(8080, configProperties.port()); } - interface ConfigsInterface { - String host(); + @Test + void configMappingUnmapped() { + final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( + KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + .build(); + // TODO - Most likely we want to do a warning here, but just to try out the feature. + assertThrows(Exception.class, () -> config.getConfigProperties(Configs.class, "server")); + } - int port(); + @Test + void configMappingIgnorePropertiesInUnregisteredRoots() { + final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( + KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) + .build(); + final Configs configProperties = config.getConfigProperties(Configs.class, "server"); + assertEquals("localhost", configProperties.host()); + assertEquals(8080, configProperties.port()); } - private static SmallRyeConfig buildConfig(String... keyValues) { - return new SmallRyeConfigBuilder() + @Test + void configMappingIgnoreProperties() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .addDefaultInterceptors() - .withSources(KeyValuesConfigSource.config(keyValues)) - .build(); + .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); + final Configs configProperties = config.getConfigProperties(Configs.class, "server"); + assertEquals("localhost", configProperties.host()); + assertEquals(8080, configProperties.port()); + } + + interface Configs { + String host(); + + int port(); } } From e45f2dfb48316f458cdf62a33bd7ffa0c681e30a Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 17 Jun 2020 12:34:25 +0100 Subject: [PATCH 06/42] Support Bean style methods. --- .../main/java/io/smallrye/config/SmallRyeConfig.java | 2 +- .../io/smallrye/config/mapper/ConfigMapping.java | 4 ++++ .../io/smallrye/config/mapper/ConfigMappingTest.java | 12 +++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 06f2d7fb8..9e06d87e9 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -228,7 +228,7 @@ public T getConfigProperties(Class klass, String prefix) { final ConfigMapping.Result result = configMapping.mapConfiguration(this); return result.getConfigRoot(prefix, klass); } catch (ConfigurationValidationException e) { - throw new IllegalArgumentException(e.getCause()); + throw new IllegalArgumentException(e.getMessage()); } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index d9aff3364..890a39f11 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -94,6 +94,10 @@ static String skewer(String camelHumps) { static String skewer(String camelHumps, int start, int end, StringBuilder b) { assert !camelHumps.isEmpty() : "Method seems to have an empty name"; + if (camelHumps.startsWith("get")) { + camelHumps = camelHumps.substring(3); + end = camelHumps.length(); + } int cp = camelHumps.codePointAt(start); b.appendCodePoint(Character.toLowerCase(cp)); start += Character.charCount(cp); diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 2348ffe7b..14aaa4a33 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -16,11 +16,13 @@ void configMapping() { KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); final Configs configProperties = config.getConfigProperties(Configs.class, "server"); assertEquals("localhost", configProperties.host()); + assertEquals("localhost", configProperties.getHost()); assertEquals(8080, configProperties.port()); + assertEquals(8080, configProperties.getPort()); } @Test - void configMappingUnmapped() { + void unmapped() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); @@ -29,7 +31,7 @@ void configMappingUnmapped() { } @Test - void configMappingIgnorePropertiesInUnregisteredRoots() { + void ignorePropertiesInUnregisteredRoots() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) .build(); @@ -39,7 +41,7 @@ void configMappingIgnorePropertiesInUnregisteredRoots() { } @Test - void configMappingIgnoreProperties() { + void ignoreProperties() { final SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); @@ -52,5 +54,9 @@ interface Configs { String host(); int port(); + + String getHost(); + + int getPort(); } } From bce48220c43909c528c11bfced2dd447fd4ed45f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 17 Jun 2020 14:10:39 +0100 Subject: [PATCH 07/42] Support optional mapping registration in the SmallRyeBuilder. --- .../io/smallrye/config/SmallRyeConfig.java | 28 +++++++++--- .../config/SmallRyeConfigBuilder.java | 15 +++++++ .../smallrye/config/mapper/ConfigMapping.java | 3 +- .../io/smallrye/config/mapper/KeyMap.java | 5 ++- .../config/mapper/MappingContext.java | 2 +- .../config/mapper/ConfigMappingTest.java | 43 +++++++++++++++++++ 6 files changed, 88 insertions(+), 8 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 9e06d87e9..d05d40cc7 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -59,9 +59,12 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); + private final ConfigMapping configMapping; + SmallRyeConfig(SmallRyeConfigBuilder builder) { this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); + this.configMapping = buildConfigMapping(builder); } @Deprecated @@ -70,6 +73,7 @@ protected SmallRyeConfig(List configSources, Map(Converters.ALL_CONVERTERS); this.converters.putAll(converters); + this.configMapping = ConfigMapping.builder().build(); } private List buildConfigSources(final SmallRyeConfigBuilder builder) { @@ -133,6 +137,14 @@ private Map> buildConverters(final SmallRyeConfigBuilder buil return converters; } + private ConfigMapping buildConfigMapping(final SmallRyeConfigBuilder builder) { + final ConfigMapping.Builder mappingBuilder = ConfigMapping.builder(); + for (final Map.Entry> mapping : builder.getMappings()) { + mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); + } + return mappingBuilder.build(); + } + // no @Override public > C getValues(String name, Class itemClass, IntFunction collectionFactory) { return getValues(name, getConverter(itemClass), collectionFactory); @@ -220,13 +232,19 @@ public T getConfigProperties(Class klass) { @Experimental("TODO") public T getConfigProperties(Class klass, String prefix) { - final ConfigMapping configMapping = ConfigMapping.builder() - .addRoot(prefix, klass) - .build(); - try { final ConfigMapping.Result result = configMapping.mapConfiguration(this); - return result.getConfigRoot(prefix, klass); + final T configRoot = result.getConfigRoot(prefix, klass); + // Build one on the fly + // Think if we want to merge the unknown one with the current configMapping. + if (configRoot == null) { + return ConfigMapping.builder() + .addRoot(prefix, klass) + .build() + .mapConfiguration(this) + .getConfigRoot(prefix, klass); + } + return configRoot; } catch (ConfigurationValidationException e) { throw new IllegalArgumentException(e.getMessage()); } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index d1823edbb..41eca6482 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -17,6 +17,7 @@ package io.smallrye.config; import java.lang.reflect.Type; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -56,6 +57,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private final Set secretKeys = new HashSet<>(); private final List interceptors = new ArrayList<>(); private final Map defaultValues = new HashMap<>(); + private final Set>> mappings = new HashSet<>(); private ClassLoader classLoader = SecuritySupport.getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDefaultInterceptors = false; @@ -224,6 +226,15 @@ public SmallRyeConfigBuilder withDefaultValues(Map defaultValues return this; } + public SmallRyeConfigBuilder withMapping(Class klass) { + return withMapping(klass, ""); + } + + public SmallRyeConfigBuilder withMapping(Class klass, String prefix) { + mappings.add(new AbstractMap.SimpleImmutableEntry<>(prefix, klass)); + return this; + } + @Override public SmallRyeConfigBuilder withConverters(Converter[] converters) { for (Converter converter : converters) { @@ -292,6 +303,10 @@ Map getDefaultValues() { return defaultValues; } + Set>> getMappings() { + return mappings; + } + protected boolean isAddDefaultSources() { return addDefaultSources; } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index 890a39f11..910c5d3b0 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -8,6 +8,7 @@ import static io.smallrye.config.mapper.ConfigurationInterface.Property; import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; +import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; @@ -30,7 +31,7 @@ /** * */ -public final class ConfigMapping { +public final class ConfigMapping implements Serializable { /** * The do-nothing action is used when the matched property is eager. */ diff --git a/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java b/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java index bab82443a..f4eebb166 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java @@ -1,5 +1,6 @@ package io.smallrye.config.mapper; +import java.io.Serializable; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; @@ -14,7 +15,9 @@ * A multi-level key map. */ public final class KeyMap extends HashMap> { - private static final Object NO_VALUE = new Object(); + private static final Object NO_VALUE = new Serializable() { + private static final long serialVersionUID = -6072559389176920349L; + }; private static final long serialVersionUID = 3584966224369608557L; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java index d56940d9c..606022b21 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java @@ -91,7 +91,7 @@ public T constructGroup(Class interfaceType) { return interfaceType.cast(instance); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "unused" }) public Converter getValueConverter(Class enclosingType, String field) { return (Converter) convertersByTypeAndField .computeIfAbsent(enclosingType, x -> new HashMap<>()) diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 14aaa4a33..0d7f46121 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -50,6 +50,39 @@ void ignoreProperties() { assertEquals(8080, configProperties.port()); } + @Test + void splitRoots() throws Exception { + final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( + KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + .build(); + + final ConfigMapping configMapping = ConfigMapping.builder() + .addRoot("server", SplitRootServerHostAndPort.class) + .addRoot("server", SplitRootServerName.class) + .build(); + + final ConfigMapping.Result result = configMapping.mapConfiguration(config); + final SplitRootServerHostAndPort server = result.getConfigRoot("server", SplitRootServerHostAndPort.class); + assertEquals("localhost", server.host()); + assertEquals(8080, server.port()); + + final SplitRootServerName name = result.getConfigRoot("server", SplitRootServerName.class); + assertEquals("konoha", name.name()); + } + + @Test + void splitRootsInConfig() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", + "konoha")) + .withMapping(SplitRootServerHostAndPort.class, "server") + .withMapping(SplitRootServerName.class, "server") + .build(); + final SplitRootServerHostAndPort server = config.getConfigProperties(SplitRootServerHostAndPort.class, "server"); + assertEquals("localhost", server.host()); + assertEquals(8080, server.port()); + } + interface Configs { String host(); @@ -59,4 +92,14 @@ interface Configs { int getPort(); } + + interface SplitRootServerHostAndPort { + String host(); + + int port(); + } + + interface SplitRootServerName { + String name(); + } } From a6875df8d0ad43cee4e87617e970f5009b86ee3f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 17 Jun 2020 14:25:19 +0100 Subject: [PATCH 08/42] Moved generic APIs to main package. Consistent annotation names. --- .../config/{mapper => }/DefaultValuesConfigSource.java | 6 +++--- .../main/java/io/smallrye/config/{mapper => }/KeyMap.java | 2 +- .../java/io/smallrye/config/{mapper => }/NameIterator.java | 2 +- .../main/java/io/smallrye/config/mapper/ConfigMapping.java | 3 +++ .../io/smallrye/config/mapper/ConfigurationInterface.java | 6 +++--- .../config/mapper/{ConvertWith.java => WithConverter.java} | 2 +- .../config/mapper/{Default.java => WithDefault.java} | 2 +- .../test/java/io/smallrye/config/mapper/MapperTests.java | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) rename implementation/src/main/java/io/smallrye/config/{mapper => }/DefaultValuesConfigSource.java (75%) rename implementation/src/main/java/io/smallrye/config/{mapper => }/KeyMap.java (99%) rename implementation/src/main/java/io/smallrye/config/{mapper => }/NameIterator.java (99%) rename implementation/src/main/java/io/smallrye/config/mapper/{ConvertWith.java => WithConverter.java} (94%) rename implementation/src/main/java/io/smallrye/config/mapper/{Default.java => WithDefault.java} (92%) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java b/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java similarity index 75% rename from implementation/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java rename to implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java index ccf46f69b..e990d9406 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/DefaultValuesConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java @@ -1,14 +1,14 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.util.Collections; import java.util.Map; import org.eclipse.microprofile.config.spi.ConfigSource; -final class DefaultValuesConfigSource implements ConfigSource { +public final class DefaultValuesConfigSource implements ConfigSource { private final KeyMap defaultValues; - DefaultValuesConfigSource(final KeyMap defaultValues) { + public DefaultValuesConfigSource(final KeyMap defaultValues) { this.defaultValues = defaultValues; } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java similarity index 99% rename from implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java rename to implementation/src/main/java/io/smallrye/config/KeyMap.java index f4eebb166..195798422 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.io.Serializable; import java.util.HashMap; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/NameIterator.java b/implementation/src/main/java/io/smallrye/config/NameIterator.java similarity index 99% rename from implementation/src/main/java/io/smallrye/config/mapper/NameIterator.java rename to implementation/src/main/java/io/smallrye/config/NameIterator.java index 0238d58d6..5fa15b1a1 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/NameIterator.java +++ b/implementation/src/main/java/io/smallrye/config/NameIterator.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.util.NoSuchElementException; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index 910c5d3b0..526bfaecc 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -25,6 +25,9 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.common.function.Functions; +import io.smallrye.config.DefaultValuesConfigSource; +import io.smallrye.config.KeyMap; +import io.smallrye.config.NameIterator; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 41dd7e24f..816aefa88 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -944,7 +944,7 @@ private static Property getPropertyDef(Method method, Type type) { Class rawType = rawTypeOf(type); if (rawType.isPrimitive()) { // primitive! - Default annotation = method.getAnnotation(Default.class); + WithDefault annotation = method.getAnnotation(WithDefault.class); return new PrimitiveProperty(method, propertyName, rawType, convertWith, annotation == null ? null : annotation.value()); } @@ -972,13 +972,13 @@ private static Property getPropertyDef(Method method, Type type) { // fall out (leaf) } // otherwise it's a leaf - Default annotation = method.getAnnotation(Default.class); + WithDefault annotation = method.getAnnotation(WithDefault.class); return new LeafProperty(method, propertyName, type, convertWith, annotation == null ? null : annotation.value()); } private static Class> getConvertWith(final Type type) { if (type instanceof AnnotatedType) { - ConvertWith annotation = ((AnnotatedType) type).getAnnotation(ConvertWith.class); + WithConverter annotation = ((AnnotatedType) type).getAnnotation(WithConverter.class); if (annotation != null) { return annotation.value(); } else { diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConvertWith.java b/implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java similarity index 94% rename from implementation/src/main/java/io/smallrye/config/mapper/ConvertWith.java rename to implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java index 25be97a84..a1b637612 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConvertWith.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java @@ -14,7 +14,7 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE_USE) -public @interface ConvertWith { +public @interface WithConverter { /** * The converter class to use. * diff --git a/implementation/src/main/java/io/smallrye/config/mapper/Default.java b/implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java similarity index 92% rename from implementation/src/main/java/io/smallrye/config/mapper/Default.java rename to implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java index e81418d9c..c510f8a38 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/Default.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java @@ -12,6 +12,6 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface Default { +public @interface WithDefault { String value(); } diff --git a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java index 128fdb465..d329d21a0 100644 --- a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java +++ b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java @@ -29,7 +29,7 @@ public class MapperTests { public interface Basic { String helloWorld(); - @Default("this is the default") + @WithDefault("this is the default") String helloWorldWithDefault(); Integer port(); From 5a737a8d457574e4fa763a48cf630454b2aa78fc Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 29 Jun 2020 21:25:20 +0100 Subject: [PATCH 09/42] Support empty root prefix. --- .../io/smallrye/config/SmallRyeConfig.java | 4 +- .../smallrye/config/mapper/ConfigMapping.java | 25 +++---- .../config/mapper/ConfigurationInterface.java | 15 ++++ .../config/mapper/ConfigMappingTest.java | 71 +++++++++++++++++++ 4 files changed, 101 insertions(+), 14 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index d05d40cc7..ec86d1a0d 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -45,9 +45,9 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; +import io.smallrye.config.common.MapBackedConfigSource; import io.smallrye.config.mapper.ConfigMapping; import io.smallrye.config.mapper.ConfigurationValidationException; -import io.smallrye.config.common.MapBackedConfigSource; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -246,7 +246,7 @@ public T getConfigProperties(Class klass, String prefix) { } return configRoot; } catch (ConfigurationValidationException e) { - throw new IllegalArgumentException(e.getMessage()); + throw new IllegalArgumentException(e); } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index 526bfaecc..a162d978e 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -35,11 +35,12 @@ * */ public final class ConfigMapping 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 { @@ -632,9 +633,19 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida NameIterator ni = new NameIterator(name); BiConsumer action = matchActions.findRootValue(ni); if (action != null) { - // ni is positioned at the end of the string action.accept(context, ni); } else { + // Search if there is an empty prefix + KeyMap> emptyPrefixMap = matchActions.get(""); + if (emptyPrefixMap != null) { + ni.goToStart(); + BiConsumer emptyPrefixAction = emptyPrefixMap.findRootValue(ni); + if (emptyPrefixAction != null) { + emptyPrefixAction.accept(context, ni); + continue; + } + } + context.unknownConfigElement(name); } } @@ -658,22 +669,12 @@ private boolean isPropertyInRoot(String propertyName) { } public static final class Builder { - SmallRyeConfig config; final Map> roots = new HashMap<>(); final List ignored = new ArrayList<>(); Builder() { } - public SmallRyeConfig getConfig() { - return config; - } - - public Builder setConfig(final SmallRyeConfig config) { - this.config = config; - return this; - } - public Builder addRoot(String path, Class type) { Assert.checkNotNullParam("type", type); return addRoot(path, getConfigurationInterface(type)); diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 816aefa88..2bf045af8 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -791,6 +791,14 @@ private boolean appendPropertyName(final MethodVisitor ctor, final Property prop return false; } // 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); + ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); // stack: sb ctor.visitLdcInsn(Character.valueOf('.')); @@ -798,6 +806,13 @@ private boolean appendPropertyName(final MethodVisitor ctor, final Property prop 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.visitLabel(_continue); + + ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); + // stack: sb if (property.hasPropertyName()) { ctor.visitLdcInsn(property.getPropertyName()); diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 0d7f46121..c29433de7 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -3,11 +3,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.HashMap; +import java.util.Map; + import org.junit.jupiter.api.Test; import io.smallrye.config.KeyValuesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.common.MapBackedConfigSource; public class ConfigMappingTest { @Test @@ -83,6 +87,41 @@ void splitRootsInConfig() { assertEquals(8080, server.port()); } + @Test + void subGroups() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", + "konoha")) + .withMapping(ServerSub.class, "server") + .build(); + final ServerSub server = config.getConfigProperties(ServerSub.class, "server"); + assertEquals("localhost", server.subHostAndPort().host()); + assertEquals(8080, server.subHostAndPort().port()); + assertEquals("konoha", server.subName().name()); + } + + @Test + void types() { + final Map typesConfig = new HashMap() { + { + put("int", "9"); + put("long", "9999999999"); + } + }; + + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(new MapBackedConfigSource("test", typesConfig) { + }) + .withMapping(SomeTypes.class) + .build(); + final SomeTypes types = config.getConfigProperties(SomeTypes.class); + + assertEquals(9, types.intPrimitive()); + assertEquals(9, types.intWrapper()); + assertEquals(9999999999L, types.longPrimitive()); + assertEquals(9999999999L, types.longWrapper()); + } + interface Configs { String host(); @@ -102,4 +141,36 @@ interface SplitRootServerHostAndPort { interface SplitRootServerName { String name(); } + + interface ServerSub { + @WithParentName + ServerSubHostAndPort subHostAndPort(); + + @WithParentName + ServerSubName subName(); + } + + interface ServerSubHostAndPort { + String host(); + + int port(); + } + + interface ServerSubName { + String name(); + } + + public interface SomeTypes { + @WithName("int") + int intPrimitive(); + + @WithName("int") + Integer intWrapper(); + + @WithName("long") + long longPrimitive(); + + @WithName("long") + Long longWrapper(); + } } From 116daebbcad193d61d00bbb648e506d161ee4b83 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 29 Jun 2020 22:20:17 +0100 Subject: [PATCH 10/42] Add Collections tests. --- .../config/KeyValuesConfigSource.java | 4 ++ .../config/mapper/ConfigMappingTest.java | 71 ++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java b/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java index 546886f62..e498fba7f 100644 --- a/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java +++ b/implementation/src/test/java/io/smallrye/config/KeyValuesConfigSource.java @@ -45,6 +45,10 @@ public String getName() { return "KeyValuesConfigSource"; } + public static ConfigSource config(Map properties) { + return new KeyValuesConfigSource(properties); + } + public static ConfigSource config(String... keyValues) { if (keyValues.length % 2 != 0) { throw new IllegalArgumentException("keyValues array must be a multiple of 2"); diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index c29433de7..ebe8a27d0 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -1,17 +1,20 @@ package io.smallrye.config.mapper; +import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import io.smallrye.config.KeyValuesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; -import io.smallrye.config.common.MapBackedConfigSource; public class ConfigMappingTest { @Test @@ -106,12 +109,15 @@ void types() { { 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(new MapBackedConfigSource("test", typesConfig) { - }) + .withSources(KeyValuesConfigSource.config(typesConfig)) .withMapping(SomeTypes.class) .build(); final SomeTypes types = config.getConfigProperties(SomeTypes.class); @@ -120,6 +126,33 @@ void types() { assertEquals(9, types.intWrapper()); assertEquals(9999999999L, types.longPrimitive()); assertEquals(9999999999L, types.longWrapper()); + assertEquals(99.9f, types.floatPrimitive()); + assertEquals(99.9f, types.floatWrapper()); + assertEquals(99.99, types.doublePrimitive()); + assertEquals(99.99, types.doubleWrapper()); + assertEquals('c', types.charPrimitive()); + assertEquals('c', types.charWrapper()); + assertTrue(types.booleanPrimitive()); + assertTrue(types.booleanWrapper()); + } + + @Test + void collectionTypes() { + final Map typesConfig = new HashMap() { + { + put("strings", "foo,bar"); + put("ints", "1,2,3"); + } + }; + + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config(typesConfig)) + .withMapping(CollectionTypes.class) + .build(); + final CollectionTypes types = config.getConfigProperties(CollectionTypes.class); + + assertEquals(Stream.of("foo", "bar").collect(toList()), types.listStrings()); + assertEquals(Stream.of(1, 2, 3).collect(toList()), types.listInts()); } interface Configs { @@ -172,5 +205,37 @@ public interface SomeTypes { @WithName("long") Long longWrapper(); + + @WithName("float") + float floatPrimitive(); + + @WithName("float") + Float floatWrapper(); + + @WithName("double") + double doublePrimitive(); + + @WithName("double") + Double doubleWrapper(); + + @WithName("char") + char charPrimitive(); + + @WithName("char") + Character charWrapper(); + + @WithName("boolean") + boolean booleanPrimitive(); + + @WithName("boolean") + Boolean booleanWrapper(); + } + + public interface CollectionTypes { + @WithName("strings") + List listStrings(); + + @WithName("ints") + List listInts(); } } From 0746069333b333aacfdf9a11edb2bef0aa72670b Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 29 Jun 2020 23:39:14 +0100 Subject: [PATCH 11/42] ConfigMapping defaults with Config. --- .../main/java/io/smallrye/config/KeyMap.java | 19 ++++++++++++ .../io/smallrye/config/SmallRyeConfig.java | 25 +++++++++------- .../smallrye/config/mapper/ConfigMapping.java | 8 ++--- .../java/io/smallrye/config/KeyMapTest.java | 30 +++++++++++++++++++ .../config/mapper/ConfigMappingTest.java | 20 +++++++++++++ 5 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 implementation/src/test/java/io/smallrye/config/KeyMapTest.java diff --git a/implementation/src/main/java/io/smallrye/config/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java index 195798422..0a0666b52 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; @@ -250,4 +251,22 @@ public StringBuilder toString(StringBuilder b) { public String toString() { return toString(new StringBuilder()).toString(); } + + public Map toMap() { + final Map map = new HashMap<>(); + unwrap(this, "", map); + return map; + } + + private void unwrap(KeyMap keyMap, String key, Map map) { + for (String path : keyMap.keySet()) { + final KeyMap nested = keyMap.get(path); + final String nestedKey = key.length() == 0 ? path : key + "." + path; + if (!nested.hasRootValue()) { + unwrap(nested, nestedKey, map); + } else { + map.put(nestedKey, nested.getRootValue()); + } + } + } } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index ec86d1a0d..b8a29193d 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -62,18 +62,31 @@ public class SmallRyeConfig implements Config, Serializable { private final ConfigMapping configMapping; SmallRyeConfig(SmallRyeConfigBuilder builder) { + this.configMapping = buildConfigMapping(builder); this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); - this.configMapping = buildConfigMapping(builder); } @Deprecated protected SmallRyeConfig(List configSources, Map> converters) { + this.configMapping = ConfigMapping.builder().build(); this.configSources = new AtomicReference<>( new ConfigSources(configSources, buildInterceptors(new SmallRyeConfigBuilder()))); this.converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS); this.converters.putAll(converters); - this.configMapping = ConfigMapping.builder().build(); + } + + private ConfigMapping buildConfigMapping(final SmallRyeConfigBuilder builder) { + final ConfigMapping.Builder mappingBuilder = ConfigMapping.builder(); + for (final Map.Entry> mapping : builder.getMappings()) { + mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); + } + + final ConfigMapping configMapping = mappingBuilder.build(); + final Map defaultValues = configMapping.getDefaultValues(); + defaultValues.forEach((name, value) -> builder.getDefaultValues().putIfAbsent(name, value)); + + return configMapping; } private List buildConfigSources(final SmallRyeConfigBuilder builder) { @@ -137,14 +150,6 @@ private Map> buildConverters(final SmallRyeConfigBuilder buil return converters; } - private ConfigMapping buildConfigMapping(final SmallRyeConfigBuilder builder) { - final ConfigMapping.Builder mappingBuilder = ConfigMapping.builder(); - for (final Map.Entry> mapping : builder.getMappings()) { - mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); - } - return mappingBuilder.build(); - } - // no @Override public > C getValues(String name, Class itemClass, IntFunction collectionFactory) { return getValues(name, getConverter(itemClass), collectionFactory); diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index a162d978e..7b791f9db 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -25,11 +25,9 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.common.function.Functions; -import io.smallrye.config.DefaultValuesConfigSource; import io.smallrye.config.KeyMap; import io.smallrye.config.NameIterator; import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; /** * @@ -602,10 +600,8 @@ public static Builder builder() { return new Builder(); } - public B registerDefaultValues(B builder) { - Assert.checkNotNullParam("builder", builder); - builder.withSources(new DefaultValuesConfigSource(defaultValues)); - return builder; + public Map getDefaultValues() { + return Collections.unmodifiableMap(defaultValues.toMap()); } public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValidationException { diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java new file mode 100644 index 000000000..8fcd5d48f --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java @@ -0,0 +1,30 @@ +package io.smallrye.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +public class KeyMapTest { + @Test + void find() { + KeyMap root = new KeyMap<>(); + root.findOrAdd("root").findOrAdd("foo").putRootValue("foo"); + root.findOrAdd("root").findOrAdd("bar").putRootValue("bar"); + + assertEquals("foo", root.findRootValue("root.foo")); + assertEquals("bar", root.findRootValue("root.bar")); + } + + @Test + void unwrap() { + KeyMap root = new KeyMap<>(); + root.findOrAdd("root").findOrAdd("foo").putRootValue("foo"); + root.findOrAdd("root").findOrAdd("bar").putRootValue("bar"); + + Map properties = root.toMap(); + assertEquals("foo", properties.get("root.foo")); + assertEquals("bar", properties.get("root.bar")); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index ebe8a27d0..954bd1f8d 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -155,6 +155,18 @@ void collectionTypes() { assertEquals(Stream.of(1, 2, 3).collect(toList()), types.listInts()); } + @Test + void defaults() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(Defaults.class) + .build(); + final Defaults defaults = config.getConfigProperties(Defaults.class); + + assertEquals("foo", defaults.foo()); + assertEquals("bar", defaults.bar()); + assertEquals("foo", config.getRawValue("foo")); + } + interface Configs { String host(); @@ -238,4 +250,12 @@ public interface CollectionTypes { @WithName("ints") List listInts(); } + + public interface Defaults { + @WithDefault("foo") + String foo(); + + @WithDefault("bar") + String bar(); + } } From 7b6f08011956c599f5200feed4da40fc2bec1c53 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Mon, 29 Jun 2020 23:47:29 +0100 Subject: [PATCH 12/42] ConfigMapping Converter test. --- .../config/mapper/ConfigMappingTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 954bd1f8d..4474dfb08 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.stream.Stream; +import org.eclipse.microprofile.config.spi.Converter; import org.junit.jupiter.api.Test; import io.smallrye.config.KeyValuesConfigSource; @@ -167,6 +168,19 @@ void defaults() { assertEquals("foo", config.getRawValue("foo")); } + @Test + void converters() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config("foo", "notbar")) + .withMapping(Converters.class) + .withConverter(String.class, 100, new FooBarConverter()) + .build(); + final Converters converters = config.getConfigProperties(Converters.class); + + assertEquals("bar", converters.foo()); + assertEquals("bar", config.getValue("foo", String.class)); + } + interface Configs { String host(); @@ -258,4 +272,16 @@ public interface Defaults { @WithDefault("bar") String bar(); } + + public interface Converters { + @WithConverter(FooBarConverter.class) + String foo(); + } + + public static class FooBarConverter implements Converter { + @Override + public String convert(final String value) { + return "bar"; + } + } } From c92e4507deb5afda1b11acfa3fcb33ac0386c4e7 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 14:13:30 +0100 Subject: [PATCH 13/42] Fixed ConfigMapping simple Optionals. --- .../config/mapper/ConfigurationInterface.java | 5 ++ .../config/mapper/ConfigMappingTest.java | 62 +++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 2bf045af8..a9eee6b6f 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -402,6 +402,11 @@ public OptionalProperty asOptional() { return this; } + @Override + public boolean isLeaf() { + return nestedProperty.isLeaf(); + } + public MayBeOptionalProperty getNestedProperty() { return nestedProperty; } diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 4474dfb08..a88a4812f 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -8,9 +8,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; import java.util.stream.Stream; import org.eclipse.microprofile.config.spi.Converter; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.smallrye.config.KeyValuesConfigSource; @@ -22,7 +25,7 @@ public class ConfigMappingTest { void configMapping() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); - final Configs configProperties = config.getConfigProperties(Configs.class, "server"); + final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals("localhost", configProperties.getHost()); assertEquals(8080, configProperties.port()); @@ -35,7 +38,7 @@ void unmapped() { KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); // TODO - Most likely we want to do a warning here, but just to try out the feature. - assertThrows(Exception.class, () -> config.getConfigProperties(Configs.class, "server")); + assertThrows(Exception.class, () -> config.getConfigProperties(Server.class, "server")); } @Test @@ -43,7 +46,7 @@ void ignorePropertiesInUnregisteredRoots() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) .build(); - final Configs configProperties = config.getConfigProperties(Configs.class, "server"); + final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -53,7 +56,7 @@ void ignoreProperties() { final SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); - final Configs configProperties = config.getConfigProperties(Configs.class, "server"); + final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -137,6 +140,33 @@ void types() { assertTrue(types.booleanWrapper()); } + @Test + void optionals() { + final Map typesConfig = new HashMap() { + { + put("server.host", "localhost"); + put("server.port", "8080"); + put("optional", "optional"); + put("optional.int", "9"); + } + }; + + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config(typesConfig)) + .withMapping(Optionals.class) + .build(); + final Optionals optionals = config.getConfigProperties(Optionals.class); + + assertTrue(optionals.server().isPresent()); + assertEquals("localhost", optionals.server().get().host()); + assertEquals(8080, optionals.server().get().port()); + + assertTrue(optionals.optional().isPresent()); + assertEquals("optional", optionals.optional().get()); + assertTrue(optionals.optionalInt().isPresent()); + assertEquals(9, optionals.optionalInt().getAsInt()); + } + @Test void collectionTypes() { final Map typesConfig = new HashMap() { @@ -168,10 +198,21 @@ void defaults() { assertEquals("foo", config.getRawValue("foo")); } + @Test + @Disabled + void defaultsOnTheFly() { + final SmallRyeConfig config = new SmallRyeConfigBuilder().build(); + final Defaults defaults = config.getConfigProperties(Defaults.class); + + assertEquals("foo", defaults.foo()); + assertEquals("bar", defaults.bar()); + assertEquals("foo", config.getRawValue("foo")); + } + @Test void converters() { final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config("foo", "notbar")) + .withSources(KeyValuesConfigSource.config("foo", "notbar")) .withMapping(Converters.class) .withConverter(String.class, 100, new FooBarConverter()) .build(); @@ -181,7 +222,7 @@ void converters() { assertEquals("bar", config.getValue("foo", String.class)); } - interface Configs { + interface Server { String host(); int port(); @@ -257,6 +298,15 @@ public interface SomeTypes { Boolean booleanWrapper(); } + public interface Optionals { + Optional server(); + + Optional optional(); + + @WithName("optional.int") + OptionalInt optionalInt(); + } + public interface CollectionTypes { @WithName("strings") List listStrings(); From 6fa6f5c1d868d0a3a36df7d9832cea8d87300799 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 15:40:25 +0100 Subject: [PATCH 14/42] Remove unused class. --- .../config/DefaultValuesConfigSource.java | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java diff --git a/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java b/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java deleted file mode 100644 index e990d9406..000000000 --- a/implementation/src/main/java/io/smallrye/config/DefaultValuesConfigSource.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.smallrye.config; - -import java.util.Collections; -import java.util.Map; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -public final class DefaultValuesConfigSource implements ConfigSource { - private final KeyMap defaultValues; - - public DefaultValuesConfigSource(final KeyMap defaultValues) { - this.defaultValues = defaultValues; - } - - public Map getProperties() { - return Collections.emptyMap(); - } - - public String getValue(final String s) { - return defaultValues.findRootValue(s); - } - - public String getName() { - return "Default values"; - } - - public int getOrdinal() { - return Integer.MIN_VALUE; - } -} From 560d7d6d3239665499afae4f0d3c80bb1dc5680f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 18:46:37 +0100 Subject: [PATCH 15/42] Added tests and fixed ConfigMapping Map support. --- .../smallrye/config/mapper/ConfigMapping.java | 7 +++-- .../config/mapper/ConfigurationInterface.java | 4 +-- .../config/mapper/ConfigMappingTest.java | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java index 7b791f9db..520c01441 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java @@ -377,20 +377,17 @@ private void processLazyMap(final ArrayDeque currentPath, Class> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null; Class keyRawType = property.getKeyRawType(); - currentPath.addLast("*"); if (valueProperty.isLeaf()) { LeafProperty leafProperty = valueProperty.asLeaf(); Class> valConvertWith = leafProperty.getConvertWith(); Class valueRawType = leafProperty.getValueRawType(); - matchActions.find(currentPath).putRootValue((mc, ni) -> { + matchActions.findOrAdd(currentPath).putRootValue((mc, ni) -> { StringBuilder sb = mc.getStringBuilder(); sb.setLength(0); - ni.previous(); sb.append(ni.getAllPreviousSegments()); String configKey = sb.toString(); Map map = getEnclosingMap.apply(mc, ni); - ni.next(); String rawMapKey = ni.getPreviousSegment(); Converter keyConv; SmallRyeConfig config = mc.getConfig(); @@ -409,6 +406,7 @@ private void processLazyMap(final ArrayDeque currentPath, ((Map) map).put(key, config.getValue(configKey, valueConv)); }); } else if (valueProperty.isMap()) { + currentPath.addLast("*"); processLazyMap(currentPath, matchActions, defaultValues, valueProperty.asMap(), (mc, ni) -> { ni.previous(); Map enclosingMap = getEnclosingMap.apply(mc, ni); @@ -426,6 +424,7 @@ private void processLazyMap(final ArrayDeque currentPath, }, enclosingGroup); } else { assert valueProperty.isGroup(); + currentPath.addLast("*"); final GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap(getEnclosingMap, property, enclosingGroup, valueProperty.asGroup()); processLazyGroupInGroup(currentPath, matchActions, defaultValues, valueProperty.asGroup(), diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index a9eee6b6f..176db98e0 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -455,7 +455,7 @@ public Type getValueType() { } public Class> getConvertWith() { - return Assert.checkNotNullParam("convertWith", convertWith); + return convertWith; } public boolean hasConvertWith() { @@ -626,7 +626,7 @@ private void addProperties(ClassVisitor cv, final String className, MethodVisito // stack: obj? obj? fio.visitJumpInsn(Opcodes.IFNULL, _continue); // stack: obj - fio.visitTypeInsn(Opcodes.CHECKCAST, getDescriptor(Map.class)); + fio.visitTypeInsn(Opcodes.CHECKCAST, I_MAP); // stack: map fio.visitVarInsn(Opcodes.ALOAD, V_THIS); // stack: map this diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index a88a4812f..1c1711d19 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -186,6 +186,26 @@ void collectionTypes() { assertEquals(Stream.of(1, 2, 3).collect(toList()), types.listInts()); } + @Test + void maps() { + final Map typesConfig = new HashMap() { + { + put("server.host", "localhost"); + put("server.port", "8080"); + put("another-server", "localhost"); + } + }; + + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(KeyValuesConfigSource.config(typesConfig)) + .withMapping(Maps.class) + .build(); + final Maps maps = config.getConfigProperties(Maps.class); + + assertEquals("localhost", maps.server().get("server").host()); + assertEquals("localhost", maps.anotherServer().get("another-server")); + } + @Test void defaults() { final SmallRyeConfig config = new SmallRyeConfigBuilder() @@ -315,6 +335,13 @@ public interface CollectionTypes { List listInts(); } + public interface Maps { + @WithParentName + Map server(); + + Map anotherServer(); + } + public interface Defaults { @WithDefault("foo") String foo(); From fd7ec82dd28f89e9fe4af71bf8d58a16b2236c45 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 19:32:47 +0100 Subject: [PATCH 16/42] Added a few more tests to ConfigMapping. --- .../main/java/io/smallrye/config/KeyMap.java | 2 +- .../config/mapper/ConfigMappingTest.java | 72 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java index 0a0666b52..79f06d089 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -169,7 +169,7 @@ public KeyMap findOrAdd(final String... keys) { public KeyMap findOrAdd(final String[] keys, int off, int len) { String seg = keys[off]; KeyMap next = seg.equals("*") ? getOrCreateAny() : computeIfAbsent(seg, k -> new KeyMap<>()); - return next.findOrAdd(keys, off + 1, len - 1); + return off + 1 > len - 1 ? next : next.findOrAdd(keys, off + 1, len - 1); } public V findRootValue(final String path) { diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java index 1c1711d19..4d9e119c1 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java @@ -1,5 +1,6 @@ package io.smallrye.config.mapper; +import static io.smallrye.config.KeyValuesConfigSource.config; import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -16,7 +17,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import io.smallrye.config.KeyValuesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; @@ -24,7 +24,7 @@ public class ConfigMappingTest { @Test void configMapping() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); + config("server.host", "localhost", "server.port", "8080")).build(); final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals("localhost", configProperties.getHost()); @@ -32,10 +32,24 @@ void configMapping() { assertEquals(8080, configProperties.getPort()); } + @Test + void configMappingBuilder() throws Exception { + final ConfigMapping configMapping = ConfigMapping.builder().addRoot("server", Server.class) + .addIgnored("server", "name").build(); + final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( + config("server.host", "localhost", "server.port", "8080", "server.name", "name")).build(); + + final Server server = configMapping.mapConfiguration(config).getConfigRoot("server", Server.class); + assertEquals("localhost", server.host()); + assertEquals("localhost", server.getHost()); + assertEquals(8080, server.port()); + assertEquals(8080, server.getPort()); + } + @Test void unmapped() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); // TODO - Most likely we want to do a warning here, but just to try out the feature. assertThrows(Exception.class, () -> config.getConfigProperties(Server.class, "server")); @@ -44,7 +58,7 @@ void unmapped() { @Test void ignorePropertiesInUnregisteredRoots() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) + config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) .build(); final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); @@ -55,7 +69,7 @@ void ignorePropertiesInUnregisteredRoots() { void ignoreProperties() { final SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() - .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080")).build(); + .withSources(config("server.host", "localhost", "server.port", "8080")).build(); final Server configProperties = config.getConfigProperties(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); @@ -64,7 +78,7 @@ void ignoreProperties() { @Test void splitRoots() throws Exception { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) + config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); final ConfigMapping configMapping = ConfigMapping.builder() @@ -84,7 +98,7 @@ void splitRoots() throws Exception { @Test void splitRootsInConfig() { final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", + .withSources(config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .withMapping(SplitRootServerHostAndPort.class, "server") .withMapping(SplitRootServerName.class, "server") @@ -97,7 +111,7 @@ void splitRootsInConfig() { @Test void subGroups() { final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config("server.host", "localhost", "server.port", "8080", "server.name", + .withSources(config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .withMapping(ServerSub.class, "server") .build(); @@ -121,7 +135,7 @@ void types() { }; final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config(typesConfig)) + .withSources(config(typesConfig)) .withMapping(SomeTypes.class) .build(); final SomeTypes types = config.getConfigProperties(SomeTypes.class); @@ -152,7 +166,7 @@ void optionals() { }; final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config(typesConfig)) + .withSources(config(typesConfig)) .withMapping(Optionals.class) .build(); final Optionals optionals = config.getConfigProperties(Optionals.class); @@ -177,7 +191,7 @@ void collectionTypes() { }; final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config(typesConfig)) + .withSources(config(typesConfig)) .withMapping(CollectionTypes.class) .build(); final CollectionTypes types = config.getConfigProperties(CollectionTypes.class); @@ -197,7 +211,7 @@ void maps() { }; final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config(typesConfig)) + .withSources(config(typesConfig)) .withMapping(Maps.class) .build(); final Maps maps = config.getConfigProperties(Maps.class); @@ -232,7 +246,7 @@ void defaultsOnTheFly() { @Test void converters() { final SmallRyeConfig config = new SmallRyeConfigBuilder() - .withSources(KeyValuesConfigSource.config("foo", "notbar")) + .withSources(config("foo", "notbar")) .withMapping(Converters.class) .withConverter(String.class, 100, new FooBarConverter()) .build(); @@ -242,6 +256,32 @@ void converters() { assertEquals("bar", config.getValue("foo", String.class)); } + @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)) + .withMapping(ComplexSample.class) + .build(); + + final ComplexSample sample = config.getConfigProperties(ComplexSample.class); + assertEquals("localhost", sample.server().subHostAndPort().host()); + assertEquals(8080, sample.server().subHostAndPort().port()); + assertTrue(sample.client().isPresent()); + assertEquals("clienthost", sample.client().get().subHostAndPort().host()); + assertEquals(80, sample.client().get().subHostAndPort().port()); + } + interface Server { String host(); @@ -350,6 +390,12 @@ public interface Defaults { String bar(); } + public interface ComplexSample { + ServerSub server(); + + Optional client(); + } + public interface Converters { @WithConverter(FooBarConverter.class) String foo(); From 13394f320d4e282dcc263df80d0e4739c5427f26 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 19:41:25 +0100 Subject: [PATCH 17/42] Rename ConfigMapping to ConfigMappingProvider (to add annotation with the same name for CDI registration). --- .../io/smallrye/config/SmallRyeConfig.java | 22 +++++++++---------- ...apping.java => ConfigMappingProvider.java} | 8 +++---- .../config/mapper/ConfigurationInterface.java | 2 +- ...st.java => ConfigMappingProviderTest.java} | 10 ++++----- 4 files changed, 21 insertions(+), 21 deletions(-) rename implementation/src/main/java/io/smallrye/config/mapper/{ConfigMapping.java => ConfigMappingProvider.java} (99%) rename implementation/src/test/java/io/smallrye/config/mapper/{ConfigMappingTest.java => ConfigMappingProviderTest.java} (96%) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index b8a29193d..4df870c88 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -46,7 +46,7 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; import io.smallrye.config.common.MapBackedConfigSource; -import io.smallrye.config.mapper.ConfigMapping; +import io.smallrye.config.mapper.ConfigMappingProvider; import io.smallrye.config.mapper.ConfigurationValidationException; /** @@ -59,34 +59,34 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final ConfigMapping configMapping; + private final ConfigMappingProvider configMappingProvider; SmallRyeConfig(SmallRyeConfigBuilder builder) { - this.configMapping = buildConfigMapping(builder); + this.configMappingProvider = buildConfigMapping(builder); this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); } @Deprecated protected SmallRyeConfig(List configSources, Map> converters) { - this.configMapping = ConfigMapping.builder().build(); + this.configMappingProvider = ConfigMappingProvider.builder().build(); this.configSources = new AtomicReference<>( new ConfigSources(configSources, buildInterceptors(new SmallRyeConfigBuilder()))); this.converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS); this.converters.putAll(converters); } - private ConfigMapping buildConfigMapping(final SmallRyeConfigBuilder builder) { - final ConfigMapping.Builder mappingBuilder = ConfigMapping.builder(); + private ConfigMappingProvider buildConfigMapping(final SmallRyeConfigBuilder builder) { + final ConfigMappingProvider.Builder mappingBuilder = ConfigMappingProvider.builder(); for (final Map.Entry> mapping : builder.getMappings()) { mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); } - final ConfigMapping configMapping = mappingBuilder.build(); - final Map defaultValues = configMapping.getDefaultValues(); + final ConfigMappingProvider configMappingProvider = mappingBuilder.build(); + final Map defaultValues = configMappingProvider.getDefaultValues(); defaultValues.forEach((name, value) -> builder.getDefaultValues().putIfAbsent(name, value)); - return configMapping; + return configMappingProvider; } private List buildConfigSources(final SmallRyeConfigBuilder builder) { @@ -238,12 +238,12 @@ public T getConfigProperties(Class klass) { @Experimental("TODO") public T getConfigProperties(Class klass, String prefix) { try { - final ConfigMapping.Result result = configMapping.mapConfiguration(this); + final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(this); final T configRoot = result.getConfigRoot(prefix, klass); // Build one on the fly // Think if we want to merge the unknown one with the current configMapping. if (configRoot == null) { - return ConfigMapping.builder() + return ConfigMappingProvider.builder() .addRoot(prefix, klass) .build() .mapConfiguration(this) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java similarity index 99% rename from implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java rename to implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index 520c01441..ed3ef8f09 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMapping.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -32,7 +32,7 @@ /** * */ -public final class ConfigMapping implements Serializable { +public final class ConfigMappingProvider implements Serializable { private static final long serialVersionUID = 3977667610888849912L; /** @@ -52,7 +52,7 @@ public final class ConfigMapping implements Serializable { private final KeyMap> matchActions; private final KeyMap defaultValues; - ConfigMapping(final Builder builder) { + ConfigMappingProvider(final Builder builder) { roots = new HashMap<>(builder.roots); final ArrayDeque currentPath = new ArrayDeque<>(); KeyMap> matchActions = new KeyMap<>(); @@ -688,8 +688,8 @@ public Builder addIgnored(String... patternSegments) { return this; } - public ConfigMapping build() { - return new ConfigMapping(this); + public ConfigMappingProvider build() { + return new ConfigMappingProvider(this); } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 176db98e0..e1012cfac 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -1,6 +1,6 @@ package io.smallrye.config.mapper; -import static io.smallrye.config.mapper.ConfigMapping.skewer; +import static io.smallrye.config.mapper.ConfigMappingProvider.skewer; import static org.objectweb.asm.Type.getDescriptor; import static org.objectweb.asm.Type.getInternalName; import static org.objectweb.asm.Type.getType; diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java similarity index 96% rename from implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java rename to implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index 4d9e119c1..adc5637ee 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -20,7 +20,7 @@ import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; -public class ConfigMappingTest { +public class ConfigMappingProviderTest { @Test void configMapping() { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( @@ -34,12 +34,12 @@ void configMapping() { @Test void configMappingBuilder() throws Exception { - final ConfigMapping configMapping = ConfigMapping.builder().addRoot("server", Server.class) + final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder().addRoot("server", Server.class) .addIgnored("server", "name").build(); final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( config("server.host", "localhost", "server.port", "8080", "server.name", "name")).build(); - final Server server = configMapping.mapConfiguration(config).getConfigRoot("server", Server.class); + final Server server = configMappingProvider.mapConfiguration(config).getConfigRoot("server", Server.class); assertEquals("localhost", server.host()); assertEquals("localhost", server.getHost()); assertEquals(8080, server.port()); @@ -81,12 +81,12 @@ void splitRoots() throws Exception { config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); - final ConfigMapping configMapping = ConfigMapping.builder() + final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder() .addRoot("server", SplitRootServerHostAndPort.class) .addRoot("server", SplitRootServerName.class) .build(); - final ConfigMapping.Result result = configMapping.mapConfiguration(config); + final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(config); final SplitRootServerHostAndPort server = result.getConfigRoot("server", SplitRootServerHostAndPort.class); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); From e779b464dd54fbf543048891710caa6f0339c948 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 20:59:29 +0100 Subject: [PATCH 18/42] Move mapping annotations that can be used outside mapping context. --- .../java/io/smallrye/config/{mapper => }/WithConverter.java | 2 +- .../main/java/io/smallrye/config/{mapper => }/WithDefault.java | 2 +- .../main/java/io/smallrye/config/{mapper => }/WithName.java | 2 +- .../java/io/smallrye/config/mapper/ConfigurationInterface.java | 3 +++ .../io/smallrye/config/mapper/ConfigMappingProviderTest.java | 3 +++ 5 files changed, 9 insertions(+), 3 deletions(-) rename implementation/src/main/java/io/smallrye/config/{mapper => }/WithConverter.java (94%) rename implementation/src/main/java/io/smallrye/config/{mapper => }/WithDefault.java (91%) rename implementation/src/main/java/io/smallrye/config/{mapper => }/WithName.java (93%) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java b/implementation/src/main/java/io/smallrye/config/WithConverter.java similarity index 94% rename from implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java rename to implementation/src/main/java/io/smallrye/config/WithConverter.java index a1b637612..cf8a5b603 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/WithConverter.java +++ b/implementation/src/main/java/io/smallrye/config/WithConverter.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java b/implementation/src/main/java/io/smallrye/config/WithDefault.java similarity index 91% rename from implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java rename to implementation/src/main/java/io/smallrye/config/WithDefault.java index c510f8a38..5051fe6cc 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/WithDefault.java +++ b/implementation/src/main/java/io/smallrye/config/WithDefault.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/WithName.java b/implementation/src/main/java/io/smallrye/config/WithName.java similarity index 93% rename from implementation/src/main/java/io/smallrye/config/mapper/WithName.java rename to implementation/src/main/java/io/smallrye/config/WithName.java index 82d265a6d..880188223 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/WithName.java +++ b/implementation/src/main/java/io/smallrye/config/WithName.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index e1012cfac..a46c8fb1d 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -34,6 +34,9 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.WithConverter; +import io.smallrye.config.WithDefault; +import io.smallrye.config.WithName; import io.smallrye.config.inject.InjectionMessages; import sun.misc.Unsafe; diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index adc5637ee..849eb6578 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -19,6 +19,9 @@ 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; public class ConfigMappingProviderTest { @Test From 681281fd8bf9d2a5823129e4edcd4e678b2fe06f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 30 Jun 2020 21:08:10 +0100 Subject: [PATCH 19/42] Remove unused mapper code. --- mapper/pom.xml | 48 ----- .../smallrye/config/mapper/CompareWith.java | 23 --- .../io/smallrye/config/mapper/Matching.java | 22 -- .../smallrye/config/mapper/MaxExclusive.java | 22 -- .../smallrye/config/mapper/MaxInclusive.java | 22 -- .../smallrye/config/mapper/MinExclusive.java | 22 -- .../smallrye/config/mapper/MinInclusive.java | 22 -- .../smallrye/config/mapper/MapperTests.java | 189 ------------------ 8 files changed, 370 deletions(-) delete mode 100644 mapper/pom.xml delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/Matching.java delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java delete mode 100644 mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java delete mode 100644 mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java diff --git a/mapper/pom.xml b/mapper/pom.xml deleted file mode 100644 index 297ac767e..000000000 --- a/mapper/pom.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - 4.0.0 - - - io.smallrye.config - smallrye-config-parent - 1.8.2-SNAPSHOT - - - smallrye-config-mapper - - SmallRye: Configuration Mapper - A configuration-to-object mapping utility - - - - org.ow2.asm - asm - - - io.smallrye.config - smallrye-config - - - io.smallrye.common - smallrye-common-constraint - - - junit - junit - test - - - - - - - maven-surefire-plugin - - false - - - - - diff --git a/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java b/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java deleted file mode 100644 index f9466c0c7..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/CompareWith.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Comparator; - -/** - * Specify the comparator to use to compare the annotated type for range. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface CompareWith { - /** - * The comparator class to use. - * - * @return the comparator class - */ - Class> value(); -} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/Matching.java b/mapper/src/main/java/io/smallrye/config/mapper/Matching.java deleted file mode 100644 index 5d749b4d6..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/Matching.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the regular expression pattern that must be matched for this property to be considered valid. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface Matching { - /** - * The pattern. - * - * @return the pattern - */ - String value(); -} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java deleted file mode 100644 index 7c76db856..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/MaxExclusive.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the maximum value (exclusive) for this property. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface MaxExclusive { - /** - * The value. - * - * @return the value - */ - String value(); -} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java deleted file mode 100644 index 9d332eb49..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/MaxInclusive.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the maximum value (inclusive) for this property. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface MaxInclusive { - /** - * The value. - * - * @return the value - */ - String value(); -} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java deleted file mode 100644 index 204b85561..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/MinExclusive.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the minimum value (exclusive) for this property. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface MinExclusive { - /** - * The value. - * - * @return the value - */ - String value(); -} diff --git a/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java b/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java deleted file mode 100644 index dea5905ec..000000000 --- a/mapper/src/main/java/io/smallrye/config/mapper/MinInclusive.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.smallrye.config.mapper; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Specify the minimum value (inclusive) for this property. - */ -@Documented -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE_USE) -public @interface MinInclusive { - /** - * The value. - * - * @return the value - */ - String value(); -} diff --git a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java b/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java deleted file mode 100644 index d329d21a0..000000000 --- a/mapper/src/test/java/io/smallrye/config/mapper/MapperTests.java +++ /dev/null @@ -1,189 +0,0 @@ -package io.smallrye.config.mapper; - -import static io.smallrye.config.mapper.ConfigMapping.*; -import static java.util.Collections.singletonMap; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalInt; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.junit.Test; - -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; -import io.smallrye.config.common.MapBackedConfigSource; - -/** - * - */ -public class MapperTests { - - public interface Basic { - String helloWorld(); - - @WithDefault("this is the default") - String helloWorldWithDefault(); - - Integer port(); - } - - @Test - public void testBasic() throws ConfigurationValidationException { - Builder mb = builder(); - mb.addRoot("test", Basic.class); - ConfigMapping mapping = mb.build(); - SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); - mapping.registerDefaultValues(cb); - SmallRyeConfig config = cb.build(); - try { - mapping.mapConfiguration(config); - } catch (ConfigurationValidationException e) { - assertEquals(2, e.getProblemCount()); - } - - cb = new SmallRyeConfigBuilder(); - cb.withSources(configSource(singletonMap("test.hello-world", "here I am!"))); - cb.withSources(configSource(singletonMap("test.port", "8080"))); - mapping.registerDefaultValues(cb); - config = cb.build(); - Result result = mapping.mapConfiguration(config); - Basic basic = result.getConfigRoot("test", Basic.class); - assertEquals("here I am!", basic.helloWorld()); - assertEquals("this is the default", basic.helloWorldWithDefault()); - assertEquals(8080, basic.port().intValue()); - } - - public interface SomeTypes { - int intProp(); - - boolean boolProp(); - - float floatProp(); - - double doubleProp(); - - long longProp(); - - char charProp(); - - Integer boxedIntProp(); - - List intListProp(); - - int[] intArrayProp(); - - OptionalInt optionalIntProp(); - } - - @Test - public void testTypes() throws ConfigurationValidationException { - Builder mb = builder(); - mb.addRoot("test.level2", SomeTypes.class); - ConfigMapping mapping = mb.build(); - SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); - mapping.registerDefaultValues(cb); - cb.withSources(configSource( - singletonMap("test.level2.int-prop", "1234"), - singletonMap("test.level2.bool-prop", "true"), - singletonMap("test.level2.float-prop", "12.25"), // choose an exact number for sanity - singletonMap("test.level2.double-prop", "555.5"), - singletonMap("test.level2.long-prop", "4000111222333"), - singletonMap("test.level2.char-prop", "X"), - singletonMap("test.level2.boxed-int-prop", "4096"), - singletonMap("test.level2.int-list-prop", "6,22,77"), - singletonMap("test.level2.int-array-prop", "10,9,8,7,3,2,1,0"), - singletonMap("test.level2.optional-int-prop", "555"))); - SmallRyeConfig config = cb.build(); - Result result = mapping.mapConfiguration(config); - SomeTypes someTypes = result.getConfigRoot("test.level2", SomeTypes.class); - assertEquals(1234, someTypes.intProp()); - assertTrue(someTypes.boolProp()); - assertEquals(12.25f, someTypes.floatProp(), 0f); - assertEquals(555.5, someTypes.doubleProp(), 0.0); - assertEquals(4000111222333L, someTypes.longProp()); - assertEquals('X', someTypes.charProp()); - assertEquals(Integer.valueOf(4096), someTypes.boxedIntProp()); - assertEquals(Arrays.asList(Integer.valueOf(6), Integer.valueOf(22), Integer.valueOf(77)), someTypes.intListProp()); - assertArrayEquals(new int[] { 10, 9, 8, 7, 3, 2, 1, 0 }, someTypes.intArrayProp()); - assertTrue(someTypes.optionalIntProp().isPresent()); - assertEquals(555, someTypes.optionalIntProp().getAsInt()); - } - - public interface WithSubGroups { - Optional optBasic1(); - - Optional optBasic2(); - - Basic reqBasic(); - } - - @Test - public void testWithSubGroups() throws ConfigurationValidationException { - Builder mb = builder(); - mb.addRoot("test", WithSubGroups.class); - ConfigMapping mapping = mb.build(); - SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); - mapping.registerDefaultValues(cb); - cb.withSources(configSource( - singletonMap("test.req-basic.hello-world", "surprise"), - singletonMap("test.req-basic.hello-world-with-default", "non-default"), - singletonMap("test.opt-basic2.hello-world", "present!"))); - SmallRyeConfig config = cb.build(); - Result result = mapping.mapConfiguration(config); - WithSubGroups root = result.getConfigRoot("test", WithSubGroups.class); - assertEquals("surprise", root.reqBasic().helloWorld()); - assertEquals("non-default", root.reqBasic().helloWorldWithDefault()); - assertFalse(root.optBasic1().isPresent()); - assertTrue(root.optBasic2().isPresent()); - assertEquals("present!", root.optBasic2().get().helloWorld()); - assertEquals("this is the default", root.optBasic2().get().helloWorldWithDefault()); - } - - public interface WithMaps { - Map theMap(); - } - - @Test - public void testWithMaps() throws ConfigurationValidationException { - Builder mb = builder(); - mb.addRoot("test", WithMaps.class); - ConfigMapping mapping = mb.build(); - SmallRyeConfigBuilder cb = new SmallRyeConfigBuilder(); - mapping.registerDefaultValues(cb); - cb.withSources(configSource( - singletonMap("test.the-map.foo.req-basic.hello-world", "surprise"), - singletonMap("test.the-map.foo.req-basic.hello-world-with-default", "non-default"), - singletonMap("test.the-map.foo.opt-basic2.hello-world", "present!"))); - SmallRyeConfig config = cb.build(); - Result result = mapping.mapConfiguration(config); - WithMaps root = result.getConfigRoot("test", WithMaps.class); - assertEquals("surprise", root.theMap().get("foo").reqBasic().helloWorld()); - assertEquals("non-default", root.theMap().get("foo").reqBasic().helloWorldWithDefault()); - assertFalse(root.theMap().get("foo").optBasic1().isPresent()); - assertTrue(root.theMap().get("foo").optBasic2().isPresent()); - assertEquals("present!", root.theMap().get("foo").optBasic2().get().helloWorld()); - assertEquals("this is the default", root.theMap().get("foo").optBasic2().get().helloWorldWithDefault()); - } - - ///////////////////////////////////////////////////////////////////////////////////////// - - @SuppressWarnings("serial") - @SafeVarargs - public static ConfigSource configSource(Map... maps) { - int length = maps.length; - Map map = new HashMap<>(length); - for (Map subMap : maps) { - map.putAll(subMap); - } - return new MapBackedConfigSource("Test config source", map) { - }; - } -} From 4e1d0b274625dcac1726c2c4d5657c819fb51cff Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 14 Jul 2020 18:28:55 +0100 Subject: [PATCH 20/42] Removed ConfigMapping on the fly. --- .../io/smallrye/config/ConfigMessages.java | 3 + .../io/smallrye/config/SmallRyeConfig.java | 15 ++--- .../mapper/ConfigMappingProviderTest.java | 58 +++++++++---------- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMessages.java b/implementation/src/main/java/io/smallrye/config/ConfigMessages.java index c560a6fdb..6b775b668 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMessages.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMessages.java @@ -94,4 +94,7 @@ interface ConfigMessages { @Message(id = 26, value = "%s cannot be converted into a UUID") IllegalArgumentException malformedUUID(@Cause Throwable cause, String malformedUUID); + + @Message(id = 27, value = "Could not find a mapping for %s with prefix %s") + NoSuchElementException mappingNotFound(String className, String prefix); } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 4df870c88..e2ee5a1c7 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -231,26 +231,21 @@ public > Optional getOptionalValues(String name, C } @Experimental("TODO") - public T getConfigProperties(Class klass) { - return getConfigProperties(klass, ""); + public T getConfigMapping(Class klass) { + return getConfigMapping(klass, ""); } @Experimental("TODO") - public T getConfigProperties(Class klass, String prefix) { + public T getConfigMapping(Class klass, String prefix) { try { final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(this); final T configRoot = result.getConfigRoot(prefix, klass); - // Build one on the fly - // Think if we want to merge the unknown one with the current configMapping. if (configRoot == null) { - return ConfigMappingProvider.builder() - .addRoot(prefix, klass) - .build() - .mapConfiguration(this) - .getConfigRoot(prefix, klass); + throw ConfigMessages.msg.mappingNotFound(klass.getName(), prefix); } return configRoot; } catch (ConfigurationValidationException e) { + // TODO - Map in ConfigMessages throw new IllegalArgumentException(e); } } diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index 849eb6578..c08e61163 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -9,12 +9,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.OptionalInt; import java.util.stream.Stream; import org.eclipse.microprofile.config.spi.Converter; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.smallrye.config.SmallRyeConfig; @@ -26,15 +26,24 @@ public class ConfigMappingProviderTest { @Test void configMapping() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - config("server.host", "localhost", "server.port", "8080")).build(); - final Server configProperties = config.getConfigProperties(Server.class, "server"); + final 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"); assertEquals("localhost", configProperties.host()); assertEquals("localhost", configProperties.getHost()); assertEquals(8080, configProperties.port()); assertEquals(8080, configProperties.getPort()); } + @Test + void noConfigMapping() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("server.host", "localhost", "server.port", "8080")).build(); + assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class, "server"), + "Could not find a mapping for " + Server.class.getName() + " with prefix server"); + } + @Test void configMappingBuilder() throws Exception { final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder().addRoot("server", Server.class) @@ -55,15 +64,16 @@ void unmapped() { config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) .build(); // TODO - Most likely we want to do a warning here, but just to try out the feature. - assertThrows(Exception.class, () -> config.getConfigProperties(Server.class, "server")); + assertThrows(Exception.class, () -> config.getConfigMapping(Server.class, "server")); } @Test void ignorePropertiesInUnregisteredRoots() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(Server.class, "server") + .withSources(config("server.host", "localhost", "server.port", "8080", "client.name", "konoha")) .build(); - final Server configProperties = config.getConfigProperties(Server.class, "server"); + final Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -72,8 +82,9 @@ void ignorePropertiesInUnregisteredRoots() { void ignoreProperties() { final SmallRyeConfig config = new SmallRyeConfigBuilder() .addDefaultSources() + .withMapping(Server.class, "server") .withSources(config("server.host", "localhost", "server.port", "8080")).build(); - final Server configProperties = config.getConfigProperties(Server.class, "server"); + final Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); assertEquals(8080, configProperties.port()); } @@ -106,7 +117,7 @@ void splitRootsInConfig() { .withMapping(SplitRootServerHostAndPort.class, "server") .withMapping(SplitRootServerName.class, "server") .build(); - final SplitRootServerHostAndPort server = config.getConfigProperties(SplitRootServerHostAndPort.class, "server"); + final SplitRootServerHostAndPort server = config.getConfigMapping(SplitRootServerHostAndPort.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); } @@ -118,7 +129,7 @@ void subGroups() { "konoha")) .withMapping(ServerSub.class, "server") .build(); - final ServerSub server = config.getConfigProperties(ServerSub.class, "server"); + final ServerSub server = config.getConfigMapping(ServerSub.class, "server"); assertEquals("localhost", server.subHostAndPort().host()); assertEquals(8080, server.subHostAndPort().port()); assertEquals("konoha", server.subName().name()); @@ -141,7 +152,7 @@ void types() { .withSources(config(typesConfig)) .withMapping(SomeTypes.class) .build(); - final SomeTypes types = config.getConfigProperties(SomeTypes.class); + final SomeTypes types = config.getConfigMapping(SomeTypes.class); assertEquals(9, types.intPrimitive()); assertEquals(9, types.intWrapper()); @@ -172,7 +183,7 @@ void optionals() { .withSources(config(typesConfig)) .withMapping(Optionals.class) .build(); - final Optionals optionals = config.getConfigProperties(Optionals.class); + final Optionals optionals = config.getConfigMapping(Optionals.class); assertTrue(optionals.server().isPresent()); assertEquals("localhost", optionals.server().get().host()); @@ -197,7 +208,7 @@ void collectionTypes() { .withSources(config(typesConfig)) .withMapping(CollectionTypes.class) .build(); - final CollectionTypes types = config.getConfigProperties(CollectionTypes.class); + final 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()); @@ -217,7 +228,7 @@ void maps() { .withSources(config(typesConfig)) .withMapping(Maps.class) .build(); - final Maps maps = config.getConfigProperties(Maps.class); + final Maps maps = config.getConfigMapping(Maps.class); assertEquals("localhost", maps.server().get("server").host()); assertEquals("localhost", maps.anotherServer().get("another-server")); @@ -228,18 +239,7 @@ void defaults() { final SmallRyeConfig config = new SmallRyeConfigBuilder() .withMapping(Defaults.class) .build(); - final Defaults defaults = config.getConfigProperties(Defaults.class); - - assertEquals("foo", defaults.foo()); - assertEquals("bar", defaults.bar()); - assertEquals("foo", config.getRawValue("foo")); - } - - @Test - @Disabled - void defaultsOnTheFly() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().build(); - final Defaults defaults = config.getConfigProperties(Defaults.class); + final Defaults defaults = config.getConfigMapping(Defaults.class); assertEquals("foo", defaults.foo()); assertEquals("bar", defaults.bar()); @@ -253,7 +253,7 @@ void converters() { .withMapping(Converters.class) .withConverter(String.class, 100, new FooBarConverter()) .build(); - final Converters converters = config.getConfigProperties(Converters.class); + final Converters converters = config.getConfigMapping(Converters.class); assertEquals("bar", converters.foo()); assertEquals("bar", config.getValue("foo", String.class)); @@ -277,7 +277,7 @@ void mix() { .withMapping(ComplexSample.class) .build(); - final ComplexSample sample = config.getConfigProperties(ComplexSample.class); + final ComplexSample sample = config.getConfigMapping(ComplexSample.class); assertEquals("localhost", sample.server().subHostAndPort().host()); assertEquals(8080, sample.server().subHostAndPort().port()); assertTrue(sample.client().isPresent()); From 700100ce5f8128563190fa7310c1f9a7875361e6 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Tue, 14 Jul 2020 18:36:24 +0100 Subject: [PATCH 21/42] Remove JavaBeans getter style method names. --- .../io/smallrye/config/mapper/ConfigMappingProvider.java | 4 ---- .../smallrye/config/mapper/ConfigMappingProviderTest.java | 8 -------- 2 files changed, 12 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index ed3ef8f09..e3631debf 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -97,10 +97,6 @@ static String skewer(String camelHumps) { static String skewer(String camelHumps, int start, int end, StringBuilder b) { assert !camelHumps.isEmpty() : "Method seems to have an empty name"; - if (camelHumps.startsWith("get")) { - camelHumps = camelHumps.substring(3); - end = camelHumps.length(); - } int cp = camelHumps.codePointAt(start); b.appendCodePoint(Character.toLowerCase(cp)); start += Character.charCount(cp); diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index c08e61163..5dc2e39c8 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -31,9 +31,7 @@ void configMapping() { .withSources(config("server.host", "localhost", "server.port", "8080")).build(); final Server configProperties = config.getConfigMapping(Server.class, "server"); assertEquals("localhost", configProperties.host()); - assertEquals("localhost", configProperties.getHost()); assertEquals(8080, configProperties.port()); - assertEquals(8080, configProperties.getPort()); } @Test @@ -53,9 +51,7 @@ void configMappingBuilder() throws Exception { final Server server = configMappingProvider.mapConfiguration(config).getConfigRoot("server", Server.class); assertEquals("localhost", server.host()); - assertEquals("localhost", server.getHost()); assertEquals(8080, server.port()); - assertEquals(8080, server.getPort()); } @Test @@ -289,10 +285,6 @@ interface Server { String host(); int port(); - - String getHost(); - - int getPort(); } interface SplitRootServerHostAndPort { From 1ea215eca8abc8069d7cbe7f7915676a887d455c Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 15 Jul 2020 14:52:03 +0100 Subject: [PATCH 22/42] Use KeyMap for default values. --- .../main/java/io/smallrye/config/KeyMap.java | 12 +- .../config/KeyMapBackedConfigSource.java | 38 ++++++ .../io/smallrye/config/SmallRyeConfig.java | 16 +-- .../config/mapper/ConfigMappingProvider.java | 4 +- .../config/KeyMapBackedConfigSourceTest.java | 51 ++++++++ .../java/io/smallrye/config/KeyMapTest.java | 110 +++++++++++++++++- 6 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java create mode 100644 implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java diff --git a/implementation/src/main/java/io/smallrye/config/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java index 79f06d089..f73bc4c86 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -143,7 +143,8 @@ public KeyMap findOrAdd(final NameIterator ni) { String seg = ni.getNextSegment(); ni.next(); try { - return computeIfAbsent(seg, k -> new KeyMap<>()).findOrAdd(ni); + KeyMap next = seg.equals("*") ? getOrCreateAny() : computeIfAbsent(seg, k -> new KeyMap<>()); + return next.findOrAdd(ni); } finally { ni.previous(); } @@ -169,7 +170,7 @@ public KeyMap findOrAdd(final String... keys) { public KeyMap findOrAdd(final String[] keys, int off, int len) { String seg = keys[off]; KeyMap next = seg.equals("*") ? getOrCreateAny() : computeIfAbsent(seg, k -> new KeyMap<>()); - return off + 1 > len - 1 ? next : next.findOrAdd(keys, off + 1, len - 1); + return off + 1 > len - 1 ? next : next.findOrAdd(keys, off + 1, len); } public V findRootValue(final String path) { @@ -262,10 +263,17 @@ private void unwrap(KeyMap keyMap, String key, Map map) { for (String path : keyMap.keySet()) { final KeyMap nested = keyMap.get(path); final String nestedKey = key.length() == 0 ? path : key + "." + path; + if (nested.any != null) { + map.put(nestedKey + ".*", nested.any.getRootValue()); + unwrap(nested.any, nestedKey + ".*", map); + } if (!nested.hasRootValue()) { unwrap(nested, nestedKey, map); } else { map.put(nestedKey, nested.getRootValue()); + if (!nested.keySet().isEmpty()) { + unwrap(nested, nestedKey, map); + } } } } diff --git a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java new file mode 100644 index 000000000..d061358ad --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java @@ -0,0 +1,38 @@ +package io.smallrye.config; + +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +import io.smallrye.config.common.AbstractConfigSource; + +public abstract class KeyMapBackedConfigSource extends AbstractConfigSource { + private static final long serialVersionUID = 4378754290346888762L; + + private final KeyMap properties; + + public KeyMapBackedConfigSource(final String name, final KeyMap properties) { + super(name, ConfigSource.DEFAULT_ORDINAL); + this.properties = properties; + } + + public KeyMapBackedConfigSource(final String name, final int ordinal, final KeyMap properties) { + super(name, ordinal); + this.properties = properties; + } + + @Override + public Map getProperties() { + return properties.toMap(); + } + + @Override + public String getValue(final String propertyName) { + final String value = properties.findRootValue(propertyName); + if (value == null) { + // In case of searching for an empty root. + return properties.findRootValue("." + propertyName); + } + return value; + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index e2ee5a1c7..e8f243450 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -45,7 +45,6 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; -import io.smallrye.config.common.MapBackedConfigSource; import io.smallrye.config.mapper.ConfigMappingProvider; import io.smallrye.config.mapper.ConfigurationValidationException; @@ -81,12 +80,7 @@ private ConfigMappingProvider buildConfigMapping(final SmallRyeConfigBuilder bui for (final Map.Entry> mapping : builder.getMappings()) { mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); } - - final ConfigMappingProvider configMappingProvider = mappingBuilder.build(); - final Map defaultValues = configMappingProvider.getDefaultValues(); - defaultValues.forEach((name, value) -> builder.getDefaultValues().putIfAbsent(name, value)); - - return configMappingProvider; + return mappingBuilder.build(); } private List buildConfigSources(final SmallRyeConfigBuilder builder) { @@ -97,9 +91,11 @@ private List buildConfigSources(final SmallRyeConfigBuilder builde if (builder.isAddDefaultSources()) { sourcesToBuild.addAll(builder.getDefaultSources()); } - if (!builder.getDefaultValues().isEmpty()) { - sourcesToBuild.add(new MapBackedConfigSource("DefaultValuesConfigSource", builder.getDefaultValues()) { - private static final long serialVersionUID = -2569643736033594267L; + if (!builder.getDefaultValues().isEmpty() || !configMappingProvider.getDefaultValues().isEmpty()) { + final KeyMap defaultValues = configMappingProvider.getDefaultValues(); + builder.getDefaultValues().forEach((key, value) -> defaultValues.findOrAdd(key).putRootValue(value)); + sourcesToBuild.add(new KeyMapBackedConfigSource("DefaultValuesConfigSource", defaultValues) { + private static final long serialVersionUID = 7847969724478280439L; @Override public int getOrdinal() { diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index e3631debf..75224e324 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -595,8 +595,8 @@ public static Builder builder() { return new Builder(); } - public Map getDefaultValues() { - return Collections.unmodifiableMap(defaultValues.toMap()); + public KeyMap getDefaultValues() { + return defaultValues; } public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValidationException { diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java new file mode 100644 index 000000000..7b8272ba6 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java @@ -0,0 +1,51 @@ +package io.smallrye.config; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.junit.jupiter.api.Test; + +class KeyMapBackedConfigSourceTest { + + @Test + void getProperties() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); + + final ConfigSource source = getSource(map); + final Map properties = source.getProperties(); + assertTrue(properties.containsKey("root.foo")); + assertTrue(properties.containsKey("root.foo.bar")); + assertTrue(properties.containsKey("root.foo.bar.*")); + assertTrue(properties.containsKey("root.foo.bar.*.baz")); + } + + @Test + void getValue() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); + + final ConfigSource source = getSource(map); + assertEquals("bar", source.getValue("root.foo")); + assertEquals("baz", source.getValue("root.foo.bar")); + assertEquals("baz", source.getValue("root.foo.bar.x")); + assertEquals("baz", source.getValue("root.foo.bar.y")); + assertEquals("anything", source.getValue("root.foo.bar.x.baz")); + assertEquals("anything", source.getValue("root.foo.bar.y.baz")); + assertNull(source.getValue("root.bar")); + assertNull(source.getValue("root.foo.bar.y.baz.z")); + } + + private ConfigSource getSource(final KeyMap properties) { + return new KeyMapBackedConfigSource("test", 0, properties) { + }; + } +} diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java index 8fcd5d48f..8816b3b06 100644 --- a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java +++ b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java @@ -1,8 +1,13 @@ package io.smallrye.config; +import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -17,14 +22,107 @@ void find() { assertEquals("bar", root.findRootValue("root.bar")); } + @Test + void findOrAddPath() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); + + assertEquals("bar", map.findRootValue("root.foo")); + assertEquals("baz", map.findRootValue("root.foo.bar")); + assertEquals("baz", map.findRootValue("root.foo.bar.x")); + assertEquals("baz", map.findRootValue("root.foo.bar.y")); + assertEquals("anything", map.findRootValue("root.foo.bar.x.baz")); + assertEquals("anything", map.findRootValue("root.foo.bar.y.baz")); + assertNull(map.findRootValue("root.bar")); + assertNull(map.findRootValue("root.foo.bar.y.baz.z")); + } + + @Test + void findOrAddVarArgs() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root", "foo").putRootValue("bar"); + map.findOrAdd("root", "foo", "bar").putRootValue("baz"); + map.findOrAdd("root", "foo", "bar", "*").putRootValue("baz"); + map.findOrAdd("root", "foo", "bar", "*", "baz").putRootValue("anything"); + + assertEquals("bar", map.findRootValue("root.foo")); + assertEquals("baz", map.findRootValue("root.foo.bar")); + assertEquals("baz", map.findRootValue("root.foo.bar.x")); + assertEquals("baz", map.findRootValue("root.foo.bar.y")); + assertEquals("anything", map.findRootValue("root.foo.bar.x.baz")); + assertEquals("anything", map.findRootValue("root.foo.bar.y.baz")); + assertNull(map.findRootValue("root.bar")); + assertNull(map.findRootValue("root.foo.bar.y.baz.z")); + } + + @Test + void findOrAddIterator() { + KeyMap map = new KeyMap<>(); + map.findOrAdd(Stream.of("root", "foo").collect(toList())).putRootValue("bar"); + map.findOrAdd(Stream.of("root", "foo", "bar").collect(toList())).putRootValue("baz"); + map.findOrAdd(Stream.of("root", "foo", "bar", "*").collect(toList())).putRootValue("baz"); + map.findOrAdd(Stream.of("root", "foo", "bar", "*", "baz").collect(toList())).putRootValue("anything"); + + assertEquals("bar", map.findRootValue("root.foo")); + assertEquals("baz", map.findRootValue("root.foo.bar")); + assertEquals("baz", map.findRootValue("root.foo.bar.x")); + assertEquals("baz", map.findRootValue("root.foo.bar.y")); + assertEquals("anything", map.findRootValue("root.foo.bar.x.baz")); + assertEquals("anything", map.findRootValue("root.foo.bar.y.baz")); + assertNull(map.findRootValue("root.bar")); + assertNull(map.findRootValue("root.foo.bar.y.baz.z")); + } + + @Test + void merge() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + Map flatMap = new HashMap<>(); + flatMap.put("root.foo", "foo"); + flatMap.put("root.foo.bar.*", "baz"); + flatMap.put("root.foo.bar.*.baz", "anything"); + + flatMap.forEach((key, value) -> map.findOrAdd(key).putRootValue(value)); + + assertEquals("foo", map.findRootValue("root.foo")); + assertEquals("baz", map.findRootValue("root.foo.bar")); + assertEquals("baz", map.findRootValue("root.foo.bar.x")); + assertEquals("baz", map.findRootValue("root.foo.bar.y")); + assertEquals("anything", map.findRootValue("root.foo.bar.x.baz")); + assertEquals("anything", map.findRootValue("root.foo.bar.y.baz")); + assertNull(map.findRootValue("root.bar")); + assertNull(map.findRootValue("root.foo.bar.y.baz.z")); + } + + @Test + void empty() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("", "foo").putRootValue("bar"); + + assertEquals("bar", map.findRootValue(".foo")); + } + @Test void unwrap() { - KeyMap root = new KeyMap<>(); - root.findOrAdd("root").findOrAdd("foo").putRootValue("foo"); - root.findOrAdd("root").findOrAdd("bar").putRootValue("bar"); + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); + + Map properties = map.toMap(); + assertTrue(properties.containsKey("root.foo")); + assertTrue(properties.containsKey("root.foo.bar")); + assertTrue(properties.containsKey("root.foo.bar.*")); + assertTrue(properties.containsKey("root.foo.bar.*.baz")); - Map properties = root.toMap(); - assertEquals("foo", properties.get("root.foo")); - assertEquals("bar", properties.get("root.bar")); + assertEquals("bar", properties.get("root.foo")); + assertEquals("baz", properties.get("root.foo.bar")); + assertEquals("baz", properties.get("root.foo.bar.*")); + assertEquals("anything", properties.get("root.foo.bar.*.baz")); } } From 2ab22a0c61ff041ab35e9476e60d0c0bfb4d3d14 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 15 Jul 2020 16:09:38 +0100 Subject: [PATCH 23/42] Add mapping ignores in the builder. --- .../io/smallrye/config/SmallRyeConfig.java | 6 +---- .../config/SmallRyeConfigBuilder.java | 16 +++++++---- .../config/mapper/ConfigMappingProvider.java | 6 ++--- .../mapper/ConfigMappingProviderTest.java | 27 ++++++++++++++++++- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index e8f243450..ed92c9c30 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -76,11 +76,7 @@ protected SmallRyeConfig(List configSources, Map> mapping : builder.getMappings()) { - mappingBuilder.addRoot(mapping.getKey(), mapping.getValue()); - } - return mappingBuilder.build(); + return builder.getMappingsBuilder().build(); } private List buildConfigSources(final SmallRyeConfigBuilder builder) { diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 41eca6482..f05de8dce 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -17,7 +17,6 @@ package io.smallrye.config; import java.lang.reflect.Type; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -41,6 +40,8 @@ import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.Converter; +import io.smallrye.config.mapper.ConfigMappingProvider; + /** * @author Jeff Mesnil (c) 2017 Red Hat inc. */ @@ -57,7 +58,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private final Set secretKeys = new HashSet<>(); private final List interceptors = new ArrayList<>(); private final Map defaultValues = new HashMap<>(); - private final Set>> mappings = new HashSet<>(); + private final ConfigMappingProvider.Builder mappingsBuilder = ConfigMappingProvider.builder(); private ClassLoader classLoader = SecuritySupport.getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDefaultInterceptors = false; @@ -231,7 +232,12 @@ public SmallRyeConfigBuilder withMapping(Class klass) { } public SmallRyeConfigBuilder withMapping(Class klass, String prefix) { - mappings.add(new AbstractMap.SimpleImmutableEntry<>(prefix, klass)); + mappingsBuilder.addRoot(prefix, klass); + return this; + } + + public SmallRyeConfigBuilder withMappingIgnore(String path) { + mappingsBuilder.addIgnored(path); return this; } @@ -303,8 +309,8 @@ Map getDefaultValues() { return defaultValues; } - Set>> getMappings() { - return mappings; + ConfigMappingProvider.Builder getMappingsBuilder() { + return mappingsBuilder; } protected boolean isAddDefaultSources() { diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index 75224e324..d91dff564 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -678,9 +678,9 @@ public Builder addRoot(String path, ConfigurationInterface info) { return this; } - public Builder addIgnored(String... patternSegments) { - Assert.checkNotNullParam("patternSegments", patternSegments); - ignored.add(patternSegments); + public Builder addIgnored(String path) { + Assert.checkNotNullParam("path", path); + ignored.add(path.split("\\.")); return this; } diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index 5dc2e39c8..c72f08503 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -45,7 +45,7 @@ void noConfigMapping() { @Test void configMappingBuilder() throws Exception { final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder().addRoot("server", Server.class) - .addIgnored("server", "name").build(); + .addIgnored("server.name").build(); final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( config("server.host", "localhost", "server.port", "8080", "server.name", "name")).build(); @@ -74,6 +74,25 @@ void ignorePropertiesInUnregisteredRoots() { assertEquals(8080, configProperties.port()); } + @Test + void ignoreSomeProperties() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(Server.class, "server") + .withMapping(Client.class, "client") + .withMappingIgnore("client.**") + .withSources(config("server.host", "localhost", "server.port", "8080", "client.host", "localhost", + "client.port", "8080", "client.name", "konoha")) + .build(); + + final Server server = config.getConfigMapping(Server.class, "server"); + assertEquals("localhost", server.host()); + assertEquals(8080, server.port()); + + final Client client = config.getConfigMapping(Client.class, "client"); + assertEquals("localhost", client.host()); + assertEquals(8080, client.port()); + } + @Test void ignoreProperties() { final SmallRyeConfig config = new SmallRyeConfigBuilder() @@ -287,6 +306,12 @@ interface Server { int port(); } + interface Client { + String host(); + + int port(); + } + interface SplitRootServerHostAndPort { String host(); From fd47521cd85c989f8f4879992995879f6b75f82b Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 15 Jul 2020 16:43:14 +0100 Subject: [PATCH 24/42] Fixed formatting. --- .../src/main/java/io/smallrye/config/NameIterator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/NameIterator.java b/implementation/src/main/java/io/smallrye/config/NameIterator.java index 5fa15b1a1..5c450857b 100644 --- a/implementation/src/main/java/io/smallrye/config/NameIterator.java +++ b/implementation/src/main/java/io/smallrye/config/NameIterator.java @@ -73,15 +73,17 @@ public int getPosition() { private static final int FS_BACKSLASH = 2; private static final int FS_QUOTE_BACKSLASH = 3; + //@formatter:off /* * Iteration cookie format * * Bit: 14...12 11 ... 0 * ┌───────┬────────────┐ - * │ state │ position │ - * │ │ (signed) │ + * │ state │ position │ + * │ │ (signed) │ * └───────┴────────────┘ */ + //@formatter:on /** * Create a new iteration cookie at the current position. From 9e75bfb9c311700988a54c288e4b51d447f85825 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 16 Jul 2020 12:34:58 +0100 Subject: [PATCH 25/42] Added javadoc to ConfigurationValidationException. Co-authored-by: David M. Lloyd --- .../config/mapper/ConfigurationValidationException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java index 9e9897c8f..0c3b7a22a 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java @@ -3,7 +3,7 @@ import io.smallrye.common.constraint.Assert; /** - * + * An exception which is thrown when a configuration validation problem occurs. */ public class ConfigurationValidationException extends Exception { private static final long serialVersionUID = -2637730579475070264L; From c2a84ecb3bb1ab945be8e49fabaebed63a238c59 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 16 Jul 2020 12:33:03 +0100 Subject: [PATCH 26/42] Add configuration property for ConfigMapping debug. --- .../smallrye/config/mapper/ConfigurationInterface.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index a46c8fb1d..c4d25ed27 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -44,7 +44,6 @@ * Information about a configuration interface. */ public final class ConfigurationInterface { - static final boolean usefulDebugInfo = true; static final ConfigurationInterface[] NO_TYPES = new ConfigurationInterface[0]; static final Property[] NO_PROPERTIES = new Property[0]; static final ClassValue cv = new ClassValue() { @@ -52,9 +51,12 @@ protected ConfigurationInterface computeValue(final Class type) { return createConfigurationInterface(type); } }; + static final boolean usefulDebugInfo; static final Unsafe unsafe; static { + usefulDebugInfo = Boolean.parseBoolean(System.getProperty("io.smallrye.config.mapper.useful-debug-info")); + unsafe = AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { try { @@ -849,9 +851,8 @@ private void restoreLength(final MethodVisitor ctor) { Class createConfigurationObjectClass() { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); String internalName = getInternalName(interfaceType); - String className = usefulDebugInfo ? getInternalName(getClass()) : internalName; // ignored by the VM, probably? ClassVisitor visitor = usefulDebugInfo ? new Debugging.ClassVisitorImpl(writer) : writer; - visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, I_OBJECT, + visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, getInternalName(getClass()), null, I_OBJECT, new String[] { I_CONFIGURATION_OBJECT, internalName }); visitor.visitSource(null, null); MethodVisitor ctor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); @@ -895,7 +896,7 @@ Class createConfigurationObjectClass() { // stack: sb fio.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); // stack: - - addProperties(visitor, className, ctor, fio, new HashSet<>()); + addProperties(visitor, getInternalName(getClass()), ctor, fio, new HashSet<>()); // stack: - fio.visitInsn(Opcodes.RETURN); fio.visitLabel(fioEnd); From d3cae3edee8f5087f12244ff80a75918e763425b Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 16 Jul 2020 14:45:59 +0100 Subject: [PATCH 27/42] Don't add additional KeyMap on empty paths. --- .../config/KeyMapBackedConfigSource.java | 7 +------ .../config/mapper/ConfigMappingProvider.java | 16 ++++------------ .../config/mapper/ConfigMappingProviderTest.java | 10 ++++++++++ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java index d061358ad..0a24d1959 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java @@ -28,11 +28,6 @@ public Map getProperties() { @Override public String getValue(final String propertyName) { - final String value = properties.findRootValue(propertyName); - if (value == null) { - // In case of searching for an empty root. - return properties.findRootValue("." + propertyName); - } - return value; + return properties.findRootValue(propertyName); } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index d91dff564..0381f8554 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -60,7 +60,10 @@ public final class ConfigMappingProvider implements Serializable { for (Map.Entry> entry : roots.entrySet()) { NameIterator rootNi = new NameIterator(entry.getKey()); while (rootNi.hasNext()) { - currentPath.add(rootNi.getNextSegment()); + final String nextSegment = rootNi.getNextSegment(); + if (!nextSegment.isEmpty()) { + currentPath.add(nextSegment); + } rootNi.next(); } List roots = entry.getValue(); @@ -626,17 +629,6 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida if (action != null) { action.accept(context, ni); } else { - // Search if there is an empty prefix - KeyMap> emptyPrefixMap = matchActions.get(""); - if (emptyPrefixMap != null) { - ni.goToStart(); - BiConsumer emptyPrefixAction = emptyPrefixMap.findRootValue(ni); - if (emptyPrefixAction != null) { - emptyPrefixAction.accept(context, ni); - continue; - } - } - context.unknownConfigElement(name); } } diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index c72f08503..8ae3b92b0 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -42,6 +42,16 @@ void noConfigMapping() { "Could not find a mapping for " + Server.class.getName() + " with prefix server"); } + @Test + void noPrefix() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withMapping(Server.class) + .withSources(config("host", "localhost", "port", "8080")).build(); + final Server configProperties = config.getConfigMapping(Server.class); + assertEquals("localhost", configProperties.host()); + assertEquals(8080, configProperties.port()); + } + @Test void configMappingBuilder() throws Exception { final ConfigMappingProvider configMappingProvider = ConfigMappingProvider.builder().addRoot("server", Server.class) From 98b2ea6eba410997ca26d52b6a0f8a7af259c7b9 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 16 Jul 2020 15:21:28 +0100 Subject: [PATCH 28/42] Some code cleanup. --- .../io/smallrye/config/mapper/ConfigMappingProvider.java | 1 - .../io/smallrye/config/mapper/ConfigurationInterface.java | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index 0381f8554..b9f1c1a92 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -43,7 +43,6 @@ public final class ConfigMappingProvider implements Serializable { static { final KeyMap> map = new KeyMap<>(); - map.putAny(map); map.putRootValue(DO_NOTHING); IGNORE_EVERYTHING = map; } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index c4d25ed27..7917fe66a 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -75,7 +75,6 @@ public Unsafe run() { private final Class interfaceType; private final ConfigurationInterface[] superTypes; private final Property[] properties; - private final Class implClass; private final Constructor constructor; private final Map propertiesByName; @@ -84,9 +83,9 @@ public Unsafe run() { this.interfaceType = interfaceType; this.superTypes = superTypes; this.properties = properties; - implClass = createConfigurationObjectClass().asSubclass(ConfigurationObject.class); try { - constructor = implClass.getDeclaredConstructor(MappingContext.class); + constructor = createConfigurationObjectClass().asSubclass(ConfigurationObject.class) + .getDeclaredConstructor(MappingContext.class); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } @@ -811,7 +810,7 @@ private boolean appendPropertyName(final MethodVisitor ctor, final Property prop ctor.visitVarInsn(Opcodes.ALOAD, V_STRING_BUILDER); // stack: sb - ctor.visitLdcInsn(Character.valueOf('.')); + ctor.visitLdcInsn('.'); // stack: sb '.' ctor.visitInsn(Opcodes.I2C); // stack: sb '.' From 07abf9372f97bc1a28418308288016f865c09928 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 18:12:45 +0100 Subject: [PATCH 29/42] Validate ConfigMapping on Builder. --- .../io/smallrye/config/SmallRyeConfig.java | 29 +++++--------- .../config/SmallRyeConfigBuilder.java | 18 +++++++-- .../config/SmallRyeConfigMappingProvider.java | 38 +++++++++++++++++++ .../config/mapper/ConfigMappingProvider.java | 10 ++++- .../mapper/ConfigMappingProviderTest.java | 31 ++++++++++++--- 5 files changed, 96 insertions(+), 30 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index ed92c9c30..5e462c0ab 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -46,7 +46,6 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; import io.smallrye.config.mapper.ConfigMappingProvider; -import io.smallrye.config.mapper.ConfigurationValidationException; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -58,27 +57,23 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final ConfigMappingProvider configMappingProvider; + private final SmallRyeConfigMappingProvider configMapping; SmallRyeConfig(SmallRyeConfigBuilder builder) { - this.configMappingProvider = buildConfigMapping(builder); + this.configMapping = builder.getConfigMapping(); this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); } @Deprecated protected SmallRyeConfig(List configSources, Map> converters) { - this.configMappingProvider = ConfigMappingProvider.builder().build(); + this.configMapping = new SmallRyeConfigMappingProvider(ConfigMappingProvider.builder().build()); this.configSources = new AtomicReference<>( new ConfigSources(configSources, buildInterceptors(new SmallRyeConfigBuilder()))); this.converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS); this.converters.putAll(converters); } - private ConfigMappingProvider buildConfigMapping(final SmallRyeConfigBuilder builder) { - return builder.getMappingsBuilder().build(); - } - private List buildConfigSources(final SmallRyeConfigBuilder builder) { final List sourcesToBuild = new ArrayList<>(builder.getSources()); if (builder.isAddDiscoveredSources()) { @@ -87,8 +82,8 @@ private List buildConfigSources(final SmallRyeConfigBuilder builde if (builder.isAddDefaultSources()) { sourcesToBuild.addAll(builder.getDefaultSources()); } - if (!builder.getDefaultValues().isEmpty() || !configMappingProvider.getDefaultValues().isEmpty()) { - final KeyMap defaultValues = configMappingProvider.getDefaultValues(); + if (!builder.getDefaultValues().isEmpty() || !configMapping.getDefaultValues().isEmpty()) { + final KeyMap defaultValues = configMapping.getDefaultValues(); builder.getDefaultValues().forEach((key, value) -> defaultValues.findOrAdd(key).putRootValue(value)); sourcesToBuild.add(new KeyMapBackedConfigSource("DefaultValuesConfigSource", defaultValues) { private static final long serialVersionUID = 7847969724478280439L; @@ -229,17 +224,11 @@ public T getConfigMapping(Class klass) { @Experimental("TODO") public T getConfigMapping(Class klass, String prefix) { - try { - final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(this); - final T configRoot = result.getConfigRoot(prefix, klass); - if (configRoot == null) { - throw ConfigMessages.msg.mappingNotFound(klass.getName(), prefix); - } - return configRoot; - } catch (ConfigurationValidationException e) { - // TODO - Map in ConfigMessages - throw new IllegalArgumentException(e); + final T configRoot = configMapping.getConfigMapping(klass, prefix); + if (configRoot == null) { + throw ConfigMessages.msg.mappingNotFound(klass.getName(), prefix); } + return configRoot; } @Override diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index f05de8dce..e47e33272 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -41,6 +41,7 @@ import org.eclipse.microprofile.config.spi.Converter; import io.smallrye.config.mapper.ConfigMappingProvider; +import io.smallrye.config.mapper.ConfigurationValidationException; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -59,6 +60,7 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private final List interceptors = new ArrayList<>(); private final Map defaultValues = new HashMap<>(); private final ConfigMappingProvider.Builder mappingsBuilder = ConfigMappingProvider.builder(); + private SmallRyeConfigMappingProvider mappingProvider; private ClassLoader classLoader = SecuritySupport.getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDefaultInterceptors = false; @@ -309,8 +311,8 @@ Map getDefaultValues() { return defaultValues; } - ConfigMappingProvider.Builder getMappingsBuilder() { - return mappingsBuilder; + SmallRyeConfigMappingProvider getConfigMapping() { + return mappingProvider; } protected boolean isAddDefaultSources() { @@ -335,7 +337,17 @@ boolean isAddDiscoveredInterceptors() { @Override public SmallRyeConfig build() { - return new SmallRyeConfig(this); + mappingProvider = new SmallRyeConfigMappingProvider(mappingsBuilder.build()); + + final SmallRyeConfig config = new SmallRyeConfig(this); + + try { + getConfigMapping().mapConfiguration(config); + } catch (ConfigurationValidationException e) { + throw new IllegalStateException(e); + } + + return config; } static class ConverterWithPriority { diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java new file mode 100644 index 000000000..d773edb1f --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java @@ -0,0 +1,38 @@ +package io.smallrye.config; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.smallrye.config.mapper.ConfigMappingProvider; +import io.smallrye.config.mapper.ConfigurationObject; +import io.smallrye.config.mapper.ConfigurationValidationException; + +public class SmallRyeConfigMappingProvider implements Serializable { + private static final long serialVersionUID = -7807593615345914368L; + + private final ConfigMappingProvider configMappingProvider; + private final Map, Map> rootsMap = new HashMap<>(); + + public SmallRyeConfigMappingProvider(final ConfigMappingProvider configMappingProvider) { + this.configMappingProvider = configMappingProvider; + } + + public KeyMap getDefaultValues() { + return configMappingProvider.getDefaultValues(); + } + + public void mapConfiguration(final SmallRyeConfig config) throws ConfigurationValidationException { + final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(config); + rootsMap.putAll(result.getRootsMap()); + } + + public T getConfigMapping(Class klass) { + return getConfigMapping(klass, ""); + } + + public T getConfigMapping(Class klass, String prefix) { + return klass.cast(rootsMap.getOrDefault(klass, Collections.emptyMap()).get(prefix)); + } +} diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index b9f1c1a92..a71fa2f09 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -602,6 +602,10 @@ public KeyMap getDefaultValues() { } public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValidationException { + if (roots.isEmpty()) { + return new Result(new HashMap<>()); + } + Assert.checkNotNullParam("config", config); final MappingContext context = new MappingContext(config); // eagerly populate roots @@ -684,11 +688,15 @@ public static final class Result { private final Map, Map> rootsMap; Result(final Map, Map> rootsMap) { - this.rootsMap = rootsMap; + this.rootsMap = Collections.unmodifiableMap(rootsMap); } public T getConfigRoot(String path, Class type) { return type.cast(rootsMap.getOrDefault(type, Collections.emptyMap()).get(path)); } + + public Map, Map> getRootsMap() { + return rootsMap; + } } } diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index 8ae3b92b0..ea985461d 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -3,6 +3,7 @@ import static io.smallrye.config.KeyValuesConfigSource.config; import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -22,6 +23,7 @@ import io.smallrye.config.WithConverter; import io.smallrye.config.WithDefault; import io.smallrye.config.WithName; +import io.smallrye.config.common.MapBackedConfigSource; public class ConfigMappingProviderTest { @Test @@ -65,12 +67,9 @@ void configMappingBuilder() throws Exception { } @Test - void unmapped() { - final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( - config("server.host", "localhost", "server.port", "8080", "server.name", "konoha")) - .build(); - // TODO - Most likely we want to do a warning here, but just to try out the feature. - assertThrows(Exception.class, () -> config.getConfigMapping(Server.class, "server")); + void unknownConfigElement() { + assertThrows(IllegalStateException.class, + () -> new SmallRyeConfigBuilder().withMapping(Server.class, "server").build()); } @Test @@ -310,6 +309,26 @@ void mix() { assertEquals(80, sample.client().get().subHostAndPort().port()); } + @Test + void noDynamicValues() { + final 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) { + return counter++ + ""; + } + }).build(); + + final Server server = config.getConfigMapping(Server.class, "server"); + + assertNotEquals(config.getRawValue("server.port"), config.getRawValue("server.port")); + assertEquals(server.port(), server.port()); + } + interface Server { String host(); From 1428b2ae5a9a2054e7a4481ac6159ed4f6716fae Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 18:47:41 +0100 Subject: [PATCH 30/42] DefaultConfigSource return empty map. --- .../src/main/java/io/smallrye/config/SmallRyeConfig.java | 5 +++++ .../io/smallrye/config/mapper/ConfigMappingProviderTest.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 5e462c0ab..59aec86a2 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -88,6 +88,11 @@ private List buildConfigSources(final SmallRyeConfigBuilder builde sourcesToBuild.add(new KeyMapBackedConfigSource("DefaultValuesConfigSource", defaultValues) { private static final long serialVersionUID = 7847969724478280439L; + @Override + public Map getProperties() { + return new HashMap<>(); + } + @Override public int getOrdinal() { return Integer.MIN_VALUE; diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java index ea985461d..0ad294932 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java @@ -2,7 +2,9 @@ import static io.smallrye.config.KeyValuesConfigSource.config; import static java.util.stream.Collectors.toList; +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.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -268,6 +270,9 @@ void defaults() { assertEquals("foo", defaults.foo()); assertEquals("bar", defaults.bar()); assertEquals("foo", config.getRawValue("foo")); + + final List propertyNames = stream(config.getPropertyNames().spliterator(), false).collect(toList()); + assertFalse(propertyNames.contains("foo")); } @Test From 4088829e6d98af0a45fa2ac5aec43977f52d124f Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 20:07:43 +0100 Subject: [PATCH 31/42] Improve KeyMap.toString performance. --- .../src/main/java/io/smallrye/config/KeyMap.java | 13 +++++++++---- .../test/java/io/smallrye/config/KeyMapTest.java | 13 +++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java index f73bc4c86..df35f6611 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -219,6 +219,7 @@ KeyMap map(P param, BiFunction conversion, IdentityHashMap return newMap; } + @SuppressWarnings("ResultOfMethodCallIgnored") public StringBuilder toString(StringBuilder b) { b.append("KeyMap("); V rootValue = this.rootValue; @@ -232,17 +233,21 @@ public StringBuilder toString(StringBuilder b) { KeyMap any = this.any; if (iterator.hasNext()) { Entry> entry = iterator.next(); - b.append(entry.getKey()).append("=>").append(entry.getValue()); + b.append(entry.getKey()).append("=>"); + entry.getValue().toString(b); while (iterator.hasNext()) { entry = iterator.next(); - b.append(',').append(entry.getKey()).append("=>").append(entry.getValue()); + b.append(',').append(entry.getKey()).append("=>"); + entry.getValue().toString(b); } if (any != null) { - b.append(',').append("(any)=>").append(any); + b.append(',').append("(any)=>"); + any.toString(b); } } else { if (any != null) { - b.append("(any)=>").append(any); + b.append("(any)=>"); + any.toString(b); } } b.append('}'); diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java index 8816b3b06..2e200196c 100644 --- a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java +++ b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java @@ -125,4 +125,17 @@ void unwrap() { assertEquals("baz", properties.get("root.foo.bar.*")); assertEquals("anything", properties.get("root.foo.bar.*.baz")); } + + @Test + void string() { + KeyMap map = new KeyMap<>(); + map.findOrAdd("root.foo").putRootValue("bar"); + map.findOrAdd("root.foo.bar").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*").putRootValue("baz"); + map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); + + assertEquals( + "KeyMap(no value) {root=>KeyMap(no value) {foo=>KeyMap(value=bar) {bar=>KeyMap(value=baz) {(any)=>KeyMap(value=baz) {baz=>KeyMap(value=anything) {}}}}}}", + map.toString()); + } } From f13b079d7b753ecb04baa6a497d217a7307205ab Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 20:14:03 +0100 Subject: [PATCH 32/42] Moved ConfigValidationException to main package. Fixed some visibility modifiers. --- ...tionException.java => ConfigValidationException.java} | 6 +++--- .../java/io/smallrye/config/SmallRyeConfigBuilder.java | 3 +-- .../smallrye/config/SmallRyeConfigMappingProvider.java | 3 +-- .../io/smallrye/config/mapper/ConfigMappingProvider.java | 9 +++++---- .../smallrye/config/mapper/ConfigurationInterface.java | 2 +- .../java/io/smallrye/config/mapper/MappingContext.java | 4 ++-- 6 files changed, 13 insertions(+), 14 deletions(-) rename implementation/src/main/java/io/smallrye/config/{mapper/ConfigurationValidationException.java => ConfigValidationException.java} (89%) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java b/implementation/src/main/java/io/smallrye/config/ConfigValidationException.java similarity index 89% rename from implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java rename to implementation/src/main/java/io/smallrye/config/ConfigValidationException.java index 0c3b7a22a..647be214b 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationValidationException.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigValidationException.java @@ -1,11 +1,11 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import io.smallrye.common.constraint.Assert; /** * An exception which is thrown when a configuration validation problem occurs. */ -public class ConfigurationValidationException extends Exception { +public class ConfigValidationException extends Exception { private static final long serialVersionUID = -2637730579475070264L; private final Problem[] problems; @@ -15,7 +15,7 @@ public class ConfigurationValidationException extends Exception { * * @param problems the reported problems */ - public ConfigurationValidationException(final Problem[] problems) { + public ConfigValidationException(final Problem[] problems) { super(list("Configuration validation failed", problems)); this.problems = problems; } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index e47e33272..c6b8dcbb5 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -41,7 +41,6 @@ import org.eclipse.microprofile.config.spi.Converter; import io.smallrye.config.mapper.ConfigMappingProvider; -import io.smallrye.config.mapper.ConfigurationValidationException; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -343,7 +342,7 @@ public SmallRyeConfig build() { try { getConfigMapping().mapConfiguration(config); - } catch (ConfigurationValidationException e) { + } catch (ConfigValidationException e) { throw new IllegalStateException(e); } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java index d773edb1f..02895ebce 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java @@ -7,7 +7,6 @@ import io.smallrye.config.mapper.ConfigMappingProvider; import io.smallrye.config.mapper.ConfigurationObject; -import io.smallrye.config.mapper.ConfigurationValidationException; public class SmallRyeConfigMappingProvider implements Serializable { private static final long serialVersionUID = -7807593615345914368L; @@ -23,7 +22,7 @@ public KeyMap getDefaultValues() { return configMappingProvider.getDefaultValues(); } - public void mapConfiguration(final SmallRyeConfig config) throws ConfigurationValidationException { + public void mapConfiguration(final SmallRyeConfig config) throws ConfigValidationException { final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(config); rootsMap.putAll(result.getRootsMap()); } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java index a71fa2f09..5be72e776 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java @@ -25,6 +25,7 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.common.function.Functions; +import io.smallrye.config.ConfigValidationException; import io.smallrye.config.KeyMap; import io.smallrye.config.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -601,7 +602,7 @@ public KeyMap getDefaultValues() { return defaultValues; } - public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValidationException { + public Result mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { if (roots.isEmpty()) { return new Result(new HashMap<>()); } @@ -635,10 +636,10 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigurationValida context.unknownConfigElement(name); } } - ArrayList problems = context.getProblems(); + ArrayList problems = context.getProblems(); if (!problems.isEmpty()) { - throw new ConfigurationValidationException( - problems.toArray(ConfigurationValidationException.Problem.NO_PROBLEMS)); + throw new ConfigValidationException( + problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS)); } context.fillInOptionals(); return new Result(context.getRootsMap()); diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 7917fe66a..9c2dd602e 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -43,7 +43,7 @@ /** * Information about a configuration interface. */ -public final class ConfigurationInterface { +final class ConfigurationInterface { static final ConfigurationInterface[] NO_TYPES = new ConfigurationInterface[0]; static final Property[] NO_PROPERTIES = new Property[0]; static final ClassValue cv = new ClassValue() { diff --git a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java index 606022b21..f93a1019c 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java @@ -1,5 +1,6 @@ package io.smallrye.config.mapper; +import static io.smallrye.config.ConfigValidationException.Problem; import static io.smallrye.config.mapper.ConfigurationInterface.LeafProperty; import static io.smallrye.config.mapper.ConfigurationInterface.MapProperty; import static io.smallrye.config.mapper.ConfigurationInterface.PrimitiveProperty; @@ -7,7 +8,6 @@ import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; import static io.smallrye.config.mapper.ConfigurationInterface.rawTypeOf; import static io.smallrye.config.mapper.ConfigurationInterface.typeOfParameter; -import static io.smallrye.config.mapper.ConfigurationValidationException.Problem; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -31,7 +31,7 @@ * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration * mapping has completed. */ -public final class MappingContext { +final class MappingContext { private final Map, Map>> enclosedThings = new IdentityHashMap<>(); private final Map, Map> roots = new IdentityHashMap<>(); From 1dc64d4e279aeedb90185c65af5fcc10be31c9b5 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 20:42:50 +0100 Subject: [PATCH 33/42] Added Config Mappings directly into SmallRyeConfig. --- .../io/smallrye/config/SmallRyeConfig.java | 38 ++++++------------- .../config/SmallRyeConfigBuilder.java | 30 ++++++++++----- .../config/SmallRyeConfigMappingProvider.java | 37 ------------------ 3 files changed, 33 insertions(+), 72 deletions(-) delete mode 100644 implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 59aec86a2..a3242f731 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -45,7 +45,7 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; -import io.smallrye.config.mapper.ConfigMappingProvider; +import io.smallrye.config.mapper.ConfigurationObject; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -57,17 +57,15 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final SmallRyeConfigMappingProvider configMapping; + private final Map, Map> mappings = new HashMap<>(); SmallRyeConfig(SmallRyeConfigBuilder builder) { - this.configMapping = builder.getConfigMapping(); this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); } @Deprecated protected SmallRyeConfig(List configSources, Map> converters) { - this.configMapping = new SmallRyeConfigMappingProvider(ConfigMappingProvider.builder().build()); this.configSources = new AtomicReference<>( new ConfigSources(configSources, buildInterceptors(new SmallRyeConfigBuilder()))); this.converters = new ConcurrentHashMap<>(Converters.ALL_CONVERTERS); @@ -82,23 +80,6 @@ private List buildConfigSources(final SmallRyeConfigBuilder builde if (builder.isAddDefaultSources()) { sourcesToBuild.addAll(builder.getDefaultSources()); } - if (!builder.getDefaultValues().isEmpty() || !configMapping.getDefaultValues().isEmpty()) { - final KeyMap defaultValues = configMapping.getDefaultValues(); - builder.getDefaultValues().forEach((key, value) -> defaultValues.findOrAdd(key).putRootValue(value)); - sourcesToBuild.add(new KeyMapBackedConfigSource("DefaultValuesConfigSource", defaultValues) { - private static final long serialVersionUID = 7847969724478280439L; - - @Override - public Map getProperties() { - return new HashMap<>(); - } - - @Override - public int getOrdinal() { - return Integer.MIN_VALUE; - } - }); - } // wrap all final Function sourceWrappersToBuild = builder.getSourceWrappers(); @@ -223,15 +204,20 @@ public > Optional getOptionalValues(String name, C } @Experimental("TODO") - public T getConfigMapping(Class klass) { - return getConfigMapping(klass, ""); + void registerValidMappings(Map, Map> mappings) { + this.mappings.putAll(mappings); + } + + @Experimental("TODO") + public T getConfigMapping(Class type) { + return getConfigMapping(type, ""); } @Experimental("TODO") - public T getConfigMapping(Class klass, String prefix) { - final T configRoot = configMapping.getConfigMapping(klass, prefix); + public T getConfigMapping(Class type, String prefix) { + final T configRoot = type.cast(mappings.getOrDefault(type, Collections.emptyMap()).get(prefix)); if (configRoot == null) { - throw ConfigMessages.msg.mappingNotFound(klass.getName(), prefix); + throw ConfigMessages.msg.mappingNotFound(type.getName(), prefix); } return configRoot; } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index c6b8dcbb5..32a56a9fd 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -59,7 +59,6 @@ public class SmallRyeConfigBuilder implements ConfigBuilder { private final List interceptors = new ArrayList<>(); private final Map defaultValues = new HashMap<>(); private final ConfigMappingProvider.Builder mappingsBuilder = ConfigMappingProvider.builder(); - private SmallRyeConfigMappingProvider mappingProvider; private ClassLoader classLoader = SecuritySupport.getContextClassLoader(); private boolean addDefaultSources = false; private boolean addDefaultInterceptors = false; @@ -310,10 +309,6 @@ Map getDefaultValues() { return defaultValues; } - SmallRyeConfigMappingProvider getConfigMapping() { - return mappingProvider; - } - protected boolean isAddDefaultSources() { return addDefaultSources; } @@ -336,17 +331,34 @@ boolean isAddDiscoveredInterceptors() { @Override public SmallRyeConfig build() { - mappingProvider = new SmallRyeConfigMappingProvider(mappingsBuilder.build()); + final ConfigMappingProvider mappingProvider = mappingsBuilder.build(); + if (!defaultValues.isEmpty() || !mappingProvider.getDefaultValues().isEmpty()) { + final KeyMap mappingProviderDefaultValues = mappingProvider.getDefaultValues(); + defaultValues.forEach((key, value) -> mappingProviderDefaultValues.findOrAdd(key).putRootValue(value)); + withSources(new KeyMapBackedConfigSource("DefaultValuesConfigSource", mappingProviderDefaultValues) { + private static final long serialVersionUID = 7847969724478280439L; + + @Override + public Map getProperties() { + return new HashMap<>(); + } + + @Override + public int getOrdinal() { + return Integer.MIN_VALUE; + } + }); + } final SmallRyeConfig config = new SmallRyeConfig(this); try { - getConfigMapping().mapConfiguration(config); + final ConfigMappingProvider.Result result = mappingProvider.mapConfiguration(config); + config.registerValidMappings(result.getRootsMap()); + return config; } catch (ConfigValidationException e) { throw new IllegalStateException(e); } - - return config; } static class ConverterWithPriority { diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java deleted file mode 100644 index 02895ebce..000000000 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigMappingProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.smallrye.config; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import io.smallrye.config.mapper.ConfigMappingProvider; -import io.smallrye.config.mapper.ConfigurationObject; - -public class SmallRyeConfigMappingProvider implements Serializable { - private static final long serialVersionUID = -7807593615345914368L; - - private final ConfigMappingProvider configMappingProvider; - private final Map, Map> rootsMap = new HashMap<>(); - - public SmallRyeConfigMappingProvider(final ConfigMappingProvider configMappingProvider) { - this.configMappingProvider = configMappingProvider; - } - - public KeyMap getDefaultValues() { - return configMappingProvider.getDefaultValues(); - } - - public void mapConfiguration(final SmallRyeConfig config) throws ConfigValidationException { - final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(config); - rootsMap.putAll(result.getRootsMap()); - } - - public T getConfigMapping(Class klass) { - return getConfigMapping(klass, ""); - } - - public T getConfigMapping(Class klass, String prefix) { - return klass.cast(rootsMap.getOrDefault(klass, Collections.emptyMap()).get(prefix)); - } -} From 73ffd2ffc9ff75ed604b1429828fc6a62d7bb83a Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 17 Jul 2020 20:58:14 +0100 Subject: [PATCH 34/42] Added doPrivileged to System property lookup. --- .../java/io/smallrye/config/mapper/ConfigurationInterface.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java index 9c2dd602e..57191a427 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java @@ -55,7 +55,8 @@ protected ConfigurationInterface computeValue(final Class type) { static final Unsafe unsafe; static { - usefulDebugInfo = Boolean.parseBoolean(System.getProperty("io.smallrye.config.mapper.useful-debug-info")); + usefulDebugInfo = Boolean.parseBoolean(AccessController.doPrivileged( + (PrivilegedAction) () -> System.getProperty("io.smallrye.config.mapper.useful-debug-info"))); unsafe = AccessController.doPrivileged(new PrivilegedAction() { public Unsafe run() { From 38fd8809ad6e51287df69f629cc08e302101fadb Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 23 Jul 2020 20:54:19 +0100 Subject: [PATCH 35/42] Removed mapper package to remove public access to ConfigMapping classes. --- ...Context.java => ConfigMappingContext.java} | 45 ++-- ...rface.java => ConfigMappingInterface.java} | 202 +++++++++++++++--- .../smallrye/config/ConfigMappingObject.java | 8 + .../{mapper => }/ConfigMappingProvider.java | 201 +++++++++-------- .../io/smallrye/config/SmallRyeConfig.java | 5 +- .../config/SmallRyeConfigBuilder.java | 2 - .../config/{mapper => }/WithParentName.java | 2 +- .../config/mapper/ConfigurationObject.java | 8 - .../io/smallrye/config/mapper/Debugging.java | 152 ------------- .../ConfigMappingProviderTest.java | 7 +- 10 files changed, 303 insertions(+), 329 deletions(-) rename implementation/src/main/java/io/smallrye/config/{mapper/MappingContext.java => ConfigMappingContext.java} (87%) rename implementation/src/main/java/io/smallrye/config/{mapper/ConfigurationInterface.java => ConfigMappingInterface.java} (84%) create mode 100644 implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java rename implementation/src/main/java/io/smallrye/config/{mapper => }/ConfigMappingProvider.java (76%) rename implementation/src/main/java/io/smallrye/config/{mapper => }/WithParentName.java (91%) delete mode 100644 implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java delete mode 100644 implementation/src/main/java/io/smallrye/config/mapper/Debugging.java rename implementation/src/test/java/io/smallrye/config/{mapper => }/ConfigMappingProviderTest.java (98%) diff --git a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java similarity index 87% rename from implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java rename to implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java index f93a1019c..cc1bdfe09 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/MappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java @@ -1,13 +1,13 @@ -package io.smallrye.config.mapper; - +package io.smallrye.config; + +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.ConfigMappingInterface.getConfigurationInterface; +import static io.smallrye.config.ConfigMappingInterface.rawTypeOf; +import static io.smallrye.config.ConfigMappingInterface.typeOfParameter; import static io.smallrye.config.ConfigValidationException.Problem; -import static io.smallrye.config.mapper.ConfigurationInterface.LeafProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.MapProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.PrimitiveProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.Property; -import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; -import static io.smallrye.config.mapper.ConfigurationInterface.rawTypeOf; -import static io.smallrye.config.mapper.ConfigurationInterface.typeOfParameter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -24,34 +24,31 @@ import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.Converters; -import io.smallrye.config.SmallRyeConfig; - /** * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration * mapping has completed. */ -final class MappingContext { +final class ConfigMappingContext { private final Map, Map>> enclosedThings = new IdentityHashMap<>(); - private final Map, Map> roots = new IdentityHashMap<>(); + private final Map, Map> roots = new IdentityHashMap<>(); private final Map, Map>> convertersByTypeAndField = new IdentityHashMap<>(); private final List, Map>>> keyConvertersByDegreeTypeAndField = new ArrayList<>(); private final Map, Converter> converterInstances = new IdentityHashMap<>(); - private final List allInstances = new ArrayList<>(); + private final List allInstances = new ArrayList<>(); private final SmallRyeConfig config; private final StringBuilder stringBuilder = new StringBuilder(); private final ArrayList problems = new ArrayList<>(); - MappingContext(final SmallRyeConfig config) { + ConfigMappingContext(final SmallRyeConfig config) { this.config = config; } - public ConfigurationObject getRoot(Class rootType, String rootPath) { + public ConfigMappingObject getRoot(Class rootType, String rootPath) { return roots.getOrDefault(rootType, Collections.emptyMap()).get(rootPath); } - public void registerRoot(Class rootType, String rootPath, ConfigurationObject root) { + public void registerRoot(Class rootType, String rootPath, ConfigMappingObject root) { roots.computeIfAbsent(rootType, x -> new HashMap<>()).put(rootPath, root); } @@ -70,8 +67,8 @@ public void registerEnclosedField(Class enclosingType, String key, Object enc } public T constructGroup(Class interfaceType) { - Constructor constructor = getConfigurationInterface(interfaceType).getConstructor(); - ConfigurationObject instance; + Constructor constructor = getConfigurationInterface(interfaceType).getConstructor(); + ConfigMappingObject instance; try { instance = constructor.newInstance(this); } catch (InstantiationException e) { @@ -96,7 +93,7 @@ public Converter getValueConverter(Class enclosingType, String field) return (Converter) convertersByTypeAndField .computeIfAbsent(enclosingType, x -> new HashMap<>()) .computeIfAbsent(field, x -> { - ConfigurationInterface ci = getConfigurationInterface(enclosingType); + ConfigMappingInterface ci = getConfigurationInterface(enclosingType); Property property = ci.getProperty(field); boolean optional = property.isOptional(); if (property.isLeaf() || optional && property.asOptional().getNestedProperty().isLeaf()) { @@ -144,7 +141,7 @@ public Converter getKeyConverter(Class enclosingType, String field, in return (Converter) map .computeIfAbsent(enclosingType, x -> new HashMap<>()) .computeIfAbsent(field, x -> { - ConfigurationInterface ci = getConfigurationInterface(enclosingType); + ConfigMappingInterface ci = getConfigurationInterface(enclosingType); MapProperty property = ci.getProperty(field).asMap(); while (degree + 1 > property.getLevels()) { property = property.getValueProperty().asMap(); @@ -199,7 +196,7 @@ public void unknownConfigElement(final String propertyName) { } void fillInOptionals() { - for (ConfigurationObject instance : allInstances) { + for (ConfigMappingObject instance : allInstances) { instance.fillInOptionals(this); } } @@ -220,7 +217,7 @@ ArrayList getProblems() { return problems; } - Map, Map> getRootsMap() { + Map, Map> getRootsMap() { return roots; } } diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java similarity index 84% rename from implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java rename to implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index 57191a427..f38f5f53f 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -1,6 +1,6 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; -import static io.smallrye.config.mapper.ConfigMappingProvider.skewer; +import static io.smallrye.config.ConfigMappingProvider.skewer; import static org.objectweb.asm.Type.getDescriptor; import static org.objectweb.asm.Type.getInternalName; import static org.objectweb.asm.Type.getType; @@ -28,26 +28,23 @@ import org.eclipse.microprofile.config.spi.Converter; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import io.smallrye.common.constraint.Assert; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.WithConverter; -import io.smallrye.config.WithDefault; -import io.smallrye.config.WithName; import io.smallrye.config.inject.InjectionMessages; import sun.misc.Unsafe; /** * Information about a configuration interface. */ -final class ConfigurationInterface { - static final ConfigurationInterface[] NO_TYPES = new ConfigurationInterface[0]; +final class ConfigMappingInterface { + static final ConfigMappingInterface[] NO_TYPES = new ConfigMappingInterface[0]; static final Property[] NO_PROPERTIES = new Property[0]; - static final ClassValue cv = new ClassValue() { - protected ConfigurationInterface computeValue(final Class type) { + static final ClassValue cv = new ClassValue() { + protected ConfigMappingInterface computeValue(final Class type) { return createConfigurationInterface(type); } }; @@ -74,19 +71,19 @@ public Unsafe run() { } private final Class interfaceType; - private final ConfigurationInterface[] superTypes; + private final ConfigMappingInterface[] superTypes; private final Property[] properties; - private final Constructor constructor; + private final Constructor constructor; private final Map propertiesByName; - ConfigurationInterface(final Class interfaceType, final ConfigurationInterface[] superTypes, + ConfigMappingInterface(final Class interfaceType, final ConfigMappingInterface[] superTypes, final Property[] properties) { this.interfaceType = interfaceType; this.superTypes = superTypes; this.properties = properties; try { - constructor = createConfigurationObjectClass().asSubclass(ConfigurationObject.class) - .getDeclaredConstructor(MappingContext.class); + constructor = createConfigurationObjectClass().asSubclass(ConfigMappingObject.class) + .getDeclaredConstructor(ConfigMappingContext.class); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } @@ -103,7 +100,7 @@ public Unsafe run() { * @param interfaceType the interface type (must not be {@code null}) * @return the configuration interface, or {@code null} if the type does not appear to be a configuration interface */ - public static ConfigurationInterface getConfigurationInterface(Class interfaceType) { + public static ConfigMappingInterface getConfigurationInterface(Class interfaceType) { Assert.checkNotNullParam("interfaceType", interfaceType); return cv.get(interfaceType); } @@ -136,7 +133,7 @@ public int getSuperTypeCount() { * @return the supertype definition * @throws IndexOutOfBoundsException if {@code index} is invalid */ - public ConfigurationInterface getSuperType(int index) throws IndexOutOfBoundsException { + public ConfigMappingInterface getSuperType(int index) throws IndexOutOfBoundsException { if (index < 0 || index >= superTypes.length) throw new IndexOutOfBoundsException(); return superTypes[index]; @@ -169,7 +166,7 @@ public Property getProperty(final String name) { return propertiesByName.get(name); } - Constructor getConstructor() { + Constructor getConstructor() { return constructor; } @@ -418,14 +415,14 @@ public MayBeOptionalProperty getNestedProperty() { } public static final class GroupProperty extends MayBeOptionalProperty { - private final ConfigurationInterface groupType; + private final ConfigMappingInterface groupType; - GroupProperty(final Method method, final String propertyName, final ConfigurationInterface groupType) { + GroupProperty(final Method method, final String propertyName, final ConfigMappingInterface groupType) { super(method, propertyName); this.groupType = groupType; } - public ConfigurationInterface getGroupType() { + public ConfigMappingInterface getGroupType() { return groupType; } @@ -542,12 +539,12 @@ public int getLevels() { } } - static ConfigurationInterface createConfigurationInterface(Class interfaceType) { + static ConfigMappingInterface createConfigurationInterface(Class interfaceType) { if (!interfaceType.isInterface() || interfaceType.getTypeParameters().length != 0) { return null; } // first, find any supertypes - ConfigurationInterface[] superTypes = getSuperTypes(interfaceType.getInterfaces(), 0, 0); + ConfigMappingInterface[] superTypes = getSuperTypes(interfaceType.getInterfaces(), 0, 0); // now find any properties Property[] properties = getProperties(interfaceType.getDeclaredMethods(), 0, 0); // is it anything? @@ -556,16 +553,16 @@ static ConfigurationInterface createConfigurationInterface(Class interfaceTyp return null; } else { // it is a proper configuration interface - return new ConfigurationInterface(interfaceType, superTypes, properties); + return new ConfigMappingInterface(interfaceType, superTypes, properties); } } 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(ConfigurationObject.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_MAPPING_CONTEXT = getInternalName(MappingContext.class); + private static final String I_MAPPING_CONTEXT = getInternalName(ConfigMappingContext.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); @@ -791,7 +788,7 @@ private void addProperties(ClassVisitor cv, final String className, MethodVisito // end loop } // subtype overrides supertype - for (ConfigurationInterface superType : superTypes) { + for (ConfigMappingInterface superType : superTypes) { superType.addProperties(cv, className, ctor, fio, visited); } } @@ -917,18 +914,18 @@ Class createConfigurationObjectClass() { return unsafe.defineAnonymousClass(usefulDebugInfo ? getClass() : interfaceType, writer.toByteArray(), null); } - private static ConfigurationInterface[] getSuperTypes(Class[] interfaces, int si, int ti) { + private static ConfigMappingInterface[] getSuperTypes(Class[] interfaces, int si, int ti) { if (si == interfaces.length) { if (ti == 0) { return NO_TYPES; } else { - return new ConfigurationInterface[ti]; + return new ConfigMappingInterface[ti]; } } Class item = interfaces[si]; - ConfigurationInterface ci = getConfigurationInterface(item); + ConfigMappingInterface ci = getConfigurationInterface(item); if (ci != null) { - ConfigurationInterface[] array = getSuperTypes(interfaces, si + 1, ti + 1); + ConfigMappingInterface[] array = getSuperTypes(interfaces, si + 1, ti + 1); array[ti] = ci; return array; } else { @@ -988,7 +985,7 @@ private static Property getPropertyDef(Method method, Type type) { Type valueType = typeOfParameter(type, 1); return new MapProperty(method, propertyName, keyType, keyConvertWith, getPropertyDef(method, valueType)); } - ConfigurationInterface configurationInterface = getConfigurationInterface(rawType); + ConfigMappingInterface configurationInterface = getConfigurationInterface(rawType); if (configurationInterface != null) { // it's a group return new GroupProperty(method, propertyName, configurationInterface); @@ -1061,4 +1058,145 @@ static Class rawTypeOf(final Type type) { } } + static final class Debugging { + static StackTraceElement getCaller() { + return new Throwable().getStackTrace()[2]; + } + + static final class MethodVisitorImpl extends MethodVisitor { + + MethodVisitorImpl(final int api) { + super(api); + } + + MethodVisitorImpl(final int api, final MethodVisitor methodVisitor) { + super(api, methodVisitor); + } + + public void visitInsn(final int opcode) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitInsn(opcode); + } + + public void visitIntInsn(final int opcode, final int operand) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitIntInsn(opcode, operand); + } + + public void visitVarInsn(final int opcode, final int var) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitVarInsn(opcode, var); + } + + public void visitTypeInsn(final int opcode, final String type) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitTypeInsn(opcode, type); + } + + public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMethodInsn(opcode, owner, name, descriptor); + } + + public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, + final boolean isInterface) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + public void visitJumpInsn(final int opcode, final Label label) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitJumpInsn(opcode, label); + } + + public void visitLdcInsn(final Object value) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitLdcInsn(value); + } + + public void visitIincInsn(final int var, final int increment) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitIincInsn(var, increment); + } + + public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + Label l = new Label(); + visitLabel(l); + visitLineNumber(getCaller().getLineNumber(), l); + super.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + static final class ClassVisitorImpl extends ClassVisitor { + + final String sourceFile; + + ClassVisitorImpl(final int api) { + super(api); + sourceFile = getCaller().getFileName(); + } + + ClassVisitorImpl(final ClassWriter cw) { + super(Opcodes.ASM7, cw); + sourceFile = getCaller().getFileName(); + } + + public void visitSource(final String source, final String debug) { + super.visitSource(sourceFile, debug); + } + + public MethodVisitor visitMethod(final int access, final String name, final String descriptor, + final String signature, + final String[] exceptions) { + return new MethodVisitorImpl(api, super.visitMethod(access, name, descriptor, signature, exceptions)); + } + } + } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java new file mode 100644 index 000000000..7aa4897fd --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java @@ -0,0 +1,8 @@ +package io.smallrye.config; + +/** + * An interface implemented internally by configuration object implementations. + */ +interface ConfigMappingObject { + void fillInOptionals(ConfigMappingContext context); +} diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java similarity index 76% rename from implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java rename to implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index 5be72e776..3d66b4969 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -1,12 +1,12 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; -import static io.smallrye.config.mapper.ConfigurationInterface.GroupProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.LeafProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.MapProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.MayBeOptionalProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.PrimitiveProperty; -import static io.smallrye.config.mapper.ConfigurationInterface.Property; -import static io.smallrye.config.mapper.ConfigurationInterface.getConfigurationInterface; +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.MayBeOptionalProperty; +import static io.smallrye.config.ConfigMappingInterface.PrimitiveProperty; +import static io.smallrye.config.ConfigMappingInterface.Property; +import static io.smallrye.config.ConfigMappingInterface.getConfigurationInterface; import java.io.Serializable; import java.lang.reflect.Method; @@ -25,39 +25,35 @@ import io.smallrye.common.constraint.Assert; import io.smallrye.common.function.Functions; -import io.smallrye.config.ConfigValidationException; -import io.smallrye.config.KeyMap; -import io.smallrye.config.NameIterator; -import io.smallrye.config.SmallRyeConfig; /** * */ -public final class ConfigMappingProvider implements Serializable { +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; + private static final BiConsumer DO_NOTHING = Functions.discardingBiConsumer(); + private static final KeyMap> IGNORE_EVERYTHING; static { - final KeyMap> map = new KeyMap<>(); + final KeyMap> map = new KeyMap<>(); map.putRootValue(DO_NOTHING); IGNORE_EVERYTHING = map; } - private final Map> roots; - private final KeyMap> matchActions; + private final Map> roots; + private final KeyMap> matchActions; private final KeyMap defaultValues; ConfigMappingProvider(final Builder builder) { roots = new HashMap<>(builder.roots); final ArrayDeque currentPath = new ArrayDeque<>(); - KeyMap> matchActions = new KeyMap<>(); + KeyMap> matchActions = new KeyMap<>(); KeyMap defaultValues = new KeyMap<>(); - for (Map.Entry> entry : roots.entrySet()) { + for (Map.Entry> entry : roots.entrySet()) { NameIterator rootNi = new NameIterator(entry.getKey()); while (rootNi.hasNext()) { final String nextSegment = rootNi.getNextSegment(); @@ -66,17 +62,17 @@ public final class ConfigMappingProvider implements Serializable { } rootNi.next(); } - List roots = entry.getValue(); - for (ConfigurationInterface root : roots) { + List roots = entry.getValue(); + for (ConfigMappingInterface root : roots) { // construct the lazy match actions for each group - BiFunction ef = new GetRootAction(root, entry); + BiFunction ef = new GetRootAction(root, entry); processEagerGroup(currentPath, matchActions, defaultValues, root, ef); } currentPath.clear(); } for (String[] ignoredPath : builder.ignored) { int len = ignoredPath.length; - KeyMap> found; + KeyMap> found; if (ignoredPath[len - 1].equals("**")) { found = matchActions.findOrAdd(ignoredPath, 0, len - 1); found.putRootValue(DO_NOTHING); @@ -167,28 +163,28 @@ static String skewer(String camelHumps, int start, int end, StringBuilder b) { // unreachable } - static final class ConsumeOneAndThen implements BiConsumer { - private final BiConsumer delegate; + static final class ConsumeOneAndThen implements BiConsumer { + private final BiConsumer delegate; - ConsumeOneAndThen(final BiConsumer delegate) { + ConsumeOneAndThen(final BiConsumer delegate) { this.delegate = delegate; } - public void accept(final MappingContext context, final NameIterator nameIterator) { + 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; + static final class ConsumeOneAndThenFn implements BiFunction { + private final BiFunction delegate; - ConsumeOneAndThenFn(final BiFunction delegate) { + ConsumeOneAndThenFn(final BiFunction delegate) { this.delegate = delegate; } - public T apply(final MappingContext context, final NameIterator nameIterator) { + public T apply(final ConfigMappingContext context, final NameIterator nameIterator) { nameIterator.previous(); T result = delegate.apply(context, nameIterator); nameIterator.next(); @@ -197,9 +193,9 @@ public T apply(final MappingContext context, final NameIterator nameIterator) { } private void processEagerGroup(final ArrayDeque currentPath, - final KeyMap> matchActions, final KeyMap defaultValues, - final ConfigurationInterface group, - final BiFunction getEnclosingFunction) { + final KeyMap> matchActions, final KeyMap defaultValues, + final ConfigMappingInterface group, + final BiFunction getEnclosingFunction) { Class type = group.getInterfaceType(); int pc = group.getPropertyCount(); int pathLen = currentPath.size(); @@ -275,12 +271,12 @@ private void processEagerGroup(final ArrayDeque currentPath, } private void processLazyGroupInGroup(ArrayDeque currentPath, - KeyMap> matchActions, + KeyMap> matchActions, KeyMap defaultValues, GroupProperty groupProperty, - BiFunction getEnclosingFunction, - BiConsumer matchAction, HashSet usedProperties) { - ConfigurationInterface group = groupProperty.getGroupType(); + BiFunction getEnclosingFunction, + BiConsumer matchAction, HashSet usedProperties) { + ConfigMappingInterface group = groupProperty.getGroupType(); int pc = group.getPropertyCount(); int pathLen = currentPath.size(); for (int i = 0; i < pc; i++) { @@ -310,7 +306,7 @@ private void processLazyGroupInGroup(ArrayDeque currentPath, property.isParentPropertyName() ? getEnclosingFunction : new ConsumeOneAndThenFn<>(getEnclosingFunction), group, asGroup); - BiConsumer nestedMatchAction; + BiConsumer nestedMatchAction; nestedMatchAction = matchAction; if (!property.isParentPropertyName()) { nestedMatchAction = new ConsumeOneAndThen(nestedMatchAction); @@ -319,7 +315,7 @@ private void processLazyGroupInGroup(ArrayDeque currentPath, nestedMatchAction, usedProperties); } else if (property.isLeaf() || property.isPrimitive() || optional && property.asOptional().getNestedProperty().isLeaf()) { - BiConsumer actualAction; + BiConsumer actualAction; if (!property.isParentPropertyName()) { actualAction = new ConsumeOneAndThen(matchAction); } else { @@ -359,9 +355,9 @@ private void processLazyGroupInGroup(ArrayDeque currentPath, } private void processLazyMapInGroup(final ArrayDeque currentPath, - final KeyMap> matchActions, final KeyMap defaultValues, - final MapProperty property, BiFunction getEnclosingGroup, - ConfigurationInterface enclosingGroup) { + final KeyMap> matchActions, final KeyMap defaultValues, + final MapProperty property, BiFunction getEnclosingGroup, + ConfigMappingInterface enclosingGroup) { GetOrCreateEnclosingMapInGroup getEnclosingMap = new GetOrCreateEnclosingMapInGroup(getEnclosingGroup, enclosingGroup, property); processLazyMap(currentPath, matchActions, defaultValues, property, getEnclosingMap, enclosingGroup); @@ -369,9 +365,9 @@ private void processLazyMapInGroup(final ArrayDeque currentPath, @SuppressWarnings({ "unchecked", "rawtypes" }) private void processLazyMap(final ArrayDeque currentPath, - final KeyMap> matchActions, final KeyMap defaultValues, - final MapProperty property, BiFunction> getEnclosingMap, - ConfigurationInterface enclosingGroup) { + final KeyMap> matchActions, final KeyMap defaultValues, + final MapProperty property, BiFunction> getEnclosingMap, + ConfigMappingInterface enclosingGroup) { Property valueProperty = property.getValueProperty(); Class> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null; Class keyRawType = property.getKeyRawType(); @@ -432,63 +428,64 @@ private void processLazyMap(final ArrayDeque currentPath, currentPath.removeLast(); } - static class GetRootAction implements BiFunction { - private final ConfigurationInterface root; - private final Map.Entry> entry; + static class GetRootAction implements BiFunction { + private final ConfigMappingInterface root; + private final Map.Entry> entry; - GetRootAction(final ConfigurationInterface root, final Map.Entry> entry) { + GetRootAction(final ConfigMappingInterface root, final Map.Entry> entry) { this.root = root; this.entry = entry; } - public ConfigurationObject apply(final MappingContext mc, final NameIterator ni) { + public ConfigMappingObject apply(final ConfigMappingContext mc, final NameIterator ni) { return mc .getRoot(root.getInterfaceType(), entry.getKey()); } } - static class GetOrCreateEnclosingGroupInGroup implements BiFunction, - BiConsumer { - private final BiFunction delegate; - private final ConfigurationInterface enclosingGroup; + static class GetOrCreateEnclosingGroupInGroup + implements BiFunction, + BiConsumer { + private final BiFunction delegate; + private final ConfigMappingInterface enclosingGroup; private final GroupProperty enclosedGroup; - GetOrCreateEnclosingGroupInGroup(final BiFunction delegate, - final ConfigurationInterface enclosingGroup, final GroupProperty enclosedGroup) { + GetOrCreateEnclosingGroupInGroup(final BiFunction delegate, + final ConfigMappingInterface enclosingGroup, final GroupProperty enclosedGroup) { this.delegate = delegate; this.enclosingGroup = enclosingGroup; this.enclosedGroup = enclosedGroup; } - public ConfigurationObject apply(final MappingContext context, final NameIterator ni) { - ConfigurationObject ourEnclosing = delegate.apply(context, ni); + public ConfigMappingObject apply(final ConfigMappingContext context, final NameIterator ni) { + ConfigMappingObject ourEnclosing = delegate.apply(context, ni); Class enclosingType = enclosingGroup.getInterfaceType(); String methodName = enclosedGroup.getMethod().getName(); - ConfigurationObject val = (ConfigurationObject) context.getEnclosedField(enclosingType, methodName, ourEnclosing); + ConfigMappingObject val = (ConfigMappingObject) context.getEnclosedField(enclosingType, methodName, ourEnclosing); if (val == null) { // it must be an optional group StringBuilder sb = context.getStringBuilder(); sb.replace(0, sb.length(), ni.getAllPreviousSegments()); - val = (ConfigurationObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType()); + val = (ConfigMappingObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType()); context.registerEnclosedField(enclosingType, methodName, ourEnclosing, val); } return val; } - public void accept(final MappingContext context, final NameIterator nameIterator) { + public void accept(final ConfigMappingContext context, final NameIterator nameIterator) { apply(context, nameIterator); } } - static class GetOrCreateEnclosingGroupInMap implements BiFunction, - BiConsumer { - final BiFunction> getEnclosingMap; + static class GetOrCreateEnclosingGroupInMap implements BiFunction, + BiConsumer { + final BiFunction> getEnclosingMap; final MapProperty enclosingMap; - final ConfigurationInterface enclosingGroup; + final ConfigMappingInterface enclosingGroup; private final GroupProperty enclosedGroup; - GetOrCreateEnclosingGroupInMap(final BiFunction> getEnclosingMap, - final MapProperty enclosingMap, final ConfigurationInterface enclosingGroup, + GetOrCreateEnclosingGroupInMap(final BiFunction> getEnclosingMap, + final MapProperty enclosingMap, final ConfigMappingInterface enclosingGroup, final GroupProperty enclosedGroup) { this.getEnclosingMap = getEnclosingMap; this.enclosingMap = enclosingMap; @@ -497,47 +494,48 @@ static class GetOrCreateEnclosingGroupInMap implements BiFunction ourEnclosing = getEnclosingMap.apply(context, ni); ni.next(); String mapKey = ni.getPreviousSegment(); Converter keyConverter = context.getKeyConverter(enclosingGroup.getInterfaceType(), enclosingMap.getMethod().getName(), enclosingMap.getLevels() - 1); - ConfigurationObject val = (ConfigurationObject) ourEnclosing.get(mapKey); + ConfigMappingObject val = (ConfigMappingObject) ourEnclosing.get(mapKey); if (val == null) { StringBuilder sb = context.getStringBuilder(); sb.replace(0, sb.length(), ni.getAllPreviousSegments()); Object convertedKey = keyConverter.convert(mapKey); ((Map) ourEnclosing).put(convertedKey, - val = (ConfigurationObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType())); + val = (ConfigMappingObject) context.constructGroup(enclosedGroup.getGroupType().getInterfaceType())); } return val; } - public void accept(final MappingContext context, final NameIterator ni) { + public void accept(final ConfigMappingContext context, final NameIterator ni) { apply(context, ni); } } - static class GetOrCreateEnclosingMapInGroup implements BiFunction>, - BiConsumer { - final BiFunction getEnclosingGroup; - final ConfigurationInterface enclosingGroup; + static class GetOrCreateEnclosingMapInGroup implements BiFunction>, + BiConsumer { + final BiFunction getEnclosingGroup; + final ConfigMappingInterface enclosingGroup; final MapProperty property; - GetOrCreateEnclosingMapInGroup(final BiFunction getEnclosingGroup, - final ConfigurationInterface enclosingGroup, final MapProperty property) { + GetOrCreateEnclosingMapInGroup( + final BiFunction getEnclosingGroup, + final ConfigMappingInterface enclosingGroup, final MapProperty property) { this.getEnclosingGroup = getEnclosingGroup; this.enclosingGroup = enclosingGroup; this.property = property; } - public Map apply(final MappingContext context, final NameIterator ni) { + public Map apply(final ConfigMappingContext context, final NameIterator ni) { boolean consumeName = !property.isParentPropertyName(); if (consumeName) ni.previous(); - ConfigurationObject ourEnclosing = getEnclosingGroup.apply(context, ni); + ConfigMappingObject ourEnclosing = getEnclosingGroup.apply(context, ni); if (consumeName) ni.next(); Class enclosingType = enclosingGroup.getInterfaceType(); @@ -551,33 +549,34 @@ static class GetOrCreateEnclosingMapInGroup implements BiFunction { - private final BiFunction getEnclosingFunction; + static class GetFieldOfEnclosing implements BiFunction { + private final BiFunction getEnclosingFunction; private final Class type; private final String memberName; - GetFieldOfEnclosing(final BiFunction getEnclosingFunction, + GetFieldOfEnclosing(final BiFunction getEnclosingFunction, final Class type, final String memberName) { this.getEnclosingFunction = getEnclosingFunction; this.type = type; this.memberName = memberName; } - public ConfigurationObject apply(final MappingContext mc, final NameIterator ni) { - ConfigurationObject outer = getEnclosingFunction.apply(mc, ni); + public ConfigMappingObject apply(final ConfigMappingContext mc, final NameIterator ni) { + ConfigMappingObject outer = getEnclosingFunction.apply(mc, ni); // eagerly populated groups will always exist - return (ConfigurationObject) mc.getEnclosedField(type, memberName, outer); + return (ConfigMappingObject) mc.getEnclosedField(type, memberName, outer); } } @SuppressWarnings({ "unchecked", "rawtypes" }) - static Map getOrCreateEnclosingMapInMap(MappingContext context, NameIterator ni, - BiFunction> getEnclosingMap, ConfigurationInterface enclosingGroup, + static Map getOrCreateEnclosingMapInMap( + ConfigMappingContext context, NameIterator ni, + BiFunction> getEnclosingMap, ConfigMappingInterface enclosingGroup, MapProperty property) { ni.previous(); Map ourEnclosing = getEnclosingMap.apply(context, ni); @@ -608,16 +607,16 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigValidationExc } Assert.checkNotNullParam("config", config); - final MappingContext context = new MappingContext(config); + final ConfigMappingContext context = new ConfigMappingContext(config); // eagerly populate roots - for (Map.Entry> entry : roots.entrySet()) { + for (Map.Entry> entry : roots.entrySet()) { String path = entry.getKey(); - List roots = entry.getValue(); - for (ConfigurationInterface root : roots) { + List roots = entry.getValue(); + for (ConfigMappingInterface root : roots) { StringBuilder sb = context.getStringBuilder(); sb.replace(0, sb.length(), path); Class type = root.getInterfaceType(); - ConfigurationObject group = (ConfigurationObject) context.constructGroup(type); + ConfigMappingObject group = (ConfigMappingObject) context.constructGroup(type); context.registerRoot(type, path, group); } } @@ -629,7 +628,7 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigValidationExc } NameIterator ni = new NameIterator(name); - BiConsumer action = matchActions.findRootValue(ni); + BiConsumer action = matchActions.findRootValue(ni); if (action != null) { action.accept(context, ni); } else { @@ -656,7 +655,7 @@ private boolean isPropertyInRoot(String propertyName) { } public static final class Builder { - final Map> roots = new HashMap<>(); + final Map> roots = new HashMap<>(); final List ignored = new ArrayList<>(); Builder() { @@ -667,7 +666,7 @@ public Builder addRoot(String path, Class type) { return addRoot(path, getConfigurationInterface(type)); } - public Builder addRoot(String path, ConfigurationInterface info) { + public Builder addRoot(String path, ConfigMappingInterface info) { Assert.checkNotNullParam("path", path); Assert.checkNotNullParam("info", info); roots.computeIfAbsent(path, k -> new ArrayList<>(4)).add(info); @@ -686,9 +685,9 @@ public ConfigMappingProvider build() { } public static final class Result { - private final Map, Map> rootsMap; + private final Map, Map> rootsMap; - Result(final Map, Map> rootsMap) { + Result(final Map, Map> rootsMap) { this.rootsMap = Collections.unmodifiableMap(rootsMap); } @@ -696,7 +695,7 @@ public T getConfigRoot(String path, Class type) { return type.cast(rootsMap.getOrDefault(type, Collections.emptyMap()).get(path)); } - public Map, Map> getRootsMap() { + public Map, Map> getRootsMap() { return rootsMap; } } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index a3242f731..6e2b52085 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -45,7 +45,6 @@ import io.smallrye.common.annotation.Experimental; import io.smallrye.config.SmallRyeConfigBuilder.InterceptorWithPriority; -import io.smallrye.config.mapper.ConfigurationObject; /** * @author Jeff Mesnil (c) 2017 Red Hat inc. @@ -57,7 +56,7 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final Map, Map> mappings = new HashMap<>(); + private final Map, Map> mappings = new HashMap<>(); SmallRyeConfig(SmallRyeConfigBuilder builder) { this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); @@ -204,7 +203,7 @@ public > Optional getOptionalValues(String name, C } @Experimental("TODO") - void registerValidMappings(Map, Map> mappings) { + void registerValidMappings(Map, Map> mappings) { this.mappings.putAll(mappings); } diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 32a56a9fd..457312e36 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -40,8 +40,6 @@ import org.eclipse.microprofile.config.spi.ConfigSourceProvider; import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.mapper.ConfigMappingProvider; - /** * @author Jeff Mesnil (c) 2017 Red Hat inc. */ diff --git a/implementation/src/main/java/io/smallrye/config/mapper/WithParentName.java b/implementation/src/main/java/io/smallrye/config/WithParentName.java similarity index 91% rename from implementation/src/main/java/io/smallrye/config/mapper/WithParentName.java rename to implementation/src/main/java/io/smallrye/config/WithParentName.java index 40e855f28..a0769c30d 100644 --- a/implementation/src/main/java/io/smallrye/config/mapper/WithParentName.java +++ b/implementation/src/main/java/io/smallrye/config/WithParentName.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java b/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java deleted file mode 100644 index a0c26db49..000000000 --- a/implementation/src/main/java/io/smallrye/config/mapper/ConfigurationObject.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.smallrye.config.mapper; - -/** - * An interface implemented internally by configuration object implementations. - */ -public interface ConfigurationObject { - void fillInOptionals(MappingContext context); -} diff --git a/implementation/src/main/java/io/smallrye/config/mapper/Debugging.java b/implementation/src/main/java/io/smallrye/config/mapper/Debugging.java deleted file mode 100644 index a5299ebe0..000000000 --- a/implementation/src/main/java/io/smallrye/config/mapper/Debugging.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.smallrye.config.mapper; - -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -/** - * - */ -final class Debugging { - static StackTraceElement getCaller() { - return new Throwable().getStackTrace()[2]; - } - - static final class MethodVisitorImpl extends MethodVisitor { - - MethodVisitorImpl(final int api) { - super(api); - } - - MethodVisitorImpl(final int api, final MethodVisitor methodVisitor) { - super(api, methodVisitor); - } - - public void visitInsn(final int opcode) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitInsn(opcode); - } - - public void visitIntInsn(final int opcode, final int operand) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitIntInsn(opcode, operand); - } - - public void visitVarInsn(final int opcode, final int var) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitVarInsn(opcode, var); - } - - public void visitTypeInsn(final int opcode, final String type) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitTypeInsn(opcode, type); - } - - public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitFieldInsn(opcode, owner, name, descriptor); - } - - public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitMethodInsn(opcode, owner, name, descriptor); - } - - public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, - final boolean isInterface) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); - } - - public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, - final Object... bootstrapMethodArguments) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); - } - - public void visitJumpInsn(final int opcode, final Label label) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitJumpInsn(opcode, label); - } - - public void visitLdcInsn(final Object value) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitLdcInsn(value); - } - - public void visitIincInsn(final int var, final int increment) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitIincInsn(var, increment); - } - - public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitTableSwitchInsn(min, max, dflt, labels); - } - - public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitLookupSwitchInsn(dflt, keys, labels); - } - - public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { - Label l = new Label(); - visitLabel(l); - visitLineNumber(getCaller().getLineNumber(), l); - super.visitMultiANewArrayInsn(descriptor, numDimensions); - } - } - - static final class ClassVisitorImpl extends ClassVisitor { - - final String sourceFile; - - ClassVisitorImpl(final int api) { - super(api); - sourceFile = getCaller().getFileName(); - } - - ClassVisitorImpl(final ClassWriter cw) { - super(Opcodes.ASM7, cw); - sourceFile = getCaller().getFileName(); - } - - public void visitSource(final String source, final String debug) { - super.visitSource(sourceFile, debug); - } - - public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, - final String[] exceptions) { - return new MethodVisitorImpl(api, super.visitMethod(access, name, descriptor, signature, exceptions)); - } - } -} diff --git a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java similarity index 98% rename from implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java rename to implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java index 0ad294932..8c4e1a51a 100644 --- a/implementation/src/test/java/io/smallrye/config/mapper/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java @@ -1,4 +1,4 @@ -package io.smallrye.config.mapper; +package io.smallrye.config; import static io.smallrye.config.KeyValuesConfigSource.config; import static java.util.stream.Collectors.toList; @@ -20,11 +20,6 @@ import org.eclipse.microprofile.config.spi.Converter; import org.junit.jupiter.api.Test; -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.common.MapBackedConfigSource; public class ConfigMappingProviderTest { From 7d19772c55338cb41cfa8eb637b23dcfbf94e769 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 23 Jul 2020 23:40:25 +0100 Subject: [PATCH 36/42] Better encapsulation of ConfigMapping data and SmallRyeConfig. --- .../config/ConfigMappingProvider.java | 23 ++---------- .../io/smallrye/config/ConfigMappings.java | 36 +++++++++++++++++++ .../io/smallrye/config/SmallRyeConfig.java | 12 +++---- .../config/SmallRyeConfigBuilder.java | 4 +-- .../config/ConfigMappingProviderTest.java | 16 ++++++--- 5 files changed, 57 insertions(+), 34 deletions(-) create mode 100644 implementation/src/main/java/io/smallrye/config/ConfigMappings.java diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index 3d66b4969..697406470 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -12,7 +12,6 @@ import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -601,9 +600,9 @@ public KeyMap getDefaultValues() { return defaultValues; } - public Result mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { + public ConfigMappings mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { if (roots.isEmpty()) { - return new Result(new HashMap<>()); + return new ConfigMappings(new HashMap<>()); } Assert.checkNotNullParam("config", config); @@ -641,7 +640,7 @@ public Result mapConfiguration(SmallRyeConfig config) throws ConfigValidationExc problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS)); } context.fillInOptionals(); - return new Result(context.getRootsMap()); + return new ConfigMappings(context.getRootsMap()); } private boolean isPropertyInRoot(String propertyName) { @@ -683,20 +682,4 @@ public ConfigMappingProvider build() { return new ConfigMappingProvider(this); } } - - public static final class Result { - private final Map, Map> rootsMap; - - Result(final Map, Map> rootsMap) { - this.rootsMap = Collections.unmodifiableMap(rootsMap); - } - - public T getConfigRoot(String path, Class type) { - return type.cast(rootsMap.getOrDefault(type, Collections.emptyMap()).get(path)); - } - - public Map, Map> getRootsMap() { - return rootsMap; - } - } } diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappings.java b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java new file mode 100644 index 000000000..c25103d9d --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java @@ -0,0 +1,36 @@ +package io.smallrye.config; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class ConfigMappings implements Serializable { + private static final long serialVersionUID = -7790784345796818526L; + + private final Map, Map> mappings; + + public ConfigMappings() { + this(new HashMap<>()); + } + + ConfigMappings(final Map, Map> mappings) { + this.mappings = mappings; + } + + void registerConfigMappings(Map, Map> mappings) { + this.mappings.putAll(mappings); + } + + public Map, Map> getMappings() { + return mappings; + } + + public T getConfigMapping(Class type, String prefix) { + final ConfigMappingObject configMappingObject = mappings.getOrDefault(type, Collections.emptyMap()).get(prefix); + if (configMappingObject == null) { + throw ConfigMessages.msg.mappingNotFound(type.getName(), prefix); + } + return type.cast(configMappingObject); + } +} diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 6e2b52085..43ce67bbd 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -56,7 +56,7 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final Map, Map> mappings = new HashMap<>(); + private final ConfigMappings mappings = new ConfigMappings(); SmallRyeConfig(SmallRyeConfigBuilder builder) { this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); @@ -203,8 +203,8 @@ public > Optional getOptionalValues(String name, C } @Experimental("TODO") - void registerValidMappings(Map, Map> mappings) { - this.mappings.putAll(mappings); + void registerMappings(Map, Map> mappings) { + this.mappings.registerConfigMappings(mappings); } @Experimental("TODO") @@ -214,11 +214,7 @@ public T getConfigMapping(Class type) { @Experimental("TODO") public T getConfigMapping(Class type, String prefix) { - final T configRoot = type.cast(mappings.getOrDefault(type, Collections.emptyMap()).get(prefix)); - if (configRoot == null) { - throw ConfigMessages.msg.mappingNotFound(type.getName(), prefix); - } - return configRoot; + return mappings.getConfigMapping(type, prefix); } @Override diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 457312e36..25d94a703 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -351,8 +351,8 @@ public int getOrdinal() { final SmallRyeConfig config = new SmallRyeConfig(this); try { - final ConfigMappingProvider.Result result = mappingProvider.mapConfiguration(config); - config.registerValidMappings(result.getRootsMap()); + final ConfigMappings result = mappingProvider.mapConfiguration(config); + config.registerMappings(result.getMappings()); return config; } catch (ConfigValidationException e) { throw new IllegalStateException(e); diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java index 8c4e1a51a..a59fee94b 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingProviderTest.java @@ -41,6 +41,14 @@ void noConfigMapping() { "Could not find a mapping for " + Server.class.getName() + " with prefix server"); } + @Test + void unregisteredConfigMapping() { + final SmallRyeConfig config = new SmallRyeConfigBuilder() + .withSources(config("host", "localhost", "port", "8080")).build(); + assertThrows(NoSuchElementException.class, () -> config.getConfigMapping(Server.class), + "Could not find a mapping for " + Server.class.getName() + "with prefix"); + } + @Test void noPrefix() { final SmallRyeConfig config = new SmallRyeConfigBuilder() @@ -58,7 +66,7 @@ void configMappingBuilder() throws Exception { final SmallRyeConfig config = new SmallRyeConfigBuilder().withSources( config("server.host", "localhost", "server.port", "8080", "server.name", "name")).build(); - final Server server = configMappingProvider.mapConfiguration(config).getConfigRoot("server", Server.class); + final Server server = configMappingProvider.mapConfiguration(config).getConfigMapping(Server.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); } @@ -121,12 +129,12 @@ void splitRoots() throws Exception { .addRoot("server", SplitRootServerName.class) .build(); - final ConfigMappingProvider.Result result = configMappingProvider.mapConfiguration(config); - final SplitRootServerHostAndPort server = result.getConfigRoot("server", SplitRootServerHostAndPort.class); + final ConfigMappings result = configMappingProvider.mapConfiguration(config); + final SplitRootServerHostAndPort server = result.getConfigMapping(SplitRootServerHostAndPort.class, "server"); assertEquals("localhost", server.host()); assertEquals(8080, server.port()); - final SplitRootServerName name = result.getConfigRoot("server", SplitRootServerName.class); + final SplitRootServerName name = result.getConfigMapping(SplitRootServerName.class, "server"); assertEquals("konoha", name.name()); } From c7f122ce0c8350ab512e1f1a8d4a5250bd756277 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 24 Jul 2020 12:15:56 +0100 Subject: [PATCH 37/42] Fix visibilities if generated class is from different package. --- .../java/io/smallrye/config/ConfigMappingContext.java | 2 +- .../io/smallrye/config/ConfigMappingInterface.java | 11 +++++++---- .../java/io/smallrye/config/ConfigMappingObject.java | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java index cc1bdfe09..d2484805a 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java @@ -28,7 +28,7 @@ * A mapping context. This is used by generated classes during configuration mapping, and is released once the configuration * mapping has completed. */ -final class ConfigMappingContext { +public final class ConfigMappingContext { private final Map, Map>> enclosedThings = new IdentityHashMap<>(); private final Map, Map> roots = new IdentityHashMap<>(); diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index f38f5f53f..9baf7c044 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -847,10 +847,13 @@ private void restoreLength(final MethodVisitor ctor) { Class createConfigurationObjectClass() { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - String internalName = getInternalName(interfaceType); ClassVisitor visitor = usefulDebugInfo ? new Debugging.ClassVisitorImpl(writer) : writer; - visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, getInternalName(getClass()), null, I_OBJECT, - new String[] { I_CONFIGURATION_OBJECT, internalName }); + String interfaceName = getInternalName(interfaceType); + String className = interfaceName + "Impl"; + visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, I_OBJECT, new String[] { + I_CONFIGURATION_OBJECT, + interfaceName + }); visitor.visitSource(null, null); MethodVisitor ctor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); ctor.visitParameter("context", Opcodes.ACC_FINAL); @@ -893,7 +896,7 @@ Class createConfigurationObjectClass() { // stack: sb fio.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); // stack: - - addProperties(visitor, getInternalName(getClass()), ctor, fio, new HashSet<>()); + addProperties(visitor, className, ctor, fio, new HashSet<>()); // stack: - fio.visitInsn(Opcodes.RETURN); fio.visitLabel(fioEnd); diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java index 7aa4897fd..daa710847 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingObject.java @@ -3,6 +3,6 @@ /** * An interface implemented internally by configuration object implementations. */ -interface ConfigMappingObject { +public interface ConfigMappingObject { void fillInOptionals(ConfigMappingContext context); } From a9e0ad392335bf8b24e5b771945bd36dea7fe039 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 24 Jul 2020 12:22:41 +0100 Subject: [PATCH 38/42] Initial CDI support for ConfigMapping. --- .../io/smallrye/config/ConfigMapping.java | 14 ++++++++ .../config/inject/ConfigExtension.java | 27 ++++++++++++++++ .../inject/ConfigMappingInjectionTest.java | 32 +++++++++++++++++++ .../inject/InjectionTestConfigFactory.java | 11 +++++++ 4 files changed, 84 insertions(+) create mode 100644 implementation/src/main/java/io/smallrye/config/ConfigMapping.java create mode 100644 implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMapping.java b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java new file mode 100644 index 000000000..a3a55c1bb --- /dev/null +++ b/implementation/src/main/java/io/smallrye/config/ConfigMapping.java @@ -0,0 +1,14 @@ +package io.smallrye.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigMapping { + String value(); +} diff --git a/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java b/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java index 2937880d5..38397f9f7 100644 --- a/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java +++ b/implementation/src/main/java/io/smallrye/config/inject/ConfigExtension.java @@ -29,7 +29,9 @@ import java.util.function.Supplier; import java.util.stream.StreamSupport; +import javax.enterprise.context.Dependent; import javax.enterprise.event.Observes; +import javax.enterprise.inject.Default; import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.AfterBeanDiscovery; import javax.enterprise.inject.spi.AfterDeploymentValidation; @@ -38,7 +40,9 @@ import javax.enterprise.inject.spi.BeforeBeanDiscovery; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.inject.spi.ProcessInjectionPoint; +import javax.enterprise.inject.spi.WithAnnotations; import javax.inject.Provider; import org.eclipse.microprofile.config.Config; @@ -46,6 +50,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.config.spi.Converter; +import io.smallrye.config.ConfigMapping; import io.smallrye.config.ConfigValue; import io.smallrye.config.SecretKeys; import io.smallrye.config.SmallRyeConfig; @@ -57,6 +62,7 @@ */ public class ConfigExtension implements Extension { private final Set injectionPoints = new HashSet<>(); + private final Set> configMappings = new HashSet<>(); public ConfigExtension() { } @@ -66,6 +72,11 @@ protected void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManage bbd.addAnnotatedType(configBean, ConfigProducer.class.getName()); } + protected void processConfigMappings( + @Observes @WithAnnotations({ ConfigMapping.class }) ProcessAnnotatedType processAnnotatedType) { + configMappings.add(processAnnotatedType.getAnnotatedType()); + } + protected void collectConfigPropertyInjectionPoints(@Observes ProcessInjectionPoint pip) { if (pip.getInjectionPoint().getAnnotated().isAnnotationPresent(ConfigProperty.class)) { injectionPoints.add(pip.getInjectionPoint()); @@ -96,6 +107,22 @@ protected void registerCustomBeans(@Observes AfterBeanDiscovery abd, BeanManager for (Class customType : customTypes) { abd.addBean(new ConfigInjectionBean<>(bm, customType)); } + + SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(getContextClassLoader()); + for (AnnotatedType configMapping : configMappings) { + abd.addBean() + .id(configMapping.getJavaClass().toString()) + .beanClass(configMapping.getJavaClass()) + .types(configMapping.getJavaClass()) + .qualifiers(Default.Literal.INSTANCE) + .scope(Dependent.class) + .createWith(creationalContext -> { + String prefix = Optional.ofNullable(configMapping.getAnnotation(ConfigMapping.class)) + .map(ConfigMapping::value) + .orElse(""); + return config.getConfigMapping(configMapping.getJavaClass(), prefix); + }); + } } protected void validate(@Observes AfterDeploymentValidation adv) { diff --git a/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java b/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java new file mode 100644 index 000000000..c18cc6604 --- /dev/null +++ b/implementation/src/test/java/io/smallrye/config/inject/ConfigMappingInjectionTest.java @@ -0,0 +1,32 @@ +package io.smallrye.config.inject; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import javax.inject.Inject; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.smallrye.config.inject.InjectionTestConfigFactory.Server; + +@ExtendWith(WeldJunit5Extension.class) +public class ConfigMappingInjectionTest extends InjectionTest { + @WeldSetup + public WeldInitiator weld = WeldInitiator.from(ConfigExtension.class, Server.class) + .inject(this) + .build(); + + @Inject + Server server; + + @Test + void configMapping() { + assertNotNull(server); + assertEquals("localhost", server.host()); + assertEquals(8080, server.port()); + } +} diff --git a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java index dbac8b7b2..d9a8a89ba 100644 --- a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java +++ b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java @@ -5,6 +5,7 @@ import org.eclipse.microprofile.config.spi.ConfigSource; +import io.smallrye.config.ConfigMapping; import io.smallrye.config.KeyValuesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigFactory; @@ -39,6 +40,16 @@ public String getName() { .withSources(KeyValuesConfigSource.config("optional.int.value", "1", "optional.long.value", "2", "optional.double.value", "3.3")) .withSecretKeys("secret") + .withMapping(Server.class, "server") + .withDefaultValue("server.host", "localhost") + .withDefaultValue("server.port", "8080") .build(); } + + @ConfigMapping("server") + interface Server { + String host(); + + int port(); + } } From 5ee3783ab47eedd970d6abc5d34506808919e121 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 24 Jul 2020 17:05:16 +0100 Subject: [PATCH 39/42] Generate classes in the mapper package. --- .../smallrye/config/ConfigMappingInterface.java | 16 ++++++++++------ .../inject/InjectionTestConfigFactory.java | 3 ++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index 9baf7c044..425f06d00 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -848,11 +848,15 @@ private void restoreLength(final MethodVisitor ctor) { Class createConfigurationObjectClass() { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = usefulDebugInfo ? new Debugging.ClassVisitorImpl(writer) : writer; - String interfaceName = getInternalName(interfaceType); - String className = interfaceName + "Impl"; - visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, I_OBJECT, new String[] { + + String interfacePackage = interfaceType.getPackage().getName(); + String className = getClass().getPackage().getName() + "." + interfaceType.getSimpleName() + + interfacePackage.hashCode() + "Impl"; + String classInternalName = className.replace('.', '/'); + + visitor.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, classInternalName, null, I_OBJECT, new String[] { I_CONFIGURATION_OBJECT, - interfaceName + getInternalName(interfaceType) }); visitor.visitSource(null, null); MethodVisitor ctor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + I_MAPPING_CONTEXT + ";)V", null, null); @@ -896,7 +900,7 @@ Class createConfigurationObjectClass() { // stack: sb fio.visitVarInsn(Opcodes.ASTORE, V_STRING_BUILDER); // stack: - - addProperties(visitor, className, ctor, fio, new HashSet<>()); + addProperties(visitor, classInternalName, ctor, fio, new HashSet<>()); // stack: - fio.visitInsn(Opcodes.RETURN); fio.visitLabel(fioEnd); @@ -914,7 +918,7 @@ Class createConfigurationObjectClass() { visitor.visitEnd(); // todo: MR JAR/JDKSpecific - return unsafe.defineAnonymousClass(usefulDebugInfo ? getClass() : interfaceType, writer.toByteArray(), null); + return unsafe.defineAnonymousClass(getClass(), writer.toByteArray(), null); } private static ConfigMappingInterface[] getSuperTypes(Class[] interfaces, int si, int ti) { diff --git a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java index d9a8a89ba..96b6c5d44 100644 --- a/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java +++ b/implementation/src/test/java/io/smallrye/config/inject/InjectionTestConfigFactory.java @@ -47,7 +47,8 @@ public String getName() { } @ConfigMapping("server") - interface Server { + // TODO - radcortez - Add validation that interface has to be public. + public interface Server { String host(); int port(); From 547eceae96ce13632f880b1bdcd25021a640be1c Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 30 Jul 2020 18:49:28 +0100 Subject: [PATCH 40/42] Use ClassDefiner from SmallRye Commons Classloader instead of Unsafe. --- implementation/pom.xml | 4 ++++ .../config/ConfigMappingInterface.java | 22 +++---------------- pom.xml | 5 +++++ 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/implementation/pom.xml b/implementation/pom.xml index 22a2de3a4..a77e7180b 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -61,6 +61,10 @@ io.smallrye.common smallrye-common-constraint + + io.smallrye.common + smallrye-common-classloader + org.jboss.logging diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index 425f06d00..41f21e448 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -5,11 +5,11 @@ import static org.objectweb.asm.Type.getInternalName; import static org.objectweb.asm.Type.getType; +import java.lang.invoke.MethodHandles; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Array; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -33,9 +33,9 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import io.smallrye.common.classloader.ClassDefiner; import io.smallrye.common.constraint.Assert; import io.smallrye.config.inject.InjectionMessages; -import sun.misc.Unsafe; /** * Information about a configuration interface. @@ -49,25 +49,10 @@ protected ConfigMappingInterface computeValue(final Class type) { } }; static final boolean usefulDebugInfo; - static final Unsafe unsafe; static { usefulDebugInfo = Boolean.parseBoolean(AccessController.doPrivileged( (PrivilegedAction) () -> System.getProperty("io.smallrye.config.mapper.useful-debug-info"))); - - unsafe = AccessController.doPrivileged(new PrivilegedAction() { - public Unsafe run() { - try { - final Field field = Unsafe.class.getDeclaredField("theUnsafe"); - field.setAccessible(true); - return (Unsafe) field.get(null); - } catch (IllegalAccessException e) { - throw new IllegalAccessError(e.getMessage()); - } catch (NoSuchFieldException e) { - throw new NoSuchFieldError(e.getMessage()); - } - } - }); } private final Class interfaceType; @@ -917,8 +902,7 @@ Class createConfigurationObjectClass() { ctor.visitMaxs(0, 0); visitor.visitEnd(); - // todo: MR JAR/JDKSpecific - return unsafe.defineAnonymousClass(getClass(), writer.toByteArray(), null); + return ClassDefiner.defineClass(MethodHandles.lookup(), getClass(), className, writer.toByteArray()); } private static ConfigMappingInterface[] getSuperTypes(Class[] interfaces, int si, int ti) { diff --git a/pom.xml b/pom.xml index fdcaa5c02..799f585c2 100644 --- a/pom.xml +++ b/pom.xml @@ -139,6 +139,11 @@ smallrye-common-constraint ${version.smallrye.common} + + io.smallrye.common + smallrye-common-classloader + ${version.smallrye.common} + From 942a132e0367a3fc4491ec904b3f5447b199273b Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Thu, 30 Jul 2020 19:17:23 +0100 Subject: [PATCH 41/42] Improve SmallRyeConfig and ConfigMappings integration. --- .../io/smallrye/config/ConfigMappingProvider.java | 12 +++++++++--- .../main/java/io/smallrye/config/ConfigMappings.java | 10 +++------- .../main/java/io/smallrye/config/SmallRyeConfig.java | 11 ++++------- .../io/smallrye/config/SmallRyeConfigBuilder.java | 9 ++++----- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index 697406470..d6018203c 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -596,11 +596,15 @@ public static Builder builder() { return new Builder(); } - public KeyMap getDefaultValues() { + KeyMap getDefaultValues() { return defaultValues; } - public ConfigMappings mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { + ConfigMappings mapConfiguration(SmallRyeConfig config) throws ConfigValidationException { + return mapConfiguration(config, new ConfigMappings()); + } + + ConfigMappings mapConfiguration(SmallRyeConfig config, ConfigMappings mappings) throws ConfigValidationException { if (roots.isEmpty()) { return new ConfigMappings(new HashMap<>()); } @@ -640,7 +644,9 @@ public ConfigMappings mapConfiguration(SmallRyeConfig config) throws ConfigValid problems.toArray(ConfigValidationException.Problem.NO_PROBLEMS)); } context.fillInOptionals(); - return new ConfigMappings(context.getRootsMap()); + + mappings.registerConfigMappings(context.getRootsMap()); + return mappings; } private boolean isPropertyInRoot(String propertyName) { diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappings.java b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java index c25103d9d..42920f7db 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappings.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappings.java @@ -5,12 +5,12 @@ import java.util.HashMap; import java.util.Map; -public final class ConfigMappings implements Serializable { +final class ConfigMappings implements Serializable { private static final long serialVersionUID = -7790784345796818526L; private final Map, Map> mappings; - public ConfigMappings() { + ConfigMappings() { this(new HashMap<>()); } @@ -22,11 +22,7 @@ void registerConfigMappings(Map, Map> mapp this.mappings.putAll(mappings); } - public Map, Map> getMappings() { - return mappings; - } - - public T getConfigMapping(Class type, String prefix) { + T getConfigMapping(Class type, String prefix) { final ConfigMappingObject configMappingObject = mappings.getOrDefault(type, Collections.emptyMap()).get(prefix); if (configMappingObject == null) { throw ConfigMessages.msg.mappingNotFound(type.getName(), prefix); diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java index 43ce67bbd..eeca7c1b8 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java @@ -56,11 +56,12 @@ public class SmallRyeConfig implements Config, Serializable { private final Map> converters; private final Map>> optionalConverters = new ConcurrentHashMap<>(); - private final ConfigMappings mappings = new ConfigMappings(); + private final ConfigMappings mappings; - SmallRyeConfig(SmallRyeConfigBuilder builder) { + SmallRyeConfig(SmallRyeConfigBuilder builder, ConfigMappings mappings) { this.configSources = new AtomicReference<>(new ConfigSources(buildConfigSources(builder), buildInterceptors(builder))); this.converters = buildConverters(builder); + this.mappings = mappings; } @Deprecated @@ -69,6 +70,7 @@ protected SmallRyeConfig(List configSources, Map(Converters.ALL_CONVERTERS); this.converters.putAll(converters); + this.mappings = new ConfigMappings(); } private List buildConfigSources(final SmallRyeConfigBuilder builder) { @@ -202,11 +204,6 @@ public > Optional getOptionalValues(String name, C return getOptionalValue(name, Converters.newCollectionConverter(converter, collectionFactory)); } - @Experimental("TODO") - void registerMappings(Map, Map> mappings) { - this.mappings.registerConfigMappings(mappings); - } - @Experimental("TODO") public T getConfigMapping(Class type) { return getConfigMapping(type, ""); diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 25d94a703..e14c9b49e 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -329,7 +329,7 @@ boolean isAddDiscoveredInterceptors() { @Override public SmallRyeConfig build() { - final ConfigMappingProvider mappingProvider = mappingsBuilder.build(); + ConfigMappingProvider mappingProvider = mappingsBuilder.build(); if (!defaultValues.isEmpty() || !mappingProvider.getDefaultValues().isEmpty()) { final KeyMap mappingProviderDefaultValues = mappingProvider.getDefaultValues(); defaultValues.forEach((key, value) -> mappingProviderDefaultValues.findOrAdd(key).putRootValue(value)); @@ -348,11 +348,10 @@ public int getOrdinal() { }); } - final SmallRyeConfig config = new SmallRyeConfig(this); - try { - final ConfigMappings result = mappingProvider.mapConfiguration(config); - config.registerMappings(result.getMappings()); + ConfigMappings configMappings = new ConfigMappings(); + SmallRyeConfig config = new SmallRyeConfig(this, configMappings); + mappingProvider.mapConfiguration(config, configMappings); return config; } catch (ConfigValidationException e) { throw new IllegalStateException(e); From 40ec717146e1748048502aa8f559e43cc4f42dcc Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Fri, 31 Jul 2020 16:18:45 +0100 Subject: [PATCH 42/42] Remove toMap from KeyMap. --- .../main/java/io/smallrye/config/KeyMap.java | 26 ------------------- .../config/KeyMapBackedConfigSource.java | 5 ++-- .../config/SmallRyeConfigBuilder.java | 15 ++--------- .../config/KeyMapBackedConfigSourceTest.java | 15 ++++------- .../java/io/smallrye/config/KeyMapTest.java | 21 --------------- 5 files changed, 10 insertions(+), 72 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/KeyMap.java b/implementation/src/main/java/io/smallrye/config/KeyMap.java index df35f6611..2ea85c7db 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMap.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMap.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; -import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; @@ -257,29 +256,4 @@ public StringBuilder toString(StringBuilder b) { public String toString() { return toString(new StringBuilder()).toString(); } - - public Map toMap() { - final Map map = new HashMap<>(); - unwrap(this, "", map); - return map; - } - - private void unwrap(KeyMap keyMap, String key, Map map) { - for (String path : keyMap.keySet()) { - final KeyMap nested = keyMap.get(path); - final String nestedKey = key.length() == 0 ? path : key + "." + path; - if (nested.any != null) { - map.put(nestedKey + ".*", nested.any.getRootValue()); - unwrap(nested.any, nestedKey + ".*", map); - } - if (!nested.hasRootValue()) { - unwrap(nested, nestedKey, map); - } else { - map.put(nestedKey, nested.getRootValue()); - if (!nested.keySet().isEmpty()) { - unwrap(nested, nestedKey, map); - } - } - } - } } diff --git a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java index 0a24d1959..b985d5f27 100644 --- a/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java +++ b/implementation/src/main/java/io/smallrye/config/KeyMapBackedConfigSource.java @@ -1,12 +1,13 @@ package io.smallrye.config; +import java.util.Collections; import java.util.Map; import org.eclipse.microprofile.config.spi.ConfigSource; import io.smallrye.config.common.AbstractConfigSource; -public abstract class KeyMapBackedConfigSource extends AbstractConfigSource { +public class KeyMapBackedConfigSource extends AbstractConfigSource { private static final long serialVersionUID = 4378754290346888762L; private final KeyMap properties; @@ -23,7 +24,7 @@ public KeyMapBackedConfigSource(final String name, final int ordinal, final KeyM @Override public Map getProperties() { - return properties.toMap(); + return Collections.emptyMap(); } @Override diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index e14c9b49e..dce4a71a6 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -333,19 +333,8 @@ public SmallRyeConfig build() { if (!defaultValues.isEmpty() || !mappingProvider.getDefaultValues().isEmpty()) { final KeyMap mappingProviderDefaultValues = mappingProvider.getDefaultValues(); defaultValues.forEach((key, value) -> mappingProviderDefaultValues.findOrAdd(key).putRootValue(value)); - withSources(new KeyMapBackedConfigSource("DefaultValuesConfigSource", mappingProviderDefaultValues) { - private static final long serialVersionUID = 7847969724478280439L; - - @Override - public Map getProperties() { - return new HashMap<>(); - } - - @Override - public int getOrdinal() { - return Integer.MIN_VALUE; - } - }); + withSources( + new KeyMapBackedConfigSource("DefaultValuesConfigSource", Integer.MIN_VALUE, mappingProviderDefaultValues)); } try { diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java index 7b8272ba6..6ce693a26 100644 --- a/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java +++ b/implementation/src/test/java/io/smallrye/config/KeyMapBackedConfigSourceTest.java @@ -8,7 +8,6 @@ import org.junit.jupiter.api.Test; class KeyMapBackedConfigSourceTest { - @Test void getProperties() { KeyMap map = new KeyMap<>(); @@ -17,12 +16,9 @@ void getProperties() { map.findOrAdd("root.foo.bar.*").putRootValue("baz"); map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); - final ConfigSource source = getSource(map); - final Map properties = source.getProperties(); - assertTrue(properties.containsKey("root.foo")); - assertTrue(properties.containsKey("root.foo.bar")); - assertTrue(properties.containsKey("root.foo.bar.*")); - assertTrue(properties.containsKey("root.foo.bar.*.baz")); + ConfigSource source = getSource(map); + Map properties = source.getProperties(); + assertTrue(properties.isEmpty()); } @Test @@ -33,7 +29,7 @@ void getValue() { map.findOrAdd("root.foo.bar.*").putRootValue("baz"); map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); - final ConfigSource source = getSource(map); + ConfigSource source = getSource(map); assertEquals("bar", source.getValue("root.foo")); assertEquals("baz", source.getValue("root.foo.bar")); assertEquals("baz", source.getValue("root.foo.bar.x")); @@ -45,7 +41,6 @@ void getValue() { } private ConfigSource getSource(final KeyMap properties) { - return new KeyMapBackedConfigSource("test", 0, properties) { - }; + return new KeyMapBackedConfigSource("test", 0, properties); } } diff --git a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java index 2e200196c..0a93065c9 100644 --- a/implementation/src/test/java/io/smallrye/config/KeyMapTest.java +++ b/implementation/src/test/java/io/smallrye/config/KeyMapTest.java @@ -3,7 +3,6 @@ import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashMap; import java.util.Map; @@ -106,26 +105,6 @@ void empty() { assertEquals("bar", map.findRootValue(".foo")); } - @Test - void unwrap() { - KeyMap map = new KeyMap<>(); - map.findOrAdd("root.foo").putRootValue("bar"); - map.findOrAdd("root.foo.bar").putRootValue("baz"); - map.findOrAdd("root.foo.bar.*").putRootValue("baz"); - map.findOrAdd("root.foo.bar.*.baz").putRootValue("anything"); - - Map properties = map.toMap(); - assertTrue(properties.containsKey("root.foo")); - assertTrue(properties.containsKey("root.foo.bar")); - assertTrue(properties.containsKey("root.foo.bar.*")); - assertTrue(properties.containsKey("root.foo.bar.*.baz")); - - assertEquals("bar", properties.get("root.foo")); - assertEquals("baz", properties.get("root.foo.bar")); - assertEquals("baz", properties.get("root.foo.bar.*")); - assertEquals("anything", properties.get("root.foo.bar.*.baz")); - } - @Test void string() { KeyMap map = new KeyMap<>();