Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a fixed list of Map keys statically @WithKeys #1220

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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