Skip to content

Commit

Permalink
Support @WithConverter in Map key and value (smallrye#700)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez authored Jan 15, 2022
1 parent b42d663 commit 0054fb7
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.smallrye.config;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
Expand Down Expand Up @@ -644,13 +645,13 @@ static Property[] getProperties(Method[] methods, int si, int ti) {
if (method.getReturnType() == void.class) {
throw new IllegalArgumentException("Void config methods are not allowed");
}
Property p = getPropertyDef(method, method.getGenericReturnType());
Property p = getPropertyDef(method, method.getAnnotatedReturnType());
Property[] array = getProperties(methods, si + 1, ti + 1);
array[ti] = p;
return array;
}

private static Property getPropertyDef(Method method, Type type) {
private static Property getPropertyDef(Method method, AnnotatedType type) {
Method defaultMethod = hasDefaultMethodImplementation(method);
if (defaultMethod != null) {
return new DefaultMethodProperty(method, defaultMethod, getPropertyDef(defaultMethod, type));
Expand All @@ -665,7 +666,7 @@ private static Property getPropertyDef(Method method, Type type) {
}
}
String propertyName = getPropertyName(method);
Class<?> rawType = rawTypeOf(type);
Class<?> rawType = rawTypeOf(type.getType());
if (rawType.isPrimitive()) {
// primitive!
WithDefault annotation = method.getAnnotation(WithDefault.class);
Expand All @@ -683,25 +684,26 @@ private static Property getPropertyDef(Method method, Type type) {
}
if (rawType == Map.class) {
// it's a map...
Type keyType = typeOfParameter(type, 0);
AnnotatedType keyType = typeOfParameter(type, 0);
Class<? extends Converter<?>> keyConvertWith = getConvertWith(keyType);
Type valueType = typeOfParameter(type, 1);
return new MapProperty(method, propertyName, keyType, keyConvertWith, getPropertyDef(method, valueType));
AnnotatedType valueType = typeOfParameter(type, 1);
return new MapProperty(method, propertyName, keyType.getType(), keyConvertWith,
getPropertyDef(method, valueType));
}
if (rawType == List.class || rawType == Set.class) {
Type elementType = typeOfParameter(type, 0);
AnnotatedType elementType = typeOfParameter(type, 0);

if (rawTypeOf(elementType) == Map.class) {
if (rawTypeOf(elementType.getType()) == Map.class) {
return new CollectionProperty(rawType, getPropertyDef(method, elementType));
}

ConfigMappingInterface configurationInterface = getConfigurationInterface((Class<?>) elementType);
ConfigMappingInterface configurationInterface = getConfigurationInterface(rawTypeOf(elementType.getType()));
if (configurationInterface != null) {
return new CollectionProperty(rawType, new GroupProperty(method, propertyName, configurationInterface));
}

WithDefault annotation = method.getAnnotation(WithDefault.class);
return new CollectionProperty(rawType, new LeafProperty(method, propertyName, elementType, null,
return new CollectionProperty(rawType, new LeafProperty(method, propertyName, elementType.getType(), null,
annotation == null ? null : annotation.value()));
}
ConfigMappingInterface configurationInterface = getConfigurationInterface(rawType);
Expand All @@ -713,15 +715,17 @@ private static Property getPropertyDef(Method method, Type type) {
}

if (rawType == List.class || rawType == Set.class) {
Type elementType = typeOfParameter(type, 0);
Type elementType = typeOfParameter(type.getType(), 0);
WithDefault annotation = method.getAnnotation(WithDefault.class);
return new CollectionProperty(rawType,
new LeafProperty(method, propertyName, elementType, null, annotation == null ? null : annotation.value()));
new LeafProperty(method, propertyName, elementType, convertWith,
annotation == null ? null : annotation.value()));
}

// otherwise it's a leaf
WithDefault annotation = method.getAnnotation(WithDefault.class);
return new LeafProperty(method, propertyName, type, convertWith, annotation == null ? null : annotation.value());
return new LeafProperty(method, propertyName, type.getType(), convertWith,
annotation == null ? null : annotation.value());
}

@SuppressWarnings("squid:S1872")
Expand All @@ -745,14 +749,10 @@ private static Method hasDefaultMethodImplementation(Method method) {
return null;
}

private static Class<? extends Converter<?>> getConvertWith(final Type type) {
if (type instanceof AnnotatedType) {
WithConverter annotation = ((AnnotatedType) type).getAnnotation(WithConverter.class);
if (annotation != null) {
return annotation.value();
} else {
return null;
}
private static Class<? extends Converter<?>> getConvertWith(final AnnotatedType type) {
WithConverter annotation = type.getAnnotation(WithConverter.class);
if (annotation != null) {
return annotation.value();
} else {
return null;
}
Expand Down Expand Up @@ -829,6 +829,14 @@ static Type typeOfParameter(final Type type, final int index) {
}
}

static AnnotatedType typeOfParameter(final AnnotatedType type, final int index) {
if (type instanceof AnnotatedParameterizedType) {
return ((AnnotatedParameterizedType) type).getAnnotatedActualTypeArguments()[index];
} else {
return null;
}
}

static Class<?> rawTypeOf(final Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,40 +481,43 @@ private void processLazyMapInGroup(
final ArrayDeque<String> currentPath,
final KeyMap<BiConsumer<ConfigMappingContext, NameIterator>> matchActions,
final KeyMap<String> defaultValues,
final MapProperty property, BiFunction<ConfigMappingContext, NameIterator, ConfigMappingObject> getEnclosingGroup,
final MapProperty mapProperty,
final BiFunction<ConfigMappingContext, NameIterator, ConfigMappingObject> getEnclosingGroup,
final NamingStrategy namingStrategy,
final ConfigMappingInterface enclosingGroup) {

GetOrCreateEnclosingMapInGroup getEnclosingMap = new GetOrCreateEnclosingMapInGroup(getEnclosingGroup, enclosingGroup,
property, currentPath);
processLazyMap(currentPath, matchActions, defaultValues, property, getEnclosingMap, namingStrategy, enclosingGroup);
mapProperty, currentPath);
processLazyMap(currentPath, matchActions, defaultValues, mapProperty, getEnclosingMap, namingStrategy, enclosingGroup,
mapProperty.getValueProperty());
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private void processLazyMap(
final ArrayDeque<String> currentPath,
final KeyMap<BiConsumer<ConfigMappingContext, NameIterator>> matchActions,
final KeyMap<String> defaultValues,
final MapProperty property, BiFunction<ConfigMappingContext, NameIterator, Map<?, ?>> getEnclosingMap,
final MapProperty mapProperty,
final BiFunction<ConfigMappingContext, NameIterator, Map<?, ?>> getEnclosingMap,
final NamingStrategy namingStrategy,
final ConfigMappingInterface enclosingGroup) {
final ConfigMappingInterface enclosingGroup,
final Property property) {

Property valueProperty = property.getValueProperty();
Class<? extends Converter<?>> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null;
Class<?> keyRawType = property.getKeyRawType();
Class<? extends Converter<?>> keyConvertWith = mapProperty.hasKeyConvertWith() ? mapProperty.getKeyConvertWith() : null;
Class<?> keyRawType = mapProperty.getKeyRawType();

if (valueProperty.isLeaf()) {
if (property.isLeaf()) {
currentPath.addLast("*");
if (matchActions.hasRootValue(currentPath)) {
currentPath.removeLast();
return;
}

LeafProperty leafProperty = valueProperty.asLeaf();
LeafProperty leafProperty = property.asLeaf();
Class<? extends Converter<?>> valConvertWith = leafProperty.getConvertWith();
Class<?> valueRawType = leafProperty.getValueRawType();

addAction(currentPath, property, (mc, ni) -> {
addAction(currentPath, mapProperty, (mc, ni) -> {
StringBuilder sb = mc.getStringBuilder();
sb.setLength(0);
sb.append(ni.getAllPreviousSegments());
Expand All @@ -537,9 +540,9 @@ private void processLazyMap(
}
((Map) map).put(key, config.getValue(configKey, valueConv));
});
} else if (valueProperty.isMap()) {
} else if (property.isMap()) {
currentPath.addLast("*");
processLazyMap(currentPath, matchActions, defaultValues, valueProperty.asMap(), (mc, ni) -> {
processLazyMap(currentPath, matchActions, defaultValues, property.asMap(), (mc, ni) -> {
ni.previous();
Map<?, ?> enclosingMap = getEnclosingMap.apply(mc, ni);
ni.next();
Expand All @@ -553,15 +556,18 @@ private void processLazyMap(
}
Object key = keyConv.convert(rawMapKey);
return (Map) ((Map) enclosingMap).computeIfAbsent(key, x -> new HashMap<>());
}, namingStrategy, enclosingGroup);
} else {
assert valueProperty.isGroup();
GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap(getEnclosingMap, property, enclosingGroup,
valueProperty.asGroup(), String.join(".", currentPath));
}, namingStrategy, enclosingGroup, property.asMap().getValueProperty());
} else if (property.isGroup()) {
GetOrCreateEnclosingGroupInMap ef = new GetOrCreateEnclosingGroupInMap(getEnclosingMap, mapProperty, enclosingGroup,
property.asGroup(), String.join(".", currentPath));
currentPath.addLast("*");
processLazyGroupInGroup(currentPath, matchActions, defaultValues, namingStrategy,
valueProperty.asGroup().getGroupType(),
property.asGroup().getGroupType(),
ef, ef, new HashSet<>());
} else if (property.isCollection()) {
CollectionProperty collectionProperty = property.asCollection();
processLazyMap(currentPath, matchActions, defaultValues, mapProperty, getEnclosingMap, namingStrategy,
enclosingGroup, collectionProperty.getElement());
}
currentPath.removeLast();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.smallrye.config;

import static io.smallrye.config.Converters.newCollectionConverter;
import static io.smallrye.config.KeyValuesConfigSource.config;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -499,4 +501,46 @@ void mappingCollectionsMap() {
assertTrue(mapping.present().isPresent());
assertEquals("localhost", mapping.present().get().get(0).get("localhost"));
}

@ConfigMapping(prefix = "map")
public interface MapOfListWithConverter {
Map<@WithConverter(KeyConverter.class) String, @WithConverter(ListConverter.class) List<String>> list();

class KeyConverter implements Converter<String> {
@Override
public String convert(final String value) throws IllegalArgumentException, NullPointerException {
if (value.equals("one")) {
return "1";
} else if (value.equals("two")) {
return "2";
} else {
throw new IllegalArgumentException();
}
}
}

class ListConverter implements Converter<List<String>> {
static final Converter<List<String>> DELEGATE = newCollectionConverter(value -> value, ArrayList::new);

@Override
public List<String> convert(final String value) throws IllegalArgumentException, NullPointerException {
return DELEGATE.convert(value);
}
}
}

@Test
void map() {
SmallRyeConfig config = new SmallRyeConfigBuilder()
.withMapping(MapOfListWithConverter.class, "map")
.withSources(config("map.list.one", "one,1"))
.withSources(config("map.list.two", "two,2"))
.build();

MapOfListWithConverter mapping = config.getConfigMapping(MapOfListWithConverter.class);
assertEquals("one", mapping.list().get("1").get(0));
assertEquals("1", mapping.list().get("1").get(1));
assertEquals("two", mapping.list().get("2").get(0));
assertEquals("2", mapping.list().get("2").get(1));
}
}

0 comments on commit 0054fb7

Please sign in to comment.