From 0054fb791cbc228ae9de8406331b176616f73ef1 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Sat, 15 Jan 2022 00:35:20 +0000 Subject: [PATCH] Support @WithConverter in Map key and value (#700) --- .../config/ConfigMappingInterface.java | 50 +++++++++++-------- .../config/ConfigMappingProvider.java | 44 +++++++++------- .../config/ConfigMappingCollectionsTest.java | 44 ++++++++++++++++ 3 files changed, 98 insertions(+), 40 deletions(-) diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java index 66e7ecef6..53de2ea75 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingInterface.java @@ -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; @@ -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)); @@ -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); @@ -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> 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); @@ -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") @@ -745,14 +749,10 @@ private static Method hasDefaultMethodImplementation(Method method) { return null; } - private static Class> 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> getConvertWith(final AnnotatedType type) { + WithConverter annotation = type.getAnnotation(WithConverter.class); + if (annotation != null) { + return annotation.value(); } else { return null; } @@ -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; diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java index 9e9ddacfe..2a3a915c9 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingProvider.java @@ -481,13 +481,15 @@ private void processLazyMapInGroup( final ArrayDeque currentPath, final KeyMap> matchActions, final KeyMap defaultValues, - final MapProperty property, BiFunction getEnclosingGroup, + final MapProperty mapProperty, + final BiFunction 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" }) @@ -495,26 +497,27 @@ private void processLazyMap( final ArrayDeque currentPath, final KeyMap> matchActions, final KeyMap defaultValues, - final MapProperty property, BiFunction> getEnclosingMap, + final MapProperty mapProperty, + final BiFunction> getEnclosingMap, final NamingStrategy namingStrategy, - final ConfigMappingInterface enclosingGroup) { + final ConfigMappingInterface enclosingGroup, + final Property property) { - Property valueProperty = property.getValueProperty(); - Class> keyConvertWith = property.hasKeyConvertWith() ? property.getKeyConvertWith() : null; - Class keyRawType = property.getKeyRawType(); + Class> 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> 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()); @@ -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(); @@ -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(); } diff --git a/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java b/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java index f0db15cbf..fbe3837b9 100644 --- a/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java +++ b/implementation/src/test/java/io/smallrye/config/ConfigMappingCollectionsTest.java @@ -1,5 +1,6 @@ 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; @@ -7,6 +8,7 @@ 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; @@ -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> list(); + + class KeyConverter implements Converter { + @Override + public String convert(final String value) throws IllegalArgumentException, NullPointerException { + if (value.equals("one")) { + return "1"; + } else if (value.equals("two")) { + return "2"; + } else { + throw new IllegalArgumentException(); + } + } + } + + class ListConverter implements Converter> { + static final Converter> DELEGATE = newCollectionConverter(value -> value, ArrayList::new); + + @Override + public List 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)); + } }