Skip to content

Commit

Permalink
Support a fixed list of Map keys statically @WithKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed Sep 16, 2024
1 parent e768190 commit 4325186
Show file tree
Hide file tree
Showing 11 changed files with 677 additions and 141 deletions.
45 changes: 45 additions & 0 deletions documentation/src/main/docs/config/mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,13 @@ keys.

A `Map` mapping is backed by an `HashMap`.

When populating a `Map`, `SmallRyeConfig` requires the configuration names listed in
`SmallRyeConfig#getPropertyNames` to find the `Map` keys. If a `ConfigSource` does not support
`getPropertyNames` (empty), the names must be provided by another `ConfigSource` that can do so. After retrieving the
map keys, `SmallRyeConfig` performs the lookup of the values with the regular `ConfigSource` ordinal ordering. Even if
a `ConfigSource` does not provide `getPropertyNames` it can provide the value by having the name listed in another
capable `ConfigSource`.

For collection types, the key requires the indexed format. The configuration name `server.aliases.localhost[0].name`
maps to the `Map<String, List<Alias>> aliases()` member, where `localhost` is the `Map` key, `[0]` is the index of the
`List<Alias>` collection where the `Alias` element will be stored, containing the name `prod`.
Expand Down Expand Up @@ -511,6 +518,44 @@ Map<String, Alias> localhost = server.aliases.get("localhost");

If the unnamed key (in this case `localhost`) is explicitly set in a property name, the mapping will throw an error.

### `@WithKeys`

The `io.smallrye.config.WithKeys` annotation allows to define which `Map` keys must be loaded by
the configuration:

```java
@ConfigMapping(prefix = "server")
public interface Server {
@WithKeys(KeysProvider.class)
Map<String, Alias> aliases();

interface Alias {
String name();
}

class KeysProvider implements Supplier<Iterable<String>> {
@Override
public Iterable<String> get() {
return List.of("dev", "test", "prod");
}
}
}
```

In this case, `SmallRyeConfig` will look for the map keys `dev`, `test` and `prod` instead of discovering the keys
with `SmallRyeConfig#getPropertyNames`:

```properties
servers.alias.dev.name=dev
servers.alias.test.name=test
servers.alias.prod.name=prod
```

The provided list will effectively substitute the lookup in `SmallRyeConfig#getPropertyNames`, thus enabling a
`ConfigSource` that does not list its properties, to contribute configuration to the `Map`. Each key must exist in
the final configuration (relative to the `Map` path segment), or the mapping will fail with a
`ConfigValidationException`.

### `@WithDefaults`

The `io.smallrye.config.WithDefaults` is a marker annotation to use only in a `Map` to return the default value for
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import org.eclipse.microprofile.config.inject.ConfigProperties;
Expand Down Expand Up @@ -94,15 +95,16 @@ public class ConfigMappingGenerator {
}

private static final String I_CLASS = getInternalName(Class.class);
private static final String I_FIELD = getInternalName(Field.class);
private static final String I_CONFIGURATION_OBJECT = getInternalName(ConfigMappingObject.class);
private static final String I_MAPPING_CONTEXT = getInternalName(ConfigMappingContext.class);
private static final String I_NAMING_STRATEGY = getInternalName(NamingStrategy.class);
private static final String I_OBJECT_CREATOR = getInternalName(ConfigMappingContext.ObjectCreator.class);
private static final String I_OBJECT = getInternalName(Object.class);
private static final String I_RUNTIME_EXCEPTION = getInternalName(RuntimeException.class);
private static final String I_STRING_BUILDER = getInternalName(StringBuilder.class);
private static final String I_STRING = getInternalName(String.class);
private static final String I_NAMING_STRATEGY = getInternalName(NamingStrategy.class);
private static final String I_FIELD = getInternalName(Field.class);
private static final String I_ITERABLE = getInternalName(Iterable.class);

