From 0547124a881f63a188a2493433ad5e6288f08764 Mon Sep 17 00:00:00 2001 From: Roberto Cortez Date: Wed, 18 Dec 2024 17:05:15 +0000 Subject: [PATCH] Generate properties names with defaults in mapping class --- .../smallrye/config/ConfigMappingContext.java | 219 +++++++----------- .../config/ConfigMappingGenerator.java | 152 +++--------- .../smallrye/config/ConfigMappingLoader.java | 58 ++--- .../smallrye/config/ConfigMappingNames.java | 114 --------- .../java/io/smallrye/config/PropertyName.java | 6 +- .../config/SmallRyeConfigBuilder.java | 23 +- .../io/smallrye/config/ObjectCreatorTest.java | 15 +- .../RelocateConfigSourceInterceptorTest.java | 41 ---- 8 files changed, 164 insertions(+), 464 deletions(-) delete mode 100644 implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java index 2d1e97bfc..cb5bf5402 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingContext.java @@ -1,8 +1,8 @@ package io.smallrye.config; -import static io.smallrye.config.ConfigMappingLoader.configMappingNames; import static io.smallrye.config.ConfigValidationException.Problem; import static io.smallrye.config.ProfileConfigSourceInterceptor.activeName; +import static io.smallrye.config.PropertyName.name; import static io.smallrye.config.common.utils.StringUtil.unindexed; import java.lang.reflect.InvocationTargetException; @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; @@ -36,47 +35,23 @@ */ public final class ConfigMappingContext { private final SmallRyeConfig config; - private final ConfigMappingNames names; private final Map, Map> roots = new IdentityHashMap<>(); private final Map, Converter> converterInstances = new IdentityHashMap<>(); private NamingStrategy namingStrategy = NamingStrategy.KEBAB_CASE; private boolean beanStyleGetters = false; - private String rootPath = null; private final StringBuilder nameBuilder = new StringBuilder(); private final Set usedProperties = new HashSet<>(); private final List problems = new ArrayList<>(); - public ConfigMappingContext(final SmallRyeConfig config, final Map, Set> roots) { - this(config, new Supplier>>>() { - @Override - public Map>> get() { - // All mapping names must be loaded first because of split mappings - Map>> names = new HashMap<>(); - for (Map.Entry, Set> mapping : roots.entrySet()) { - for (Map.Entry>> entry : configMappingNames(mapping.getKey()).entrySet()) { - names.putIfAbsent(entry.getKey(), new HashMap<>()); - names.get(entry.getKey()).putAll(entry.getValue()); - } - } - return names; - } - }.get(), roots); - } - - ConfigMappingContext( + public ConfigMappingContext( final SmallRyeConfig config, - final Map>> names, - final Map, Set> roots) { + final Map, Set> mappings) { this.config = config; - this.names = new ConfigMappingNames(names); - Set mappingsPrefixes = new HashSet<>(); - for (Set mappingPrefixes : roots.values()) { - mappingsPrefixes.addAll(mappingPrefixes); - } - matchPropertiesWithEnv(mappingsPrefixes); - for (Map.Entry, Set> mapping : roots.entrySet()) { + + matchPropertiesWithEnv(mappings); + for (Map.Entry, Set> mapping : mappings.entrySet()) { Map mappingObjects = new HashMap<>(); for (String rootPath : mapping.getValue()) { applyRootPath(rootPath); @@ -140,7 +115,6 @@ public void applyBeanStyleGetters(final Boolean beanStyleGetters) { } public void applyRootPath(final String rootPath) { - this.rootPath = rootPath; this.nameBuilder.replace(0, nameBuilder.length(), rootPath); } @@ -177,42 +151,20 @@ Map, Map> getRootsMap() { return roots; } - private void matchPropertiesWithEnv(final Set mappingsPrefixes) { - // TODO - We shouldn't be mutating the EnvSource. - // We should do the calculation when creating the EnvSource, but right now mappings and sources are not well integrated. - - List prefixes = new ArrayList<>(mappingsPrefixes); - // Sort by number of segments to match the most specific ones first - prefixes.sort(new Comparator() { - @Override - public int compare(final String o1, final String o2) { - int segmentsO1 = 0; - for (int i = 0; i < o1.length(); i++) { - if (o1.charAt(i) == '.') { - segmentsO1++; - } - } - - int segmentsO2 = 0; - for (int i = 0; i < o2.length(); i++) { - if (o2.charAt(i) == '.') { - segmentsO2++; - } - } - return Integer.compare(segmentsO2, segmentsO1); + // TODO - We shouldn't be mutating the EnvSource. + // We should do the calculation when creating the EnvSource, but right now mappings and sources are not well integrated. + private void matchPropertiesWithEnv(final Map, Set> mappings) { + Map>> prefixes = new HashMap<>(); + for (Map.Entry, Set> entry : mappings.entrySet()) { + for (String prefix : entry.getValue()) { + prefixes.computeIfAbsent(prefix, k -> new ArrayList<>()).add(entry.getKey()); } - }); - boolean all = prefixes.contains(""); - StringBuilder sb = new StringBuilder(); + } + StringBuilder sb = new StringBuilder(); for (ConfigSource configSource : config.getConfigSources(EnvConfigSource.class)) { - if (prefixes.isEmpty()) { - break; - } - EnvConfigSource envConfigSource = (EnvConfigSource) configSource; - Set mutableEnvProperties = envConfigSource.getPropertyNames(); - List envProperties = new ArrayList<>(mutableEnvProperties); + List envProperties = new ArrayList<>(envConfigSource.getPropertyNames()); for (String envProperty : envProperties) { String activeEnvProperty; if (envProperty.charAt(0) == '%') { @@ -221,45 +173,34 @@ public int compare(final String o1, final String o2) { activeEnvProperty = envProperty; } - String matchedRoot = null; - if (!all) { - for (String rootPath : prefixes) { - if (StringUtil.isInPath(rootPath, activeEnvProperty)) { - matchedRoot = rootPath; - break; - } - } - if (matchedRoot == null) { - continue; - } - } else { - matchedRoot = ""; - } - - for (Map> mappingsNames : names.getNames().values()) { - List propertyNames = mappingsNames.get(new PropertyName("")); - if (propertyNames == null) { - continue; - } - - for (PropertyName mappedName : propertyNames) { - String name = matchedRoot.isEmpty() ? mappedName.getName() : matchedRoot + "." + mappedName.getName(); - // Try to match Env with Root mapped property and generate the expected format - List indexOfDashes = indexOfDashes(name, activeEnvProperty); - if (indexOfDashes != null) { - sb.append(activeEnvProperty); - for (Integer dash : indexOfDashes) { - sb.setCharAt(dash, '-'); - } - String expectedEnvProperty = sb.toString(); - if (!activeEnvProperty.equals(expectedEnvProperty)) { - envConfigSource.getPropertyNames().add(sb.toString()); - envConfigSource.getPropertyNames().remove(envProperty); - // TODO - https://github.com/quarkusio/quarkus/issues/38479 - //ignoredPaths.add(activeEnvProperty); + outer: for (String prefix : prefixes.keySet()) { + if (StringUtil.isInPath(prefix, activeEnvProperty)) { + String mappedEnvProperty = prefix.isEmpty() ? activeEnvProperty + : activeEnvProperty.substring(prefix.length() + 1); + for (Class mapping : prefixes.get(prefix)) { + for (String mappedProperty : ConfigMappingLoader.configMappingProperties(mapping).keySet()) { + List prefixDashes = indexOfDashes(prefix, + activeEnvProperty.substring(0, prefix.length())); + List nameDashes = indexOfDashes(mappedProperty, mappedEnvProperty); + if (prefixDashes != null && nameDashes != null) { + sb.append(activeEnvProperty); + for (Integer dash : prefixDashes) { + sb.setCharAt(dash, '-'); + } + for (Integer dash : nameDashes) { + sb.setCharAt(prefix.length() + 1 + dash, '-'); + } + String expectedEnvProperty = sb.toString(); + if (!activeEnvProperty.equals(expectedEnvProperty)) { + envConfigSource.getPropertyNames().add(sb.toString()); + envConfigSource.getPropertyNames().remove(envProperty); + // TODO - https://github.com/quarkusio/quarkus/issues/38479 + //ignoredPaths.add(activeEnvProperty); + } + sb.setLength(0); + break outer; + } } - sb.setLength(0); - break; } } } @@ -275,14 +216,15 @@ public int compare(final String o1, final String o2) { * * @param mappedProperty the mapping property name. * @param envProperty a generated dotted property from the {@link EnvConfigSource}. - * @return a List of indexes from the env property name to replace with a dash. + * @return a List of indexes from the env property name to replace with a dash, or null if the + * properties do not match. */ private static List indexOfDashes(final String mappedProperty, final String envProperty) { if (mappedProperty.length() > envProperty.length()) { return null; } - List dashesPosition = null; + List dashesPosition = new ArrayList<>(); int matchPosition = envProperty.length() - 1; for (int i = mappedProperty.length() - 1; i >= 0; i--) { if (matchPosition == -1) { @@ -296,9 +238,6 @@ private static List indexOfDashes(final String mappedProperty, final St return null; } if (c == '-') { - if (dashesPosition == null) { - dashesPosition = new ArrayList<>(); - } dashesPosition.add(matchPosition); } matchPosition--; @@ -374,37 +313,6 @@ void reportUnknown(final Set ignoredPaths) { } } - /** - * Filters the full list of properties names in Config to only the property names that can match any of the - * prefixes (namespaces) registered in mappings. - * - * @param properties the available property names in Config. - * @param roots the registered mapping roots. - * - * @return the property names that match to at least one root. - */ - private static Iterable filterPropertiesInRoots(final Iterable properties, final Set roots) { - if (roots.isEmpty()) { - return properties; - } - - // Will match everything, so no point in filtering - if (roots.contains("")) { - return properties; - } - - List matchedProperties = new ArrayList<>(); - for (String property : properties) { - for (String root : roots) { - if (isPropertyInRoot(property, root)) { - matchedProperties.add(property); - break; - } - } - } - return matchedProperties; - } - private static boolean isPropertyInRoot(final String property, final String root) { if (property.equals(root)) { return true; @@ -889,8 +797,41 @@ private Converter getConverter(final Class rawType, final ClassOptional or Map. + * + * @param groupType the class of the mapping + * @param path the relative path to the mapping + * @return true if a runtime config name exits in the mapping names or false otherwise + */ private boolean createRequired(final Class groupType, final String path) { - return ConfigMappingContext.this.names.hasAnyName(groupType.getName(), rootPath, path, config.getPropertyNames()); + List candidates = new ArrayList<>(); + for (String name : config.getPropertyNames()) { + if (name.startsWith(path)) { + String candidate = name.length() > path.length() && name.charAt(path.length()) == '.' + ? name.substring(path.length() + 1) + : name.substring(path.length()); + if (namingStrategy.equals(NamingStrategy.KEBAB_CASE)) { + candidates.add(candidate); + } else { + candidates.add(NamingStrategy.KEBAB_CASE.apply(candidate)); + } + } + } + + if (!candidates.isEmpty()) { + Map properties = ConfigMappingLoader.configMappingProperties(groupType); + for (String mappedProperty : properties.keySet()) { + for (String candidate : candidates) { + if (PropertyName.equals(candidate, mappedProperty)) { + return true; + } + } + } + } + + return false; } private IntFunction> createCollectionFactory(final Class type) { diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java index 7dffea9f6..85910cb3f 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingGenerator.java @@ -1,53 +1,6 @@ package io.smallrye.config; -import static org.objectweb.asm.Opcodes.AASTORE; -import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; -import static org.objectweb.asm.Opcodes.ACC_FINAL; -import static org.objectweb.asm.Opcodes.ACC_INTERFACE; -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; -import static org.objectweb.asm.Opcodes.ACC_PUBLIC; -import static org.objectweb.asm.Opcodes.ACC_STATIC; -import static org.objectweb.asm.Opcodes.ACONST_NULL; -import static org.objectweb.asm.Opcodes.ALOAD; -import static org.objectweb.asm.Opcodes.ANEWARRAY; -import static org.objectweb.asm.Opcodes.ARETURN; -import static org.objectweb.asm.Opcodes.ASM7; -import static org.objectweb.asm.Opcodes.ASTORE; -import static org.objectweb.asm.Opcodes.BIPUSH; -import static org.objectweb.asm.Opcodes.CHECKCAST; -import static org.objectweb.asm.Opcodes.DCMPL; -import static org.objectweb.asm.Opcodes.DRETURN; -import static org.objectweb.asm.Opcodes.DUP; -import static org.objectweb.asm.Opcodes.FCMPL; -import static org.objectweb.asm.Opcodes.FRETURN; -import static org.objectweb.asm.Opcodes.F_SAME; -import static org.objectweb.asm.Opcodes.GETFIELD; -import static org.objectweb.asm.Opcodes.GETSTATIC; -import static org.objectweb.asm.Opcodes.GOTO; -import static org.objectweb.asm.Opcodes.I2C; -import static org.objectweb.asm.Opcodes.ICONST_0; -import static org.objectweb.asm.Opcodes.ICONST_1; -import static org.objectweb.asm.Opcodes.IFEQ; -import static org.objectweb.asm.Opcodes.IFNE; -import static org.objectweb.asm.Opcodes.IFNULL; -import static org.objectweb.asm.Opcodes.IF_ACMPEQ; -import static org.objectweb.asm.Opcodes.IF_ACMPNE; -import static org.objectweb.asm.Opcodes.IF_ICMPNE; -import static org.objectweb.asm.Opcodes.ILOAD; -import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; -import static org.objectweb.asm.Opcodes.INVOKESPECIAL; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; -import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; -import static org.objectweb.asm.Opcodes.IRETURN; -import static org.objectweb.asm.Opcodes.ISTORE; -import static org.objectweb.asm.Opcodes.LCMP; -import static org.objectweb.asm.Opcodes.LRETURN; -import static org.objectweb.asm.Opcodes.NEW; -import static org.objectweb.asm.Opcodes.POP; -import static org.objectweb.asm.Opcodes.PUTFIELD; -import static org.objectweb.asm.Opcodes.RETURN; -import static org.objectweb.asm.Opcodes.SWAP; -import static org.objectweb.asm.Opcodes.V1_8; +import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Type.getDescriptor; import static org.objectweb.asm.Type.getInternalName; import static org.objectweb.asm.Type.getMethodDescriptor; @@ -70,6 +23,7 @@ import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; @@ -197,11 +151,10 @@ static byte[] generate(final ConfigMappingInterface mapping) { ctor.visitMaxs(0, 0); visitor.visitEnd(); + generateProperties(visitor, mapping); generateEquals(visitor, mapping); generateHashCode(visitor, mapping); generateToString(visitor, mapping); - generateNames(visitor, mapping); - generateDefaults(visitor, mapping); return writer.toByteArray(); } @@ -949,81 +902,50 @@ private static void generateHashCode(final ClassVisitor visitor, final ConfigMap hc.visitEnd(); } - private static void generateNames(final ClassVisitor classVisitor, final ConfigMappingInterface mapping) { - MethodVisitor mv = classVisitor.visitMethod(ACC_PUBLIC | ACC_STATIC, "getNames", "()Ljava/util/Map;", - "()Ljava/util/Map;>;>;", - null); - - mv.visitTypeInsn(NEW, "java/util/HashMap"); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "", "()V", false); - mv.visitVarInsn(ASTORE, 0); - - for (Map.Entry>> mappings : mapping.getNames().entrySet()) { - mv.visitTypeInsn(NEW, "java/util/HashMap"); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "", "()V", false); - mv.visitVarInsn(ASTORE, 1); - for (Map.Entry> paths : mappings.getValue().entrySet()) { - mv.visitTypeInsn(NEW, "java/util/HashSet"); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashSet", "", "()V", false); - mv.visitVarInsn(ASTORE, 2); - for (String name : paths.getValue()) { - mv.visitVarInsn(ALOAD, 2); - mv.visitLdcInsn(name); - mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true); - mv.visitInsn(POP); - } - mv.visitVarInsn(ALOAD, 1); - mv.visitLdcInsn(paths.getKey()); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true); - mv.visitInsn(POP); - } - mv.visitVarInsn(ALOAD, 0); - mv.visitLdcInsn(mappings.getKey()); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true); - mv.visitInsn(POP); - } - mv.visitVarInsn(ALOAD, 0); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } + private static void generateProperties(final ClassVisitor classVisitor, final ConfigMappingInterface mapping) { + Map properties = ConfigMappingInterface.getProperties(mapping).get(mapping.getInterfaceType()) + .get(""); - private static void generateDefaults(final ClassVisitor classVisitor, final ConfigMappingInterface mapping) { - MethodVisitor mv = classVisitor.visitMethod(ACC_PUBLIC | ACC_STATIC, "getDefaults", "()Ljava/util/Map;", - "()Ljava/util/Map;", - null); + FieldVisitor fieldVisitor = classVisitor.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, "PROPERTIES", + "Ljava/util/Map;", "Ljava/util/Map;", null); + fieldVisitor.visitEnd(); - mv.visitTypeInsn(NEW, "java/util/HashMap"); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "", "()V", false); - mv.visitVarInsn(ASTORE, 0); + MethodVisitor clinit = classVisitor.visitMethod(ACC_STATIC, "", "()V", null, null); + clinit.visitTypeInsn(NEW, "java/util/HashMap"); + clinit.visitInsn(DUP); + if (properties.size() < 3) { + clinit.visitIntInsn(BIPUSH, properties.size() + 1); + } else { + clinit.visitIntInsn(SIPUSH, (int) ((float) properties.size() / 0.75f + 1.0f)); + } + clinit.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "", "(I)V", false); + clinit.visitFieldInsn(PUTSTATIC, mapping.getClassInternalName(), "PROPERTIES", "Ljava/util/Map;"); - for (Map.Entry entry : ConfigMappingInterface.getProperties(mapping) - .get(mapping.getInterfaceType()) - .get("").entrySet()) { + for (Map.Entry entry : properties.entrySet()) { + clinit.visitFieldInsn(GETSTATIC, mapping.getClassInternalName(), "PROPERTIES", "Ljava/util/Map;"); + clinit.visitLdcInsn(entry.getKey()); if (entry.getValue().hasDefaultValue()) { // Defaults for collections also come as a simple property with comma separated values, no need for the star name if (entry.getKey().endsWith("[*]")) { - continue; + clinit.visitInsn(ACONST_NULL); + } else { + clinit.visitLdcInsn(entry.getValue().getDefaultValue()); } - - mv.visitVarInsn(ALOAD, 0); - mv.visitLdcInsn(entry.getKey()); - mv.visitLdcInsn(entry.getValue().getDefaultValue()); - mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true); - mv.visitInsn(POP); + } else { + clinit.visitInsn(ACONST_NULL); } + clinit.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true); + clinit.visitInsn(POP); } - mv.visitVarInsn(ALOAD, 0); + clinit.visitInsn(RETURN); + clinit.visitMaxs(0, 0); + clinit.visitEnd(); + + MethodVisitor mv = classVisitor.visitMethod(ACC_PUBLIC | ACC_STATIC, "getProperties", "()Ljava/util/Map;", + "()Ljava/util/Map;", null); + mv.visitFieldInsn(GETSTATIC, mapping.getClassInternalName(), "PROPERTIES", "Ljava/util/Map;"); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java index 57b3b52b5..7442797b8 100644 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java +++ b/implementation/src/main/java/io/smallrye/config/ConfigMappingLoader.java @@ -1,15 +1,15 @@ package io.smallrye.config; +import static java.lang.invoke.MethodType.methodType; + +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.microprofile.config.inject.ConfigProperties; @@ -62,29 +62,11 @@ static Class getConfigMappingClass(final Class type) { } @SuppressWarnings("unchecked") - static Map>> configMappingNames(final Class interfaceType) { - try { - Method getNames = CACHE.get(interfaceType).getImplementationClass().getDeclaredMethod("getNames"); - return (Map>>) getNames.invoke(null); - } catch (NoSuchMethodException e) { - throw new NoSuchMethodError(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); - } - } - } - - static Map configMappingDefaults(final Class interfaceType) { + static Map configMappingProperties(final Class interfaceType) { try { - Method getDefaults = CACHE.get(interfaceType).getImplementationClass().getDeclaredMethod("getDefaults"); - return (Map) getDefaults.invoke(null); + MethodHandle getDefaults = LOOKUP.findStatic(CACHE.get(interfaceType).getImplementationClass(), "getProperties", + methodType(Map.class)); + return (Map) getDefaults.invoke(); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } catch (IllegalAccessException e) { @@ -92,36 +74,40 @@ static Map configMappingDefaults(final Class interfaceTyp } catch (InvocationTargetException e) { try { throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; + } catch (RuntimeException | Error r) { + throw r; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } + } catch (RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); } } static T configMappingObject(final Class interfaceType, final ConfigMappingContext configMappingContext) { - ConfigMappingObject instance; try { - Constructor constructor = CACHE.get(interfaceType).getImplementationClass() - .getDeclaredConstructor(ConfigMappingContext.class); - instance = constructor.newInstance(configMappingContext); + MethodHandle constructor = LOOKUP.findConstructor(CACHE.get(interfaceType).getImplementationClass(), + methodType(void.class, ConfigMappingContext.class)); + return interfaceType.cast(constructor.invoke(configMappingContext)); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); - } 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 (RuntimeException | Error r) { + throw r; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } + } catch (RuntimeException e) { + throw e; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); } - return interfaceType.cast(instance); } @SuppressWarnings("unchecked") diff --git a/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java b/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java deleted file mode 100644 index 5a901a67d..000000000 --- a/implementation/src/main/java/io/smallrye/config/ConfigMappingNames.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.smallrye.config; - -import static io.smallrye.config.PropertyName.name; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Represents the full structure of mapping classes per class name, relative path and property name. - */ -class ConfigMappingNames { - private final Map>> names; - - ConfigMappingNames(final Map>> names) { - this.names = new HashMap<>(names.size()); - - for (Map.Entry>> mappings : names.entrySet()) { - // We chance this to be a List because different names may equal the PropertyName - Map> mappingPropertyNames = new HashMap<>(); - for (Map.Entry> mappingNames : mappings.getValue().entrySet()) { - PropertyName key = name(mappingNames.getKey()); - mappingPropertyNames.putIfAbsent(key, new ArrayList<>()); - // Give priority to the star key - if (key.getName().contains("*")) { - mappingPropertyNames.put(key, mappingPropertyNames.remove(key)); - } - - for (String value : mappingNames.getValue()) { - mappingPropertyNames.get(key).add(name(value)); - } - } - this.names.put(mappings.getKey(), mappingPropertyNames); - } - } - - public Map>> getNames() { - return names; - } - - /** - * Matches that at least one runtime configuration name is in the root path and relative path of a mapping class. - * This is required to trigger the construction of lazy mapping objects like Optional or - * Map. - * - * @param mapping the class name of the mapping - * @param rootPath the root path of the mapping - * @param path the relative path to the mapping - * @param names the runtime config names - * @return true if a runtime config name exits in the mapping names or false otherwise - */ - boolean hasAnyName(final String mapping, final String rootPath, final String path, final Iterable names) { - Map> mappings = this.names.get(mapping); - if (mappings == null) { - return false; - } - - // Simple case, no need to remove the rootPath from searched path and names - if (rootPath == null || rootPath.isEmpty()) { - return hasAnyName(mappings, path, names); - } - - // The path does not match the rootPath or the next char is not a separator dot we can skip - if (!path.startsWith(rootPath) || (path.length() > rootPath.length() && path.charAt(rootPath.length()) != '.' - && path.charAt(rootPath.length()) != '[')) { - return false; - } - - // Same length replace with empty string since we know they already match, or start after the next separator - PropertyName mappingName; - if (path.length() == rootPath.length()) { - mappingName = name(""); - } else if (path.charAt(rootPath.length()) == '.') { - mappingName = name(path.substring(rootPath.length() + 1)); - } else { - mappingName = name(path.substring(rootPath.length())); - } - List mappingNames = mappings.get(mappingName); - if (mappingNames == null) { - return false; - } - - for (String name : names) { - // We can't check for next char separator dot because we may find collection names with square brackets - if (name.startsWith(path)) { - PropertyName propertyName = name.length() > rootPath.length() && name.charAt(rootPath.length()) == '.' - ? name(name.substring(rootPath.length() + 1)) - : name(name.substring(rootPath.length())); - if (mappingNames.contains(propertyName)) { - return true; - } - } - } - - return false; - } - - boolean hasAnyName(final Map> mappings, final String path, - final Iterable names) { - List mappingNames = mappings.get(name(path)); - if (mappingNames == null || mappingNames.isEmpty()) { - return false; - } - - for (String name : names) { - if (name.startsWith(path) && mappingNames.contains(name(name))) { - return true; - } - } - return false; - } -} diff --git a/implementation/src/main/java/io/smallrye/config/PropertyName.java b/implementation/src/main/java/io/smallrye/config/PropertyName.java index a7aafee2a..c501a2f39 100644 --- a/implementation/src/main/java/io/smallrye/config/PropertyName.java +++ b/implementation/src/main/java/io/smallrye/config/PropertyName.java @@ -25,8 +25,12 @@ public boolean equals(final Object o) { return equals(this.name, that.name) || equals(that.name, this.name); } - @SuppressWarnings("squid:S4973") static boolean equals(final String name, final String other) { + return equalsInternal(name, other) || equalsInternal(other, name); + } + + @SuppressWarnings("squid:S4973") + private static boolean equalsInternal(final String name, final String other) { //noinspection StringEquality if (name == other) { return true; diff --git a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java index 403fbeb02..5b312714f 100644 --- a/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java +++ b/implementation/src/main/java/io/smallrye/config/SmallRyeConfigBuilder.java @@ -781,19 +781,21 @@ public void mapping(ConfigClass configClass) { public void mapping(Class type, String prefix) { Assert.checkNotNullParam("type", type); Assert.checkNotNullParam("path", prefix); + Class mappingClass = getConfigMappingClass(type); - // It is an MP ConfigProperties, so ignore unmapped properties - if (ConfigMappingLoader.ConfigMappingClass.getConfigurationClass(type) != null) { - ignoredPaths.add(prefix.isEmpty() ? "*" : prefix + ".**"); - } mappings.computeIfAbsent(mappingClass, k -> new HashSet<>(4)).add(prefix); - // Load the mapping defaults, to make the defaults available to all config sources + // Load the mapping defaults, to make the defaults available to all config sources + Map properties = ConfigMappingLoader.configMappingProperties(mappingClass); sb.setLength(0); sb.append(prefix); - for (Map.Entry defaultEntry : ConfigMappingLoader.configMappingDefaults(mappingClass).entrySet()) { + for (Map.Entry property : properties.entrySet()) { + if (property.getValue() == null) { + continue; + } + // Do not override builder defaults with mapping defaults - String path = defaultEntry.getKey(); + String path = property.getKey(); String name; if (prefix.isEmpty()) { name = path; @@ -805,7 +807,12 @@ public void mapping(Class type, String prefix) { name = sb.append(".").append(path).toString(); } sb.setLength(prefix.length()); - defaultValues.putIfAbsent(name, defaultEntry.getValue()); + defaultValues.putIfAbsent(name, property.getValue()); + } + + // It is an MP ConfigProperties, so ignore unmapped properties + if (ConfigMappingLoader.ConfigMappingClass.getConfigurationClass(type) != null) { + ignoredPaths.add(prefix.isEmpty() ? "*" : prefix + ".**"); } } diff --git a/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java b/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java index 49d90ca23..95c2ed404 100644 --- a/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java +++ b/implementation/src/test/java/io/smallrye/config/ObjectCreatorTest.java @@ -46,8 +46,7 @@ void objectCreator() { "optional-list-group[0].value", "value")) .build(); - ConfigMappingContext context = new ConfigMappingContext(config, - ConfigMappingLoader.getConfigMapping(ObjectCreator.class).getNames(), new HashMap<>()); + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>()); ObjectCreator mapping = new ObjectCreatorImpl(context); assertEquals(2, mapping.unnamed().size()); @@ -274,8 +273,7 @@ void optionalGroup() { "optional.value", "value")) .build(); - ConfigMappingContext context = new ConfigMappingContext(config, - ConfigMappingLoader.getConfigMapping(OptionalGroup.class).getNames(), new HashMap<>()); + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>()); OptionalGroup mapping = new OptionalGroupImpl(context); assertTrue(mapping.optional().isPresent()); @@ -337,8 +335,7 @@ void unnamedKeys() { "unnamed.key.value", "value")) .build(); - ConfigMappingContext context = new ConfigMappingContext(config, - ConfigMappingLoader.getConfigMapping(UnnamedKeys.class).getNames(), new HashMap<>()); + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>()); context.applyRootPath("unnamed"); UnnamedKeys mapping = new UnnamedKeysImpl(context); @@ -396,8 +393,7 @@ void mapDefaults() { "map.defaults-list.one[0].value", "value")) .build(); - ConfigMappingContext context = new ConfigMappingContext(config, - ConfigMappingLoader.getConfigMapping(MapDefaults.class).getNames(), new HashMap<>()); + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>()); context.applyRootPath("map"); MapDefaults mapping = new MapDefaultsImpl(context); @@ -508,8 +504,7 @@ void namingStrategy() { "naming.nested_value.value", "value")) .build(); - ConfigMappingContext context = new ConfigMappingContext(config, - ConfigMappingLoader.getConfigMapping(Naming.class).getNames(), new HashMap<>()); + ConfigMappingContext context = new ConfigMappingContext(config, new HashMap<>()); context.applyRootPath("naming"); Naming naming = new NamingImpl(context); diff --git a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java index 03589f17f..86e1610c9 100644 --- a/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java +++ b/implementation/src/test/java/io/smallrye/config/RelocateConfigSourceInterceptorTest.java @@ -4,7 +4,6 @@ import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_PROFILE; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; import static java.util.stream.StreamSupport.stream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -12,7 +11,6 @@ import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -327,25 +325,6 @@ void relocateMapping() { assertEquals("new", mapping.map().get("key")); assertEquals("new", mapping.map().get("new")); assertEquals("old", mapping.map().get("old")); - - Set properties = stream(config.getPropertyNames().spliterator(), false).collect(toSet()); - ConfigMappingNames names = new ConfigMappingNames( - ConfigMappingLoader.getConfigMapping(RelocateMapping.class).getNames()); - Set mappedProperties = new HashSet<>(); - for (String property : properties) { - if (names.hasAnyName(RelocateMapping.class.getName(), "reloc.old", "reloc.old", Set.of(property))) { - mappedProperties.add(property); - } - } - properties.removeAll(mappedProperties); - Set relocateProperties = new HashSet<>(); - for (String property : properties) { - if (mappedProperties.contains(property)) { - ConfigValue configValue = config.getConfigValue(property); - relocateProperties.add(configValue.getName()); - } - } - properties.removeAll(relocateProperties); } @Test @@ -370,26 +349,6 @@ void fallbackMapping() { assertEquals(2, mapping.map().size()); assertEquals("old", mapping.map().get("key")); assertEquals("old", mapping.map().get("old")); - - Set properties = stream(config.getPropertyNames().spliterator(), false).collect(toSet()); - ConfigMappingNames names = new ConfigMappingNames( - ConfigMappingLoader.getConfigMapping(FallbackMapping.class).getNames()); - Set mappedProperties = new HashSet<>(); - for (String property : properties) { - if (names.hasAnyName(FallbackMapping.class.getName(), "fall.new", "fall.new", Set.of(property))) { - mappedProperties.add(property); - } - } - properties.removeAll(mappedProperties); - Set fallbackProperties = new HashSet<>(); - for (String property : properties) { - ConfigValue configValue = config.getConfigValue(property); - if (mappedProperties.contains(configValue.getName())) { - fallbackProperties.add(property); - } - } - properties.removeAll(fallbackProperties); - assertTrue(properties.isEmpty()); } @ConfigMapping(prefix = "reloc.old")