private static final int V_THIS = 0;
private static final int V_MAPPING_CONTEXT = 1;
Expand Down Expand Up @@ -519,13 +521,18 @@ private static void generateProperty(final MethodVisitor ctor, final Property pr
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasKeyProvider()) {
generateMapKeysProvider(ctor, mapProperty.getKeysProvider());
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasDefaultValue() && mapProperty.getDefaultValue() != null) {
ctor.visitLdcInsn(mapProperty.getDefaultValue());
} else {
ctor.visitInsn(ACONST_NULL);
}
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS
+ ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false);
+ ";L" + I_CLASS + ";L" + I_ITERABLE + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false);
} else if (valueProperty.isGroup()) {
ctor.visitLdcInsn(getType(mapProperty.getKeyRawType()));
if (mapProperty.hasKeyConvertWith()) {
Expand All @@ -538,17 +545,28 @@ private static void generateProperty(final MethodVisitor ctor, final Property pr
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasKeyProvider()) {
generateMapKeysProvider(ctor, mapProperty.getKeysProvider());
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasDefaultValue()) {
ctor.visitLdcInsn(getType(valueProperty.asGroup().getGroupType().getInterfaceType()));
} else {
ctor.visitInsn(ACONST_NULL);
}
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "map",
"(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";",
"(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";L" + I_ITERABLE + ";L" + I_CLASS + ";)L"
+ I_OBJECT_CREATOR + ";",
false);
ctor.visitLdcInsn(getType(valueProperty.asGroup().getGroupType().getInterfaceType()));
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "lazyGroup",
"(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false);
if (mapProperty.hasKeyProvider()) {
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "group",
"(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false);
} else {
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "lazyGroup",
"(L" + I_CLASS + ";)L" + I_OBJECT_CREATOR + ";", false);
}
} else if (valueProperty.isCollection() && valueProperty.asCollection().getElement().isLeaf()) {
ctor.visitLdcInsn(getType(mapProperty.getKeyRawType()));
if (mapProperty.hasKeyConvertWith()) {
Expand All @@ -564,13 +582,21 @@ private static void generateProperty(final MethodVisitor ctor, final Property pr
ctor.visitInsn(ACONST_NULL);
}
ctor.visitLdcInsn(getType(mapProperty.getValueProperty().asCollection().getCollectionRawType()));
if (mapProperty.hasKeyProvider()) {
generateMapKeysProvider(ctor, mapProperty.getKeysProvider());
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasDefaultValue()) {
ctor.visitLdcInsn(mapProperty.getDefaultValue());
} else {
ctor.visitInsn(ACONST_NULL);
}
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS
+ ";L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false);
ctor.visitMethodInsn(
INVOKEVIRTUAL, I_OBJECT_CREATOR, "values", "(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_CLASS + ";L"
+ I_CLASS + ";L" + I_CLASS + ";L" + I_ITERABLE + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR
+ ";",
false);
} else {
unwrapProperty(ctor, property);
}
Expand Down Expand Up @@ -635,8 +661,14 @@ private static void unwrapProperty(final MethodVisitor ctor, final Property prop
} else {
ctor.visitInsn(ACONST_NULL);
}
if (mapProperty.hasKeyProvider()) {
generateMapKeysProvider(ctor, mapProperty.getKeysProvider());
} else {
ctor.visitInsn(ACONST_NULL);
}
ctor.visitMethodInsn(INVOKEVIRTUAL, I_OBJECT_CREATOR, "map",
"(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";)L" + I_OBJECT_CREATOR + ";", false);
"(L" + I_CLASS + ";L" + I_CLASS + ";L" + I_STRING + ";L" + I_ITERABLE + ";)L" + I_OBJECT_CREATOR + ";",
false);
generateProperty(ctor, mapProperty.getValueProperty());
} else if (property.isCollection()) {
CollectionProperty collectionProperty = property.asCollection();
Expand All @@ -649,6 +681,15 @@ private static void unwrapProperty(final MethodVisitor ctor, final Property prop
}
}

private static void generateMapKeysProvider(final MethodVisitor ctor,
final Class<? extends Supplier<Iterable<String>>> mapKeysProvider) {
String provider = getInternalName(mapKeysProvider);
ctor.visitTypeInsn(NEW, provider);
ctor.visitInsn(DUP);
ctor.visitMethodInsn(INVOKESPECIAL, provider, "<init>", "()V", false);
ctor.visitMethodInsn(INVOKEVIRTUAL, provider, "get", "()L" + I_ITERABLE + ";", false);
}

private static void appendPropertyName(final MethodVisitor ctor, final Property property) {
if (property.isParentPropertyName()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

import org.eclipse.microprofile.config.spi.Converter;

Expand All @@ -32,7 +33,7 @@
public final class ConfigMappingInterface implements ConfigMappingMetadata {
static final ConfigMappingInterface[] NO_TYPES = new ConfigMappingInterface[0];
static final Property[] NO_PROPERTIES = new Property[0];
static final ClassValue<ConfigMappingInterface> cv = new ClassValue<ConfigMappingInterface>() {
static final ClassValue<ConfigMappingInterface> cv = new ClassValue<>() {
protected ConfigMappingInterface computeValue(final Class<?> type) {
return createConfigurationInterface(type);
}
Expand Down Expand Up @@ -594,6 +595,7 @@ public LeafProperty asLeaf() {
public static final class MapProperty extends Property {
private final Type keyType;
private final String keyUnnamed;
private final Class<? extends Supplier<Iterable<String>>> keysProvider;
private final Class<? extends Converter<?>> keyConvertWith;
private final Property valueProperty;
private final boolean hasDefault;
Expand All @@ -604,6 +606,7 @@ public static final class MapProperty extends Property {
final String propertyName,
final Type keyType,
final String keyUnnamed,
final Class<? extends Supplier<Iterable<String>>> keysProvider,
final Class<? extends Converter<?>> keyConvertWith,
final Property valueProperty,
final boolean hasDefault,
Expand All @@ -612,6 +615,7 @@ public static final class MapProperty extends Property {
super(method, propertyName);
this.keyType = keyType;
this.keyUnnamed = keyUnnamed;
this.keysProvider = keysProvider;
this.keyConvertWith = keyConvertWith;
this.valueProperty = valueProperty;
this.hasDefault = hasDefault;
Expand All @@ -634,6 +638,14 @@ public boolean hasKeyUnnamed() {
return keyUnnamed != null;
}

public Class<? extends Supplier<Iterable<String>>> getKeysProvider() {
return Assert.checkNotNullParam("keyProvider", keysProvider);
}

public boolean hasKeyProvider() {
return keysProvider != null;
}

public Class<? extends Converter<?>> getKeyConvertWith() {
return Assert.checkNotNullParam("keyConvertWith", keyConvertWith);
}
Expand Down Expand Up @@ -846,9 +858,14 @@ private static Property getPropertyDef(Method method, AnnotatedType type) {
AnnotatedType keyType = typeOfParameter(type, 0);
AnnotatedType valueType = typeOfParameter(type, 1);
String defaultValue = getDefaultValue(method);
return new MapProperty(method, propertyName, keyType.getType(), getUnnamedKey(keyType, method),
getConverter(keyType, method), getPropertyDef(method, valueType),
defaultValue != null || hasDefaults(method), defaultValue);
return new MapProperty(method,
propertyName, keyType.getType(),
getUnnamedKey(keyType, method),
getKeysProvider(keyType, method),
getConverter(keyType, method),
getPropertyDef(method, valueType),
defaultValue != null || hasDefaults(method),
defaultValue);
}
if (rawType == List.class || rawType == Set.class) {
AnnotatedType elementType = typeOfParameter(type, 0);
Expand Down Expand Up @@ -940,6 +957,14 @@ private static String getUnnamedKey(final AnnotatedType type, final Method metho
return annotation != null ? annotation.value() : null;
}

private static Class<? extends Supplier<Iterable<String>>> getKeysProvider(final AnnotatedType type, final Method method) {
WithKeys annotation = type.getAnnotation(WithKeys.class);
if (annotation == null) {
annotation = method.getAnnotation(WithKeys.class);
}
return annotation != null ? annotation.value() : null;
}

private static Class<? extends Converter<?>> getConverter(final AnnotatedType type, final Method method) {
WithConverter annotation = type.getAnnotation(WithConverter.class);
// fallback to method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,6 @@ public void next() {
pos = getNextEnd();
}

public void next(int segments) {
for (int i = 0; i < segments; i++) {
next();
}
}

public void previous() {
pos = getPreviousStart() - 1;
}
Expand Down
Loading

0 comments on commit 4325186

Please sign in to comment.