diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 118f2211057042..4d6d468c15f1bb 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -31,7 +31,7 @@ 1.3.1 1.0 1.3.4 - 1.3.9 + 1.4.0 2.1.0 2.3.0 1.1.19 @@ -1067,6 +1067,11 @@ + + io.smallrye + smallrye-config-common + ${smallrye-config.version} + io.smallrye smallrye-health diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java index 2da772eaae17ae..bc238ed19f3b80 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentTask.java @@ -15,7 +15,9 @@ import java.util.Properties; import java.util.function.Consumer; +import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import io.quarkus.bootstrap.BootstrapDependencyProcessingException; @@ -36,7 +38,10 @@ import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.JarBuildItem; import io.quarkus.deployment.pkg.builditem.NativeImageBuildItem; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; import io.smallrye.config.SmallRyeConfigProviderResolver; /** @@ -110,20 +115,26 @@ public AugmentOutcome run(CurateOutcome appState, CuratedApplicationCreator ctx) } //first lets look for some config, as it is not on the current class path //and we need to load it to run the build process - Path config = configDir.resolve("application.properties"); - if (Files.exists(config)) { + Path configPath = configDir.resolve("application.properties"); + if (Files.exists(configPath)) { try { - ConfigBuilder builder = SmallRyeConfigProviderResolver.instance().getBuilder() + SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder() .addDefaultSources() .addDiscoveredConverters() .addDiscoveredSources() - .withSources(new PropertiesConfigSource(config.toUri().toURL())); + .withSources(new PropertiesConfigSource(configPath.toUri().toURL())); if (configCustomizer != null) { configCustomizer.accept(builder); } - SmallRyeConfigProviderResolver.instance().registerConfig(builder.build(), - Thread.currentThread().getContextClassLoader()); + final SmallRyeConfig config = builder.build(); + QuarkusConfigFactory.setConfig(config); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + final Config existing = cpr.getConfig(); + if (existing != config) { + cpr.releaseConfig(existing); + // subsequent calls will get the new config + } } catch (Exception e) { throw new RuntimeException(e); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java index 180ae5ad3a1135..e430259613cdca 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java @@ -1,5 +1,7 @@ package io.quarkus.deployment; +import java.util.Optional; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -12,12 +14,12 @@ public class ApplicationConfig { * If not set, defaults to the name of the project. */ @ConfigItem - public String name; + public Optional name; /** * The version of the application. * If not set, defaults to the version of the project */ @ConfigItem - public String version; + public Optional version; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 2ccecac799b296..6ea739b2192252 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -25,12 +25,11 @@ import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Properties; -import java.util.Set; import java.util.concurrent.Executor; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -40,6 +39,7 @@ import java.util.function.Supplier; import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import org.wildfly.common.function.Functions; @@ -62,33 +62,34 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.annotations.Weak; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem; -import io.quarkus.deployment.builditem.BuildTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; +import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; import io.quarkus.deployment.builditem.CapabilityBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.DeploymentClassLoaderBuildItem; import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationProxyBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; -import io.quarkus.deployment.builditem.UnmatchedConfigBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource; +import io.quarkus.deployment.configuration.definition.RootDefinition; import io.quarkus.deployment.recording.BytecodeRecorderImpl; +import io.quarkus.deployment.recording.ObjectLoader; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.ReflectUtil; import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSource; +import io.quarkus.runtime.configuration.ConfigUtils; import io.quarkus.runtime.configuration.ConverterSupport; -import io.quarkus.runtime.configuration.DeploymentProfileConfigSource; -import io.quarkus.runtime.configuration.ExpandingConfigSource; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; import io.smallrye.config.PropertiesConfigSource; import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigBuilder; -import io.smallrye.config.SmallRyeConfigProviderResolver; /** * Utility class to load build steps, runtime recorders, and configuration roots from a given extension class. @@ -104,11 +105,6 @@ private ExtensionLoader() { public static final String RUN_TIME_CONFIG = "io.quarkus.runtime.generated.RunTimeConfig"; public static final String RUN_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.RunTimeConfigRoot"; - private static final FieldDescriptor RUN_TIME_CONFIG_FIELD = FieldDescriptor.of(RUN_TIME_CONFIG, "runConfig", - RUN_TIME_CONFIG_ROOT); - private static final FieldDescriptor BUILD_TIME_CONFIG_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "buildConfig", - BUILD_TIME_CONFIG_ROOT); - private static final String CONFIG_ROOTS_LIST = "META-INF/quarkus-config-roots.list"; @SuppressWarnings("deprecation") @@ -171,44 +167,29 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, LaunchMode launchMode, Consumer configCustomizer) throws IOException, ClassNotFoundException { - // set up the configuration definitions - final ConfigDefinition buildTimeConfig = new ConfigDefinition(FieldDescriptor.of("Bogus", "No field", "Nothing")); - final ConfigDefinition buildTimeRunTimeConfig = new ConfigDefinition(BUILD_TIME_CONFIG_FIELD); - final ConfigDefinition runTimeConfig = new ConfigDefinition(RUN_TIME_CONFIG_FIELD, true); - - // populate it with all known types + // populate with all known types + List> roots = new ArrayList<>(); for (Class clazz : ServiceUtil.classesNamedIn(classLoader, CONFIG_ROOTS_LIST)) { final ConfigRoot annotation = clazz.getAnnotation(ConfigRoot.class); if (annotation == null) { cfgLog.warnf("Ignoring configuration root %s because it has no annotation", clazz); } else { - final ConfigPhase phase = annotation.phase(); - if (phase == ConfigPhase.RUN_TIME) { - runTimeConfig.registerConfigRoot(clazz); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - buildTimeRunTimeConfig.registerConfigRoot(clazz); - } else if (phase == ConfigPhase.BUILD_TIME) { - buildTimeConfig.registerConfigRoot(clazz); - } else { - cfgLog.warnf("Unrecognized configuration phase \"%s\" on %s", phase, clazz); - } + roots.add(clazz); } } + final BuildTimeConfigurationReader reader = new BuildTimeConfigurationReader(roots); + // now prepare & load the build configuration - final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); - - // expand properties - final ExpandingConfigSource.Cache cache = new ExpandingConfigSource.Cache(); - builder.withWrapper(ExpandingConfigSource.wrapper(cache)); - builder.withWrapper(DeploymentProfileConfigSource.wrapper()); - builder.addDefaultSources(); - final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); - final DefaultValuesConfigurationSource defaultSource = new DefaultValuesConfigurationSource( - buildTimeConfig.getLeafPatterns()); + final SmallRyeConfigBuilder builder = ConfigUtils.configBuilder(); + + final DefaultValuesConfigurationSource ds1 = new DefaultValuesConfigurationSource( + reader.getBuildTimePatternMap()); + final DefaultValuesConfigurationSource ds2 = new DefaultValuesConfigurationSource( + reader.getBuildTimeRunTimePatternMap()); final PropertiesConfigSource pcs = new PropertiesConfigSource(buildSystemProps, "Build system"); - builder.withSources(inJar, defaultSource, pcs); + builder.withSources(ds1, ds2, pcs); // populate builder with all converters loaded from ServiceLoader ConverterSupport.populateConverters(builder); @@ -216,43 +197,53 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, if (configCustomizer != null) { configCustomizer.accept(builder); } - final SmallRyeConfig src = (SmallRyeConfig) builder - .addDefaultSources() - .addDiscoveredSources() - .addDiscoveredConverters() - .build(); - - SmallRyeConfigProviderResolver.instance().registerConfig(src, classLoader); - - Set unmatched = new HashSet<>(); - - ConfigDefinition.loadConfiguration(cache, src, - unmatched, - buildTimeConfig, - buildTimeRunTimeConfig, // this one is only for generating a default-values config source - runTimeConfig); + final SmallRyeConfig src = builder.build(); + + // install globally + QuarkusConfigFactory.setConfig(src); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } - unmatched.removeIf(s -> !inJar.getPropertyNames().contains(s) && !s.startsWith("quarkus.")); + final BuildTimeConfigurationReader.ReadResult readResult = reader.readConfiguration(src); + // the proxy objects used for run time config in the recorders + Map, Object> proxies = new HashMap<>(); Consumer result = Functions.discardingConsumer(); - result = result.andThen(bcb -> bcb.addBuildStep(bc -> { - bc.produce(new BuildTimeConfigurationBuildItem(buildTimeConfig)); - bc.produce(new BuildTimeRunTimeFixedConfigurationBuildItem(buildTimeRunTimeConfig)); - bc.produce(new RunTimeConfigurationBuildItem(runTimeConfig)); - bc.produce(new UnmatchedConfigBuildItem(Collections.unmodifiableSet(unmatched))); - }).produces(BuildTimeConfigurationBuildItem.class) - .produces(BuildTimeRunTimeFixedConfigurationBuildItem.class) - .produces(RunTimeConfigurationBuildItem.class) - .produces(UnmatchedConfigBuildItem.class) - .build()); for (Class clazz : ServiceUtil.classesNamedIn(classLoader, "META-INF/quarkus-build-steps.list")) { try { - result = result - .andThen(ExtensionLoader.loadStepsFrom(clazz, buildTimeConfig, buildTimeRunTimeConfig, launchMode)); + result = result.andThen( + ExtensionLoader.loadStepsFrom(clazz, readResult, proxies, launchMode)); } catch (Throwable e) { throw new RuntimeException("Failed to load steps from " + clazz, e); } } + // this has to be an identity hash map else the recorder will get angry + Map proxyFields = new IdentityHashMap<>(); + for (Map.Entry, Object> entry : proxies.entrySet()) { + final RootDefinition def = readResult.requireRootDefinitionForClass(entry.getKey()); + proxyFields.put(entry.getValue(), def.getDescriptor()); + } + result = result.andThen(bcb -> bcb.addBuildStep(bc -> { + bc.produce(new ConfigurationBuildItem(readResult)); + bc.produce(new RunTimeConfigurationProxyBuildItem(proxies)); + final ObjectLoader loader = new ObjectLoader() { + public ResultHandle load(final BytecodeCreator body, final Object obj, final boolean staticInit) { + return body.readStaticField(proxyFields.get(obj)); + } + + public boolean canHandleObject(final Object obj, final boolean staticInit) { + return proxyFields.containsKey(obj); + } + }; + bc.produce(new BytecodeRecorderObjectLoaderBuildItem(loader)); + }).produces(ConfigurationBuildItem.class) + .produces(RunTimeConfigurationProxyBuildItem.class) + .produces(BytecodeRecorderObjectLoaderBuildItem.class) + .build()); return result; } @@ -260,13 +251,13 @@ public static Consumer loadStepsFrom(ClassLoader classLoader, * Load all the build steps from the given class. * * @param clazz the class to load from (must not be {@code null}) - * @param buildTimeConfig the build time configuration (must not be {@code null}) - * @param buildTimeRunTimeConfig the build time/run time visible config (must not be {@code null}) - * @param launchMode + * @param readResult the build time configuration read result (must not be {@code null}) + * @param runTimeProxies the map of run time proxy objects to populate for recorders (must not be {@code null}) + * @param launchMode the launch mode * @return a consumer which adds the steps to the given chain builder */ - public static Consumer loadStepsFrom(Class clazz, ConfigDefinition buildTimeConfig, - ConfigDefinition buildTimeRunTimeConfig, final LaunchMode launchMode) { + public static Consumer loadStepsFrom(Class clazz, BuildTimeConfigurationReader.ReadResult readResult, + Map, Object> runTimeProxies, final LaunchMode launchMode) { final Constructor[] constructors = clazz.getDeclaredConstructors(); // this is the chain configuration that will contain all steps on this class and be returned Consumer chainConfig = Functions.discardingConsumer(); @@ -365,15 +356,14 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); consumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { - ctorParamFns.add(bc -> bc.consume(BuildTimeConfigurationBuildItem.class).getConfigDefinition() - .getRealizedInstance(parameterClass)); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - ctorParamFns.add(bc -> bc.consume(BuildTimeRunTimeFixedConfigurationBuildItem.class) - .getConfigDefinition().getRealizedInstance(parameterClass)); + if (phase.isAvailableAtBuild()) { + ctorParamFns.add(bc -> bc.consume(ConfigurationBuildItem.class).getReadResult() + .requireRootObjectForClass(parameterClass)); + if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - ctorParamFns.add(bc -> bc.consume(RunTimeConfigurationBuildItem.class).getConfigDefinition() - .getRealizedInstance(parameterClass)); + throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { throw reportError(parameterClass, "Unknown value for ConfigPhase"); } @@ -477,27 +467,18 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); consumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { - stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final BuildTimeConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeConfigurationBuildItem.class); - ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); - }); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + if (phase.isAvailableAtBuild()) { stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final BuildTimeRunTimeFixedConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeRunTimeFixedConfigurationBuildItem.class); + final ConfigurationBuildItem configurationBuildItem = bc + .consume(ConfigurationBuildItem.class); ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); + configurationBuildItem.getReadResult().requireRootObjectForClass(fieldClass)); }); + if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(fieldClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final RunTimeConfigurationBuildItem configurationBuildItem = bc - .consume(RunTimeConfigurationBuildItem.class); - ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); - }); + throw reportError(field, "Run time configuration cannot be consumed here"); } else { throw reportError(fieldClass, "Unknown value for ConfigPhase"); } @@ -566,16 +547,14 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe } else if (parameterClass.isAnnotationPresent(ConfigRoot.class)) { final ConfigRoot annotation = parameterClass.getAnnotation(ConfigRoot.class); final ConfigPhase phase = annotation.phase(); - ConfigDefinition confDef; - if (phase == ConfigPhase.BUILD_TIME) { - confDef = buildTimeConfig; - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - confDef = buildTimeRunTimeConfig; + if (phase.isAvailableAtBuild()) { + paramSuppList.add(() -> readResult.requireRootObjectForClass(parameterClass)); + } else if (phase == ConfigPhase.RUN_TIME) { + throw reportError(parameter, "Run time configuration cannot be consumed here"); } else { throw reportError(parameter, "Unsupported conditional class configuration build phase " + phase); } - paramSuppList.add(() -> confDef.getRealizedInstance(parameterClass)); } else { throw reportError(parameter, "Unsupported conditional class constructor parameter type " + parameterClass); @@ -600,17 +579,15 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe } else if (fieldClass.isAnnotationPresent(ConfigRoot.class)) { final ConfigRoot annotation = fieldClass.getAnnotation(ConfigRoot.class); final ConfigPhase phase = annotation.phase(); - ConfigDefinition confDef; - if (phase == ConfigPhase.BUILD_TIME) { - confDef = buildTimeConfig; - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - confDef = buildTimeRunTimeConfig; + if (phase.isAvailableAtBuild()) { + setup = setup.andThen(o -> ReflectUtil.setFieldVal(field, o, + readResult.requireRootObjectForClass(fieldClass))); + } else if (phase == ConfigPhase.RUN_TIME) { + throw reportError(field, "Run time configuration cannot be consumed here"); } else { throw reportError(field, "Unsupported conditional class configuration build phase " + phase); } - setup = setup.andThen( - o -> ReflectUtil.setFieldVal(field, o, confDef.getRealizedInstance(fieldClass))); } else { throw reportError(field, "Unsupported conditional class field type " + fieldClass); } @@ -757,24 +734,27 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe final ConfigPhase phase = annotation.phase(); methodConsumingConfigPhases.add(phase); - if (phase == ConfigPhase.BUILD_TIME) { + if (phase.isAvailableAtBuild()) { methodParamFns.add((bc, bri) -> { - final BuildTimeConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); - }); - } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { - methodParamFns.add((bc, bri) -> { - final BuildTimeRunTimeFixedConfigurationBuildItem configurationBuildItem = bc - .consume(BuildTimeRunTimeFixedConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); + final ConfigurationBuildItem configurationBuildItem = bc + .consume(ConfigurationBuildItem.class); + return configurationBuildItem.getReadResult().requireRootObjectForClass(parameterClass); }); + if (isRecorder && phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + runTimeProxies.computeIfAbsent(parameterClass, readResult::requireRootObjectForClass); + } } else if (phase == ConfigPhase.RUN_TIME) { - methodParamFns.add((bc, bri) -> { - final RunTimeConfigurationBuildItem configurationBuildItem = bc - .consume(RunTimeConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); - }); + if (isRecorder) { + methodParamFns.add((bc, bri) -> { + final RunTimeConfigurationProxyBuildItem proxies = bc + .consume(RunTimeConfigurationProxyBuildItem.class); + return proxies.getProxyObjectFor(parameterClass); + }); + runTimeProxies.computeIfAbsent(parameterClass, ReflectUtil::newInstance); + } else { + throw reportError(parameter, + "Run time configuration cannot be consumed here unless the method is a @Recorder"); + } } else { throw reportError(parameterClass, "Unknown value for ConfigPhase"); } @@ -873,15 +853,12 @@ public static Consumer loadStepsFrom(Class clazz, ConfigDe "Bytecode recorder is static but an injected config object is declared as run time"); } methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(RunTimeConfigurationBuildItem.class)); - } - if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) { - methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(BuildTimeRunTimeFixedConfigurationBuildItem.class)); + .andThen(bsb -> bsb.consumes(RunTimeConfigurationProxyBuildItem.class)); } - if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { + if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED) + || methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(BuildTimeConfigurationBuildItem.class)); + .andThen(bsb -> bsb.consumes(ConfigurationBuildItem.class)); } final Consume[] consumes = method.getAnnotationsByType(Consume.class); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java index ed314d1ae7649f..1b667e9618af17 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/JniProcessor.java @@ -1,6 +1,8 @@ package io.quarkus.deployment; +import java.util.Collections; import java.util.List; +import java.util.Optional; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -19,7 +21,7 @@ static class JniConfig { * Paths of library to load. */ @ConfigItem - List libraryPaths; + Optional> libraryPaths; /** * Enable JNI support. @@ -30,8 +32,8 @@ static class JniConfig { @BuildStep void setupJni(BuildProducer jniProducer) { - if ((jni.enable) || !jni.libraryPaths.isEmpty()) { - jniProducer.produce(new JniBuildItem(jni.libraryPaths)); + if ((jni.enable) || jni.libraryPaths.isPresent()) { + jniProducer.produce(new JniBuildItem(jni.libraryPaths.orElse(Collections.emptyList()))); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java index 96d949aa42914e..9960962fc63cd3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java @@ -12,6 +12,10 @@ import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.configuration.ConfigurationError; +/** + * @deprecated Do not use this class anymore, instead try {@code ConfigProvider.getConfig.getValue()} instead. + */ +@Deprecated public final class QuarkusConfig extends SimpleBuildItem { public static final QuarkusConfig INSTANCE = new QuarkusConfig(); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java index b2f3e696c276ce..46502b45e8d414 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java @@ -1,5 +1,7 @@ package io.quarkus.deployment.builditem; +import java.util.Optional; + import io.quarkus.builder.item.SimpleBuildItem; public final class ApplicationInfoBuildItem extends SimpleBuildItem { @@ -9,16 +11,16 @@ public final class ApplicationInfoBuildItem extends SimpleBuildItem { private final String name; private final String version; - public ApplicationInfoBuildItem(String name, String version) { - this.name = name; - this.version = version; + public ApplicationInfoBuildItem(Optional name, Optional version) { + this.name = name.orElse(UNSET_VALUE); + this.version = version.orElse(UNSET_VALUE); } public String getName() { - return name == null ? UNSET_VALUE : name; + return name; } public String getVersion() { - return version == null ? UNSET_VALUE : version; + return version; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java deleted file mode 100644 index 70d4c58fb3a1de..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the build time configuration. - */ -public final class BuildTimeConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public BuildTimeConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java deleted file mode 100644 index 1c129c15a8a7e6..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the build time configuration that is visible from run time. - */ -public final class BuildTimeRunTimeFixedConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public BuildTimeRunTimeFixedConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java new file mode 100644 index 00000000000000..0885df05f505a5 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; + +/** + * The build item which carries the build time configuration. + */ +public final class ConfigurationBuildItem extends SimpleBuildItem { + private final BuildTimeConfigurationReader.ReadResult readResult; + + public ConfigurationBuildItem(final BuildTimeConfigurationReader.ReadResult readResult) { + this.readResult = readResult; + } + + public BuildTimeConfigurationReader.ReadResult getReadResult() { + return readResult; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java deleted file mode 100644 index 83fe0e8ad0fc19..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.deployment.builditem; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; - -/** - * The build item which carries the run time configuration. - */ -public final class RunTimeConfigurationBuildItem extends SimpleBuildItem { - private final ConfigDefinition configDefinition; - - public RunTimeConfigurationBuildItem(final ConfigDefinition configDefinition) { - this.configDefinition = configDefinition; - } - - public ConfigDefinition getConfigDefinition() { - return configDefinition; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java new file mode 100644 index 00000000000000..7fb9c3a7e12bb3 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationProxyBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.deployment.builditem; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item that carries all the "fake" run time config objects for use by recorders. + */ +public final class RunTimeConfigurationProxyBuildItem extends SimpleBuildItem { + private final Map, Object> objects; + + public RunTimeConfigurationProxyBuildItem(final Map, Object> objects) { + this.objects = objects; + } + + public Object getProxyObjectFor(Class clazz) { + return objects.get(clazz); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java deleted file mode 100644 index efa0d13064d489..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/UnmatchedConfigBuildItem.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.quarkus.deployment.builditem; - -import java.util.Set; - -import org.wildfly.common.Assert; - -import io.quarkus.builder.item.SimpleBuildItem; - -/** - * An internal build item which relays the unmatched configuration key set from the extension loader - * to configuration setup stages. - */ -public final class UnmatchedConfigBuildItem extends SimpleBuildItem { - private final Set set; - - /** - * Construct a new instance. - * - * @param set the non-{@code null}, immutable set - */ - public UnmatchedConfigBuildItem(final Set set) { - Assert.checkNotNullParam("set", set); - this.set = set; - } - - /** - * Get the set. - * - * @return the set - */ - public Set getSet() { - return set; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java deleted file mode 100644 index ec86a06eeeeff4..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class BooleanConfigType extends LeafConfigType { - private static final MethodDescriptor BOOL_VALUE_METHOD = MethodDescriptor.ofMethod(Boolean.class, "booleanValue", - boolean.class); - - final String defaultValue; - private final Class> converterClass; - - public BooleanConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Optional optionalValue = ConfigUtils.getOptionalValue(config, name.toString(), Boolean.class, - converterClass); - field.setBoolean(enclosing, optionalValue.orElse(Boolean.FALSE).booleanValue()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // ConfigUtils.getOptionalValue(config, name.toString(), Boolean.class, converterClass).orElse(Boolean.FALSE).booleanValue() - final ResultHandle optionalValue = body.checkCast(body.invokeStaticMethod( - CU_GET_OPT_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Boolean.class), loadConverterClass(body)), Optional.class); - final ResultHandle convertedDefault = body.readStaticField(FieldDescriptor.of(Boolean.class, "FALSE", Boolean.class)); - final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( - OPT_OR_ELSE_METHOD, - optionalValue, - convertedDefault), Boolean.class); - final ResultHandle booleanValue = body.invokeVirtualMethod(BOOL_VALUE_METHOD, defaultedValue); - body.invokeStaticMethod(setter, enclosing, booleanValue); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class> getConverterClass() { - return converterClass; - } - - @Override - public Class getItemClass() { - return boolean.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Boolean value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Boolean.class, - converterClass); - field.setBoolean(enclosing, value.booleanValue()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle value = body.invokeVirtualMethod(BOOL_VALUE_METHOD, getConvertedDefault(body, cache, config)); - body.invokeStaticMethod(setter, enclosing, value); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(BOOL_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Boolean.class), loadConverterClass(body)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java new file mode 100644 index 00000000000000..4d064a7a0d112e --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BuildTimeConfigurationReader.java @@ -0,0 +1,748 @@ +package io.quarkus.deployment.configuration; + +import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; +import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; +import static io.quarkus.deployment.util.ReflectUtil.reportError; +import static io.quarkus.deployment.util.ReflectUtil.toError; +import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; +import static io.quarkus.deployment.util.ReflectUtil.unwrapInvocationTargetException; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.config.spi.Converter; +import org.jboss.logging.Logger; +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.GroupDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.deployment.configuration.matching.FieldContainer; +import io.quarkus.deployment.configuration.matching.MapContainer; +import io.quarkus.deployment.configuration.matching.PatternMapBuilder; +import io.quarkus.deployment.configuration.type.ArrayOf; +import io.quarkus.deployment.configuration.type.CollectionOf; +import io.quarkus.deployment.configuration.type.ConverterType; +import io.quarkus.deployment.configuration.type.Leaf; +import io.quarkus.deployment.configuration.type.LowerBoundCheckOf; +import io.quarkus.deployment.configuration.type.MinMaxValidated; +import io.quarkus.deployment.configuration.type.OptionalOf; +import io.quarkus.deployment.configuration.type.PatternValidated; +import io.quarkus.deployment.configuration.type.UpperBoundCheckOf; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.ExpandingConfigSource; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; +import io.quarkus.runtime.configuration.NameIterator; +import io.smallrye.config.Converters; +import io.smallrye.config.SmallRyeConfig; + +/** + * A configuration reader. + */ +public final class BuildTimeConfigurationReader { + private static final Logger log = Logger.getLogger("io.quarkus.config.build"); + + final ConfigPatternMap buildTimePatternMap; + final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap runTimePatternMap; + + final List buildTimeVisibleRoots; + final List allRoots; + + /** + * Construct a new instance. + * + * @param configRoots the configuration root class list (must not be {@code null}) + */ + public BuildTimeConfigurationReader(final List> configRoots) { + Assert.checkNotNullParam("configRoots", configRoots); + + List runTimeRoots = new ArrayList<>(); + List buildTimeRunTimeRoots = new ArrayList<>(); + List buildTimeRoots = new ArrayList<>(); + Map, GroupDefinition> groups = new HashMap<>(); + for (Class configRoot : configRoots) { + String name = ConfigItem.HYPHENATED_ELEMENT_NAME; + ConfigPhase phase = ConfigPhase.BUILD_TIME; + ConfigRoot annotation = configRoot.getAnnotation(ConfigRoot.class); + if (annotation != null) { + name = annotation.name(); + phase = annotation.phase(); + } + RootDefinition.Builder defBuilder = new RootDefinition.Builder(); + defBuilder.setConfigPhase(phase); + defBuilder.setRootName(name); + processClass(defBuilder, configRoot, groups); + RootDefinition definition = defBuilder.build(); + if (phase == ConfigPhase.BUILD_TIME) { + buildTimeRoots.add(definition); + } else if (phase == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + buildTimeRunTimeRoots.add(definition); + } else { + assert phase == ConfigPhase.RUN_TIME; + runTimeRoots.add(definition); + } + } + + runTimePatternMap = PatternMapBuilder.makePatterns(runTimeRoots); + buildTimeRunTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRunTimeRoots); + buildTimePatternMap = PatternMapBuilder.makePatterns(buildTimeRoots); + + buildTimeVisibleRoots = new ArrayList<>(buildTimeRoots.size() + buildTimeRunTimeRoots.size()); + buildTimeVisibleRoots.addAll(buildTimeRoots); + buildTimeVisibleRoots.addAll(buildTimeRunTimeRoots); + + List allRoots = new ArrayList<>(buildTimeVisibleRoots.size() + runTimeRoots.size()); + allRoots.addAll(buildTimeVisibleRoots); + allRoots.addAll(runTimeRoots); + + this.allRoots = allRoots; + } + + private static void processClass(ClassDefinition.Builder builder, Class clazz, + final Map, GroupDefinition> groups) { + builder.setConfigurationClass(clazz); + for (Field field : clazz.getDeclaredFields()) { + int mods = field.getModifiers(); + if (Modifier.isStatic(mods)) { + continue; + } + if (Modifier.isFinal(mods)) { + continue; + } + if (Modifier.isPrivate(mods)) { + throw reportError(field, "Configuration field may not be private"); + } + if (!Modifier.isPublic(mods) || !Modifier.isPublic(clazz.getModifiers())) { + field.setAccessible(true); + } + builder.addMember(processValue(field, field.getGenericType(), groups)); + } + } + + private static ClassDefinition.ClassMember.Specification processValue(Field field, Type valueType, + Map, GroupDefinition> groups) { + + Class valueClass = rawTypeOf(valueType); + final boolean isOptional = valueClass == Optional.class; + + if (valueClass == Map.class) { + if (!(valueType instanceof ParameterizedType)) { + throw reportError(field, "Map values must be parameterized"); + } + Class keyClass = rawTypeOfParameter(valueType, 0); + if (keyClass != String.class) { + throw reportError(field, "Map key types other than String are not yet supported"); + } + final ClassDefinition.ClassMember.Specification nested = processValue(field, typeOfParameter(valueType, 1), groups); + if (nested instanceof ClassDefinition.GroupMember.Specification + && ((ClassDefinition.GroupMember.Specification) nested).isOptional()) { + throw reportError(field, "Group map values may not be optional"); + } + return new ClassDefinition.MapMember.Specification(nested); + } else if (valueClass.getAnnotation(ConfigGroup.class) != null + || isOptional && rawTypeOfParameter(valueType, 0).getAnnotation(ConfigGroup.class) != null) { + Class groupClass; + if (isOptional) { + groupClass = rawTypeOfParameter(valueType, 0); + } else { + groupClass = valueClass; + } + GroupDefinition def = groups.get(groupClass); + if (def == null) { + final GroupDefinition.Builder subBuilder = new GroupDefinition.Builder(); + processClass(subBuilder, groupClass, groups); + groups.put(groupClass, def = subBuilder.build()); + } + return new ClassDefinition.GroupMember.Specification(field, def, isOptional); + } else { + final String defaultDefault; + // primitive values generally get their normal initializers as a default value + if (valueClass == boolean.class) { + defaultDefault = "false"; + } else if (valueClass.isPrimitive() && valueClass != char.class) { + defaultDefault = "0"; + } else { + defaultDefault = null; + } + ConfigItem configItem = field.getAnnotation(ConfigItem.class); + if (configItem != null) { + final String defaultVal = configItem.defaultValue(); + return new ClassDefinition.ItemMember.Specification(field, + defaultVal.equals(ConfigItem.NO_DEFAULT) ? defaultDefault : defaultVal); + } else { + ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class); + if (configProperty != null) { + log.warnf("Using @ConfigProperty for Quarkus configuration items is deprecated " + + "(use @ConfigItem instead) at %s#%s", field.getClass().getName(), field.getName()); + final String defaultVal = configProperty.defaultValue(); + return new ClassDefinition.ItemMember.Specification(field, + defaultVal.equals(ConfigProperty.UNCONFIGURED_VALUE) ? defaultDefault : defaultVal); + } else { + // todo: should we log a warning that there is no annotation for the property, or just allow it? + return new ClassDefinition.ItemMember.Specification(field, defaultDefault); + } + } + } + } + + public ConfigPatternMap getBuildTimePatternMap() { + return buildTimePatternMap; + } + + public ConfigPatternMap getBuildTimeRunTimePatternMap() { + return buildTimeRunTimePatternMap; + } + + public ConfigPatternMap getRunTimePatternMap() { + return runTimePatternMap; + } + + public List getBuildTimeVisibleRoots() { + return buildTimeVisibleRoots; + } + + public List getAllRoots() { + return allRoots; + } + + public ReadResult readConfiguration(final SmallRyeConfig config) { + return new ReadOperation(config).run(); + } + + final class ReadOperation { + final SmallRyeConfig config; + final Set processedNames = new HashSet<>(); + + final Map, Object> objectsByRootClass = new HashMap<>(); + final Map specifiedRunTimeDefaultValues = new TreeMap<>(); + final Map buildTimeRunTimeVisibleValues = new TreeMap<>(); + + final Map> convByType = new HashMap<>(); + + ReadOperation(final SmallRyeConfig config) { + this.config = config; + } + + ReadResult run() { + final StringBuilder nameBuilder; + nameBuilder = new StringBuilder().append("quarkus"); + // eager init first + int len = nameBuilder.length(); + for (RootDefinition root : buildTimeVisibleRoots) { + Class clazz = root.getConfigurationClass(); + Object instance; + try { + Constructor cons = clazz.getDeclaredConstructor(); + cons.setAccessible(true); + instance = cons.newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + objectsByRootClass.put(clazz, instance); + String rootName = root.getRootName(); + nameBuilder.append('.').append(rootName); + readConfigGroup(root, instance, nameBuilder); + nameBuilder.setLength(len); + } + // sweep-up + for (String propertyName : config.getPropertyNames()) { + NameIterator ni = new NameIterator(propertyName); + if (ni.hasNext() && ni.nextSegmentEquals("quarkus")) { + ni.next(); + // build time patterns + Container matched = buildTimePatternMap.match(ni); + if (matched instanceof FieldContainer) { + ni.goToEnd(); + // cursor is located after group property key (if any) + getGroup((FieldContainer) matched, ni); + // we don't have to set the field because the group object init does it for us + continue; + } else if (matched != null) { + assert matched instanceof MapContainer; + // it's a leaf value within a map + // these must always be explicitly set + ni.goToEnd(); + // cursor is located after the map key + final String key = ni.getPreviousSegment(); + final Map map = getMap((MapContainer) matched, ni); + // we always have to set the map entry ourselves + Field field = matched.findField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + map.put(key, config.getValue(propertyName, converter)); + continue; + } + // build time (run time visible) patterns + ni.goToStart(); + ni.next(); + matched = buildTimeRunTimePatternMap.match(ni); + if (matched instanceof FieldContainer) { + ni.goToEnd(); + // cursor is located after group property key (if any) + getGroup((FieldContainer) matched, ni); + buildTimeRunTimeVisibleValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + continue; + } else if (matched != null) { + assert matched instanceof MapContainer; + // it's a leaf value within a map + // these must always be explicitly set + ni.goToEnd(); + // cursor is located after the map key + final String key = ni.getPreviousSegment(); + final Map map = getMap((MapContainer) matched, ni); + // we always have to set the map entry ourselves + Field field = matched.findField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + map.put(key, config.getValue(propertyName, converter)); + // cache the resolved value + buildTimeRunTimeVisibleValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + continue; + } + // run time patterns + ni.goToStart(); + ni.next(); + matched = runTimePatternMap.match(ni); + if (matched != null) { + // it's a specified run-time default (record for later) + boolean old = ExpandingConfigSource.setExpanding(false); + try { + specifiedRunTimeDefaultValues.put(propertyName, + config.getOptionalValue(propertyName, String.class).orElse("")); + } finally { + ExpandingConfigSource.setExpanding(old); + } + } + } + } + return new ReadResult(objectsByRootClass, specifiedRunTimeDefaultValues, buildTimeRunTimeVisibleValues, + buildTimePatternMap, buildTimeRunTimePatternMap, runTimePatternMap, allRoots); + } + + /** + * Get a matched group. The tree node points to the property within the group that was matched. + * + * @param matched the matcher tree node + * @param ni the name iterator, positioned after the group member key (if any) + * @return the (possibly new) group instance + */ + private Object getGroup(FieldContainer matched, NameIterator ni) { + final ClassDefinition.ClassMember classMember = matched.getClassMember(); + ClassDefinition definition = matched.findEnclosingClass(); + Class configurationClass = definition.getConfigurationClass(); + if (definition instanceof RootDefinition) { + // found the root + return objectsByRootClass.get(configurationClass); + } + Container parent = matched.getParent(); + final boolean consume = !classMember.getPropertyName().isEmpty(); + if (consume) { + ni.previous(); + } + // now the cursor is *before* the group member key but after the base group + if (parent instanceof FieldContainer) { + FieldContainer parentClass = (FieldContainer) parent; + Field field = parentClass.findField(); + // the cursor is located after the enclosing group's property name (if any) + Object enclosing = getGroup(parentClass, ni); + // cursor restored to after group member key + if (consume) { + ni.next(); + } + if ((classMember instanceof ClassDefinition.GroupMember) + && ((ClassDefinition.GroupMember) classMember).isOptional()) { + Optional opt; + try { + opt = (Optional) field.get(enclosing); + } catch (IllegalAccessException e) { + throw toError(e); + } + if (opt.isPresent()) { + return opt.get(); + } else { + Object instance = recreateGroup(ni, definition, configurationClass); + try { + field.set(enclosing, Optional.of(instance)); + } catch (IllegalAccessException e) { + throw toError(e); + } + return instance; + } + } else { + try { + return field.get(enclosing); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + } else { + assert parent instanceof MapContainer; + final MapContainer parentMap = (MapContainer) parent; + Map map = getMap(parentMap, ni); + // the base group is a map, so the previous segment is the key of the map + String key = ni.getPreviousSegment(); + Object instance = map.get(key); + if (instance == null) { + instance = recreateGroup(ni, definition, configurationClass); + map.put(key, instance); + } + // cursor restored to after group member key + if (consume) { + ni.next(); + } + return instance; + } + } + + /** + * Get a matched map. The tree node points to the position after the map key. + * + * @param matched the matcher tree node + * @param ni the name iterator, positioned just after the map key; restored on exit + * @return the map + */ + private Map getMap(MapContainer matched, NameIterator ni) { + Container parent = matched.getParent(); + if (parent instanceof FieldContainer) { + FieldContainer parentClass = (FieldContainer) parent; + Field field = parentClass.findField(); + ni.previous(); + // now the cursor is before our map key and after the enclosing group property (if any) + Object instance = getGroup(parentClass, ni); + ni.next(); + // cursor restored + try { + return getFieldAsMap(field, instance); + } catch (IllegalAccessException e) { + throw toError(e); + } + } else { + assert parent instanceof MapContainer; + ni.previous(); + // now the cursor is before our map key and after the enclosing map key + Map map = getMap((MapContainer) parent, ni); + ni.next(); + // cursor restored + String key = ni.getPreviousSegment(); + Map instance = getAsMap(map, key); + if (instance == null) { + instance = new HashMap<>(); + map.put(key, instance); + } + return instance; + } + } + + private Object recreateGroup(final NameIterator ni, final ClassDefinition definition, + final Class configurationClass) { + // re-create this config group + final Object instance; + try { + instance = configurationClass.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + // the name includes everything up to (but not including) the member key + final StringBuilder nameBuilder = new StringBuilder(ni.getAllPreviousSegments()); + readConfigGroup(definition, instance, nameBuilder); + return instance; + } + + @SuppressWarnings("unchecked") + private Map getAsMap(final Map map, final String key) { + return (Map) map.get(key); + } + + @SuppressWarnings("unchecked") + private Map getFieldAsMap(final Field field, final Object instance) throws IllegalAccessException { + return (Map) field.get(instance); + } + + /** + * Read a configuration group, recursing into nested groups and instantiating empty maps. + * + * @param definition the definition of the configuration group + * @param instance the group instance + * @param nameBuilder the name builder (set to the last segment before the current group's property names) + */ + private void readConfigGroup(ClassDefinition definition, Object instance, final StringBuilder nameBuilder) { + for (ClassDefinition.ClassMember member : definition.getMembers()) { + Field field = member.getField(); + if (member instanceof ClassDefinition.MapMember) { + // get these on the sweep-up + try { + field.set(instance, new TreeMap<>()); + } catch (IllegalAccessException e) { + throw toError(e); + } + continue; + } + String propertyName = member.getPropertyName(); + if (member instanceof ClassDefinition.ItemMember) { + ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + int len = nameBuilder.length(); + try { + if (!propertyName.isEmpty()) { + nameBuilder.append('.').append(propertyName); + } + String fullName = nameBuilder.toString(); + if (processedNames.add(fullName)) { + readConfigValue(fullName, leafMember, instance); + } + } finally { + nameBuilder.setLength(len); + } + } else { + assert member instanceof ClassDefinition.GroupMember; + // construct the nested instance + ClassDefinition.GroupMember groupMember = (ClassDefinition.GroupMember) member; + if (groupMember.isOptional()) { + try { + field.set(instance, Optional.empty()); + } catch (IllegalAccessException e) { + throw toError(e); + } + } else { + Class clazz = groupMember.getGroupDefinition().getConfigurationClass(); + Object nestedInstance; + try { + nestedInstance = clazz.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } + try { + field.set(instance, nestedInstance); + } catch (IllegalAccessException e) { + throw toError(e); + } + if (propertyName.isEmpty()) { + readConfigGroup( + groupMember.getGroupDefinition(), nestedInstance, nameBuilder); + } else { + int len = nameBuilder.length(); + try { + nameBuilder.append('.').append(propertyName); + readConfigGroup( + groupMember.getGroupDefinition(), nestedInstance, nameBuilder); + } finally { + nameBuilder.setLength(len); + } + } + } + } + } + } + + private void readConfigValue(String fullName, ClassDefinition.ItemMember member, Object instance) { + Field field = member.getField(); + Converter converter = getConverter(config, field, ConverterType.of(field)); + Object val = config.getValue(fullName, converter); + try { + field.set(instance, val); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Converter getConverter(SmallRyeConfig config, Field field, ConverterType valueType) { + Converter converter = convByType.get(valueType); + if (converter != null) { + return converter; + } + if (valueType instanceof ArrayOf) { + ArrayOf arrayOf = (ArrayOf) valueType; + converter = Converters.newArrayConverter( + getConverter(config, field, arrayOf.getElementType()), + arrayOf.getArrayType()); + } else if (valueType instanceof CollectionOf) { + CollectionOf collectionOf = (CollectionOf) valueType; + Class collectionClass = collectionOf.getCollectionClass(); + final Converter nested = getConverter(config, field, collectionOf.getElementType()); + if (collectionClass == List.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.listFactory()); + } else if (collectionClass == Set.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.setFactory()); + } else if (collectionClass == SortedSet.class) { + converter = Converters.newCollectionConverter(nested, ConfigUtils.sortedSetFactory()); + } else { + throw reportError(field, "Unsupported configuration collection type: %s", collectionClass); + } + } else if (valueType instanceof Leaf) { + Leaf leaf = (Leaf) valueType; + Class> convertWith = leaf.getConvertWith(); + if (convertWith != null) { + try { + final Constructor> ctor; + // TODO: temporary until type param inference is in + if (convertWith == HyphenateEnumConverter.class.asSubclass(Converter.class)) { + ctor = convertWith.getConstructor(Class.class); + converter = ctor.newInstance(valueType.getLeafType()); + } else { + ctor = convertWith.getConstructor(); + converter = ctor.newInstance(); + } + } catch (InstantiationException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } catch (InvocationTargetException e) { + throw unwrapInvocationTargetException(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } + } else { + converter = config.getConverter(leaf.getLeafType()); + } + } else if (valueType instanceof LowerBoundCheckOf) { + // todo: add in bounds checker + converter = getConverter(config, field, ((LowerBoundCheckOf) valueType).getClassConverterType()); + } else if (valueType instanceof UpperBoundCheckOf) { + // todo: add in bounds checker + converter = getConverter(config, field, ((UpperBoundCheckOf) valueType).getClassConverterType()); + } else if (valueType instanceof MinMaxValidated) { + MinMaxValidated minMaxValidated = (MinMaxValidated) valueType; + String min = minMaxValidated.getMin(); + boolean minInclusive = minMaxValidated.isMinInclusive(); + String max = minMaxValidated.getMax(); + boolean maxInclusive = minMaxValidated.isMaxInclusive(); + Converter nestedConverter = getConverter(config, field, minMaxValidated.getNestedType()); + if (min != null) { + if (max != null) { + converter = Converters.rangeValueStringConverter((Converter) nestedConverter, min, minInclusive, max, + maxInclusive); + } else { + converter = Converters.minimumValueStringConverter((Converter) nestedConverter, min, minInclusive); + } + } else { + assert min == null && max != null; + converter = Converters.maximumValueStringConverter((Converter) nestedConverter, max, maxInclusive); + } + } else if (valueType instanceof OptionalOf) { + OptionalOf optionalOf = (OptionalOf) valueType; + converter = Converters.newOptionalConverter(getConverter(config, field, optionalOf.getNestedType())); + } else if (valueType instanceof PatternValidated) { + PatternValidated patternValidated = (PatternValidated) valueType; + converter = Converters.patternValidatingConverter(getConverter(config, field, patternValidated.getNestedType()), + patternValidated.getPatternString()); + } else { + throw Assert.unreachableCode(); + } + convByType.put(valueType, converter); + return converter; + } + } + + public static final class ReadResult { + final Map, Object> objectsByRootClass; + final Map specifiedRunTimeDefaultValues; + final Map buildTimeRunTimeVisibleValues; + final ConfigPatternMap buildTimePatternMap; + final ConfigPatternMap buildTimeRunTimePatternMap; + final ConfigPatternMap runTimePatternMap; + final Map, RootDefinition> runTimeRootsByClass; + final List allRoots; + + ReadResult(final Map, Object> objectsByRootClass, final Map specifiedRunTimeDefaultValues, + final Map buildTimeRunTimeVisibleValues, + final ConfigPatternMap buildTimePatternMap, + final ConfigPatternMap buildTimeRunTimePatternMap, + final ConfigPatternMap runTimePatternMap, final List allRoots) { + this.objectsByRootClass = objectsByRootClass; + this.specifiedRunTimeDefaultValues = specifiedRunTimeDefaultValues; + this.buildTimeRunTimeVisibleValues = buildTimeRunTimeVisibleValues; + this.buildTimePatternMap = buildTimePatternMap; + this.buildTimeRunTimePatternMap = buildTimeRunTimePatternMap; + this.runTimePatternMap = runTimePatternMap; + this.allRoots = allRoots; + Map, RootDefinition> map = new HashMap<>(); + for (RootDefinition root : allRoots) { + map.put(root.getConfigurationClass(), root); + } + runTimeRootsByClass = map; + } + + public Map, Object> getObjectsByRootClass() { + return objectsByRootClass; + } + + public Object requireRootObjectForClass(Class clazz) { + Object obj = objectsByRootClass.get(clazz); + if (obj == null) { + throw new IllegalStateException("No root found for " + clazz); + } + return obj; + } + + public Map getSpecifiedRunTimeDefaultValues() { + return specifiedRunTimeDefaultValues; + } + + public Map getBuildTimeRunTimeVisibleValues() { + return buildTimeRunTimeVisibleValues; + } + + public ConfigPatternMap getBuildTimePatternMap() { + return buildTimePatternMap; + } + + public ConfigPatternMap getBuildTimeRunTimePatternMap() { + return buildTimeRunTimePatternMap; + } + + public ConfigPatternMap getRunTimePatternMap() { + return runTimePatternMap; + } + + public List getAllRoots() { + return allRoots; + } + + public RootDefinition requireRootDefinitionForClass(Class clazz) { + final RootDefinition def = runTimeRootsByClass.get(clazz); + if (def == null) { + throw new IllegalStateException("No root definition found for " + clazz); + } + return def; + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java deleted file mode 100644 index 4cc3734a8d0d4b..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.quarkus.deployment.configuration; - -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A node which contains other nodes. - */ -public abstract class CompoundConfigType extends ConfigType { - CompoundConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - super(containingName, container, consumeSegment); - } - - /** - * Get or create a child instance of this node. - * - * @param name the property name of the child instance (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @param self the instance of this node (must not be {@code null}) - * @param childName the static child name, or {@code null} if the child name is dynamic - * @return the child instance - */ - abstract Object getChildObject(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config, - Object self, String childName); - - abstract ResultHandle generateGetChildObject(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config, - ResultHandle self, String childName); - - /** - * Set a child object on the given instance. - * - * @param name the child property name iterator - * @param self the instance of this configuration type - * @param containingName the child property name - * @param value the child property value - */ - abstract void setChildObject(NameIterator name, Object self, String containingName, Object value); - - abstract void generateSetChildObject(BytecodeCreator body, ResultHandle name, ResultHandle self, String containingName, - ResultHandle value); - - /** - * Get or create the instance of this root, recursively adding it to its parent if necessary. - * - * @param name the name of this property node (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @return the possibly new object instance - */ - abstract Object getOrCreate(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config); - - abstract ResultHandle generateGetOrCreate(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config); - - abstract void acceptConfigurationValueIntoLeaf(LeafConfigType leafType, NameIterator name, - final ExpandingConfigSource.Cache cache, SmallRyeConfig config); - - abstract void generateAcceptConfigurationValueIntoLeaf(BytecodeCreator body, LeafConfigType leafType, ResultHandle name, - final ResultHandle cache, ResultHandle config); -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java deleted file mode 100644 index 427e5c9c1b7c15..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java +++ /dev/null @@ -1,607 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; -import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; -import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; -import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; -import static io.quarkus.runtime.util.StringUtil.hyphenate; -import static io.quarkus.runtime.util.StringUtil.join; -import static io.quarkus.runtime.util.StringUtil.lowerCase; -import static io.quarkus.runtime.util.StringUtil.lowerCaseFirst; -import static io.quarkus.runtime.util.StringUtil.withoutSuffix; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.OptionalLong; -import java.util.Set; -import java.util.TreeMap; - -import org.eclipse.microprofile.config.spi.Converter; -import org.jboss.logging.Logger; -import org.objectweb.asm.Opcodes; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.DescriptorUtils; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; -import io.quarkus.runtime.annotations.ConvertWith; -import io.quarkus.runtime.annotations.DefaultConverter; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.HyphenateEnumConverter; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A configuration definition. This class represents the configuration space as trees of nodes, where each tree - * has a root which recursively contains all of the elements within the configuration. - */ -public class ConfigDefinition extends CompoundConfigType { - private static final Logger log = Logger.getLogger("io.quarkus.config"); - - public static final String NO_CONTAINING_NAME = "<>"; - - private static final String QUARKUS_NAMESPACE = "quarkus"; - - // for now just list the values manually - private static final List FALSE_POSITIVE_QUARKUS_CONFIG_MISSES = Arrays - .asList(QUARKUS_NAMESPACE + ".live-reload.password", QUARKUS_NAMESPACE + ".live-reload.url", - QUARKUS_NAMESPACE + ".debug.generated-classes-dir", QUARKUS_NAMESPACE + ".debug.reflection", - QUARKUS_NAMESPACE + ".build.skip", - QUARKUS_NAMESPACE + ".version", QUARKUS_NAMESPACE + ".profile", QUARKUS_NAMESPACE + ".test.profile", - QUARKUS_NAMESPACE + ".test.native-image-wait-time", - QUARKUS_NAMESPACE + ".test.native-image-profile"); - - private final TreeMap rootObjectsByContainingName = new TreeMap<>(); - private final HashMap, Object> rootObjectsByClass = new HashMap<>(); - private final ConfigPatternMap leafPatterns = new ConfigPatternMap<>(); - private final IdentityHashMap realizedInstances = new IdentityHashMap<>(); - private final TreeMap rootTypesByContainingName = new TreeMap<>(); - private final FieldDescriptor rootField; - private final TreeMap loadedProperties = new TreeMap<>(); - private final boolean deferResolution; - - public ConfigDefinition(final FieldDescriptor rootField, final boolean deferResolution) { - super(null, null, false); - this.deferResolution = deferResolution; - Assert.checkNotNullParam("rootField", rootField); - this.rootField = rootField; - } - - public ConfigDefinition(final FieldDescriptor rootField) { - this(rootField, false); - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - // primitive/leaf values without a config group - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - // primitive/leaf values without a config group - throw Assert.unsupported(); - } - - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - return rootObjectsByContainingName.get(childName); - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - return body.readInstanceField(rootTypesByContainingName.get(childName).getFieldDescriptor(), self); - } - - TreeMap getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - return rootObjectsByContainingName; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - return body.readStaticField(rootField); - } - - void setChildObject(final NameIterator name, final Object self, final String childName, final Object value) { - if (self != rootObjectsByContainingName) - throw new IllegalStateException("Wrong self pointer: " + self); - final RootInfo rootInfo = rootTypesByContainingName.get(childName); - assert rootInfo != null : "Unknown child: " + childName; - assert !rootObjectsByContainingName.containsKey(childName) : "Child added twice: " + childName; - rootObjectsByContainingName.put(childName, value); - rootObjectsByClass.put(rootInfo.getRootClass(), value); - realizedInstances.put(value, new ValueInfo(childName, rootInfo)); - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - // objects should always be pre-initialized - throw Assert.unsupported(); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - throw Assert.unsupported(); - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - throw Assert.unsupported(); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - throw Assert.unsupported(); - } - - public void load() { - loadFrom(leafPatterns); - } - - public void initialize(final SmallRyeConfig config, final ExpandingConfigSource.Cache cache) { - for (Map.Entry entry : rootTypesByContainingName.entrySet()) { - final RootInfo rootInfo = entry.getValue(); - // name iterator and config are always ignored because no root types are ever stored in a map node and no conversion is ever done - // TODO: make a separate create method for root types just to avoid this kind of thing - rootInfo.getRootType().getOrCreate(new NameIterator("ignored", true), cache, config); - } - } - - public void registerConfigRoot(Class configRoot) { - final AccessorFinder accessorFinder = new AccessorFinder(); - final ConfigRoot configRootAnnotation = configRoot.getAnnotation(ConfigRoot.class); - final ConfigPhase configPhase = configRootAnnotation.phase(); - if (configRoot.isAnnotationPresent(ConfigGroup.class)) { - throw reportError(configRoot, "Roots cannot have a @ConfigGroup annotation"); - } - final String containingName; - if (configPhase == ConfigPhase.RUN_TIME) { - containingName = join( - withoutSuffix(lowerCaseFirst(camelHumpsIterator(configRoot.getSimpleName())), "Config", "Configuration", - "RunTimeConfig", "RunTimeConfiguration")); - } else { - containingName = join( - withoutSuffix(lowerCaseFirst(camelHumpsIterator(configRoot.getSimpleName())), "Config", "Configuration", - "BuildTimeConfig", "BuildTimeConfiguration")); - } - final String name = configRootAnnotation.name(); - final String rootName; - if (name.equals(ConfigItem.PARENT)) { - throw reportError(configRoot, "Root cannot inherit parent name because it has no parent"); - } else if (name.equals(ConfigItem.ELEMENT_NAME)) { - rootName = containingName; - } else if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { - rootName = join("-", - withoutSuffix(lowerCase(camelHumpsIterator(configRoot.getSimpleName())), "config", "configuration")); - } else { - rootName = name; - } - if (rootTypesByContainingName.containsKey(containingName)) - throw reportError(configRoot, "Duplicate configuration root name \"" + containingName + "\""); - final GroupConfigType configGroup = processConfigGroup(containingName, this, true, rootName, configRoot, - accessorFinder); - final RootInfo rootInfo = new RootInfo(configRoot, configGroup, FieldDescriptor - .of(DescriptorUtils.getTypeStringFromDescriptorFormat(rootField.getType()), containingName, Object.class), - configPhase); - rootTypesByContainingName.put(containingName, rootInfo); - } - - private GroupConfigType processConfigGroup(final String containingName, final CompoundConfigType container, - final boolean consumeSegment, final String baseKey, final Class configGroupClass, - final AccessorFinder accessorFinder) { - GroupConfigType gct = new GroupConfigType(containingName, container, consumeSegment, configGroupClass, accessorFinder); - final Field[] fields = configGroupClass.getDeclaredFields(); - for (Field field : fields) { - String javadocKey = field.getDeclaringClass().getName().replace("$", ".") + "." + field.getName(); - final int mods = field.getModifiers(); - if (Modifier.isStatic(mods)) { - // ignore static fields - continue; - } - if (Modifier.isFinal(mods)) { - // ignore final fields - continue; - } - final ConfigItem configItemAnnotation = field.getAnnotation(ConfigItem.class); - final String name = configItemAnnotation == null ? hyphenate(field.getName()) : configItemAnnotation.name(); - String subKey; - boolean consume; - if (name.equals(ConfigItem.PARENT)) { - subKey = baseKey; - consume = false; - } else if (name.equals(ConfigItem.ELEMENT_NAME)) { - subKey = baseKey + "." + field.getName(); - consume = true; - } else if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { - subKey = baseKey + "." + hyphenate(field.getName()); - consume = true; - } else { - subKey = baseKey + "." + name; - consume = true; - } - final String defaultValue = configItemAnnotation == null ? ConfigItem.NO_DEFAULT - : configItemAnnotation.defaultValue(); - final Type fieldType = field.getGenericType(); - final Class fieldClass = field.getType(); - if (fieldClass.isAnnotationPresent(ConfigGroup.class)) { - if (!defaultValue.equals(ConfigItem.NO_DEFAULT)) { - throw reportError(field, "Unsupported default value"); - } - gct.addField(processConfigGroup(field.getName(), gct, consume, subKey, fieldClass, accessorFinder)); - } else if (fieldClass.isPrimitive()) { - final LeafConfigType leaf; - if (fieldClass == boolean.class) { - gct.addField(leaf = new BooleanConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "false" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Boolean.class, subKey))); - } else if (fieldClass == int.class) { - gct.addField(leaf = new IntConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Integer.class, subKey))); - } else if (fieldClass == long.class) { - gct.addField(leaf = new LongConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Long.class, subKey))); - } else if (fieldClass == double.class) { - gct.addField(leaf = new DoubleConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Double.class, subKey))); - } else if (fieldClass == float.class) { - gct.addField(leaf = new FloatConfigType(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "0" : defaultValue, javadocKey, subKey, - loadEnhancedConverter(field, Float.class, subKey))); - } else { - throw reportError(field, "Unsupported primitive field type"); - } - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (fieldClass == Map.class) { - if (rawTypeOfParameter(fieldType, 0) != String.class) { - throw reportError(field, "Map key must be " + String.class); - } - - Type mapValueType = typeOfParameter(fieldType, 1); - Class mapValueRawType = rawTypeOf(mapValueType); - addMapField(field, gct, consume, subKey, mapValueType, accessorFinder, javadocKey, mapValueRawType); - } else if (fieldClass == List.class) { - // list leaf class - final LeafConfigType leaf; - ObjectListConfigType objectListConfigType = newObjectListConfigType(field, gct, consume, defaultValue, - javadocKey, subKey); - gct.addField(leaf = objectListConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (fieldClass == Optional.class) { - final LeafConfigType leaf; - // optional config property - OptionalObjectConfigType optionalObjectConfigType = newOptionalObjectConfigType(field, gct, consume, - defaultValue, javadocKey, subKey); - gct.addField(leaf = optionalObjectConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else { - final LeafConfigType leaf; - // it's a plain config property - ObjectConfigType objectConfigType = newObjectConfigType(field, gct, consume, defaultValue, javadocKey, - subKey); - gct.addField(leaf = objectConfigType); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } - } - return gct; - } - - private void addMapField(Field field, GroupConfigType gct, boolean consume, String subKey, Type mapValueType, - AccessorFinder accessorFinder, String javadocKey, Class mapValueRawType) { - final Class> converterClass = loadEnhancedConverter(field, mapValueRawType, subKey); - gct.addField(processMap(field.getName(), gct, field, consume, subKey, mapValueType, accessorFinder, javadocKey, - converterClass)); - } - - private ObjectConfigType newObjectConfigType(Field field, GroupConfigType gct, boolean consume, String defaultValue, - String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - Class fieldClass = (Class) field.getType(); - return new ObjectConfigType<>(field.getName(), gct, consume, - mapDefaultValue(defaultValue, fieldClass), fieldClass, javadocKey, subKey, - loadEnhancedConverter(field, fieldClass, subKey)); - } - - private OptionalObjectConfigType newOptionalObjectConfigType(Field field, GroupConfigType gct, boolean consume, - String defaultValue, String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - final Class optionalType = (Class) rawTypeOfParameter(field.getGenericType(), 0); - return new OptionalObjectConfigType<>(field.getName(), gct, consume, - defaultValue.equals(ConfigItem.NO_DEFAULT) ? "" : defaultValue, optionalType, javadocKey, subKey, - loadEnhancedConverter(field, optionalType, subKey)); - } - - private ObjectListConfigType newObjectListConfigType(Field field, GroupConfigType gct, boolean consume, - String defaultValue, String javadocKey, String subKey) { - @SuppressWarnings("unchecked") - final Class listType = (Class) rawTypeOfParameter(field.getGenericType(), 0); - return new ObjectListConfigType<>(field.getName(), gct, consume, mapDefaultValue(defaultValue, listType), listType, - javadocKey, subKey, loadEnhancedConverter(field, listType, subKey)); - } - - private Class> loadEnhancedConverter(Field field, Class clazz, String configProperty) { - final DefaultConverter defaultConverter = field.getAnnotation(DefaultConverter.class); - final ConvertWith convertWith = field.getAnnotation(ConvertWith.class); - - if (defaultConverter != null && convertWith != null) { - throw new IllegalArgumentException(String.format( - "Duplicate conversion behaviour specified on property %s : %s annotation and %s annotation given", - configProperty, DefaultConverter.class.getName(), ConvertWith.class.getName())); - } - - if (defaultConverter != null) { - return null; // use built in MP converters or custom converters - } - - if (convertWith != null) { - @SuppressWarnings("unchecked") - final Class> converterClass = (Class>) convertWith.value(); - try { - final Method method = converterClass.getMethod("convert", String.class); - final Type type = method.getAnnotatedReturnType().getType(); - if (clazz.isAssignableFrom(rawTypeOf(type))) { - return converterClass; - } - throw new IllegalArgumentException(String.format( - "Invalid converter supplied. Cannot convert %s to %s using the given converter %s", - configProperty, clazz, converterClass)); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(e); - } - } - - if (clazz.isEnum()) { - // clean up with SmallRye Config upgrade - @SuppressWarnings({ "unchecked", "RedundantCast" }) - final Class> converterClass = (Class>) (Class) HyphenateEnumConverter.class; - return converterClass; - } - - return null; // use built in MP converters or custom converters - } - - private MapConfigType processMap(final String containingName, final CompoundConfigType container, - final AnnotatedElement containingElement, final boolean consumeSegment, final String baseKey, - final Type mapValueType, final AccessorFinder accessorFinder, String javadocKey, - Class> converterClass) { - MapConfigType mct = new MapConfigType(containingName, container, consumeSegment); - final Class valueClass = rawTypeOf(mapValueType); - final String subKey = baseKey + ".{*}"; - if (valueClass == Map.class) { - if (!(mapValueType instanceof ParameterizedType)) - throw reportError(containingElement, "Map must be parameterized"); - processMap(NO_CONTAINING_NAME, mct, containingElement, true, subKey, typeOfParameter(mapValueType, 1), - accessorFinder, javadocKey, converterClass); - } else if (valueClass.isAnnotationPresent(ConfigGroup.class)) { - processConfigGroup(NO_CONTAINING_NAME, mct, true, subKey, valueClass, accessorFinder); - } else if (valueClass == List.class) { - if (!(mapValueType instanceof ParameterizedType)) - throw reportError(containingElement, "List must be parameterized"); - @SuppressWarnings("unchecked") - Class listType = (Class) rawTypeOfParameter(mapValueType, 0); - final ObjectListConfigType leaf = new ObjectListConfigType<>(NO_CONTAINING_NAME, mct, consumeSegment, "", - listType, javadocKey, subKey, converterClass); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } else if (valueClass == Optional.class || valueClass == OptionalInt.class || valueClass == OptionalDouble.class - || valueClass == OptionalLong.class) { - throw reportError(containingElement, "Optionals are not allowed as a map value type"); - } else { - // treat as a plain object - @SuppressWarnings("unchecked") - final ObjectConfigType leaf = new ObjectConfigType<>(NO_CONTAINING_NAME, mct, true, "", (Class) valueClass, - javadocKey, subKey, converterClass); - container.getConfigDefinition().getLeafPatterns().addPattern(subKey, leaf); - } - return mct; - } - - private String mapDefaultValue(String defaultValue, Class fieldClass) { - String mappedDefault = defaultValue; - if (defaultValue.equals(ConfigItem.NO_DEFAULT)) { - if (Number.class.isAssignableFrom(fieldClass)) { - mappedDefault = "0"; - } else { - mappedDefault = ""; - } - } - return mappedDefault; - } - - private static IllegalArgumentException reportError(AnnotatedElement e, String msg) { - if (e instanceof Member) { - return new IllegalArgumentException(msg + " at " + e + " of " + ((Member) e).getDeclaringClass()); - } else if (e instanceof Parameter) { - return new IllegalArgumentException(msg + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " - + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); - } else { - return new IllegalArgumentException(msg + " at " + e); - } - } - - public void generateConfigRootClass(ClassOutput classOutput, AccessorFinder accessorFinder) { - try (ClassCreator cc = ClassCreator.builder().classOutput(classOutput) - .className(DescriptorUtils.getTypeStringFromDescriptorFormat(rootField.getType())).superClass(Object.class) - .build()) { - try (MethodCreator ctor = cc.getMethodCreator("", void.class, SmallRyeConfig.class)) { - ctor.setModifiers(Opcodes.ACC_PUBLIC); - final ResultHandle self = ctor.getThis(); - final ResultHandle config = ctor.getMethodParam(0); - ctor.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), self); - final ResultHandle cache = ctor.newInstance(ECS_CACHE_CTOR); - // initialize all fields to defaults - for (RootInfo value : rootTypesByContainingName.values()) { - if (value.getConfigPhase().isAvailableAtRun()) { - final CompoundConfigType rootType = value.getRootType(); - final String containingName = rootType.getContainingName(); - final FieldDescriptor fieldDescriptor = cc.getFieldCreator(containingName, Object.class) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL).getFieldDescriptor(); - ctor.writeInstanceField(fieldDescriptor, self, - rootType.writeInitialization(ctor, accessorFinder, cache, config)); - } - } - ctor.returnValue(null); - } - } - } - - public static void loadConfiguration(final ExpandingConfigSource.Cache cache, SmallRyeConfig config, - final Set unmatched, - ConfigDefinition... definitions) { - for (ConfigDefinition definition : definitions) { - definition.initialize(config, cache); - } - outer: for (String propertyName : config.getPropertyNames()) { - final NameIterator name = new NameIterator(propertyName); - if (name.hasNext()) { - if (name.nextSegmentEquals(QUARKUS_NAMESPACE)) { - name.next(); - for (ConfigDefinition definition : definitions) { - final LeafConfigType leafType = definition.leafPatterns.match(name); - if (leafType != null) { - name.goToEnd(); - final String nameString = name.toString(); - if (definition.deferResolution) { - boolean old = ExpandingConfigSource.setExpanding(false); - try { - leafType.acceptConfigurationValue(name, cache, config); - definition.loadedProperties.put(nameString, - config.getOptionalValue(nameString, String.class).orElse("")); - } finally { - ExpandingConfigSource.setExpanding(old); - } - } else { - leafType.acceptConfigurationValue(name, cache, config); - definition.loadedProperties.put(nameString, - config.getOptionalValue(nameString, String.class).orElse("")); - } - continue outer; - } - } - for (String entry : FALSE_POSITIVE_QUARKUS_CONFIG_MISSES) { - if (propertyName.equals(entry)) { - continue outer; - } - } - log.warnf("Unrecognized configuration key \"%s\" provided", propertyName); - } else { - // non-Quarkus value; capture it in the unmatched map for storage as a default value - unmatched.add(propertyName); - } - } - } - } - - public ConfigPatternMap getLeafPatterns() { - return leafPatterns; - } - - public ConfigDefinition getConfigDefinition() { - return this; - } - - public TreeMap getLoadedProperties() { - return loadedProperties; - } - - private void loadFrom(ConfigPatternMap map) { - final LeafConfigType matched = map.getMatched(); - if (matched != null) { - matched.load(); - } - for (String name : map.childNames()) { - loadFrom(map.getChild(name)); - } - } - - public Object getRealizedInstance(final Class rootClass) { - final Object obj = rootObjectsByClass.get(rootClass); - if (obj == null) { - throw new IllegalArgumentException("Unknown root class: " + rootClass); - } - return obj; - } - - public RootInfo getInstanceInfo(final Object obj) { - final ValueInfo valueInfo = realizedInstances.get(obj); - if (valueInfo == null) - return null; - return valueInfo.getRootInfo(); - } - - public static final class RootInfo { - private final Class rootClass; - private final GroupConfigType rootType; - private final FieldDescriptor fieldDescriptor; - private final ConfigPhase configPhase; - - RootInfo(final Class rootClass, final GroupConfigType rootType, final FieldDescriptor fieldDescriptor, - final ConfigPhase configPhase) { - this.rootClass = rootClass; - this.rootType = rootType; - this.fieldDescriptor = fieldDescriptor; - this.configPhase = configPhase; - } - - public Class getRootClass() { - return rootClass; - } - - public GroupConfigType getRootType() { - return rootType; - } - - public FieldDescriptor getFieldDescriptor() { - return fieldDescriptor; - } - - public ConfigPhase getConfigPhase() { - return configPhase; - } - } - - static final class ValueInfo { - private final String key; - private final RootInfo rootInfo; - - ValueInfo(final String key, final RootInfo rootInfo) { - this.key = key; - this.rootInfo = rootInfo; - } - - String getKey() { - return key; - } - - RootInfo getRootInfo() { - return rootInfo; - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java deleted file mode 100644 index e5f744b32c0359..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public abstract class ConfigType { - static final MethodDescriptor NI_PREV_METHOD = MethodDescriptor.ofMethod(NameIterator.class, "previous", void.class); - - static final MethodDescriptor NI_NEXT_METHOD = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); - - static final MethodDescriptor NI_GET_NEXT_SEGMENT = MethodDescriptor.ofMethod(NameIterator.class, "getNextSegment", - String.class); - - static final MethodDescriptor OBJ_TO_STRING_METHOD = MethodDescriptor.ofMethod(Object.class, "toString", String.class); - - static final MethodDescriptor OPT_OR_ELSE_METHOD = MethodDescriptor.ofMethod(Optional.class, "orElse", Object.class, - Object.class); - - static final MethodDescriptor OPT_OF_NULLABLE_METHOD = MethodDescriptor.ofMethod(Optional.class, "ofNullable", - Optional.class, Object.class); - - static final MethodDescriptor OPT_EMPTY_METHOD = MethodDescriptor.ofMethod(Optional.class, "empty", Optional.class); - - static final MethodDescriptor MAP_PUT_METHOD = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, - Object.class); - - static final MethodDescriptor ECS_CACHE_CTOR = MethodDescriptor.ofConstructor(ExpandingConfigSource.Cache.class); - - /** - * Containing name. This is a field name or a map key, not a configuration key segment; as such, it is - * never {@code null} unless the containing name is intentionally dynamic. - */ - private final String containingName; - /** - * The containing node, or {@code null} if the node is a root. - */ - private final CompoundConfigType container; - /** - * Consume a segment of the name when traversing this node. Always {@code true} if the containing name is dynamic, - * otherwise only {@code true} if the node is a configuration group node with an empty relative name. - */ - private final boolean consumeSegment; - - ConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - this.containingName = containingName; - this.container = container; - this.consumeSegment = consumeSegment; - } - - static IllegalAccessError toError(final IllegalAccessException e) { - IllegalAccessError e2 = new IllegalAccessError(e.getMessage()); - e2.setStackTrace(e.getStackTrace()); - return e2; - } - - static InstantiationError toError(final InstantiationException e) { - InstantiationError e2 = new InstantiationError(e.getMessage()); - e2.setStackTrace(e.getStackTrace()); - return e2; - } - - public String getContainingName() { - return containingName; - } - - public CompoundConfigType getContainer() { - return container; - } - - public T getContainer(Class expect) { - final CompoundConfigType container = getContainer(); - if (expect.isInstance(container)) - return expect.cast(container); - throw new IllegalStateException( - "Container is not a supported type; expected " + expect + " but got " + container.getClass()); - } - - public boolean isConsumeSegment() { - return consumeSegment; - } - - /** - * Load all configuration classes to enable configuration to be instantiated. - * - * @throws ClassNotFoundException if a required class was not found - */ - public abstract void load() throws ClassNotFoundException; - - /** - * A reusable method which returns an exception that can be thrown when a configuration - * node is used without its class being loaded. - * - * @return the not-loaded exception - */ - protected static IllegalStateException notLoadedException() { - return new IllegalStateException("Configuration tree classes not loaded"); - } - - /** - * Get the default value of this type into the enclosing element. - * - * @param enclosing the instance of the enclosing type (must not be {@code null}) - * @param cache - * @param config the configuration (must not be {@code null}) - * @param field the field to read the value into - */ - abstract void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field); - - abstract void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config); - - public abstract ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig); - - public ConfigDefinition getConfigDefinition() { - return container.getConfigDefinition(); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java index a4b851c0d2b003..289f7d4d74a70d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java @@ -5,13 +5,17 @@ import org.eclipse.microprofile.config.spi.ConfigSource; +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; + /** * */ public class DefaultValuesConfigurationSource implements ConfigSource { - private final ConfigPatternMap leafs; + private final ConfigPatternMap leafs; - public DefaultValuesConfigurationSource(final ConfigPatternMap leafs) { + public DefaultValuesConfigurationSource(final ConfigPatternMap leafs) { this.leafs = leafs; } @@ -20,15 +24,19 @@ public Map getProperties() { } public String getValue(final String propertyName) { - final LeafConfigType match = leafs.match(propertyName); - if (match == null) { + if (!propertyName.startsWith("quarkus.")) { return null; } - final String defaultValueString = match.getDefaultValueString(); - if (defaultValueString == null || defaultValueString.isEmpty()) { + final Container match = leafs.match(propertyName.substring(8)); + if (match == null) { return null; } - return defaultValueString; + final ClassDefinition.ClassMember member = match.getClassMember(); + if (member instanceof ClassDefinition.ItemMember) { + final ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + return leafMember.getDefaultValue(); + } + return null; } public String getName() { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java deleted file mode 100644 index 709ed9210176e4..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class DoubleConfigType extends LeafConfigType { - private static final MethodDescriptor DOUBLE_VALUE_METHOD = MethodDescriptor.ofMethod(Double.class, "doubleValue", - double.class); - - final String defaultValue; - private final Class> converterClass; - - public DoubleConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Double value = ConfigUtils.getValue(config, name.toString(), Double.class, converterClass); - field.setDouble(enclosing, value != null ? value.doubleValue() : 0d); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Double doubleValue = ConfigUtils.getValue(config, name.toString(), Double.class, converterClass); - // final double d = doubleValue != null ? doubleValue.doubleValue() : 0d; - final AssignableResultHandle result = body.createVariable(double.class); - final ResultHandle doubleValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Double.class), loadConverterClass(body)), Double.class); - final BranchResult ifNull = body.ifNull(doubleValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0d)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - DOUBLE_VALUE_METHOD, - doubleValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return double.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Double value = ConfigUtils.convert(config, - ExpandingConfigSource.expandValue(defaultValue, cache), Double.class, converterClass); - field.setDouble(enclosing, value != null ? value.doubleValue() : 0d); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Double.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java deleted file mode 100644 index 70bf1c039ffa2c..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class FloatConfigType extends LeafConfigType { - - private static final MethodDescriptor FLOAT_VALUE_METHOD = MethodDescriptor.ofMethod(Float.class, "floatValue", - float.class); - - final String defaultValue; - private final Class> converterClass; - - public FloatConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - final Float value = ConfigUtils.getValue(config, name.toString(), Float.class, converterClass); - field.setFloat(enclosing, value != null ? value.floatValue() : 0f); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Float floatValue = ConfigUtils.getValue(config, name.toString(), Float.class, converterClass); - // final float f = floatValue != null ? floatValue.floatValue() : 0f; - final AssignableResultHandle result = body.createVariable(float.class); - final ResultHandle floatValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Float.class), loadConverterClass(body)), Float.class); - final BranchResult ifNull = body.ifNull(floatValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0f)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - FLOAT_VALUE_METHOD, - floatValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return float.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - final Float value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Float.class, - converterClass); - field.setFloat(enclosing, value != null ? value.floatValue() : 0f); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Float.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java deleted file mode 100644 index a26c35c3323c52..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java +++ /dev/null @@ -1,300 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeSet; - -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A configuration definition node describing a configuration group. - */ -public class GroupConfigType extends CompoundConfigType { - - private final Map fields; - private final Class class_; - private final Constructor constructor; - private final MethodDescriptor constructorAccessor; - private final Map fieldInfos; - - public GroupConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final Class class_, final AccessorFinder accessorFinder) { - super(containingName, container, consumeSegment); - Assert.checkNotNullParam("containingName", containingName); - Assert.checkNotNullParam("container", container); - Assert.checkNotNullParam("class_", class_); - Assert.checkNotNullParam("accessorFinder", accessorFinder); - fields = new HashMap<>(); - this.class_ = class_; - try { - constructor = class_.getDeclaredConstructor(); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("Constructor of " + class_ + " is missing"); - } - if ((constructor.getModifiers() & Modifier.PRIVATE) != 0) { - throw new IllegalArgumentException("Constructor of " + class_ + " must not be private"); - } else if ((constructor.getModifiers() & Modifier.PUBLIC) == 0) { - constructor.setAccessible(true); - } - constructorAccessor = accessorFinder.getConstructorFor(MethodDescriptor.ofConstructor(class_)); - fieldInfos = new HashMap<>(); - for (Field field : class_.getDeclaredFields()) { - int modifiers = field.getModifiers(); - if ((modifiers & Modifier.STATIC) == 0) { - // consider this one - if ((modifiers & Modifier.PRIVATE) != 0) { - throw new IllegalArgumentException( - "Field \"" + field.getName() + "\" of " + class_ + " must not be private"); - } - field.setAccessible(true); - final FieldDescriptor descr = FieldDescriptor.of(field); - fieldInfos.put(field.getName(), - new FieldInfo(field, accessorFinder.getSetterFor(descr), accessorFinder.getGetterFor(descr))); - } - } - } - - public void load() throws ClassNotFoundException { - assert class_ != null && constructor != null; - if (!fieldInfos.keySet().containsAll(fields.keySet())) { - final TreeSet missing = new TreeSet<>(fields.keySet()); - missing.removeAll(fieldInfos.keySet()); - throw new IllegalArgumentException("Fields missing from " + class_ + ": " + missing); - } - if (!fields.keySet().containsAll(fieldInfos.keySet())) { - final TreeSet extra = new TreeSet<>(fieldInfos.keySet()); - extra.removeAll(fields.keySet()); - throw new IllegalArgumentException("Extra unknown fields on " + class_ + ": " + extra); - } - for (ConfigType node : fields.values()) { - node.load(); - } - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - final ResultHandle instance = body - .invokeStaticMethod(accessorFinder.getConstructorFor(MethodDescriptor.ofConstructor(class_))); - for (Map.Entry entry : fields.entrySet()) { - final String fieldName = entry.getKey(); - final ConfigType fieldType = entry.getValue(); - final FieldDescriptor fieldDescriptor = FieldDescriptor.of(fieldInfos.get(fieldName).getField()); - final ResultHandle value = fieldType.writeInitialization(body, accessorFinder, cache, smallRyeConfig); - body.invokeStaticMethod(accessorFinder.getSetterFor(fieldDescriptor), instance, value); - } - return instance; - } - - public ConfigType getField(String name) { - return fields.get(name); - } - - public void addField(ConfigType node) { - final String containingName = node.getContainingName(); - final ConfigType existing = fields.putIfAbsent(containingName, node); - if (existing != null) { - throw new IllegalArgumentException("Cannot add duplicate field \"" + containingName + "\" to " + this); - } - } - - private Field findField(final String name) { - if (class_ == null) - throw notLoadedException(); - final FieldInfo fieldInfo = fieldInfos.get(name); - if (fieldInfo == null) - throw new IllegalStateException("Missing field " + name + " on " + class_); - return fieldInfo.getField(); - } - - private Object create(final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - Object self; - try { - self = constructor.newInstance(); - } catch (InstantiationException e) { - throw toError(e); - } catch (IllegalAccessException e) { - throw toError(e); - } catch (InvocationTargetException e) { - try { - throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; - } catch (Throwable t) { - throw new UndeclaredThrowableException(t); - } - } - for (Map.Entry entry : fields.entrySet()) { - entry.getValue().getDefaultValueIntoEnclosingGroup(self, cache, config, findField(entry.getKey())); - } - return self; - } - - private ResultHandle generateCreate(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - final ResultHandle self = body.invokeStaticMethod(constructorAccessor); - for (Map.Entry entry : fields.entrySet()) { - final ConfigType childType = entry.getValue(); - final MethodDescriptor setter = fieldInfos.get(entry.getKey()).getSetter(); - childType.generateGetDefaultValueIntoEnclosingGroup(body, self, setter, cache, config); - } - return self; - } - - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - final Field field = findField(childName); - Object val = getFromField(field, self); - if (val == null) { - final ConfigType childType = getField(childName); - childType.getDefaultValueIntoEnclosingGroup(self, cache, config, field); - val = getFromField(field, self); - } - return val; - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - final AssignableResultHandle val = body.createVariable(Object.class); - final FieldInfo fieldInfo = fieldInfos.get(childName); - body.assign(val, body.invokeStaticMethod(fieldInfo.getGetter(), self)); - try (BytecodeCreator isNull = body.ifNull(val).trueBranch()) { - final ConfigType childType = getField(childName); - childType.generateGetDefaultValueIntoEnclosingGroup(isNull, self, fieldInfo.getSetter(), cache, config); - isNull.assign(val, isNull.invokeStaticMethod(fieldInfo.getGetter(), self)); - } - return val; - } - - private static Object getFromField(Field field, Object obj) { - try { - return field.get(obj); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - Object getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - final Object enclosing = container.getOrCreate(name, cache, config); - Object self = container.getChildObject(name, cache, config, enclosing, getContainingName()); - if (isConsumeSegment()) - name.next(); - if (self == null) { - // it's a map, and it doesn't contain our key. - self = create(cache, config); - if (isConsumeSegment()) - name.previous(); - container.setChildObject(name, enclosing, getContainingName(), self); - if (isConsumeSegment()) - name.next(); - } - return self; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - final ResultHandle enclosing = container.generateGetOrCreate(body, name, cache, config); - final AssignableResultHandle var = body.createVariable(Object.class); - body.assign(var, container.generateGetChildObject(body, name, cache, config, enclosing, getContainingName())); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_NEXT_METHOD, name); - if (container.getClass() == MapConfigType.class) { - // it could be null - try (BytecodeCreator createBranch = body.ifNull(var).trueBranch()) { - createBranch.assign(var, generateCreate(createBranch, cache, config)); - if (isConsumeSegment()) - createBranch.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateSetChildObject(createBranch, name, enclosing, getContainingName(), var); - if (isConsumeSegment()) - createBranch.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - } - return var; - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - final FieldInfo fieldInfo = fieldInfos.get(leafType.getContainingName()); - leafType.acceptConfigurationValueIntoGroup(getOrCreate(name, cache, config), fieldInfo.getField(), name, config); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - final FieldInfo fieldInfo = fieldInfos.get(leafType.getContainingName()); - leafType.generateAcceptConfigurationValueIntoGroup(body, generateGetOrCreate(body, name, cache, config), - fieldInfo.getSetter(), - name, config); - } - - void setChildObject(final NameIterator name, final Object self, final String containingName, final Object value) { - try { - findField(containingName).set(self, value); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - body.invokeStaticMethod(fieldInfos.get(containingName).getSetter(), self, value); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - field.set(enclosing, create(cache, config)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle self = generateCreate(body, cache, config); - body.invokeStaticMethod(setter, enclosing, self); - } - - static final class FieldInfo { - private final Field field; - private final MethodDescriptor setter; - private final MethodDescriptor getter; - - public FieldInfo(final Field field, final MethodDescriptor setter, final MethodDescriptor getter) { - this.field = field; - this.setter = setter; - this.getter = getter; - } - - public Field getField() { - return field; - } - - public MethodDescriptor getSetter() { - return setter; - } - - public MethodDescriptor getGetter() { - return getter; - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java deleted file mode 100644 index f4e85da179f400..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class IntConfigType extends LeafConfigType { - private static final MethodDescriptor INT_VALUE_METHOD = MethodDescriptor.ofMethod(Integer.class, "intValue", int.class); - - final String defaultValue; - private final Class> converterClass; - - public IntConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Integer value = ConfigUtils.getValue(config, name.toString(), Integer.class, converterClass); - field.setInt(enclosing, value != null ? value.intValue() : 0); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Integer integerValue = ConfigUtils.getValue(config, name.toString(), Integer.class, converterClass); - // final int i = integerValue != null ? integerValue.intValue() : 0; - final AssignableResultHandle result = body.createVariable(int.class); - final ResultHandle integerValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Integer.class), loadConverterClass(body)), Integer.class); - final BranchResult ifNull = body.ifNull(integerValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - INT_VALUE_METHOD, - integerValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return int.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Integer value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), - Integer.class, converterClass); - field.setInt(enclosing, value != null ? value.intValue() : 0); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Integer.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java deleted file mode 100644 index 4ff48cdcc5ac6f..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; -import org.wildfly.common.annotation.NotNull; - -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - * A node which contains a regular value. Leaf nodes can never be directly acquired. - */ -public abstract class LeafConfigType extends ConfigType { - static final MethodDescriptor CU_CONVERT = MethodDescriptor.ofMethod(ConfigUtils.class, "convert", Object.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - static final MethodDescriptor CU_GET_VALUE = MethodDescriptor.ofMethod(ConfigUtils.class, "getValue", Object.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - static final MethodDescriptor CU_GET_OPT_VALUE = MethodDescriptor.ofMethod(ConfigUtils.class, "getOptionalValue", - Optional.class, SmallRyeConfig.class, String.class, Class.class, Class.class); - - private final String javadocKey; - private final String configKey; - - LeafConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - String javadocKey, String configKey) { - super(containingName, container, consumeSegment); - this.javadocKey = javadocKey; - this.configKey = configKey; - } - - /** - * - * @return the key that the javadoc was saved under - */ - public String getJavadocKey() { - return javadocKey; - } - - public String getConfigKey() { - return configKey; - } - - public void load() { - } - - /** - * Get the class of the individual item. This is the unwrapped type of {@code Optional}, {@code Collection}, etc. - * - * @return the item class (must not be {@code null}) - */ - public abstract Class getItemClass(); - - /** - * Handle a configuration key from the input file. - * - * @param name the configuration property name - * @param cache - * @param config the source configuration - */ - public abstract void acceptConfigurationValue(@NotNull NameIterator name, final ExpandingConfigSource.Cache cache, - @NotNull SmallRyeConfig config); - - public abstract void generateAcceptConfigurationValue(BytecodeCreator body, ResultHandle name, final ResultHandle cache, - ResultHandle config); - - abstract void acceptConfigurationValueIntoGroup(Object enclosing, Field field, NameIterator name, SmallRyeConfig config); - - abstract void generateAcceptConfigurationValueIntoGroup(BytecodeCreator body, ResultHandle enclosing, - final MethodDescriptor setter, ResultHandle name, ResultHandle config); - - void acceptConfigurationValueIntoMap(Map enclosing, NameIterator name, SmallRyeConfig config) { - // only non-primitives are supported - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoMap(BytecodeCreator body, ResultHandle enclosing, - ResultHandle name, ResultHandle config) { - throw Assert.unsupported(); - } - - public abstract String getDefaultValueString(); - - public abstract Class> getConverterClass(); - - protected final ResultHandle loadConverterClass(BytecodeCreator body) { - Class> converterClass = getConverterClass(); - ResultHandle converter = body.loadNull(); - if (converterClass != null) { - converter = body.loadClass(converterClass); - } - return converter; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java deleted file mode 100644 index 1ca42e42a4c75f..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.steps.ConfigurationSetup; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class LongConfigType extends LeafConfigType { - private static final MethodDescriptor LONG_VALUE_METHOD = MethodDescriptor.ofMethod(Long.class, "longValue", long.class); - - final String defaultValue; - private final Class> converterClass; - - public LongConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, String javadocKey, String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - Assert.checkNotEmptyParam("defaultValue", defaultValue); - this.defaultValue = defaultValue; - this.converterClass = converterClass; - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final GroupConfigType container = getContainer(GroupConfigType.class); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - Long value = ConfigUtils.getValue(config, name.toString(), Long.class, converterClass); - field.setLong(enclosing, value != null ? value.longValue() : 0L); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // final Long longValue = ConfigUtils.getValue(config, name.toString(), Long.class, converterClass); - // final long l = longValue != null ? longValue.longValue() : 0l; - final AssignableResultHandle result = body.createVariable(long.class); - final ResultHandle longValue = body.checkCast(body.invokeStaticMethod( - CU_GET_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(Long.class), loadConverterClass(body)), Long.class); - final BranchResult ifNull = body.ifNull(longValue); - final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, isNull.load(0L)); - final BytecodeCreator isNotNull = ifNull.falseBranch(); - isNotNull.assign(result, - isNotNull.invokeVirtualMethod( - LONG_VALUE_METHOD, - longValue)); - body.invokeStaticMethod(setter, enclosing, result); - } - - public String getDefaultValueString() { - return defaultValue; - } - - @Override - public Class getItemClass() { - return long.class; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - Long value = ConfigUtils.convert(config, ExpandingConfigSource.expandValue(defaultValue, cache), Long.class, - converterClass); - field.setLong(enclosing, value != null ? value.longValue() : 0L); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, config))); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); - } - - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { - return body.invokeStaticMethod( - CU_CONVERT, - config, - cache == null ? body.load(defaultValue) - : body.invokeStaticMethod( - ConfigurationSetup.ECS_EXPAND_VALUE, - body.load(defaultValue), - cache), - body.loadClass(Long.class), loadConverterClass(body)); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java deleted file mode 100644 index a2ce0e360c52a7..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.quarkus.deployment.configuration; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.TreeMap; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class MapConfigType extends CompoundConfigType { - - private static final MethodDescriptor TREE_MAP_CTOR = MethodDescriptor.ofConstructor(TreeMap.class); - private static final MethodDescriptor MAP_GET_METHOD = MethodDescriptor.ofMethod(Map.class, "get", Object.class, - Object.class); - private static final MethodDescriptor MAP_PUT_METHOD = MethodDescriptor.ofMethod(Map.class, "put", Object.class, - Object.class, Object.class); - - public MapConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment) { - super(containingName, container, consumeSegment); - } - - public void load() { - } - - @SuppressWarnings("unchecked") - Object getChildObject(final NameIterator name, final ExpandingConfigSource.Cache cache, final SmallRyeConfig config, - final Object self, final String childName) { - return ((TreeMap) self).get(name.getNextSegment()); - } - - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config, - final ResultHandle self, final String childName) { - return body.invokeInterfaceMethod(MAP_GET_METHOD, body.checkCast(self, Map.class), - body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name)); - } - - @SuppressWarnings("unchecked") - void setChildObject(final NameIterator name, final Object self, final String childName, final Object value) { - ((TreeMap) self).put(name.getNextSegment(), value); - } - - void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle self, - final String containingName, final ResultHandle value) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, body.checkCast(self, Map.class), - body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), value); - } - - TreeMap getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - TreeMap self; - if (container != null) { - if (isConsumeSegment()) - name.previous(); - final Object enclosing = container.getOrCreate(name, cache, config); - self = (TreeMap) container.getChildObject(name, cache, config, enclosing, getContainingName()); - if (self == null) { - self = new TreeMap<>(); - container.setChildObject(name, enclosing, getContainingName(), self); - } - if (isConsumeSegment()) - name.next(); - } else { - self = new TreeMap<>(); - } - return self; - } - - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle cache, - final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (container != null) { - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - final ResultHandle enclosing = container.generateGetOrCreate(body, name, cache, config); - final AssignableResultHandle self = body.createVariable(TreeMap.class); - body.assign(self, body.checkCast( - container.generateGetChildObject(body, name, cache, config, enclosing, getContainingName()), Map.class)); - try (BytecodeCreator selfIsNull = body.ifNull(self).trueBranch()) { - selfIsNull.assign(self, selfIsNull.newInstance(TREE_MAP_CTOR)); - container.generateSetChildObject(selfIsNull, name, enclosing, getContainingName(), self); - } - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_NEXT_METHOD, name); - return self; - } else { - return body.newInstance(TREE_MAP_CTOR); - } - } - - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, - final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { - leafType.acceptConfigurationValueIntoMap(getOrCreate(name, cache, config), name, config); - } - - void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle cache, final ResultHandle config) { - leafType.generateAcceptConfigurationValueIntoMap(body, generateGetOrCreate(body, name, cache, config), name, config); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - return body.newInstance(TREE_MAP_CTOR); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - field.set(enclosing, new TreeMap<>()); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, body.newInstance(TREE_MAP_CTOR)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java deleted file mode 100644 index 90a3c95de31469..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.Map; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class ObjectConfigType extends LeafConfigType { - final String defaultValue; - final Class expectedType; - Class> converterClass; - - public ObjectConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, final Class expectedType, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, javadocKey, configKey); - this.defaultValue = defaultValue; - this.expectedType = expectedType; - this.converterClass = converterClass; - } - - @Override - public Class getItemClass() { - return expectedType; - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - String value = ExpandingConfigSource.expandValue(defaultValue, cache); - field.set(enclosing, ConfigUtils.convert(config, value, expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - ResultHandle resultHandle = getResultHandle(body, cache, config); - body.invokeStaticMethod(setter, enclosing, resultHandle); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle smallRyeConfig) { - ResultHandle resultHandle = getResultHandle(body, cache, smallRyeConfig); - return body.checkCast(resultHandle, expectedType); - } - - private ResultHandle getResultHandle(BytecodeCreator body, ResultHandle cache, ResultHandle smallRyeConfig) { - ResultHandle clazz = body.loadClass(expectedType); - ResultHandle cacheResultHandle = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - - return body.invokeStaticMethod(CU_CONVERT, smallRyeConfig, cacheResultHandle, clazz, loadConverterClass(body)); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - if (isConsumeSegment()) - name.previous(); - getContainer().acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - getContainer().generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, - ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass) - .orElse(null)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, generateGetValue(body, name, config)); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - enclosing.put(name.getNextSegment(), - ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass).orElse(null)); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, enclosing, body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), - generateGetValue(body, name, config)); - } - - public String getDefaultValueString() { - return defaultValue; - } - - private ResultHandle generateGetValue(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { - final ResultHandle optionalValue = body.invokeStaticMethod( - CU_GET_OPT_VALUE, - config, - body.invokeVirtualMethod( - OBJ_TO_STRING_METHOD, - name), - body.loadClass(expectedType), loadConverterClass(body)); - return body.invokeVirtualMethod(OPT_OR_ELSE_METHOD, optionalValue, body.loadNull()); - } - - @Override - public Class> getConverterClass() { - return converterClass; - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java deleted file mode 100644 index d956fe8ece8639..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java +++ /dev/null @@ -1,138 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.IntFunction; - -import org.eclipse.microprofile.config.spi.Converter; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ArrayListFactory; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class ObjectListConfigType extends ObjectConfigType { - static final MethodDescriptor ALF_GET_INST_METHOD = MethodDescriptor.ofMethod(ArrayListFactory.class, "getInstance", - ArrayListFactory.class); - static final MethodDescriptor EMPTY_LIST_METHOD = MethodDescriptor.ofMethod(Collections.class, "emptyList", List.class); - static final MethodDescriptor CU_GET_DEFAULTS_METHOD = MethodDescriptor.ofMethod(ConfigUtils.class, "getDefaults", - Collection.class, SmallRyeConfig.class, String.class, Class.class, Class.class, IntFunction.class); - - static final MethodDescriptor GET_VALUES = MethodDescriptor.ofMethod(ConfigUtils.class, "getValues", ArrayList.class, - SmallRyeConfig.class, String.class, Class.class, Class.class); - - public ObjectListConfigType(final String containingName, final CompoundConfigType container, final boolean consumeSegment, - final String defaultValue, final Class expectedType, String javadocKey, String configKey, - Class> converterClass) { - super(containingName, container, consumeSegment, defaultValue, expectedType, javadocKey, configKey, converterClass); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - if (defaultValue.isEmpty()) { - field.set(enclosing, Collections.emptyList()); - } else { - final ArrayList defaults = ConfigUtils.getDefaults( - config, - ExpandingConfigSource.expandValue(defaultValue, cache), - expectedType, - converterClass, - ArrayListFactory.getInstance()); - field.set(enclosing, defaults); - } - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle value; - if (defaultValue.isEmpty()) { - value = body.invokeStaticMethod(EMPTY_LIST_METHOD); - } else { - ResultHandle cacheValue = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - value = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, cacheValue, body.loadClass(expectedType), - loadConverterClass(body), - body.invokeStaticMethod(ALF_GET_INST_METHOD)); - } - body.invokeStaticMethod(setter, enclosing, value); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, ConfigUtils.getValues(config, name.toString(), expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - body.invokeStaticMethod(setter, enclosing, generateGetValues(body, name, config)); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - enclosing.put(name.getNextSegment(), - ConfigUtils.getValues(config, name.toString(), expectedType, converterClass)); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - body.invokeInterfaceMethod(MAP_PUT_METHOD, enclosing, body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), - generateGetValues(body, name, config)); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle config) { - ResultHandle arrayListFactory = body.invokeStaticMethod(ALF_GET_INST_METHOD); - final ResultHandle resultHandle = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, body.load(defaultValue), - body.loadClass(expectedType), loadConverterClass(body), arrayListFactory); - return body.checkCast(resultHandle, List.class); - } - - private ResultHandle generateGetValues(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { - ResultHandle propertyName = body.invokeVirtualMethod(OBJ_TO_STRING_METHOD, name); - return body.invokeStaticMethod(GET_VALUES, config, propertyName, body.loadClass(expectedType), - loadConverterClass(body)); - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java deleted file mode 100644 index 385269fe68a28f..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java +++ /dev/null @@ -1,120 +0,0 @@ -package io.quarkus.deployment.configuration; - -import static io.quarkus.deployment.steps.ConfigurationSetup.ECS_EXPAND_VALUE; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.microprofile.config.spi.Converter; -import org.wildfly.common.Assert; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.NameIterator; -import io.smallrye.config.SmallRyeConfig; - -/** - */ -public class OptionalObjectConfigType extends ObjectConfigType { - - public OptionalObjectConfigType(final String containingName, final CompoundConfigType container, - final boolean consumeSegment, final String defaultValue, final Class expectedType, String javadocKey, - String configKey, Class> converterClass) { - super(containingName, container, consumeSegment, defaultValue, expectedType, javadocKey, configKey, converterClass); - } - - public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - name.previous(); - container.acceptConfigurationValueIntoLeaf(this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) name.next(); - } - - public void generateAcceptConfigurationValue(final BytecodeCreator body, final ResultHandle name, - final ResultHandle cache, final ResultHandle config) { - final CompoundConfigType container = getContainer(); - if (isConsumeSegment()) - body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); - // the iterator is not used after this point - // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); - } - - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, - final SmallRyeConfig config, final Field field) { - try { - if (defaultValue.isEmpty()) { - field.set(enclosing, Optional.empty()); - } else { - String value = ExpandingConfigSource.expandValue(defaultValue, cache); - field.set(enclosing, - Optional.ofNullable(ConfigUtils.convert(config, value, expectedType, converterClass))); - } - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { - final ResultHandle optValue; - if (defaultValue.isEmpty()) { - optValue = body.invokeStaticMethod(OPT_EMPTY_METHOD); - } else { - optValue = body.invokeStaticMethod(OPT_OF_NULLABLE_METHOD, body.invokeStaticMethod(CU_CONVERT, config, - body.load(defaultValue), body.loadClass(expectedType), loadConverterClass(body))); - } - body.invokeStaticMethod(setter, enclosing, optValue); - } - - public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, - final SmallRyeConfig config) { - try { - field.set(enclosing, ConfigUtils.getOptionalValue(config, name.toString(), expectedType, converterClass)); - } catch (IllegalAccessException e) { - throw toError(e); - } - } - - public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - ResultHandle propertyName = body.invokeVirtualMethod(OBJ_TO_STRING_METHOD, name); - final ResultHandle optionalValue = body.invokeStaticMethod(CU_GET_OPT_VALUE, config, propertyName, - body.loadClass(expectedType), loadConverterClass(body)); - body.invokeStaticMethod(setter, enclosing, optionalValue); - } - - void acceptConfigurationValueIntoMap(final Map enclosing, final NameIterator name, - final SmallRyeConfig config) { - throw Assert.unsupported(); - } - - void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final ResultHandle enclosing, - final ResultHandle name, final ResultHandle config) { - throw Assert.unsupported(); - } - - public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle cache, final ResultHandle config) { - if (defaultValue.isEmpty()) { - return body.invokeStaticMethod(OPT_EMPTY_METHOD); - } else { - ResultHandle classResultHandle = body.loadClass(expectedType); - ResultHandle cacheResultHandle = cache == null ? body.load(defaultValue) - : body.invokeStaticMethod(ECS_EXPAND_VALUE, - body.load(defaultValue), - cache); - ResultHandle resultHandle = body.invokeStaticMethod(CU_CONVERT, config, cacheResultHandle, classResultHandle, - loadConverterClass(body)); - return body.invokeStaticMethod(OPT_OF_NULLABLE_METHOD, resultHandle); - } - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java index 4b20a9f1544444..c8eb215cdb1f60 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/PropertiesUtil.java @@ -4,20 +4,20 @@ public class PropertiesUtil { private PropertiesUtil() { } - public static boolean escape(int codePoint) { + public static boolean needsEscape(int codePoint) { return codePoint == '#' || codePoint == '!' || codePoint == '=' || codePoint == ':'; } - public static boolean escapeForKey(int codePoint) { - return Character.isSpaceChar(codePoint) || escape(codePoint); + public static boolean needsEscapeForKey(int codePoint) { + return Character.isSpaceChar(codePoint) || needsEscape(codePoint); } - public static boolean escapeForValueFirst(int codePoint) { - return escapeForKey(codePoint); + public static boolean needsEscapeForValueFirst(int codePoint) { + return needsEscapeForKey(codePoint); } - public static boolean escapeForValueSubsequent(int codePoint) { - return escape(codePoint); + public static boolean needsEscapeForValueSubsequent(int codePoint) { + return needsEscape(codePoint); } public static String quotePropertyName(String name) { @@ -25,7 +25,7 @@ public static String quotePropertyName(String name) { int cp; for (int i = 0; i < length; i = name.offsetByCodePoints(i, 1)) { cp = name.codePointAt(i); - if (escapeForKey(cp)) { + if (needsEscapeForKey(cp)) { final StringBuilder b = new StringBuilder(length + (length >> 2)); // get leading section b.append(name, 0, i); @@ -33,7 +33,7 @@ public static String quotePropertyName(String name) { b.append('\\').appendCodePoint(cp); for (i = name.offsetByCodePoints(i, 1); i < length; i = name.offsetByCodePoints(i, 1)) { cp = name.codePointAt(i); - if (escapeForKey(cp)) { + if (needsEscapeForKey(cp)) { b.append('\\'); } b.appendCodePoint(cp); @@ -50,7 +50,7 @@ public static String quotePropertyValue(String value) { int cp; for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { cp = value.codePointAt(i); - if (i == 0 ? escapeForValueFirst(cp) : escapeForValueSubsequent(cp)) { + if (i == 0 ? needsEscapeForValueFirst(cp) : needsEscapeForValueSubsequent(cp)) { final StringBuilder b = new StringBuilder(length + (length >> 2)); // get leading section b.append(value, 0, i); @@ -58,7 +58,7 @@ public static String quotePropertyValue(String value) { b.append('\\').appendCodePoint(cp); for (i = value.offsetByCodePoints(i, 1); i < length; i = value.offsetByCodePoints(i, 1)) { cp = value.codePointAt(i); - if (escapeForValueSubsequent(cp)) { + if (needsEscapeForValueSubsequent(cp)) { b.append('\\'); } b.appendCodePoint(cp); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java new file mode 100644 index 00000000000000..432e31ff368b34 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/RunTimeConfigurationGenerator.java @@ -0,0 +1,1215 @@ +package io.quarkus.deployment.configuration; + +import static io.quarkus.deployment.util.ReflectUtil.reportError; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.function.BiFunction; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigBuilder; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.Converter; +import org.objectweb.asm.Opcodes; +import org.wildfly.common.Assert; + +import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.GroupDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.deployment.configuration.matching.FieldContainer; +import io.quarkus.deployment.configuration.matching.MapContainer; +import io.quarkus.deployment.configuration.type.ArrayOf; +import io.quarkus.deployment.configuration.type.CollectionOf; +import io.quarkus.deployment.configuration.type.ConverterType; +import io.quarkus.deployment.configuration.type.Leaf; +import io.quarkus.deployment.configuration.type.LowerBoundCheckOf; +import io.quarkus.deployment.configuration.type.MinMaxValidated; +import io.quarkus.deployment.configuration.type.OptionalOf; +import io.quarkus.deployment.configuration.type.PatternValidated; +import io.quarkus.deployment.configuration.type.UpperBoundCheckOf; +import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.configuration.AbstractRawDefaultConfigSource; +import io.quarkus.runtime.configuration.ConfigDiagnostic; +import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; +import io.quarkus.runtime.configuration.NameIterator; +import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; +import io.smallrye.config.Converters; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigBuilder; + +/** + * + */ +public final class RunTimeConfigurationGenerator { + + public static final String CONFIG_CLASS_NAME = "io.quarkus.runtime.generated.Config"; + static final String RTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.RunTimeDefaultValuesConfigSource"; + static final String BTRTDVCS_CLASS_NAME = "io.quarkus.runtime.generated.BuildTimeRunTimeDefaultValuesConfigSource"; + + // member descriptors + + static final MethodDescriptor BTRTDVCS_NEW = MethodDescriptor.ofConstructor(BTRTDVCS_CLASS_NAME); + + static final FieldDescriptor C_BUILD_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, "buildTimeConfigSource", + ConfigSource.class); + static final FieldDescriptor C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "buildTimeRunTimeDefaultsConfigSource", ConfigSource.class); + public static final MethodDescriptor C_CREATE_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + "createRunTimeConfig", void.class); + public static final MethodDescriptor C_ENSURE_INITIALIZED = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + "ensureInitialized", void.class); + static final FieldDescriptor C_RUN_TIME_DEFAULTS_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "runTimeDefaultsConfigSource", ConfigSource.class); + static final MethodDescriptor C_READ_CONFIG = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "readConfig", void.class); + static final FieldDescriptor C_SPECIFIED_RUN_TIME_CONFIG_SOURCE = FieldDescriptor.of(CONFIG_CLASS_NAME, + "specifiedRunTimeConfigSource", + ConfigSource.class); + + static final MethodDescriptor CD_INVALID_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "invalidValue", + void.class, String.class, IllegalArgumentException.class); + static final MethodDescriptor CD_IS_ERROR = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "isError", + boolean.class); + static final MethodDescriptor CD_MISSING_VALUE = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "missingValue", + void.class, String.class, NoSuchElementException.class); + static final MethodDescriptor CD_RESET_ERROR = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "resetError", void.class); + static final MethodDescriptor CD_UNKNOWN = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "unknown", + void.class, NameIterator.class); + static final MethodDescriptor CD_UNKNOWN_RT = MethodDescriptor.ofMethod(ConfigDiagnostic.class, "unknownRunTime", + void.class, NameIterator.class); + + static final MethodDescriptor CONVS_NEW_ARRAY_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newArrayConverter", Converter.class, Converter.class, Class.class); + static final MethodDescriptor CONVS_NEW_COLLECTION_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newCollectionConverter", Converter.class, Converter.class, IntFunction.class); + static final MethodDescriptor CONVS_NEW_OPTIONAL_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "newOptionalConverter", Converter.class, Converter.class); + static final MethodDescriptor CONVS_RANGE_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "rangeValueStringConverter", Converter.class, Converter.class, String.class, boolean.class, String.class, + boolean.class); + static final MethodDescriptor CONVS_MINIMUM_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "minimumValueStringConverter", Converter.class, Converter.class, String.class, boolean.class); + static final MethodDescriptor CONVS_MAXIMUM_VALUE_STRING_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "maximumValueStringConverter", Converter.class, Converter.class, String.class, boolean.class); + static final MethodDescriptor CONVS_PATTERN_CONVERTER = MethodDescriptor.ofMethod(Converters.class, + "patternConverter", Converter.class, Converter.class, Pattern.class); + + static final MethodDescriptor CPR_GET_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "getConfig", + Config.class); + static final MethodDescriptor CPR_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "instance", + ConfigProviderResolver.class); + static final MethodDescriptor CPR_RELEASE_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, "releaseConfig", + void.class, Config.class); + + static final MethodDescriptor CU_LIST_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "listFactory", + IntFunction.class); + static final MethodDescriptor CU_SET_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "setFactory", + IntFunction.class); + static final MethodDescriptor CU_SORTED_SET_FACTORY = MethodDescriptor.ofMethod(ConfigUtils.class, "sortedSetFactory", + IntFunction.class); + static final MethodDescriptor CU_CONFIG_BUILDER = MethodDescriptor.ofMethod(ConfigUtils.class, "configBuilder", + SmallRyeConfigBuilder.class); + + static final MethodDescriptor HM_NEW = MethodDescriptor.ofConstructor(HashMap.class); + static final MethodDescriptor HM_PUT = MethodDescriptor.ofMethod(HashMap.class, "put", Object.class, Object.class, + Object.class); + + static final MethodDescriptor ITRA_ITERATOR = MethodDescriptor.ofMethod(Iterable.class, "iterator", Iterator.class); + + static final MethodDescriptor ITR_HAS_NEXT = MethodDescriptor.ofMethod(Iterator.class, "hasNext", boolean.class); + static final MethodDescriptor ITR_NEXT = MethodDescriptor.ofMethod(Iterator.class, "next", Object.class); + + static final MethodDescriptor MAP_GET = MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class); + static final MethodDescriptor MAP_PUT = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, + Object.class); + + static final MethodDescriptor NI_GET_ALL_PREVIOUS_SEGMENTS = MethodDescriptor.ofMethod(NameIterator.class, + "getAllPreviousSegments", String.class); + static final MethodDescriptor NI_GET_NAME = MethodDescriptor.ofMethod(NameIterator.class, "getName", String.class); + static final MethodDescriptor NI_GET_PREVIOUS_SEGMENT = MethodDescriptor.ofMethod(NameIterator.class, "getPreviousSegment", + String.class); + static final MethodDescriptor NI_HAS_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "hasNext", boolean.class); + static final MethodDescriptor NI_NEW_STRING = MethodDescriptor.ofConstructor(NameIterator.class, String.class); + static final MethodDescriptor NI_NEXT_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "nextSegmentEquals", + boolean.class, String.class); + static final MethodDescriptor NI_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); + static final MethodDescriptor NI_PREVIOUS = MethodDescriptor.ofMethod(NameIterator.class, "previous", void.class); + static final MethodDescriptor NI_PREVIOUS_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "previousSegmentEquals", + boolean.class, String.class); + + static final MethodDescriptor OBJ_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); + + static final MethodDescriptor OPT_EMPTY = MethodDescriptor.ofMethod(Optional.class, "empty", Optional.class); + static final MethodDescriptor OPT_GET = MethodDescriptor.ofMethod(Optional.class, "get", Object.class); + static final MethodDescriptor OPT_IS_PRESENT = MethodDescriptor.ofMethod(Optional.class, "isPresent", boolean.class); + static final MethodDescriptor OPT_OF = MethodDescriptor.ofMethod(Optional.class, "of", Optional.class, Object.class); + + static final MethodDescriptor PCS_NEW = MethodDescriptor.ofConstructor(PropertiesConfigSource.class, + Map.class, String.class, int.class); + + static final MethodDescriptor PM_SET_RUNTIME_DEFAULT_PROFILE = MethodDescriptor.ofMethod(ProfileManager.class, + "setRuntimeDefaultProfile", void.class, String.class); + + static final MethodDescriptor SB_NEW = MethodDescriptor.ofConstructor(StringBuilder.class); + static final MethodDescriptor SB_NEW_STR = MethodDescriptor.ofConstructor(StringBuilder.class, String.class); + static final MethodDescriptor SB_APPEND_STRING = MethodDescriptor.ofMethod(StringBuilder.class, "append", + StringBuilder.class, String.class); + static final MethodDescriptor SB_APPEND_CHAR = MethodDescriptor.ofMethod(StringBuilder.class, "append", + StringBuilder.class, char.class); + static final MethodDescriptor SB_LENGTH = MethodDescriptor.ofMethod(StringBuilder.class, "length", + int.class); + static final MethodDescriptor SB_SET_LENGTH = MethodDescriptor.ofMethod(StringBuilder.class, "setLength", + void.class, int.class); + + static final MethodDescriptor QCF_SET_CONFIG = MethodDescriptor.ofMethod(QuarkusConfigFactory.class, "setConfig", + void.class, SmallRyeConfig.class); + + static final MethodDescriptor RTDVCS_NEW = MethodDescriptor.ofConstructor(RTDVCS_CLASS_NAME); + + static final MethodDescriptor SRC_GET_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getConverter", + Converter.class, Class.class); + static final MethodDescriptor SRC_GET_PROPERTY_NAMES = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getPropertyNames", + Iterable.class); + static final MethodDescriptor SRC_GET_VALUE = MethodDescriptor.ofMethod(SmallRyeConfig.class, "getValue", + Object.class, String.class, Converter.class); + + static final MethodDescriptor SRCB_WITH_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, + "withSources", ConfigBuilder.class, ConfigSource[].class); + static final MethodDescriptor SRCB_BUILD = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, "build", + SmallRyeConfig.class); + + // todo: more space-efficient sorted map impl + static final MethodDescriptor TM_NEW = MethodDescriptor.ofConstructor(TreeMap.class); + + private RunTimeConfigurationGenerator() { + } + + public static void generate(BuildTimeConfigurationReader.ReadResult readResult, final ClassOutput classOutput, + final Map runTimeDefaults) { + new GenerateOperation.Builder().setBuildTimeReadResult(readResult).setClassOutput(classOutput) + .setRunTimeDefaults(runTimeDefaults).build().run(); + } + + static final class GenerateOperation implements AutoCloseable { + final AccessorFinder accessorFinder; + final ClassOutput classOutput; + final ClassCreator cc; + final MethodCreator clinit; + final BytecodeCreator converterSetup; + final MethodCreator readConfig; + final ResultHandle readConfigNameBuilder; + final ResultHandle clinitNameBuilder; + final BuildTimeConfigurationReader.ReadResult buildTimeConfigResult; + final ConfigPatternMap runTimePatternMap; + final List roots; + // default values given in the build configuration + final Map specifiedRunTimeDefaultValues; + final Map buildTimeRunTimeVisibleValues; + // default values produced by extensions via build item + final Map runTimeDefaults; + final Map enclosingMemberMethods = new HashMap<>(); + final Map, MethodDescriptor> groupInitMethods = new HashMap<>(); + final Map, FieldDescriptor> configRootsByType = new HashMap<>(); + final ResultHandle clinitConfig; + /** + * Regular converters organized by type. Each converter is stored in a separate field. Some are used + * only at build time, some only at run time, and some at both times. + * Producing a native image will automatically delete the converters which are not used at run time from the + * final image. + */ + final Map convertersByType = new HashMap<>(); + /** + * Cache of things created in `clinit` which are then stored in fields, including config roots and converter + * instances. The result handles are usable only from `clinit`. + */ + final Map instanceCache = new HashMap<>(); + /** + * Converter fields have numeric names to keep space down. + */ + int converterIndex = 0; + + GenerateOperation(Builder builder) { + final BuildTimeConfigurationReader.ReadResult buildTimeReadResult = builder.buildTimeReadResult; + buildTimeConfigResult = Assert.checkNotNullParam("buildTimeReadResult", buildTimeReadResult); + specifiedRunTimeDefaultValues = Assert.checkNotNullParam("specifiedRunTimeDefaultValues", + buildTimeReadResult.getSpecifiedRunTimeDefaultValues()); + buildTimeRunTimeVisibleValues = Assert.checkNotNullParam("buildTimeRunTimeVisibleValues", + buildTimeReadResult.getBuildTimeRunTimeVisibleValues()); + classOutput = Assert.checkNotNullParam("classOutput", builder.getClassOutput()); + roots = Assert.checkNotNullParam("builder.roots", builder.getBuildTimeReadResult().getAllRoots()); + runTimeDefaults = Assert.checkNotNullParam("runTimeDefaults", builder.getRunTimeDefaults()); + cc = ClassCreator.builder().classOutput(classOutput).className(CONFIG_CLASS_NAME).setFinal(true).build(); + // not instantiable + try (MethodCreator mc = cc.getMethodCreator(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME))) { + mc.setModifiers(Opcodes.ACC_PRIVATE); + mc.invokeSpecialMethod(MethodDescriptor.ofConstructor(Object.class), mc.getThis()); + mc.returnValue(null); + } + + // create + clinit = cc.getMethodCreator(MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "", void.class)); + clinit.setModifiers(Opcodes.ACC_STATIC); + clinit.invokeStaticMethod(PM_SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); + clinitNameBuilder = clinit.newInstance(SB_NEW); + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load("quarkus.")); + + // create the map for build time config source + final ResultHandle buildTimeValues = clinit.newInstance(HM_NEW); + for (Map.Entry entry : buildTimeRunTimeVisibleValues.entrySet()) { + clinit.invokeVirtualMethod(HM_PUT, buildTimeValues, clinit.load(entry.getKey()), clinit.load(entry.getValue())); + } + + // the build time config source field, to feed into the run time config + cc.getFieldCreator(C_BUILD_TIME_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + final ResultHandle buildTimeConfigSource = clinit.newInstance(PCS_NEW, buildTimeValues, + clinit.load("Build time config"), clinit.load(100)); + clinit.writeStaticField(C_BUILD_TIME_CONFIG_SOURCE, buildTimeConfigSource); + + // the build time run time visible default values config source + cc.getFieldCreator(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(BTRTDVCS_NEW)); + + // the run time default values config source + cc.getFieldCreator(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE, clinit.newInstance(RTDVCS_NEW)); + + // the build time config, which is for user use only (not used by us other than for loading converters) + final ResultHandle buildTimeBuilder = clinit.invokeStaticMethod(CU_CONFIG_BUILDER); + final ResultHandle array = clinit.newArray(ConfigSource[].class, clinit.load(2)); + // build time values + clinit.writeArrayValue(array, 0, buildTimeConfigSource); + // build time defaults + clinit.writeArrayValue(array, 1, clinit.newInstance(BTRTDVCS_NEW)); + clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, buildTimeBuilder, array); + clinitConfig = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, buildTimeBuilder), + SmallRyeConfig.class); + + // block for converter setup + converterSetup = clinit.createScope(); + // create readConfig + readConfig = cc.getMethodCreator(C_READ_CONFIG); + // the readConfig name builder + readConfigNameBuilder = readConfig.newInstance(SB_NEW); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, readConfig.load("quarkus.")); + runTimePatternMap = buildTimeReadResult.getRunTimePatternMap(); + accessorFinder = new AccessorFinder(); + } + + public void run() { + // in clinit, load the build-time config + + // make the build time config global until we read the run time config - + // at run time (when we're ready) we update the factory and then release the build time config + clinit.invokeStaticMethod(QCF_SET_CONFIG, clinitConfig); + // release any previous configuration + final ResultHandle clinitCpr = clinit.invokeStaticMethod(CPR_INSTANCE); + try (TryBlock getConfigTry = clinit.tryBlock()) { + final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, + clinitCpr); + getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, clinitCpr, initialConfigHandle); + // ignore + getConfigTry.addCatch(IllegalStateException.class); + } + + // create the run time config + final ResultHandle runTimeBuilder = readConfig.invokeStaticMethod(CU_CONFIG_BUILDER); + + // create the map for run time specified values config source + final ResultHandle specifiedRunTimeValues = clinit.newInstance(HM_NEW); + for (Map.Entry entry : specifiedRunTimeDefaultValues.entrySet()) { + clinit.invokeVirtualMethod(HM_PUT, specifiedRunTimeValues, clinit.load(entry.getKey()), + clinit.load(entry.getValue())); + } + for (Map.Entry entry : runTimeDefaults.entrySet()) { + if (!specifiedRunTimeDefaultValues.containsKey(entry.getKey())) { + // only add entry if the user didn't override it + clinit.invokeVirtualMethod(HM_PUT, specifiedRunTimeValues, clinit.load(entry.getKey()), + clinit.load(entry.getValue())); + } + } + final ResultHandle specifiedRunTimeSource = clinit.newInstance(PCS_NEW, specifiedRunTimeValues, + clinit.load("Specified default values"), clinit.load(Integer.MIN_VALUE + 100)); + cc.getFieldCreator(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + clinit.writeStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE, specifiedRunTimeSource); + + // add in our custom sources + final ResultHandle array = readConfig.newArray(ConfigSource[].class, readConfig.load(4)); + // build time config (expanded values) + readConfig.writeArrayValue(array, 0, readConfig.readStaticField(C_BUILD_TIME_CONFIG_SOURCE)); + // specified run time config default values + readConfig.writeArrayValue(array, 1, readConfig.readStaticField(C_SPECIFIED_RUN_TIME_CONFIG_SOURCE)); + // run time config default values + readConfig.writeArrayValue(array, 2, readConfig.readStaticField(C_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); + // build time run time visible default config source + readConfig.writeArrayValue(array, 3, readConfig.readStaticField(C_BUILD_TIME_RUN_TIME_DEFAULTS_CONFIG_SOURCE)); + + // put them in the builder + readConfig.invokeVirtualMethod(SRCB_WITH_SOURCES, runTimeBuilder, array); + + final ResultHandle runTimeConfig = readConfig.invokeVirtualMethod(SRCB_BUILD, runTimeBuilder); + // install run time config + readConfig.invokeStaticMethod(QCF_SET_CONFIG, runTimeConfig); + // now invalidate the cached config, so the next one to load the config gets the new one + final ResultHandle configProviderResolver = readConfig.invokeStaticMethod(CPR_INSTANCE); + try (TryBlock getConfigTry = readConfig.tryBlock()) { + final ResultHandle initialConfigHandle = getConfigTry.invokeVirtualMethod(CPR_GET_CONFIG, + configProviderResolver); + getConfigTry.invokeVirtualMethod(CPR_RELEASE_CONFIG, configProviderResolver, initialConfigHandle); + // ignore + getConfigTry.addCatch(IllegalStateException.class); + } + + final ResultHandle clInitOldLen = clinit.invokeVirtualMethod(SB_LENGTH, clinitNameBuilder); + final ResultHandle rcOldLen = readConfig.invokeVirtualMethod(SB_LENGTH, readConfigNameBuilder); + + // generate eager config read (both build and run time at once) + for (RootDefinition root : roots) { + // common things for all config phases + final Class configurationClass = root.getConfigurationClass(); + FieldDescriptor rootFieldDescriptor = root.getDescriptor(); + + // Get or generate group init method + MethodDescriptor initGroup = generateInitGroup(root); + + // specific actions based on config phase + if (root.getConfigPhase() == ConfigPhase.BUILD_AND_RUN_TIME_FIXED) { + // config root field is final; we initialize it from clinit + cc.getFieldCreator(rootFieldDescriptor) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + // construct instance in + final ResultHandle instance = clinit.newInstance(MethodDescriptor.ofConstructor(configurationClass)); + // assign instance to field + clinit.writeStaticField(rootFieldDescriptor, instance); + instanceCache.put(rootFieldDescriptor, instance); + // eager init as appropriate + clinit.invokeVirtualMethod(SB_APPEND_STRING, clinitNameBuilder, clinit.load(root.getRootName())); + clinit.invokeStaticMethod(initGroup, clinitConfig, clinitNameBuilder, instance); + clinit.invokeVirtualMethod(SB_SET_LENGTH, clinitNameBuilder, clInitOldLen); + } else if (root.getConfigPhase() == ConfigPhase.RUN_TIME) { + // config root field is volatile; we initialize and read config from the readConfig method + cc.getFieldCreator(rootFieldDescriptor) + .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); + // construct instance in readConfig + final ResultHandle instance = readConfig.newInstance(MethodDescriptor.ofConstructor(configurationClass)); + // assign instance to field + readConfig.writeStaticField(rootFieldDescriptor, instance); + readConfig.invokeVirtualMethod(SB_APPEND_STRING, readConfigNameBuilder, + readConfig.load(root.getRootName())); + readConfig.invokeStaticMethod(initGroup, runTimeConfig, readConfigNameBuilder, instance); + readConfig.invokeVirtualMethod(SB_SET_LENGTH, readConfigNameBuilder, rcOldLen); + } else { + assert root.getConfigPhase() == ConfigPhase.BUILD_TIME; + // ignore explicitly for now (no eager read for these) + } + configRootsByType.put(configurationClass, rootFieldDescriptor); + } + + ResultHandle nameSet; + ResultHandle iterator; + + // generate sweep for clinit + nameSet = clinit.invokeVirtualMethod(SRC_GET_PROPERTY_NAMES, clinitConfig); + iterator = clinit.invokeInterfaceMethod(ITRA_ITERATOR, nameSet); + + final ConfigPatternMap buildTimePatternMap = buildTimeConfigResult.getBuildTimePatternMap(); + final ConfigPatternMap buildTimeRunTimePatternMap = buildTimeConfigResult + .getBuildTimeRunTimePatternMap(); + final ConfigPatternMap runTimePatternMap = buildTimeConfigResult.getRunTimePatternMap(); + + final BiFunction combinator = (a, b) -> a == null ? b : a; + final ConfigPatternMap buildTimeRunTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, + runTimePatternMap, combinator); + final ConfigPatternMap runTimeIgnored = ConfigPatternMap.merge(buildTimePatternMap, + buildTimeRunTimePatternMap, combinator); + + try (BytecodeCreator sweepLoop = clinit.createScope()) { + try (BytecodeCreator hasNext = sweepLoop.ifNonZero(sweepLoop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)) + .trueBranch()) { + + final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); + // NameIterator keyIter = new NameIterator(key); + final ResultHandle keyIter = hasNext.newInstance(NI_NEW_STRING, key); + // if (! keyIter.hasNext()) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(sweepLoop); + // if (! keyIter.nextSegmentEquals("quarkus")) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))) + .falseBranch().continueScope(sweepLoop); + // keyIter.next(); // skip "quarkus" + hasNext.invokeVirtualMethod(NI_NEXT, keyIter); + // parse(config, keyIter); + hasNext.invokeStaticMethod( + generateParserBody(buildTimeRunTimePatternMap, buildTimeRunTimeIgnored, + new StringBuilder("siParseKey"), + false, false), + clinitConfig, keyIter); + // continue sweepLoop; + hasNext.continueScope(sweepLoop); + } + } + + // generate sweep for run time + nameSet = readConfig.invokeVirtualMethod(SRC_GET_PROPERTY_NAMES, runTimeConfig); + iterator = readConfig.invokeInterfaceMethod(ITRA_ITERATOR, nameSet); + + try (BytecodeCreator sweepLoop = readConfig.createScope()) { + try (BytecodeCreator hasNext = sweepLoop.ifNonZero(sweepLoop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)) + .trueBranch()) { + + final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); + // NameIterator keyIter = new NameIterator(key); + final ResultHandle keyIter = hasNext.newInstance(NI_NEW_STRING, key); + // if (! keyIter.hasNext()) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(sweepLoop); + // if (! keyIter.nextSegmentEquals("quarkus")) continue sweepLoop; + hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))) + .falseBranch().continueScope(sweepLoop); + // keyIter.next(); // skip "quarkus" + hasNext.invokeVirtualMethod(NI_NEXT, keyIter); + // parse(config, keyIter); + hasNext.invokeStaticMethod( + generateParserBody(runTimePatternMap, runTimeIgnored, new StringBuilder("rtParseKey"), + false, true), + runTimeConfig, keyIter); + // continue sweepLoop; + hasNext.continueScope(sweepLoop); + } + } + + // generate ensure-initialized method + try (MethodCreator mc = cc.getMethodCreator(C_ENSURE_INITIALIZED)) { + mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); + mc.returnValue(null); + } + + // generate run time entry point + try (MethodCreator mc = cc.getMethodCreator(C_CREATE_RUN_TIME_CONFIG)) { + mc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); + ResultHandle instance = mc.newInstance(MethodDescriptor.ofConstructor(CONFIG_CLASS_NAME)); + mc.invokeVirtualMethod(C_READ_CONFIG, instance); + mc.returnValue(null); + } + + // wrap it up + final BytecodeCreator isError = readConfig.ifNonZero(readConfig.invokeStaticMethod(CD_IS_ERROR)).trueBranch(); + readConfig.invokeStaticMethod(CD_RESET_ERROR); + isError.throwException(ConfigurationException.class, + "One or more configuration errors has prevented the application from starting"); + readConfig.returnValue(null); + readConfig.close(); + clinit.returnValue(null); + clinit.close(); + cc.close(); + + // generate run time default values config source class + try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(RTDVCS_CLASS_NAME) + .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { + final ResultHandle keyIter = mc.getMethodParam(0); + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) + mc.addAnnotation(Override.class); + final MethodDescriptor md = generateDefaultValueParse(dvcc, runTimePatternMap, + new StringBuilder("getDefaultFor")); + if (md != null) { + // there is at least one default value + final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter)); + try (BytecodeCreator true1 = if1.trueBranch()) { + true1.invokeVirtualMethod(NI_NEXT, keyIter); + final BranchResult if2 = true1 + .ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus"))); + try (BytecodeCreator true2 = if2.trueBranch()) { + final ResultHandle result = true2.invokeVirtualMethod( + md, mc.getThis(), keyIter); + true2.returnValue(result); + } + } + } + + mc.returnValue(mc.loadNull()); + } + } + + // generate build time run time visible default values config source class + try (ClassCreator dvcc = ClassCreator.builder().classOutput(classOutput).className(BTRTDVCS_CLASS_NAME) + .superClass(AbstractRawDefaultConfigSource.class).setFinal(true).build()) { + try (MethodCreator mc = dvcc.getMethodCreator("getValue", String.class, NameIterator.class)) { + final ResultHandle keyIter = mc.getMethodParam(0); + // implements abstract method AbstractRawDefaultConfigSource#getValue(NameIterator) + mc.addAnnotation(Override.class); + final MethodDescriptor md = generateDefaultValueParse(dvcc, buildTimeRunTimePatternMap, + new StringBuilder("getDefaultFor")); + if (md != null) { + // there is at least one default value + final BranchResult if1 = mc.ifNonZero(mc.invokeVirtualMethod(NI_HAS_NEXT, keyIter)); + try (BytecodeCreator true1 = if1.trueBranch()) { + true1.invokeVirtualMethod(NI_NEXT, keyIter); + final BranchResult if2 = true1 + .ifNonZero(true1.invokeVirtualMethod(NI_PREVIOUS_EQUALS, keyIter, true1.load("quarkus"))); + try (BytecodeCreator true2 = if2.trueBranch()) { + final ResultHandle result = true2.invokeVirtualMethod( + md, mc.getThis(), keyIter); + true2.returnValue(result); + } + } + } + + mc.returnValue(mc.loadNull()); + } + } + } + + private MethodDescriptor generateInitGroup(ClassDefinition definition) { + final Class clazz = definition.getConfigurationClass(); + MethodDescriptor methodDescriptor = groupInitMethods.get(clazz); + if (methodDescriptor != null) { + return methodDescriptor; + } + methodDescriptor = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, "initGroup$" + clazz.getName().replace('.', '$'), + void.class, SmallRyeConfig.class, StringBuilder.class, Object.class); + final MethodCreator bc = cc.getMethodCreator(methodDescriptor).setModifiers(Opcodes.ACC_STATIC); + final ResultHandle config = bc.getMethodParam(0); + // on entry, nameBuilder is our name + final ResultHandle nameBuilder = bc.getMethodParam(1); + final ResultHandle instance = bc.getMethodParam(2); + final ResultHandle length = bc.invokeVirtualMethod(SB_LENGTH, nameBuilder); + for (ClassDefinition.ClassMember member : definition.getMembers()) { + // common setup + final String propertyName = member.getPropertyName(); + final MethodDescriptor setter = accessorFinder.getSetterFor(member.getDescriptor()); + if (!propertyName.isEmpty()) { + // append the property name + bc.invokeVirtualMethod(SB_APPEND_CHAR, nameBuilder, bc.load('.')); + bc.invokeVirtualMethod(SB_APPEND_STRING, nameBuilder, bc.load(propertyName)); + } + if (member instanceof ClassDefinition.ItemMember) { + ClassDefinition.ItemMember leafMember = (ClassDefinition.ItemMember) member; + final FieldDescriptor convField = getOrCreateConverterInstance(leafMember.getField()); + final ResultHandle name = bc.invokeVirtualMethod(OBJ_TO_STRING, nameBuilder); + final ResultHandle converter = bc.readStaticField(convField); + try (TryBlock tryBlock = bc.tryBlock()) { + final ResultHandle val = tryBlock.invokeVirtualMethod(SRC_GET_VALUE, config, name, converter); + tryBlock.invokeStaticMethod(setter, instance, val); + try (CatchBlockCreator catchBadValue = tryBlock.addCatch(IllegalArgumentException.class)) { + catchBadValue.invokeStaticMethod(CD_INVALID_VALUE, name, catchBadValue.getCaughtException()); + } + try (CatchBlockCreator catchNoValue = tryBlock.addCatch(NoSuchElementException.class)) { + catchNoValue.invokeStaticMethod(CD_MISSING_VALUE, name, catchNoValue.getCaughtException()); + } + } + } else if (member instanceof ClassDefinition.GroupMember) { + ClassDefinition.GroupMember groupMember = (ClassDefinition.GroupMember) member; + if (groupMember.isOptional()) { + bc.invokeStaticMethod(setter, instance, bc.invokeStaticMethod(OPT_EMPTY)); + } else { + final GroupDefinition groupDefinition = groupMember.getGroupDefinition(); + final MethodDescriptor nested = generateInitGroup(groupDefinition); + final ResultHandle nestedInstance = bc + .newInstance(MethodDescriptor.ofConstructor(groupDefinition.getConfigurationClass())); + bc.invokeStaticMethod(nested, config, nameBuilder, nestedInstance); + bc.invokeStaticMethod(setter, instance, nestedInstance); + } + } else { + assert member instanceof ClassDefinition.MapMember; + final ResultHandle map = bc.newInstance(TM_NEW); + bc.invokeStaticMethod(setter, instance, map); + } + if (!propertyName.isEmpty()) { + // restore length + bc.invokeVirtualMethod(SB_SET_LENGTH, nameBuilder, length); + } + } + bc.returnValue(null); + groupInitMethods.put(clazz, methodDescriptor); + return methodDescriptor; + } + + private static MethodDescriptor generateDefaultValueParse(final ClassCreator dvcc, + final ConfigPatternMap keyMap, final StringBuilder methodName) { + + final Container matched = keyMap.getMatched(); + final boolean hasDefault; + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + hasDefault = itemMember.getDefaultValue() != null; + } else { + hasDefault = false; + } + + final Iterable names = keyMap.childNames(); + final Map children = new HashMap<>(); + MethodDescriptor wildCard = null; + for (String name : names) { + final int length = methodName.length(); + if (name.equals(ConfigPatternMap.WILD_CARD)) { + methodName.append(":*"); + wildCard = generateDefaultValueParse(dvcc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName); + } else { + methodName.append(':').append(name); + final MethodDescriptor value = generateDefaultValueParse(dvcc, keyMap.getChild(name), methodName); + if (value != null) { + children.put(name, value); + } + } + methodName.setLength(length); + } + if (children.isEmpty() && wildCard == null && !hasDefault) { + // skip parse trees with no default values in them + return null; + } + + try (MethodCreator body = dvcc.getMethodCreator(methodName.toString(), String.class, NameIterator.class)) { + body.setModifiers(Opcodes.ACC_PRIVATE); + + final ResultHandle keyIter = body.getMethodParam(0); + // if we've matched the whole thing... + // if (! keyIter.hasNext()) { + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .falseBranch()) { + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + // match? + final String defaultValue = itemMember.getDefaultValue(); + if (defaultValue != null) { + // matched with default value + // return "defaultValue"; + matchedBody.returnValue(matchedBody.load(defaultValue)); + } else { + // matched but no default value + // return null; + matchedBody.returnValue(matchedBody.loadNull()); + } + } else { + // no match + // return null; + matchedBody.returnValue(matchedBody.loadNull()); + } + } + // } + // branches for each next-string + for (String name : children.keySet()) { + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))).trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + // result = getDefault$..$name(keyIter); + ResultHandle result = nameMatched.invokeVirtualMethod(children.get(name), body.getThis(), keyIter); + // return result; + nameMatched.returnValue(result); + } + // } + } + if (wildCard != null) { + // consume and parse + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .trueBranch()) { + // keyIter.next(); + matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + // result = getDefault$..$*(keyIter); + final ResultHandle result = matchedBody.invokeVirtualMethod(wildCard, body.getThis(), keyIter); + // return result; + matchedBody.returnValue(result); + } + } + // unknown + // return null; + body.returnValue(body.loadNull()); + + return body.getMethodDescriptor(); + } + } + + private MethodDescriptor generateParserBody(final ConfigPatternMap keyMap, + final ConfigPatternMap ignoredMap, final StringBuilder methodName, final boolean dynamic, + final boolean isRunTime) { + try (MethodCreator body = cc.getMethodCreator(methodName.toString(), void.class, + SmallRyeConfig.class, NameIterator.class)) { + body.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); + final ResultHandle config = body.getMethodParam(0); + final ResultHandle keyIter = body.getMethodParam(1); + final Container matched = keyMap == null ? null : keyMap.getMatched(); + final Object ignoreMatched = ignoredMap == null ? null : ignoredMap.getMatched(); + // if (! keyIter.hasNext()) { + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .falseBranch()) { + if (matched != null) { + final ClassDefinition.ClassMember member = matched.getClassMember(); + // matched members *must* be item members + assert member instanceof ClassDefinition.ItemMember; + ClassDefinition.ItemMember itemMember = (ClassDefinition.ItemMember) member; + + if (matched instanceof FieldContainer) { + final FieldContainer fieldContainer = (FieldContainer) matched; + if (dynamic) { + if (!itemMember.getPropertyName().isEmpty()) { + // consume segment + matchedBody.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + // we have to get or create all containing (and contained) groups of this member + matchedBody.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), keyIter, + config); + } + // else ignore (already populated eagerly) + } else { + assert matched instanceof MapContainer; + MapContainer mapContainer = (MapContainer) matched; + // map leafs are always dynamic + final ResultHandle lastSeg = matchedBody.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + matchedBody.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle mapHandle = matchedBody + .invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, config); + // populate the map + final Field field = mapContainer.findField(); + final FieldDescriptor fd = getOrCreateConverterInstance(field); + final ResultHandle key = matchedBody.invokeVirtualMethod(NI_GET_NAME, keyIter); + final ResultHandle converter = matchedBody.readStaticField(fd); + final ResultHandle value = matchedBody.invokeVirtualMethod(SRC_GET_VALUE, config, key, converter); + matchedBody.invokeInterfaceMethod(MAP_PUT, mapHandle, lastSeg, value); + } + } else if (ignoreMatched == null) { + // name is unknown + matchedBody.invokeStaticMethod(isRunTime ? CD_UNKNOWN_RT : CD_UNKNOWN, keyIter); + } + // return; + matchedBody.returnValue(null); + } + // } + boolean hasWildCard = false; + // branches for each next-string + if (keyMap != null) { + final Iterable names = keyMap.childNames(); + for (String name : names) { + if (name.equals(ConfigPatternMap.WILD_CARD)) { + hasWildCard = true; + } else { + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))) + .trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(':').append(name); + nameMatched.invokeStaticMethod( + generateParserBody(keyMap.getChild(name), + ignoredMap == null ? null : ignoredMap.getChild(name), methodName, dynamic, + isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + nameMatched.returnValue(null); + } + // } + } + } + } + // branches for each ignored child + if (ignoredMap != null) { + final Iterable names = ignoredMap.childNames(); + for (String name : names) { + if (name.equals(ConfigPatternMap.WILD_CARD)) { + hasWildCard = true; + } else { + final ConfigPatternMap keyChildMap = keyMap == null ? null : keyMap.getChild(name); + if (keyChildMap != null) { + // we already did this one + continue; + } + // TODO: string switch + // if (keyIter.nextSegmentEquals(name)) { + try (BytecodeCreator nameMatched = body + .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))) + .trueBranch()) { + // keyIter.next(); + nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(':').append(name); + nameMatched.invokeStaticMethod( + generateParserBody(null, ignoredMap.getChild(name), methodName, false, isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + nameMatched.returnValue(null); + } + // } + } + } + } + if (hasWildCard) { + assert keyMap != null || ignoredMap != null; + // consume and parse + try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) + .trueBranch()) { + // keyIter.next(); + matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); + // (generated recursive) + final int length = methodName.length(); + methodName.append(":*"); + matchedBody.invokeStaticMethod( + generateParserBody(keyMap == null ? null : keyMap.getChild(ConfigPatternMap.WILD_CARD), + ignoredMap == null ? null : ignoredMap.getChild(ConfigPatternMap.WILD_CARD), + methodName, + true, isRunTime), + config, keyIter); + methodName.setLength(length); + // return; + matchedBody.returnValue(null); + } + } + body.invokeStaticMethod(isRunTime ? CD_UNKNOWN_RT : CD_UNKNOWN, keyIter); + body.returnValue(null); + return body.getMethodDescriptor(); + } + } + + private MethodDescriptor generateGetEnclosing(final FieldContainer matchNode, final boolean isRunTime) { + // name iterator cursor is placed BEFORE the field name on entry + MethodDescriptor md = enclosingMemberMethods.get(matchNode); + if (md != null) { + return md; + } + md = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + (isRunTime ? "rt" : "si") + "GetEnclosing:" + matchNode.getCombinedName(), Object.class, + NameIterator.class, SmallRyeConfig.class); + try (MethodCreator mc = cc.getMethodCreator(md)) { + mc.setModifiers(Opcodes.ACC_STATIC); + final ResultHandle keyIter = mc.getMethodParam(0); + final ResultHandle config = mc.getMethodParam(1); + final ClassDefinition.ClassMember member = matchNode.getClassMember(); + final Container parent = matchNode.getParent(); + if (parent == null) { + // it's a root + final RootDefinition definition = (RootDefinition) member.getEnclosingDefinition(); + FieldDescriptor fieldDescriptor = configRootsByType.get(definition.getConfigurationClass()); + assert fieldDescriptor != null : "Field descriptor defined for " + definition.getConfigurationClass(); + mc.returnValue(mc.readStaticField(fieldDescriptor)); + } else if (parent instanceof FieldContainer) { + // get the parent + final FieldContainer fieldContainer = (FieldContainer) parent; + final ClassDefinition.ClassMember classMember = fieldContainer.getClassMember(); + if (!classMember.getPropertyName().isEmpty()) { + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + final ResultHandle enclosing = mc.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), + keyIter, config); + final MethodDescriptor getter = accessorFinder.getGetterFor(classMember.getDescriptor()); + final ResultHandle fieldVal = mc.invokeStaticMethod(getter, enclosing); + final AssignableResultHandle group = mc.createVariable(Object.class); + if (classMember instanceof ClassDefinition.GroupMember + && ((ClassDefinition.GroupMember) classMember).isOptional()) { + final BranchResult isPresent = mc.ifNonZero(mc.invokeVirtualMethod(OPT_IS_PRESENT, fieldVal)); + final BytecodeCreator trueBranch = isPresent.trueBranch(); + final BytecodeCreator falseBranch = isPresent.falseBranch(); + // it already exists + trueBranch.assign(group, trueBranch.invokeVirtualMethod(OPT_GET, fieldVal)); + // it doesn't exist, recreate it + final ResultHandle instance = falseBranch.newInstance( + MethodDescriptor.ofConstructor(member.getEnclosingDefinition().getConfigurationClass())); + final ResultHandle precedingKey = falseBranch.invokeVirtualMethod(NI_GET_ALL_PREVIOUS_SEGMENTS, + keyIter); + final ResultHandle nameBuilder = falseBranch.newInstance(SB_NEW_STR, precedingKey); + falseBranch.invokeStaticMethod(generateInitGroup(member.getEnclosingDefinition()), config, nameBuilder, + instance); + final MethodDescriptor setter = accessorFinder.getSetterFor(classMember.getDescriptor()); + falseBranch.invokeStaticMethod(setter, fieldVal, falseBranch.invokeStaticMethod(OPT_OF, instance)); + falseBranch.assign(group, instance); + } else { + mc.assign(group, fieldVal); + } + if (!classMember.getPropertyName().isEmpty()) { + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + } + mc.returnValue(group); + } else { + assert parent instanceof MapContainer; + // the map might or might not contain this group + final MapContainer mapContainer = (MapContainer) parent; + final ResultHandle key = mc.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle map = mc.invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, + config); + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + final ResultHandle existing = mc.invokeInterfaceMethod(MAP_GET, map, key); + mc.ifNull(existing).falseBranch().returnValue(existing); + // add the map key and initialize the enclosed item + final ResultHandle instance = mc.newInstance( + MethodDescriptor.ofConstructor(member.getEnclosingDefinition().getConfigurationClass())); + final ResultHandle precedingKey = mc.invokeVirtualMethod(NI_GET_ALL_PREVIOUS_SEGMENTS, keyIter); + final ResultHandle nameBuilder = mc.newInstance(SB_NEW_STR, precedingKey); + mc.invokeStaticMethod(generateInitGroup(member.getEnclosingDefinition()), config, nameBuilder, instance); + mc.invokeInterfaceMethod(MAP_PUT, map, key, instance); + mc.returnValue(instance); + } + } + enclosingMemberMethods.put(matchNode, md); + return md; + } + + private MethodDescriptor generateGetEnclosing(final MapContainer matchNode, final boolean isRunTime) { + // name iterator cursor is placed BEFORE the map key on entry + MethodDescriptor md = enclosingMemberMethods.get(matchNode); + if (md != null) { + return md; + } + md = MethodDescriptor.ofMethod(CONFIG_CLASS_NAME, + (isRunTime ? "rt" : "si") + "GetEnclosing:" + matchNode.getCombinedName(), Object.class, + NameIterator.class, SmallRyeConfig.class); + try (MethodCreator mc = cc.getMethodCreator(md)) { + mc.setModifiers(Opcodes.ACC_STATIC); + final ResultHandle keyIter = mc.getMethodParam(0); + final ResultHandle config = mc.getMethodParam(1); + final Container parent = matchNode.getParent(); + if (parent instanceof FieldContainer) { + // get the parent + final FieldContainer fieldContainer = (FieldContainer) parent; + if (!fieldContainer.getClassMember().getPropertyName().isEmpty()) { + // consume segment + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + } + final ResultHandle enclosing = mc.invokeStaticMethod(generateGetEnclosing(fieldContainer, isRunTime), + keyIter, config); + if (!fieldContainer.getClassMember().getPropertyName().isEmpty()) { + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + } + final MethodDescriptor getter = accessorFinder + .getGetterFor(fieldContainer.getClassMember().getDescriptor()); + mc.returnValue(mc.invokeStaticMethod(getter, enclosing)); + } else { + assert parent instanceof MapContainer; + // the map might or might not contain this map + final MapContainer mapContainer = (MapContainer) parent; + final ResultHandle key = mc.invokeVirtualMethod(NI_GET_PREVIOUS_SEGMENT, keyIter); + // consume enclosing map key + mc.invokeVirtualMethod(NI_PREVIOUS, keyIter); + final ResultHandle map = mc.invokeStaticMethod(generateGetEnclosing(mapContainer, isRunTime), keyIter, + config); + // restore + mc.invokeVirtualMethod(NI_NEXT, keyIter); + final ResultHandle existing = mc.invokeInterfaceMethod(MAP_GET, map, key); + mc.ifNull(existing).falseBranch().returnValue(existing); + // add the map key and initialize the enclosed item + final ResultHandle instance = mc.newInstance(TM_NEW); + mc.invokeInterfaceMethod(MAP_PUT, map, key, instance); + mc.returnValue(instance); + } + } + enclosingMemberMethods.put(matchNode, md); + return md; + } + + private FieldDescriptor getOrCreateConverterInstance(Field field) { + return getOrCreateConverterInstance(field, ConverterType.of(field)); + } + + private FieldDescriptor getOrCreateConverterInstance(Field field, ConverterType type) { + FieldDescriptor fd = convertersByType.get(type); + if (fd != null) { + return fd; + } + + fd = FieldDescriptor.of(cc.getClassName(), "conv$" + converterIndex++, Converter.class); + ResultHandle converter; + if (type instanceof Leaf) { + // simple type + final Leaf leaf = (Leaf) type; + final Class> convertWith = leaf.getConvertWith(); + if (convertWith != null) { + // TODO: temporary until type param inference is in + if (convertWith == HyphenateEnumConverter.class.asSubclass(Converter.class)) { + converter = converterSetup.newInstance(MethodDescriptor.ofConstructor(convertWith, Class.class), + converterSetup.loadClass(type.getLeafType())); + } else { + converter = converterSetup.newInstance(MethodDescriptor.ofConstructor(convertWith)); + } + } else { + final ResultHandle clazzHandle = converterSetup.loadClass(leaf.getLeafType()); + converter = converterSetup.invokeVirtualMethod(SRC_GET_CONVERTER, clinitConfig, clazzHandle); + } + } else if (type instanceof ArrayOf) { + final ArrayOf arrayOf = (ArrayOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, arrayOf.getElementType())); + converter = converterSetup.invokeStaticMethod(CONVS_NEW_ARRAY_CONVERTER, nestedConv, + converterSetup.loadClass(arrayOf.getArrayType())); + } else if (type instanceof CollectionOf) { + final CollectionOf collectionOf = (CollectionOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, collectionOf.getElementType())); + final ResultHandle factory; + final Class collectionClass = collectionOf.getCollectionClass(); + if (collectionClass == List.class) { + factory = converterSetup.invokeStaticMethod(CU_LIST_FACTORY); + } else if (collectionClass == Set.class) { + factory = converterSetup.invokeStaticMethod(CU_SET_FACTORY); + } else if (collectionClass == SortedSet.class) { + factory = converterSetup.invokeStaticMethod(CU_SORTED_SET_FACTORY); + } else { + throw reportError(field, "Unsupported configuration collection type: %s", collectionClass); + } + converter = converterSetup.invokeStaticMethod(CONVS_NEW_COLLECTION_CONVERTER, nestedConv, factory); + } else if (type instanceof LowerBoundCheckOf) { + final LowerBoundCheckOf boundCheckOf = (LowerBoundCheckOf) type; + // todo: add in bounds checker + converter = instanceCache + .get(getOrCreateConverterInstance(field, boundCheckOf.getClassConverterType())); + } else if (type instanceof UpperBoundCheckOf) { + final UpperBoundCheckOf boundCheckOf = (UpperBoundCheckOf) type; + // todo: add in bounds checker + converter = instanceCache + .get(getOrCreateConverterInstance(field, boundCheckOf.getClassConverterType())); + } else if (type instanceof MinMaxValidated) { + MinMaxValidated minMaxValidated = (MinMaxValidated) type; + String min = minMaxValidated.getMin(); + boolean minInclusive = minMaxValidated.isMinInclusive(); + String max = minMaxValidated.getMax(); + boolean maxInclusive = minMaxValidated.isMaxInclusive(); + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, minMaxValidated.getNestedType())); + if (min != null) { + if (max != null) { + converter = converterSetup.invokeStaticMethod( + CONVS_RANGE_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(min), + converterSetup.load(minInclusive), + converterSetup.load(max), + converterSetup.load(maxInclusive)); + } else { + converter = converterSetup.invokeStaticMethod( + CONVS_MINIMUM_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(min), + converterSetup.load(minInclusive)); + } + } else { + assert min == null && max != null; + converter = converterSetup.invokeStaticMethod( + CONVS_MAXIMUM_VALUE_STRING_CONVERTER, + nestedConv, + converterSetup.load(max), + converterSetup.load(maxInclusive)); + } + } else if (type instanceof OptionalOf) { + OptionalOf optionalOf = (OptionalOf) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, optionalOf.getNestedType())); + converter = converterSetup.invokeStaticMethod(CONVS_NEW_OPTIONAL_CONVERTER, nestedConv); + } else if (type instanceof PatternValidated) { + PatternValidated patternValidated = (PatternValidated) type; + final ResultHandle nestedConv = instanceCache + .get(getOrCreateConverterInstance(field, patternValidated.getNestedType())); + final ResultHandle patternStr = converterSetup.load(patternValidated.getPatternString()); + converter = converterSetup.invokeStaticMethod(CONVS_PATTERN_CONVERTER, nestedConv, patternStr); + } else { + throw Assert.unreachableCode(); + } + cc.getFieldCreator(fd).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); + converterSetup.writeStaticField(fd, converter); + convertersByType.put(type, fd); + instanceCache.put(fd, converter); + return fd; + } + + public void close() { + try { + clinit.close(); + } catch (Throwable t) { + try { + cc.close(); + } catch (Throwable t2) { + t2.addSuppressed(t); + throw t2; + } + throw t; + } + cc.close(); + } + + static final class Builder { + private ClassOutput classOutput; + private BuildTimeConfigurationReader.ReadResult buildTimeReadResult; + private Map runTimeDefaults; + + Builder() { + } + + ClassOutput getClassOutput() { + return classOutput; + } + + Builder setClassOutput(final ClassOutput classOutput) { + this.classOutput = classOutput; + return this; + } + + BuildTimeConfigurationReader.ReadResult getBuildTimeReadResult() { + return buildTimeReadResult; + } + + Builder setBuildTimeReadResult(final BuildTimeConfigurationReader.ReadResult buildTimeReadResult) { + this.buildTimeReadResult = buildTimeReadResult; + return this; + } + + Map getRunTimeDefaults() { + return runTimeDefaults; + } + + Builder setRunTimeDefaults(final Map runTimeDefaults) { + this.runTimeDefaults = runTimeDefaults; + return this; + } + + GenerateOperation build() { + return new GenerateOperation(this); + } + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java new file mode 100644 index 00000000000000..3049c764e71a42 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/ClassDefinition.java @@ -0,0 +1,279 @@ +package io.quarkus.deployment.configuration.definition; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.wildfly.common.Assert; + +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.util.StringUtil; + +/** + * + */ +public abstract class ClassDefinition extends Definition { + private final Class configurationClass; + private final Map members; + + ClassDefinition(final Builder builder) { + super(); + final Class configurationClass = builder.configurationClass; + if (configurationClass == null) { + throw new IllegalArgumentException("No configuration class given"); + } + this.configurationClass = configurationClass; + final LinkedHashMap map = new LinkedHashMap<>(builder.members.size()); + for (Map.Entry entry : builder.members.entrySet()) { + map.put(entry.getKey(), entry.getValue().construct(this)); + } + this.members = Collections.unmodifiableMap(map); + } + + public final int getMemberCount() { + return members.size(); + } + + public final Iterable getMemberNames() { + return members.keySet(); + } + + public final Iterable getMembers() { + return members.values(); + } + + public Class getConfigurationClass() { + return configurationClass; + } + + public final ClassMember getMember(String name) { + final ClassMember member = members.get(name); + if (member == null) { + throw new IllegalArgumentException("No member named \"" + name + "\" is present on " + configurationClass); + } + return member; + } + + public static abstract class ClassMember extends Member { + public abstract ClassDefinition getEnclosingDefinition(); + + public final String getName() { + return getField().getName(); + } + + public abstract Field getField(); + + public abstract FieldDescriptor getDescriptor(); + + public abstract String getPropertyName(); + + public static abstract class Specification { + Specification() { + } + + abstract Field getField(); + + abstract ClassMember construct(ClassDefinition enclosing); + } + } + + static abstract class LeafMember extends ClassMember { + private final ClassDefinition classDefinition; + private final Field field; + private final FieldDescriptor descriptor; + private final String propertyName; + + LeafMember(final ClassDefinition classDefinition, final Field field) { + this.classDefinition = Assert.checkNotNullParam("classDefinition", classDefinition); + this.field = Assert.checkNotNullParam("field", field); + final Class declaringClass = field.getDeclaringClass(); + final Class configurationClass = classDefinition.configurationClass; + if (declaringClass != configurationClass) { + throw new IllegalArgumentException( + "Member declaring " + declaringClass + " does not match configuration " + configurationClass); + } + descriptor = FieldDescriptor.of(field); + final ConfigItem configItem = field.getAnnotation(ConfigItem.class); + String propertyName = ConfigItem.HYPHENATED_ELEMENT_NAME; + if (configItem != null) { + propertyName = configItem.name(); + if (propertyName.isEmpty()) { + throw reportError(field, "Invalid empty property name"); + } + } + if (propertyName.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { + this.propertyName = StringUtil.hyphenate(field.getName()); + } else if (propertyName.equals(ConfigItem.ELEMENT_NAME)) { + this.propertyName = field.getName(); + } else if (propertyName.equals(ConfigItem.PARENT)) { + this.propertyName = ""; + } else { + this.propertyName = propertyName; + } + } + + public Field getField() { + return field; + } + + public FieldDescriptor getDescriptor() { + return descriptor; + } + + public ClassDefinition getEnclosingDefinition() { + return classDefinition; + } + + public String getPropertyName() { + return propertyName; + } + + public static abstract class Specification extends ClassMember.Specification { + final Field field; + + Specification(final Field field) { + this.field = Assert.checkNotNullParam("field", field); + } + + Field getField() { + return field; + } + } + } + + public static final class GroupMember extends LeafMember { + private final GroupDefinition groupDefinition; + private final boolean optional; + + GroupMember(final ClassDefinition classDefinition, final Field field, final GroupDefinition groupDefinition, + final boolean optional) { + super(classDefinition, field); + this.groupDefinition = groupDefinition; + this.optional = optional; + } + + public GroupDefinition getGroupDefinition() { + return groupDefinition; + } + + public boolean isOptional() { + return optional; + } + + public static final class Specification extends LeafMember.Specification { + private final GroupDefinition groupDefinition; + private final boolean optional; + + public Specification(final Field field, final GroupDefinition groupDefinition, final boolean optional) { + super(field); + this.groupDefinition = Assert.checkNotNullParam("groupDefinition", groupDefinition); + this.optional = optional; + } + + public boolean isOptional() { + return optional; + } + + ClassMember construct(final ClassDefinition enclosing) { + return new GroupMember(enclosing, field, groupDefinition, optional); + } + } + } + + public static final class ItemMember extends LeafMember { + private final String defaultValue; + + ItemMember(final ClassDefinition classDefinition, final Field field, final String defaultValue) { + super(classDefinition, field); + this.defaultValue = defaultValue; + } + + public String getDefaultValue() { + return defaultValue; + } + + public static final class Specification extends LeafMember.Specification { + private final String defaultValue; + + public Specification(final Field field, final String defaultValue) { + super(field); + // nullable + this.defaultValue = defaultValue; + } + + ClassMember construct(final ClassDefinition enclosing) { + return new ItemMember(enclosing, field, defaultValue); + } + } + } + + public static final class MapMember extends ClassMember { + private final ClassMember nested; + + MapMember(final ClassMember nested) { + this.nested = nested; + } + + public ClassMember getNested() { + return nested; + } + + public ClassDefinition getEnclosingDefinition() { + return nested.getEnclosingDefinition(); + } + + public Field getField() { + return nested.getField(); + } + + public FieldDescriptor getDescriptor() { + return nested.getDescriptor(); + } + + public String getPropertyName() { + return nested.getPropertyName(); + } + + public static final class Specification extends ClassMember.Specification { + private final ClassMember.Specification nested; + + public Specification(final ClassMember.Specification nested) { + this.nested = Assert.checkNotNullParam("nested", nested); + } + + Field getField() { + return nested.getField(); + } + + ClassMember construct(final ClassDefinition enclosing) { + return new MapMember(nested.construct(enclosing)); + } + } + } + + public static abstract class Builder extends Definition.Builder { + Builder() { + } + + private Class configurationClass; + private final Map members = new LinkedHashMap<>(); + + public Builder setConfigurationClass(final Class configurationClass) { + this.configurationClass = configurationClass; + return this; + } + + public Class getConfigurationClass() { + return configurationClass; + } + + public void addMember(ClassMember.Specification spec) { + Assert.checkNotNullParam("spec", spec); + members.put(spec.getField().getName(), spec); + } + + public abstract ClassDefinition build(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java new file mode 100644 index 00000000000000..a6919959dd4872 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/Definition.java @@ -0,0 +1,35 @@ +package io.quarkus.deployment.configuration.definition; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Parameter; + +/** + * A configuration definition. Definitions always contain links to the things they contain, but not to their own + * containers. + */ +public abstract class Definition { + Definition() { + } + + public static abstract class Builder { + Builder() { + } + + public abstract Definition build(); + } + + static IllegalArgumentException reportError(AnnotatedElement e, String msg) { + if (e instanceof Member) { + return new IllegalArgumentException(msg + " at " + e + " of " + ((Member) e).getEnclosingDefinition()); + } else if (e instanceof Parameter) { + return new IllegalArgumentException(msg + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " + + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); + } else { + return new IllegalArgumentException(msg + " at " + e); + } + } + + public static abstract class Member { + public abstract Definition getEnclosingDefinition(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java new file mode 100644 index 00000000000000..549e4f49dedba7 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/GroupDefinition.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.configuration.definition; + +/** + * + */ +public final class GroupDefinition extends ClassDefinition { + GroupDefinition(final Builder builder) { + super(builder); + } + + public static final class Builder extends ClassDefinition.Builder { + public Builder() { + } + + public GroupDefinition build() { + return new GroupDefinition(this); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java new file mode 100644 index 00000000000000..636d28efbba318 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/definition/RootDefinition.java @@ -0,0 +1,108 @@ +package io.quarkus.deployment.configuration.definition; + +import static io.quarkus.deployment.configuration.RunTimeConfigurationGenerator.CONFIG_CLASS_NAME; +import static io.quarkus.runtime.util.StringUtil.camelHumpsIterator; +import static io.quarkus.runtime.util.StringUtil.lowerCase; +import static io.quarkus.runtime.util.StringUtil.lowerCaseFirst; +import static io.quarkus.runtime.util.StringUtil.toList; +import static io.quarkus.runtime.util.StringUtil.withoutSuffix; + +import java.util.List; + +import org.wildfly.common.Assert; + +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; + +/** + * + */ +public final class RootDefinition extends ClassDefinition { + private final ConfigPhase configPhase; + private final String rootName; + private final FieldDescriptor descriptor; + + RootDefinition(final Builder builder) { + super(builder); + this.configPhase = builder.configPhase; + String rootName = builder.rootName; + final Class configClass = getConfigurationClass(); + final List segments = toList(camelHumpsIterator(configClass.getSimpleName())); + final List trimmedSegments; + if (configPhase == ConfigPhase.RUN_TIME) { + trimmedSegments = withoutSuffix( + withoutSuffix( + withoutSuffix( + withoutSuffix( + segments, + "Run", "Time", "Configuration"), + "Run", "Time", "Config"), + "Configuration"), + "Config"); + } else { + trimmedSegments = withoutSuffix( + withoutSuffix( + withoutSuffix( + withoutSuffix( + segments, + "Build", "Time", "Configuration"), + "Build", "Time", "Config"), + "Configuration"), + "Config"); + } + if (rootName.equals(ConfigItem.PARENT)) { + throw reportError(configClass, "Root cannot inherit parent name because it has no parent"); + } else if (rootName.equals(ConfigItem.ELEMENT_NAME)) { + rootName = String.join("", (Iterable) () -> lowerCaseFirst(trimmedSegments.iterator())); + } else if (rootName.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { + rootName = String.join("-", (Iterable) () -> lowerCase(trimmedSegments.iterator())); + } + this.rootName = rootName; + this.descriptor = FieldDescriptor.of(CONFIG_CLASS_NAME, String.join("", segments), Object.class); + } + + public ConfigPhase getConfigPhase() { + return configPhase; + } + + public String getRootName() { + return rootName; + } + + public FieldDescriptor getDescriptor() { + return descriptor; + } + + public static final class Builder extends ClassDefinition.Builder { + private ConfigPhase configPhase = ConfigPhase.BUILD_TIME; + private String rootName = ConfigItem.HYPHENATED_ELEMENT_NAME; + + public Builder() { + } + + public ConfigPhase getConfigPhase() { + return configPhase; + } + + public Builder setConfigPhase(final ConfigPhase configPhase) { + Assert.checkNotNullParam("configPhase", configPhase); + this.configPhase = configPhase; + return this; + } + + public String getRootName() { + return rootName; + } + + public Builder setRootName(final String rootName) { + Assert.checkNotNullParam("rootName", rootName); + this.rootName = rootName; + return this; + } + + public RootDefinition build() { + return new RootDefinition(this); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java similarity index 62% rename from core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java rename to core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java index e2ef97f22b4dd8..fbf525fc1c6bfc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigPatternMap.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/ConfigPatternMap.java @@ -1,9 +1,10 @@ -package io.quarkus.deployment.configuration; +package io.quarkus.deployment.configuration.matching; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Objects; import java.util.TreeMap; +import java.util.function.BiFunction; import org.wildfly.common.Assert; @@ -127,6 +128,89 @@ public void addChild(final String childName, final ConfigPatternMap child) { children.put(childName, child); } + public static ConfigPatternMap merge(final ConfigPatternMap param0, + final ConfigPatternMap param1, final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final T matched0 = param0.getMatched(); + final U matched1 = param1.getMatched(); + result.setMatched(combinator.apply(matched0, matched1)); + + // they're sorted; combine them in order + final Iterator iter0 = param0.childNames().iterator(); + final Iterator iter1 = param1.childNames().iterator(); + String next0; + String next1; + if (iter0.hasNext() && iter1.hasNext()) { + next0 = iter0.next(); + next1 = iter1.next(); + for (;;) { + if (next0.compareTo(next1) < 0) { + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + if (iter0.hasNext()) { + next0 = iter0.next(); + } else { + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + break; + } + } else if (next0.compareTo(next1) > 0) { + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + if (iter1.hasNext()) { + next1 = iter1.next(); + } else { + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + break; + } + } else { + assert next0.compareTo(next1) == 0; + result.addChild(next0, merge(param0.getChild(next0), param1.getChild(next1), combinator)); + if (iter0.hasNext() && iter1.hasNext()) { + next0 = iter0.next(); + next1 = iter1.next(); + } else { + break; + } + } + } + } + while (iter0.hasNext()) { + next0 = iter0.next(); + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + } + while (iter1.hasNext()) { + next1 = iter1.next(); + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + } + return result; + } + + private static ConfigPatternMap merge0(final ConfigPatternMap param0, + final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final T matched0 = param0.getMatched(); + result.setMatched(combinator.apply(matched0, null)); + final Iterator iter0 = param0.childNames().iterator(); + String next0; + while (iter0.hasNext()) { + next0 = iter0.next(); + result.addChild(next0, merge0(param0.getChild(next0), combinator)); + } + return result; + } + + private static ConfigPatternMap merge1(final ConfigPatternMap param1, + final BiFunction combinator) { + final ConfigPatternMap result = new ConfigPatternMap<>(); + final U matched1 = param1.getMatched(); + result.setMatched(combinator.apply(null, matched1)); + final Iterator iter1 = param1.childNames().iterator(); + String next1; + while (iter1.hasNext()) { + next1 = iter1.next(); + result.addChild(next1, merge1(param1.getChild(next1), combinator)); + } + return result; + } + public static class PatternIterator implements Iterator { ConfigPatternMap current; ConfigPatternMap next; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java new file mode 100644 index 00000000000000..b1e87d7ae7770e --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/Container.java @@ -0,0 +1,63 @@ +package io.quarkus.deployment.configuration.matching; + +import java.lang.reflect.Field; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; + +/** + * A container for a configuration key path. + */ +public abstract class Container { + Container() { + } + + /** + * Get the parent container, or {@code null} if the container is a root. Presently only + * field containers may be roots. + * + * @return the parent container + */ + public abstract Container getParent(); + + /** + * Find the field that will ultimately hold this value. + * + * @return the field (must not be {@code null}) + */ + public final Field findField() { + return getClassMember().getField(); + } + + /** + * Find the enclosing class definition that will ultimately hold this value. + * + * @return the class definition (must not be {@code null}) + */ + public final ClassDefinition findEnclosingClass() { + return getClassMember().getEnclosingDefinition(); + } + + /** + * Find the enclosing class member. + * + * @return the enclosing class member + */ + public abstract ClassDefinition.ClassMember getClassMember(); + + /** + * Get the combined name of this item. + * + * @return the combined name (must not be {@code null}) + */ + public final String getCombinedName() { + return getCombinedName(new StringBuilder()).toString(); + } + + abstract StringBuilder getCombinedName(StringBuilder sb); + + public final String getPropertyName() { + return getPropertyName(new StringBuilder()).toString(); + } + + abstract StringBuilder getPropertyName(StringBuilder sb); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java new file mode 100644 index 00000000000000..820aa00a0a214a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/FieldContainer.java @@ -0,0 +1,62 @@ +package io.quarkus.deployment.configuration.matching; + +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; + +/** + * + */ +public final class FieldContainer extends Container { + private final Container parent; + private final ClassDefinition.ClassMember member; + + public FieldContainer(final Container parent, final ClassDefinition.ClassMember member) { + this.parent = parent; + this.member = Assert.checkNotNullParam("member", member); + } + + public Container getParent() { + return parent; + } + + public ClassDefinition.ClassMember getClassMember() { + return member; + } + + StringBuilder getCombinedName(final StringBuilder sb) { + Container parent = getParent(); + if (parent != null) { + parent.getCombinedName(sb); + } + final ClassDefinition enclosing = member.getEnclosingDefinition(); + if (enclosing instanceof RootDefinition) { + sb.append(((RootDefinition) enclosing).getRootName().replace('.', ':')); + } + if (sb.length() > 0) { + sb.append(':'); + } + sb.append(member.getName()); + return sb; + } + + StringBuilder getPropertyName(final StringBuilder sb) { + Container parent = getParent(); + if (parent != null) { + parent.getPropertyName(sb); + } + final ClassDefinition enclosing = member.getEnclosingDefinition(); + if (enclosing instanceof RootDefinition) { + sb.append(((RootDefinition) enclosing).getRootName()); + } + final String propertyName = member.getPropertyName(); + if (!propertyName.isEmpty()) { + if (sb.length() > 0) { + sb.append('.'); + } + sb.append(propertyName); + } + return sb; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java new file mode 100644 index 00000000000000..9360e81635e074 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/MapContainer.java @@ -0,0 +1,46 @@ +package io.quarkus.deployment.configuration.matching; + +import org.wildfly.common.Assert; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; + +/** + * A map container. + */ +public final class MapContainer extends Container { + private final Container parent; + private final ClassDefinition.ClassMember mapMember; + + public MapContainer(final Container parent, final ClassDefinition.ClassMember mapMember) { + this.parent = Assert.checkNotNullParam("parent", parent); + this.mapMember = mapMember; + } + + public ClassDefinition.ClassMember getClassMember() { + return mapMember; + } + + public Container getParent() { + return parent; + } + + StringBuilder getCombinedName(final StringBuilder sb) { + // maps always have a parent + getParent().getCombinedName(sb); + if (sb.length() > 0) { + sb.append(':'); + } + sb.append('*'); + return sb; + } + + StringBuilder getPropertyName(final StringBuilder sb) { + // maps always have a parent + getParent().getPropertyName(sb); + if (sb.length() > 0) { + sb.append('.'); + } + sb.append('*'); + return sb; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java new file mode 100644 index 00000000000000..53a5190632b11b --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/matching/PatternMapBuilder.java @@ -0,0 +1,85 @@ +package io.quarkus.deployment.configuration.matching; + +import java.util.List; + +import io.quarkus.deployment.configuration.definition.ClassDefinition; +import io.quarkus.deployment.configuration.definition.RootDefinition; +import io.quarkus.runtime.configuration.NameIterator; + +/** + * + */ +public final class PatternMapBuilder { + + private PatternMapBuilder() { + } + + public static ConfigPatternMap makePatterns(List rootDefinitions) { + ConfigPatternMap patternMap = new ConfigPatternMap<>(); + for (RootDefinition rootDefinition : rootDefinitions) { + final String rootName = rootDefinition.getRootName(); + ConfigPatternMap addTo = patternMap, child; + assert !rootName.isEmpty(); + NameIterator ni = new NameIterator(rootName); + assert ni.hasNext(); + do { + final String seg = ni.getNextSegment(); + child = addTo.getChild(seg); + ni.next(); + if (child == null) { + addTo.addChild(seg, child = new ConfigPatternMap<>()); + } + addTo = child; + } while (ni.hasNext()); + addGroup(addTo, rootDefinition, null); + } + return patternMap; + } + + private static void addGroup(ConfigPatternMap patternMap, ClassDefinition current, + Container parent) { + for (ClassDefinition.ClassMember member : current.getMembers()) { + final String propertyName = member.getPropertyName(); + ConfigPatternMap addTo = patternMap; + FieldContainer newNode; + if (!propertyName.isEmpty()) { + NameIterator ni = new NameIterator(propertyName); + assert ni.hasNext(); + do { + final String seg = ni.getNextSegment(); + ConfigPatternMap child = addTo.getChild(seg); + if (child == null) { + addTo.addChild(seg, child = new ConfigPatternMap<>()); + } + addTo = child; + ni.next(); + } while (ni.hasNext()); + } + newNode = new FieldContainer(parent, member); + addMember(addTo, member, newNode); + } + } + + private static void addMember(ConfigPatternMap patternMap, ClassDefinition.ClassMember member, + Container container) { + if (member instanceof ClassDefinition.ItemMember) { + Container matched = patternMap.getMatched(); + if (matched != null) { + throw new IllegalArgumentException( + "Multiple matching properties for name \"" + matched.getPropertyName() + "\""); + } + patternMap.setMatched(container); + } else if (member instanceof ClassDefinition.MapMember) { + ClassDefinition.MapMember mapMember = (ClassDefinition.MapMember) member; + ConfigPatternMap addTo = patternMap.getChild(ConfigPatternMap.WILD_CARD); + if (addTo == null) { + patternMap.addChild(ConfigPatternMap.WILD_CARD, addTo = new ConfigPatternMap<>()); + } + final ClassDefinition.ClassMember nestedMember = mapMember.getNested(); + addMember(addTo, nestedMember, new MapContainer(container, nestedMember)); + } else { + assert member instanceof ClassDefinition.GroupMember; + addGroup(patternMap, ((ClassDefinition.GroupMember) member).getGroupDefinition(), container); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java new file mode 100644 index 00000000000000..0e050069f64557 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ArrayOf.java @@ -0,0 +1,53 @@ +package io.quarkus.deployment.configuration.type; + +import java.lang.reflect.Array; +import java.util.Objects; + +/** + * + */ +public final class ArrayOf extends ConverterType { + private final ConverterType type; + private int hashCode; + private Class arrayType; + + public ArrayOf(final ConverterType type) { + this.type = type; + } + + public ConverterType getElementType() { + return type; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public Class getArrayType() { + Class arrayType = this.arrayType; + if (arrayType == null) { + this.arrayType = arrayType = Array.newInstance(getLeafType(), 0).getClass(); + } + return arrayType; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, ArrayOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof ArrayOf && equals((ArrayOf) obj); + } + + public boolean equals(final ArrayOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java new file mode 100644 index 00000000000000..842ba505b15461 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/CollectionOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class CollectionOf extends ConverterType { + private final ConverterType type; + private final Class collectionClass; + private int hashCode; + + public CollectionOf(final ConverterType type, final Class collectionClass) { + this.type = type; + this.collectionClass = collectionClass; + } + + public ConverterType getElementType() { + return type; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public Class getCollectionClass() { + return collectionClass; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, collectionClass, CollectionOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof CollectionOf && equals((CollectionOf) obj); + } + + public boolean equals(final CollectionOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java new file mode 100644 index 00000000000000..15805bf22e0ab0 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/ConverterType.java @@ -0,0 +1,112 @@ +package io.quarkus.deployment.configuration.type; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Optional; +import java.util.Set; +import java.util.SortedSet; + +import io.quarkus.deployment.util.ReflectUtil; +import io.quarkus.runtime.annotations.ConvertWith; +import io.quarkus.runtime.annotations.DefaultConverter; +import io.quarkus.runtime.configuration.HyphenateEnumConverter; + +/** + * + */ +public abstract class ConverterType { + ConverterType() { + } + + public abstract Class getLeafType(); + + public static ConverterType of(Field member) { + return of(member.getGenericType(), member); + } + + public static ConverterType of(Parameter parameter) { + return of(parameter.getParameterizedType(), parameter); + } + + public static ConverterType of(Type type, AnnotatedElement element) { + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + return new ArrayOf(of(genericArrayType.getGenericComponentType(), element)); + } else if (type instanceof Class) { + // simple type + Class clazz = (Class) type; + if (clazz.isArray()) { + return new ArrayOf(of(clazz.getComponentType(), element)); + } + ConvertWith convertWith = element.getAnnotation(ConvertWith.class); + Leaf leaf; + if (convertWith == null && element.getAnnotation(DefaultConverter.class) == null && clazz.isEnum()) { + // use our hyphenated converter by default + leaf = new Leaf(clazz, HyphenateEnumConverter.class); + } else { + leaf = new Leaf(clazz, convertWith == null ? null : convertWith.value()); + } + // vvv todo: add validations here vvv + // return result + return leaf; + } else if (type instanceof ParameterizedType) { + final ParameterizedType paramType = (ParameterizedType) type; + final Class rawType = ReflectUtil.rawTypeOf(paramType); + final Type[] args = paramType.getActualTypeArguments(); + if (args.length == 1) { + final Type arg = args[0]; + if (rawType == Class.class) { + ConverterType result = of(Class.class, element); + if (arg instanceof WildcardType) { + final WildcardType wcType = (WildcardType) arg; + // gather bounds for validation + Class[] upperBounds = ReflectUtil.rawTypesOfDestructive(wcType.getUpperBounds()); + Class[] lowerBounds = ReflectUtil.rawTypesOfDestructive(wcType.getLowerBounds()); + for (Class upperBound : upperBounds) { + if (upperBound != Object.class) { + result = new UpperBoundCheckOf(upperBound, result); + } + } + for (Class lowerBound : lowerBounds) { + result = new LowerBoundCheckOf(lowerBound, result); + } + return result; + } + throw new IllegalArgumentException("Class configuration item types cannot be invariant"); + } + final ConverterType nested = of(arg, element); + if (rawType == List.class || rawType == Set.class || rawType == SortedSet.class + || rawType == NavigableSet.class) { + return new CollectionOf(nested, rawType); + } else if (rawType == Optional.class) { + return new OptionalOf(nested); + } else { + throw unsupportedType(type); + } + } else if (args.length == 2) { + if (rawType == Map.class) { + // the real converter is the converter for the value type + return of(ReflectUtil.typeOfParameter(paramType, 1), element); + } else { + throw unsupportedType(type); + } + } else { + throw unsupportedType(type); + } + } else { + throw unsupportedType(type); + } + } + + private static IllegalArgumentException unsupportedType(final Type type) { + return new IllegalArgumentException("Unsupported type: " + type); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java new file mode 100644 index 00000000000000..4a0073e33660fb --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/Leaf.java @@ -0,0 +1,47 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +import org.eclipse.microprofile.config.spi.Converter; + +/** + * + */ +public final class Leaf extends ConverterType { + private final Class type; + private final Class> convertWith; + private int hashCode; + + public Leaf(final Class type, final Class convertWith) { + this.type = type; + this.convertWith = (Class>) convertWith; + } + + public Class getLeafType() { + return type; + } + + public Class> getConvertWith() { + return convertWith; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, convertWith); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof Leaf && equals((Leaf) obj); + } + + public boolean equals(final Leaf obj) { + return obj == this || obj != null && type == obj.type && convertWith == obj.convertWith; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java new file mode 100644 index 00000000000000..ea37614dfc7ac1 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/LowerBoundCheckOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class LowerBoundCheckOf extends ConverterType { + private final Class lowerBound; + private final ConverterType classConverterType; + private int hashCode; + + public LowerBoundCheckOf(final Class lowerBound, final ConverterType classConverterType) { + this.lowerBound = lowerBound; + this.classConverterType = classConverterType; + } + + public Class getLowerBound() { + return lowerBound; + } + + public ConverterType getClassConverterType() { + return classConverterType; + } + + public Class getLeafType() { + return classConverterType.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(classConverterType, lowerBound, LowerBoundCheckOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof LowerBoundCheckOf && equals((LowerBoundCheckOf) obj); + } + + public boolean equals(final LowerBoundCheckOf obj) { + return obj == this || obj != null && lowerBound == obj.lowerBound && classConverterType.equals(obj.classConverterType); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java new file mode 100644 index 00000000000000..320c29923ad7c1 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/MinMaxValidated.java @@ -0,0 +1,69 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class MinMaxValidated extends ConverterType { + private final ConverterType type; + private final String min; + private final boolean minInclusive; + private final String max; + private final boolean maxInclusive; + private int hashCode; + + public MinMaxValidated(final ConverterType type, final String min, final boolean minInclusive, final String max, + final boolean maxInclusive) { + this.type = type; + this.min = min; + this.minInclusive = minInclusive; + this.max = max; + this.maxInclusive = maxInclusive; + } + + public ConverterType getNestedType() { + return type; + } + + public String getMin() { + return min; + } + + public boolean isMinInclusive() { + return minInclusive; + } + + public String getMax() { + return max; + } + + public boolean isMaxInclusive() { + return maxInclusive; + } + + public Class getLeafType() { + return type.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, min, Boolean.valueOf(minInclusive), max, Boolean.valueOf(maxInclusive)); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof MinMaxValidated && equals((MinMaxValidated) obj); + } + + public boolean equals(final MinMaxValidated obj) { + return this == obj || obj != null && Objects.equals(type, obj.type) && Objects.equals(min, obj.min) + && Objects.equals(max, obj.max) && maxInclusive == obj.maxInclusive && minInclusive == obj.minInclusive; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java new file mode 100644 index 00000000000000..dcbc01ddfa9023 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/OptionalOf.java @@ -0,0 +1,43 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class OptionalOf extends ConverterType { + private final ConverterType type; + private int hashCode; + + public OptionalOf(final ConverterType type) { + this.type = type; + } + + public ConverterType getNestedType() { + return type; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, OptionalOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof OptionalOf && equals((OptionalOf) obj); + } + + public boolean equals(final OptionalOf obj) { + return this == obj || obj != null && type.equals(obj.type); + } + + public Class getLeafType() { + return type.getLeafType(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java new file mode 100644 index 00000000000000..cba5df78daf1f6 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/PatternValidated.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class PatternValidated extends ConverterType { + private final ConverterType type; + private final String patternString; + private int hashCode; + + public PatternValidated(final ConverterType type, final String patternString) { + this.type = type; + this.patternString = patternString; + } + + public ConverterType getNestedType() { + return type; + } + + public String getPatternString() { + return patternString; + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(type, patternString); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof PatternValidated && equals((PatternValidated) obj); + } + + public boolean equals(final PatternValidated obj) { + return obj == this || obj != null && type.equals(obj.type) && patternString.equals(obj.patternString); + } + + public Class getLeafType() { + return type.getLeafType(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java new file mode 100644 index 00000000000000..7a328de862dd7b --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/type/UpperBoundCheckOf.java @@ -0,0 +1,49 @@ +package io.quarkus.deployment.configuration.type; + +import java.util.Objects; + +/** + * + */ +public final class UpperBoundCheckOf extends ConverterType { + private final Class upperBound; + private final ConverterType classConverterType; + private int hashCode; + + public UpperBoundCheckOf(final Class upperBound, final ConverterType classConverterType) { + this.upperBound = upperBound; + this.classConverterType = classConverterType; + } + + public Class getUpperBound() { + return upperBound; + } + + public ConverterType getClassConverterType() { + return classConverterType; + } + + public Class getLeafType() { + return classConverterType.getLeafType(); + } + + public int hashCode() { + int hashCode = this.hashCode; + if (hashCode == 0) { + hashCode = Objects.hash(classConverterType, upperBound, UpperBoundCheckOf.class); + if (hashCode == 0) { + hashCode = 0x8000_0000; + } + this.hashCode = hashCode; + } + return hashCode; + } + + public boolean equals(final Object obj) { + return obj instanceof UpperBoundCheckOf && equals((UpperBoundCheckOf) obj); + } + + public boolean equals(final UpperBoundCheckOf obj) { + return obj == this || obj != null && upperBound == obj.upperBound && classConverterType.equals(obj.classConverterType); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java index e1ace83ee58545..6b50966b97fcd5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/NativeConfig.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.pkg; import java.io.File; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -16,7 +15,7 @@ public class NativeConfig { * Additional arguments to pass to the build process */ @ConfigItem - public List additionalBuildArgs; + public Optional> additionalBuildArgs; /** * If the HTTP url handler should be enabled, allowing you to do URL.openConnection() for HTTP URLs @@ -52,7 +51,7 @@ public class NativeConfig { * The location of the Graal distribution */ @ConfigItem(defaultValue = "${GRAALVM_HOME:}") - public String graalvmHome; + public Optional graalvmHome; /** * The location of the JDK @@ -153,13 +152,13 @@ public class NativeConfig { * a container build is always done. */ @ConfigItem - public String containerRuntime = ""; + public Optional containerRuntime; /** * Options to pass to the container runtime */ @ConfigItem - public List containerRuntimeOptions = new ArrayList<>(); + public Optional> containerRuntimeOptions; /** * If the resulting image should allow VM introspection diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java index c98f1a080e7a82..3e922e93ad2cbd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java @@ -1,6 +1,7 @@ package io.quarkus.deployment.pkg; import java.util.List; +import java.util.Optional; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigRoot; @@ -41,7 +42,7 @@ public class PackageConfig { * Files that should not be copied to the output artifact */ @ConfigItem - public List userConfiguredIgnoredEntries; + public Optional> userConfiguredIgnoredEntries; /** * The suffix that is applied to the runner jar and native images diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index f6022ef75c202f..c2dcc4cb0648cc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -178,7 +178,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem, final StringBuilder classPath = new StringBuilder(); final Map> services = new HashMap<>(); Set finalIgnoredEntries = new HashSet<>(IGNORED_ENTRIES); - finalIgnoredEntries.addAll(packageConfig.userConfiguredIgnoredEntries); + packageConfig.userConfiguredIgnoredEntries.ifPresent(finalIgnoredEntries::addAll); final List appDeps = curateOutcomeBuildItem.getEffectiveModel().getUserDependencies(); @@ -529,6 +529,7 @@ private void generateManifest(FileSystem runnerZipFs, final String classPath, Pa } Files.delete(manifestPath); } + Files.createDirectories(manifestPath.getParent()); Attributes attributes = manifest.getMainAttributes(); attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); if (attributes.containsKey(Attributes.Name.CLASS_PATH)) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java index ff22a5b628b3c0..3a659a657ccca5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildStep.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import org.jboss.logging.Logger; @@ -77,8 +78,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa String noPIE = ""; - if (!"".equals(nativeConfig.containerRuntime) || nativeConfig.containerBuild) { - String containerRuntime = nativeConfig.containerRuntime.isEmpty() ? "docker" : nativeConfig.containerRuntime; + if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) { + String containerRuntime = nativeConfig.containerRuntime.orElse("docker"); // E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image" nativeImage = new ArrayList<>(); Collections.addAll(nativeImage, containerRuntime, "run", "-v", @@ -96,7 +97,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeImage.add("--userns=keep-id"); } } - nativeImage.addAll(nativeConfig.containerRuntimeOptions); + nativeConfig.containerRuntimeOptions.ifPresent(nativeImage::addAll); if (nativeConfig.debugBuildProcess && nativeConfig.publishDebugBuildProcessPort) { // publish the debug port onto the host if asked for nativeImage.add("--publish=" + DEBUG_BUILD_PROCESS_PORT + ":" + DEBUG_BUILD_PROCESS_PORT); @@ -107,12 +108,10 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa noPIE = detectNoPIE(); } - String graal = nativeConfig.graalvmHome; + Optional graal = nativeConfig.graalvmHome; File java = nativeConfig.javaHome; - if (graal != null) { - env.put(GRAALVM_HOME, graal); - } else { - graal = env.get(GRAALVM_HOME); + if (graal.isPresent()) { + env.put(GRAALVM_HOME, graal.get()); } if (java == null) { // try system property first - it will be the JAVA_HOME used by the current JVM @@ -169,9 +168,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa nativeConfig.enableAllSecurityServices = true; } - if (nativeConfig.additionalBuildArgs != null) { - command.addAll(nativeConfig.additionalBuildArgs); - } + nativeConfig.additionalBuildArgs.ifPresent(command::addAll); command.add("--initialize-at-build-time="); command.add("-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime"); //the default collection policy results in full GC's 50% of the time command.add("-jar"); @@ -308,10 +305,10 @@ private boolean isThisGraalVMVersionObsolete() { return false; } - private static File getNativeImageExecutable(String graalVmHome, File javaHome, Map env) { + private static File getNativeImageExecutable(Optional graalVmHome, File javaHome, Map env) { String imageName = IS_WINDOWS ? "native-image.cmd" : "native-image"; - if (graalVmHome != null) { - File file = Paths.get(graalVmHome, "bin", imageName).toFile(); + if (graalVmHome.isPresent()) { + File file = Paths.get(graalVmHome.get(), "bin", imageName).toFile(); if (file.exists()) { return file; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java index c2cb9e9da1a6ca..c2f72b3a82a831 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigDescriptionBuildStep.java @@ -1,6 +1,7 @@ package io.quarkus.deployment.steps; import java.io.InputStream; +import java.lang.reflect.Field; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; @@ -8,21 +9,20 @@ import java.util.Properties; import java.util.function.Consumer; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.BuildTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigDescriptionBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; -import io.quarkus.deployment.configuration.LeafConfigType; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; +import io.quarkus.deployment.configuration.matching.ConfigPatternMap; +import io.quarkus.deployment.configuration.matching.Container; +import io.quarkus.runtime.annotations.ConfigItem; public class ConfigDescriptionBuildStep { @BuildStep List createConfigDescriptions( - RunTimeConfigurationBuildItem runtimeConfig, - BuildTimeConfigurationBuildItem buildTimeConfig, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRuntimeConfig) throws Exception { + ConfigurationBuildItem config) throws Exception { Properties javadoc = new Properties(); Enumeration resources = Thread.currentThread().getContextClassLoader() .getResources("META-INF/quarkus-javadoc.properties"); @@ -32,20 +32,46 @@ List createConfigDescriptions( } } List ret = new ArrayList<>(); - processConfig(runtimeConfig.getConfigDefinition(), ret, javadoc); - processConfig(buildTimeConfig.getConfigDefinition(), ret, javadoc); - processConfig(buildTimeRuntimeConfig.getConfigDefinition(), ret, javadoc); + processConfig(config.getReadResult().getBuildTimePatternMap(), ret, javadoc); + processConfig(config.getReadResult().getBuildTimeRunTimePatternMap(), ret, javadoc); + processConfig(config.getReadResult().getRunTimePatternMap(), ret, javadoc); return ret; } - private void processConfig(ConfigDefinition configDefinition, List ret, Properties javadoc) { + private void processConfig(ConfigPatternMap patterns, List ret, + Properties javadoc) { - configDefinition.getLeafPatterns().forEach(new Consumer() { + patterns.forEach(new Consumer() { @Override - public void accept(LeafConfigType leafConfigType) { - ret.add(new ConfigDescriptionBuildItem("quarkus." + leafConfigType.getConfigKey(), - leafConfigType.getItemClass(), - leafConfigType.getDefaultValueString(), javadoc.getProperty(leafConfigType.getJavadocKey()))); + public void accept(Container node) { + Field field = node.findField(); + ConfigItem configItem = field.getAnnotation(ConfigItem.class); + final ConfigProperty configProperty = field.getAnnotation(ConfigProperty.class); + String defaultDefault; + final Class valueClass = field.getType(); + if (valueClass == boolean.class) { + defaultDefault = "false"; + } else if (valueClass.isPrimitive() && valueClass != char.class) { + defaultDefault = "0"; + } else { + defaultDefault = null; + } + String defVal = defaultDefault; + if (configItem != null) { + final String itemDefVal = configItem.defaultValue(); + if (!itemDefVal.equals(ConfigItem.NO_DEFAULT)) { + defVal = itemDefVal; + } + } else if (configProperty != null) { + final String propDefVal = configProperty.defaultValue(); + if (!propDefVal.equals(ConfigProperty.UNCONFIGURED_VALUE)) { + defVal = propDefVal; + } + } + String javadocKey = field.getDeclaringClass().getName().replace("$", ".") + "." + field.getName(); + ret.add(new ConfigDescriptionBuildItem("quarkus." + node.getPropertyName(), + node.findEnclosingClass().getConfigurationClass(), + defVal, javadoc.getProperty(javadocKey))); } }); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigSourcesBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigSourcesBuildStep.java new file mode 100644 index 00000000000000..17ea4ddb48f2b7 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigSourcesBuildStep.java @@ -0,0 +1,72 @@ +package io.quarkus.deployment.steps; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalInt; + +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.config.spi.ConfigSourceProvider; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationSourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; + +class ConfigSourcesBuildStep { + + static final String PROVIDER_CLASS_NAME = "io.quarkus.runtime.generated.ConfigSourceProviderImpl"; + + @BuildStep + void generateConfigSources(List runTimeSources, + final BuildProducer generatedClass, + final BuildProducer generatedResource, + final BuildProducer providerProducer) { + ClassOutput classOutput = new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); + } + }; + + try (ClassCreator cc = ClassCreator.builder().interfaces(ConfigSourceProvider.class).setFinal(true) + .className(PROVIDER_CLASS_NAME) + .classOutput(classOutput).build()) { + try (MethodCreator mc = cc.getMethodCreator(MethodDescriptor.ofMethod(ConfigSourceProvider.class, + "getConfigSources", Iterable.class, ClassLoader.class))) { + + final ResultHandle array = mc.newArray(ConfigSource.class, mc.load(runTimeSources.size())); + for (int i = 0; i < runTimeSources.size(); i++) { + final RunTimeConfigurationSourceBuildItem runTimeSource = runTimeSources.get(i); + final String className = runTimeSource.getClassName(); + final OptionalInt priority = runTimeSource.getPriority(); + ResultHandle value; + if (priority.isPresent()) { + value = mc.newInstance(MethodDescriptor.ofConstructor(className, int.class), + mc.load(priority.getAsInt())); + } else { + value = mc.newInstance(MethodDescriptor.ofConstructor(className)); + } + mc.writeArrayValue(array, i, value); + } + final ResultHandle list = mc.invokeStaticMethod( + MethodDescriptor.ofMethod(Arrays.class, "asList", List.class, Object[].class), array); + mc.returnValue(list); + } + } + + generatedResource.produce(new GeneratedResourceBuildItem( + "META-INF/services/" + ConfigSourceProvider.class.getName(), + PROVIDER_CLASS_NAME.getBytes(StandardCharsets.UTF_8))); + + // todo + providerProducer.produce(new ServiceProviderBuildItem(ConfigSourceProvider.class.getName(), PROVIDER_CLASS_NAME)); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java deleted file mode 100644 index 54d07113608705..00000000000000 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java +++ /dev/null @@ -1,824 +0,0 @@ -package io.quarkus.deployment.steps; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.OptionalInt; -import java.util.Properties; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.UnaryOperator; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.ConfigSourceProvider; -import org.eclipse.microprofile.config.spi.Converter; -import org.graalvm.nativeimage.ImageInfo; -import org.jboss.logging.Logger; -import org.objectweb.asm.Opcodes; - -import io.quarkus.deployment.AccessorFinder; -import io.quarkus.deployment.ApplicationArchive; -import io.quarkus.deployment.GizmoAdaptor; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; -import io.quarkus.deployment.builditem.ArchiveRootBuildItem; -import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; -import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; -import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem; -import io.quarkus.deployment.builditem.ExtensionClassLoaderBuildItem; -import io.quarkus.deployment.builditem.GeneratedClassBuildItem; -import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; -import io.quarkus.deployment.builditem.RunTimeConfigurationSourceBuildItem; -import io.quarkus.deployment.builditem.UnmatchedConfigBuildItem; -import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; -import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.deployment.configuration.ConfigDefinition; -import io.quarkus.deployment.configuration.ConfigPatternMap; -import io.quarkus.deployment.configuration.LeafConfigType; -import io.quarkus.deployment.recording.ObjectLoader; -import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.runtime.annotations.ConfigRoot; -import io.quarkus.runtime.configuration.AbstractRawDefaultConfigSource; -import io.quarkus.runtime.configuration.ApplicationPropertiesConfigSource; -import io.quarkus.runtime.configuration.BuildTimeConfigFactory; -import io.quarkus.runtime.configuration.ConfigUtils; -import io.quarkus.runtime.configuration.ConverterSupport; -import io.quarkus.runtime.configuration.DefaultConfigSource; -import io.quarkus.runtime.configuration.DeploymentProfileConfigSource; -import io.quarkus.runtime.configuration.ExpandingConfigSource; -import io.quarkus.runtime.configuration.HyphenateEnumConverter; -import io.quarkus.runtime.configuration.NameIterator; -import io.quarkus.runtime.configuration.ProfileManager; -import io.quarkus.runtime.configuration.SimpleConfigurationProviderResolver; -import io.smallrye.config.Converters; -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.SmallRyeConfigBuilder; - -/** - * Setup steps for configuration purposes. - */ -public class ConfigurationSetup { - - private static final Logger log = Logger.getLogger("io.quarkus.configuration"); - - public static final String BUILD_TIME_CONFIG = "io.quarkus.runtime.generated.BuildTimeConfig"; - public static final String BUILD_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.BuildTimeConfigRoot"; - public static final String RUN_TIME_CONFIG = "io.quarkus.runtime.generated.RunTimeConfig"; - public static final String RUN_TIME_CONFIG_ROOT = "io.quarkus.runtime.generated.RunTimeConfigRoot"; - public static final String RUN_TIME_DEFAULTS = "io.quarkus.runtime.generated.RunTimeDefaultConfigSource"; - - public static final MethodDescriptor CREATE_RUN_TIME_CONFIG = MethodDescriptor.ofMethod(RUN_TIME_CONFIG, - "getRunTimeConfiguration", void.class); - public static final MethodDescriptor ECS_EXPAND_VALUE = MethodDescriptor.ofMethod(ExpandingConfigSource.class, - "expandValue", - String.class, String.class, ExpandingConfigSource.Cache.class); - - private static final FieldDescriptor RUN_TIME_CONFIG_FIELD = FieldDescriptor.of(RUN_TIME_CONFIG, "runConfig", - RUN_TIME_CONFIG_ROOT); - private static final FieldDescriptor BUILD_TIME_CONFIG_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "buildConfig", - BUILD_TIME_CONFIG_ROOT); - private static final FieldDescriptor CONVERTERS_FIELD = FieldDescriptor.of(BUILD_TIME_CONFIG, "converters", - Converter[].class); - - private static final MethodDescriptor NI_HAS_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "hasNext", boolean.class); - private static final MethodDescriptor NI_NEXT_EQUALS = MethodDescriptor.ofMethod(NameIterator.class, "nextSegmentEquals", - boolean.class, String.class); - private static final MethodDescriptor NI_NEXT = MethodDescriptor.ofMethod(NameIterator.class, "next", void.class); - private static final MethodDescriptor ITR_HAS_NEXT = MethodDescriptor.ofMethod(Iterator.class, "hasNext", boolean.class); - private static final MethodDescriptor ITR_NEXT = MethodDescriptor.ofMethod(Iterator.class, "next", Object.class); - private static final MethodDescriptor C_GET_IMPLICIT_CONVERTER = MethodDescriptor.ofMethod(Converters.class, - "getImplicitConverter", Converter.class, Class.class); - private static final MethodDescriptor CPR_SET_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "setInstance", void.class, ConfigProviderResolver.class); - private static final MethodDescriptor CPR_REGISTER_CONFIG = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "registerConfig", void.class, Config.class, ClassLoader.class); - private static final MethodDescriptor CPR_INSTANCE = MethodDescriptor.ofMethod(ConfigProviderResolver.class, - "instance", ConfigProviderResolver.class); - private static final MethodDescriptor SCPR_CONSTRUCT = MethodDescriptor - .ofConstructor(SimpleConfigurationProviderResolver.class); - private static final MethodDescriptor SRCB_BUILD = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, "build", - Config.class); - private static final MethodDescriptor SRCB_WITH_CONVERTER = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withConverter", ConfigBuilder.class, Class.class, int.class, Converter.class); - private static final MethodDescriptor SRCB_WITH_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withSources", ConfigBuilder.class, ConfigSource[].class); - private static final MethodDescriptor SRCB_ADD_DEFAULT_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "addDefaultSources", ConfigBuilder.class); - private static final MethodDescriptor SRCB_ADD_DISCOVERED_SOURCES = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "addDiscoveredSources", ConfigBuilder.class); - private static final MethodDescriptor SRCB_CONSTRUCT = MethodDescriptor.ofConstructor(SmallRyeConfigBuilder.class); - private static final MethodDescriptor II_IN_IMAGE_RUN = MethodDescriptor.ofMethod(ImageInfo.class, "inImageRuntimeCode", - boolean.class); - private static final MethodDescriptor SRCB_WITH_WRAPPER = MethodDescriptor.ofMethod(SmallRyeConfigBuilder.class, - "withWrapper", SmallRyeConfigBuilder.class, UnaryOperator.class); - - private static final MethodDescriptor BTCF_GET_CONFIG_SOURCE = MethodDescriptor.ofMethod(BuildTimeConfigFactory.class, - "getBuildTimeConfigSource", ConfigSource.class); - private static final MethodDescriptor ECS_CACHE_CONSTRUCT = MethodDescriptor - .ofConstructor(ExpandingConfigSource.Cache.class); - private static final MethodDescriptor ECS_WRAPPER = MethodDescriptor.ofMethod(ExpandingConfigSource.class, "wrapper", - UnaryOperator.class, ExpandingConfigSource.Cache.class); - - private static final MethodDescriptor PROFILE_WRAPPER = MethodDescriptor.ofMethod(DeploymentProfileConfigSource.class, - "wrapper", - UnaryOperator.class); - - private static final MethodDescriptor RTD_CTOR = MethodDescriptor.ofConstructor(RUN_TIME_DEFAULTS); - private static final MethodDescriptor RTD_GET_VALUE = MethodDescriptor.ofMethod(RUN_TIME_DEFAULTS, "getValue", String.class, - NameIterator.class); - private static final MethodDescriptor ARDCS_CTOR = MethodDescriptor.ofConstructor(AbstractRawDefaultConfigSource.class); - - private static final MethodDescriptor CS_POPULATE_CONVERTERS = MethodDescriptor.ofMethod(ConverterSupport.class, - "populateConverters", void.class, ConfigBuilder.class); - - private static final MethodDescriptor SET_RUNTIME_DEFAULT_PROFILE = MethodDescriptor.ofMethod(ProfileManager.class, - "setRuntimeDefaultProfile", void.class, String.class); - private static final MethodDescriptor HYPHENATED_ENUM_CONVERTER_CTOR = MethodDescriptor - .ofConstructor(HyphenateEnumConverter.class, Class.class); - private static final MethodDescriptor CU_EXPLICIT_RUNTIME_CONVERTER = MethodDescriptor.ofMethod(ConfigUtils.class, - "populateExplicitRuntimeConverter", void.class, Class.class, Class.class, Converter.class); - - private static final String[] NO_STRINGS = new String[0]; - - public ConfigurationSetup() { - } - - /** - * Run before anything that consumes configuration; sets up the main configuration definition instance. - * - * @param runTimeConfigItem the run time config item - * @param buildTimeRunTimeConfigItem the build time/run time fixed config item - * @param resourceConsumer - * @param niResourceConsumer - * @param runTimeDefaultConsumer - * @param unmatchedConfigBuildItem the build item holding the unmatched config keys - * @param extensionClassLoaderBuildItem the extension class loader build item - * @param archiveRootBuildItem the application archive root - * @throws IOException - */ - @BuildStep - public void initializeConfiguration( - RunTimeConfigurationBuildItem runTimeConfigItem, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRunTimeConfigItem, - Consumer resourceConsumer, - Consumer niResourceConsumer, - Consumer runTimeDefaultConsumer, - UnmatchedConfigBuildItem unmatchedConfigBuildItem, - ExtensionClassLoaderBuildItem extensionClassLoaderBuildItem, - ArchiveRootBuildItem archiveRootBuildItem) throws IOException { - - SmallRyeConfig src = (SmallRyeConfig) ConfigProvider.getConfig(); - - final ConfigDefinition runTimeConfig = runTimeConfigItem.getConfigDefinition(); - final ConfigDefinition buildTimeRunTimeConfig = buildTimeRunTimeConfigItem.getConfigDefinition(); - - // store the expanded values from the build - final byte[] bytes; - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - try (OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { - final Properties properties = new Properties(); - properties.putAll(buildTimeRunTimeConfig.getLoadedProperties()); - properties.store(osw, "This file is generated from captured build-time values; do not edit this file manually"); - } - os.flush(); - bytes = os.toByteArray(); - } - resourceConsumer.accept( - new GeneratedResourceBuildItem(BuildTimeConfigFactory.BUILD_TIME_CONFIG_NAME, bytes)); - niResourceConsumer.accept( - new NativeImageResourceBuildItem(BuildTimeConfigFactory.BUILD_TIME_CONFIG_NAME)); - - // produce defaults for user-provided config - - final Set unmatched = new HashSet<>(); - unmatched.addAll(unmatchedConfigBuildItem.getSet()); - unmatched.addAll(runTimeConfig.getLoadedProperties().keySet()); - final boolean old = ExpandingConfigSource.setExpanding(false); - try { - for (String propName : unmatched) { - runTimeDefaultConsumer - .accept(new RunTimeConfigurationDefaultBuildItem(propName, - src.getOptionalValue(propName, String.class).orElse(""))); - } - } finally { - ExpandingConfigSource.setExpanding(old); - } - - } - - @BuildStep - public void addDiscoveredSources(ApplicationArchivesBuildItem archives, Consumer providerConsumer) - throws IOException { - final Collection sources = new LinkedHashSet<>(); - final Collection sourceProviders = new LinkedHashSet<>(); - for (ApplicationArchive archive : archives.getAllApplicationArchives()) { - Path childPath = archive.getChildPath("META-INF/services/" + ConfigSource.class.getName()); - if (childPath != null) { - sources.addAll(ServiceUtil.classNamesNamedIn(childPath)); - } - childPath = archive.getChildPath("META-INF/services/" + ConfigSourceProvider.class.getName()); - if (childPath != null) { - sourceProviders.addAll(ServiceUtil.classNamesNamedIn(childPath)); - } - } - if (sources.size() > 0) { - providerConsumer.accept(new ServiceProviderBuildItem(ConfigSource.class.getName(), sources.toArray(NO_STRINGS))); - } - if (sourceProviders.size() > 0) { - providerConsumer.accept( - new ServiceProviderBuildItem(ConfigSourceProvider.class.getName(), sourceProviders.toArray(NO_STRINGS))); - } - } - - /** - * Add a config sources for {@code application.properties}. - */ - @BuildStep - void setUpConfigFile(BuildProducer configSourceConsumer) { - configSourceConsumer.produce(new RunTimeConfigurationSourceBuildItem( - ApplicationPropertiesConfigSource.InJar.class.getName(), OptionalInt.empty())); - configSourceConsumer.produce(new RunTimeConfigurationSourceBuildItem( - ApplicationPropertiesConfigSource.InFileSystem.class.getName(), OptionalInt.empty())); - } - - /** - * Write the default run time configuration. - */ - @BuildStep - RunTimeConfigurationSourceBuildItem writeDefaults( - List defaults, - Consumer resourceConsumer, - Consumer niResourceConsumer) throws IOException { - final Properties properties = new Properties(); - for (RunTimeConfigurationDefaultBuildItem item : defaults) { - final String key = item.getKey(); - final String value = item.getValue(); - final String existing = properties.getProperty(key); - if (existing != null && !existing.equals(value)) { - log.warnf( - "Two conflicting default values were specified for configuration key \"%s\": \"%s\" and \"%s\" (using \"%2$s\")", - key, - existing, - value); - } else { - properties.setProperty(key, value); - } - } - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - try (OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) { - properties.store(osw, "This is the generated set of default configuration values"); - osw.flush(); - resourceConsumer.accept( - new GeneratedResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME, os.toByteArray())); - niResourceConsumer.accept( - new NativeImageResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME)); - } - } - return new RunTimeConfigurationSourceBuildItem(DefaultConfigSource.class.getName(), OptionalInt.empty()); - } - - /** - * Generate the bytecode to load configuration objects at static init and run time. - * - * @param runTimeConfigItem the config build item - * @param classConsumer the consumer of generated classes - * @param runTimeInitConsumer the consumer of runtime init classes - */ - @BuildStep - void finalizeConfigLoader( - RunTimeConfigurationBuildItem runTimeConfigItem, - BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRunTimeConfigItem, - BuildProducer classConsumer, - Consumer runTimeInitConsumer, - Consumer objectLoaderConsumer, - List configTypeItems, - List runTimeSources) { - final ClassOutput classOutput = new GizmoAdaptor(classConsumer, true); - - // General run time setup - - AccessorFinder accessorFinder = new AccessorFinder(); - - final ConfigDefinition runTimeConfigDef = runTimeConfigItem.getConfigDefinition(); - final ConfigPatternMap runTimePatterns = runTimeConfigDef.getLeafPatterns(); - - runTimeConfigDef.generateConfigRootClass(classOutput, accessorFinder); - - final ConfigDefinition buildTimeConfigDef = buildTimeRunTimeConfigItem.getConfigDefinition(); - final ConfigPatternMap buildTimePatterns = buildTimeConfigDef.getLeafPatterns(); - - buildTimeConfigDef.generateConfigRootClass(classOutput, accessorFinder); - - // Traverse all known run-time config types and ensure we have converters for them when image building runs - // This code is specific to native image and run time config, because the build time config is read during static init - - final HashSet> encountered = new HashSet<>(); - final ArrayList> configTypes = new ArrayList<>(); - for (ConfigurationTypeBuildItem item : configTypeItems) { - configTypes.add(item.getValueType()); - } - - for (LeafConfigType item : runTimePatterns) { - final Class typeClass = item.getItemClass(); - if (!typeClass.isPrimitive() && encountered.add(typeClass) - && Converters.getImplicitConverter(typeClass) != null) { - configTypes.add(typeClass); - } - } - - // stability - configTypes.sort(Comparator.comparing(Class::getName)); - int converterCnt = configTypes.size(); - - // Build time configuration class, also holds converters - try (final ClassCreator cc = new ClassCreator(classOutput, BUILD_TIME_CONFIG, null, Object.class.getName())) { - // field to stash converters into - cc.getFieldCreator(CONVERTERS_FIELD).setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_FINAL); - // holder for the build-time configuration - cc.getFieldCreator(BUILD_TIME_CONFIG_FIELD) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); - - // static init block - try (MethodCreator clinit = cc.getMethodCreator("", void.class)) { - clinit.setModifiers(Opcodes.ACC_STATIC); - // set default profile to build profile - clinit.invokeStaticMethod(SET_RUNTIME_DEFAULT_PROFILE, clinit.load(ProfileManager.getActiveProfile())); - - // make implicit converters available to native image run time - final BranchResult inImageBuild = clinit.ifNonZero(clinit - .invokeStaticMethod(MethodDescriptor.ofMethod(ImageInfo.class, "inImageBuildtimeCode", boolean.class))); - try (BytecodeCreator yes = inImageBuild.trueBranch()) { - - final ResultHandle array = yes.newArray(Converter.class, yes.load(converterCnt)); - for (int i = 0; i < converterCnt; i++) { - yes.writeArrayValue(array, i, - yes.invokeStaticMethod(C_GET_IMPLICIT_CONVERTER, yes.loadClass(configTypes.get(i)))); - } - yes.writeStaticField(CONVERTERS_FIELD, array); - } - try (BytecodeCreator no = inImageBuild.falseBranch()) { - no.writeStaticField(CONVERTERS_FIELD, no.loadNull()); - } - - // create build time configuration object - - final ResultHandle builder = clinit.newInstance(SRCB_CONSTRUCT); - // todo: custom build time converters - final ResultHandle array = clinit.newArray(ConfigSource[].class, clinit.load(1)); - clinit.writeArrayValue(array, 0, clinit.invokeStaticMethod(BTCF_GET_CONFIG_SOURCE)); - clinit.invokeVirtualMethod(SRCB_WITH_SOURCES, builder, array); - // add default sources, which are only visible during static init - clinit.invokeVirtualMethod(SRCB_ADD_DEFAULT_SOURCES, builder); - - // create the actual config object - final ResultHandle config = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, builder), - SmallRyeConfig.class); - - // create the config root - clinit.writeStaticField(BUILD_TIME_CONFIG_FIELD, clinit - .newInstance(MethodDescriptor.ofConstructor(BUILD_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - - // write out the parsing for the stored build time config - writeParsing(cc, clinit, config, null, buildTimePatterns); - - clinit.returnValue(null); - } - } - - // Run time configuration class - try (final ClassCreator cc = new ClassCreator(classOutput, RUN_TIME_CONFIG, null, Object.class.getName())) { - // holder for the run-time configuration - cc.getFieldCreator(RUN_TIME_CONFIG_FIELD) - .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE); - - // config object initialization - try (MethodCreator carc = cc.getMethodCreator(ConfigurationSetup.CREATE_RUN_TIME_CONFIG)) { - carc.setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC); - - // create run time configuration object - final ResultHandle builder = carc.newInstance(SRCB_CONSTRUCT); - carc.invokeVirtualMethod(SRCB_ADD_DEFAULT_SOURCES, builder); - - // discovered sources - carc.invokeVirtualMethod(SRCB_ADD_DISCOVERED_SOURCES, builder); - - // custom run time sources - final int size = runTimeSources.size(); - if (size > 0) { - final ResultHandle arrayHandle = carc.newArray(ConfigSource[].class, carc.load(size)); - for (int i = 0; i < size; i++) { - final RunTimeConfigurationSourceBuildItem source = runTimeSources.get(i); - final OptionalInt priority = source.getPriority(); - final ResultHandle val; - if (priority.isPresent()) { - val = carc.newInstance(MethodDescriptor.ofConstructor(source.getClassName(), int.class), - carc.load(priority.getAsInt())); - } else { - val = carc.newInstance(MethodDescriptor.ofConstructor(source.getClassName())); - } - carc.writeArrayValue(arrayHandle, i, val); - } - carc.invokeVirtualMethod( - SRCB_WITH_SOURCES, - builder, - arrayHandle); - } - // default value source - final ResultHandle defaultSourceArray = carc.newArray(ConfigSource[].class, carc.load(1)); - carc.writeArrayValue(defaultSourceArray, 0, carc.newInstance(RTD_CTOR)); - carc.invokeVirtualMethod(SRCB_WITH_SOURCES, builder, defaultSourceArray); - - // custom run time converters - carc.invokeStaticMethod(CS_POPULATE_CONVERTERS, builder); - - // cache explicit converts and make them available during runtime - for (LeafConfigType item : runTimePatterns) { - final Class typeClass = item.getItemClass(); - Class> itemConverterClass = item.getConverterClass(); - if (itemConverterClass == null) { - continue; - } - - ResultHandle typeClassHandle = carc.loadClass(typeClass); - final ResultHandle converter; - if (HyphenateEnumConverter.class.equals(itemConverterClass)) { - converter = carc.newInstance(HYPHENATED_ENUM_CONVERTER_CTOR, typeClassHandle); - } else { - converter = carc.newInstance(MethodDescriptor.ofConstructor(itemConverterClass)); - } - - carc.invokeStaticMethod(CU_EXPLICIT_RUNTIME_CONVERTER, typeClassHandle, carc.loadClass(itemConverterClass), - converter); - } - - // property expansion - final ResultHandle cache = carc.newInstance(ECS_CACHE_CONSTRUCT); - ResultHandle wrapper = carc.invokeStaticMethod(ECS_WRAPPER, cache); - carc.invokeVirtualMethod(SRCB_WITH_WRAPPER, builder, wrapper); - - //profiles - wrapper = carc.invokeStaticMethod(PROFILE_WRAPPER); - carc.invokeVirtualMethod(SRCB_WITH_WRAPPER, builder, wrapper); - - // write out loader for converter types - final BranchResult imgRun = carc.ifNonZero(carc.invokeStaticMethod(II_IN_IMAGE_RUN)); - try (BytecodeCreator inImageRun = imgRun.trueBranch()) { - final ResultHandle array = inImageRun.readStaticField(CONVERTERS_FIELD); - for (int i = 0; i < converterCnt; i++) { - // implicit converters will have a priority of 100. - inImageRun.invokeVirtualMethod( - SRCB_WITH_CONVERTER, - builder, - inImageRun.loadClass(configTypes.get(i)), - inImageRun.load(100), - inImageRun.readArrayValue(array, i)); - } - } - - // Build the config - - final ResultHandle config = carc.checkCast(carc.invokeVirtualMethod(SRCB_BUILD, builder), SmallRyeConfig.class); - - // IMPL NOTE: we do invoke ConfigProviderResolver.setInstance() in RUNTIME_INIT when an app starts, but ConfigProvider only obtains the - // resolver once when initializing ConfigProvider.INSTANCE. That is why we store the current Config as a static field on the - // SimpleConfigurationProviderResolver - carc.invokeStaticMethod(CPR_SET_INSTANCE, carc.newInstance(SCPR_CONSTRUCT)); - carc.invokeVirtualMethod(CPR_REGISTER_CONFIG, carc.invokeStaticMethod(CPR_INSTANCE), config, carc.loadNull()); - - // create the config root - carc.writeStaticField(RUN_TIME_CONFIG_FIELD, - carc.newInstance(MethodDescriptor.ofConstructor(RUN_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - - writeParsing(cc, carc, config, cache, runTimePatterns); - - carc.returnValue(null); - } - } - - // now construct the default values class - try (ClassCreator cc = ClassCreator - .builder() - .classOutput(classOutput) - .className(RUN_TIME_DEFAULTS) - .superClass(AbstractRawDefaultConfigSource.class) - .build()) { - - // constructor - try (MethodCreator ctor = cc.getMethodCreator(RTD_CTOR)) { - ctor.setModifiers(Opcodes.ACC_PUBLIC); - ctor.invokeSpecialMethod(ARDCS_CTOR, ctor.getThis()); - ctor.returnValue(null); - } - - try (MethodCreator gv = cc.getMethodCreator(RTD_GET_VALUE)) { - final ResultHandle nameIter = gv.getMethodParam(0); - // if (! nameIter.hasNext()) return null; - gv.ifNonZero(gv.invokeVirtualMethod(NI_HAS_NEXT, nameIter)).falseBranch().returnValue(gv.loadNull()); - // if (! nameIter.nextSegmentEquals("quarkus")) return null; - gv.ifNonZero(gv.invokeVirtualMethod(NI_NEXT_EQUALS, nameIter, gv.load("quarkus"))).falseBranch() - .returnValue(gv.loadNull()); - // nameIter.next(); // skip "quarkus" - gv.invokeVirtualMethod(NI_NEXT, nameIter); - // return getValue_xx(nameIter); - gv.returnValue(gv.invokeVirtualMethod( - generateGetValue(cc, runTimePatterns, new StringBuilder("getValue"), new HashMap<>()), gv.getThis(), - nameIter)); - } - } - - objectLoaderConsumer.accept(new BytecodeRecorderObjectLoaderBuildItem(new ObjectLoader() { - public ResultHandle load(final BytecodeCreator body, final Object obj, final boolean staticInit) { - if (!canHandleObject(obj, staticInit)) { - return null; - } - boolean buildTime = false; - ConfigDefinition.RootInfo rootInfo = runTimeConfigDef.getInstanceInfo(obj); - if (rootInfo == null) { - rootInfo = buildTimeConfigDef.getInstanceInfo(obj); - buildTime = true; - } - final FieldDescriptor fieldDescriptor = rootInfo.getFieldDescriptor(); - final ResultHandle configRoot = body - .readStaticField(buildTime ? BUILD_TIME_CONFIG_FIELD : RUN_TIME_CONFIG_FIELD); - return body.readInstanceField(fieldDescriptor, configRoot); - } - - @Override - public boolean canHandleObject(Object obj, boolean staticInit) { - boolean buildTime = false; - ConfigDefinition.RootInfo rootInfo = runTimeConfigDef.getInstanceInfo(obj); - if (rootInfo == null) { - rootInfo = buildTimeConfigDef.getInstanceInfo(obj); - buildTime = true; - } - if (rootInfo == null || staticInit && !buildTime) { - final Class objClass = obj.getClass(); - if (objClass.isAnnotationPresent(ConfigRoot.class)) { - String msg = String.format( - "You are trying to use a ConfigRoot[%s] at static initialization time", - objClass.getName()); - throw new IllegalStateException(msg); - } - return false; - } - return true; - } - })); - - runTimeInitConsumer.accept(new RuntimeInitializedClassBuildItem(RUN_TIME_CONFIG)); - } - - private MethodDescriptor generateGetValue(final ClassCreator cc, final ConfigPatternMap keyMap, - final StringBuilder methodName, final Map cache) { - final String methodNameStr = methodName.toString(); - final MethodDescriptor existing = cache.get(methodNameStr); - if (existing != null) { - return existing; - } - try (MethodCreator body = cc.getMethodCreator(methodNameStr, String.class, NameIterator.class)) { - body.setModifiers(Opcodes.ACC_PROTECTED); - final ResultHandle nameIter = body.getMethodParam(0); - final LeafConfigType matched = keyMap.getMatched(); - // if (! keyIter.hasNext()) { - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, nameIter)).falseBranch()) { - if (matched != null) { - // (exact match generated code) - matchedBody.returnValue( - matchedBody.load(matched.getDefaultValueString())); - } else { - // return; - matchedBody.returnValue(matchedBody.loadNull()); - } - } - // } - // branches for each next-string - boolean hasWildCard = false; - final Iterable names = keyMap.childNames(); - for (String name : names) { - if (name.equals(ConfigPatternMap.WILD_CARD)) { - hasWildCard = true; - } else { - // TODO: string switch - // if (keyIter.nextSegmentEquals(name)) { - try (BytecodeCreator nameMatched = body - .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, nameIter, body.load(name))).trueBranch()) { - // keyIter.next(); - nameMatched.invokeVirtualMethod(NI_NEXT, nameIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append(name); - // result = this.getValue_xxx(nameIter); - final ResultHandle result = nameMatched.invokeVirtualMethod( - generateGetValue(cc, keyMap.getChild(name), methodName, cache), nameMatched.getThis(), - nameIter); - methodName.setLength(length); - // return result; - nameMatched.returnValue(result); - } - // } - } - } - if (hasWildCard) { - // consume and parse - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, nameIter)) - .trueBranch()) { - // keyIter.next(); - matchedBody.invokeVirtualMethod(NI_NEXT, nameIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append("wildcard"); - // result = this.getValue_xxx(nameIter); - final ResultHandle result = matchedBody.invokeVirtualMethod( - generateGetValue(cc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName, cache), - matchedBody.getThis(), nameIter); - methodName.setLength(length); - // return result; - matchedBody.returnValue(result); - } - } - // it's not found - body.returnValue(body.loadNull()); - final MethodDescriptor md = body.getMethodDescriptor(); - cache.put(methodNameStr, md); - return md; - } - } - - private void writeParsing(final ClassCreator cc, final BytecodeCreator body, final ResultHandle config, - final ResultHandle cache, final ConfigPatternMap keyMap) { - // setup - // Iterable iterable = config.getPropertyNames(); - final ResultHandle iterable = body.invokeVirtualMethod( - MethodDescriptor.ofMethod(SmallRyeConfig.class, "getPropertyNames", Iterable.class), config); - // Iterator iterator = iterable.iterator(); - final ResultHandle iterator = body - .invokeInterfaceMethod(MethodDescriptor.ofMethod(Iterable.class, "iterator", Iterator.class), iterable); - - // loop: { - try (BytecodeCreator loop = body.createScope()) { - // if (iterator.hasNext()) - final BranchResult ifHasNext = loop.ifNonZero(loop.invokeInterfaceMethod(ITR_HAS_NEXT, iterator)); - // { - try (BytecodeCreator hasNext = ifHasNext.trueBranch()) { - // key = iterator.next(); - final ResultHandle key = hasNext.checkCast(hasNext.invokeInterfaceMethod(ITR_NEXT, iterator), String.class); - // NameIterator keyIter = new NameIterator(key); - final ResultHandle keyIter = hasNext - .newInstance(MethodDescriptor.ofConstructor(NameIterator.class, String.class), key); - // if (! keyIter.hasNext()) continue loop; - hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch().continueScope(loop); - // if (! keyIter.nextSegmentEquals("quarkus")) continue loop; - hasNext.ifNonZero(hasNext.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, hasNext.load("quarkus"))).falseBranch() - .continueScope(loop); - // keyIter.next(); // skip "quarkus" - hasNext.invokeVirtualMethod(NI_NEXT, keyIter); - // parse(config, cache, keyIter); - or - parse(config, keyIter); - final ResultHandle[] args; - final boolean expand = cache != null; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - hasNext.invokeStaticMethod( - generateParserBody(cc, keyMap, new StringBuilder("parseKey"), new HashMap<>(), expand), - args); - // continue loop; - hasNext.continueScope(loop); - } - // } - } - // } - body.returnValue(body.loadNull()); - } - - private MethodDescriptor generateParserBody(final ClassCreator cc, final ConfigPatternMap keyMap, - final StringBuilder methodName, final Map parseMethodCache, final boolean expand) { - final String methodNameStr = methodName.toString(); - final MethodDescriptor existing = parseMethodCache.get(methodNameStr); - if (existing != null) { - return existing; - } - final Class[] argTypes; - if (expand) { - argTypes = new Class[] { SmallRyeConfig.class, ExpandingConfigSource.Cache.class, NameIterator.class }; - } else { - argTypes = new Class[] { SmallRyeConfig.class, NameIterator.class }; - } - try (MethodCreator body = cc.getMethodCreator(methodName.toString(), void.class, - argTypes)) { - body.setModifiers(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC); - final ResultHandle config = body.getMethodParam(0); - final ResultHandle cache = expand ? body.getMethodParam(1) : null; - final ResultHandle keyIter = expand ? body.getMethodParam(2) : body.getMethodParam(1); - final LeafConfigType matched = keyMap.getMatched(); - // if (! keyIter.hasNext()) { - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)).falseBranch()) { - if (matched != null) { - // (exact match generated code) - matched.generateAcceptConfigurationValue(matchedBody, keyIter, cache, config); - } else { - // todo: unknown name warning goes here - } - // return; - matchedBody.returnValue(null); - } - // } - // branches for each next-string - boolean hasWildCard = false; - final Iterable names = keyMap.childNames(); - for (String name : names) { - if (name.equals(ConfigPatternMap.WILD_CARD)) { - hasWildCard = true; - } else { - // TODO: string switch - // if (keyIter.nextSegmentEquals(name)) { - try (BytecodeCreator nameMatched = body - .ifNonZero(body.invokeVirtualMethod(NI_NEXT_EQUALS, keyIter, body.load(name))).trueBranch()) { - // keyIter.next(); - nameMatched.invokeVirtualMethod(NI_NEXT, keyIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append(name); - final ResultHandle[] args; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - nameMatched.invokeStaticMethod( - generateParserBody(cc, keyMap.getChild(name), methodName, parseMethodCache, expand), - args); - methodName.setLength(length); - // return; - nameMatched.returnValue(null); - } - // } - } - } - if (hasWildCard) { - // consume and parse - try (BytecodeCreator matchedBody = body.ifNonZero(body.invokeVirtualMethod(NI_HAS_NEXT, keyIter)) - .trueBranch()) { - // keyIter.next(); - matchedBody.invokeVirtualMethod(NI_NEXT, keyIter); - // (generated recursive) - final int length = methodName.length(); - methodName.append('_').append("wildcard"); - final ResultHandle[] args; - if (expand) { - args = new ResultHandle[] { config, cache, keyIter }; - } else { - args = new ResultHandle[] { config, keyIter }; - } - matchedBody.invokeStaticMethod( - generateParserBody(cc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName, parseMethodCache, - expand), - args); - methodName.setLength(length); - // return; - matchedBody.returnValue(null); - } - } - // todo: unknown name warning goes here - body.returnValue(null); - final MethodDescriptor md = body.getMethodDescriptor(); - parseMethodCache.put(methodNameStr, md); - return md; - } - } - - @BuildStep - void writeDefaultConfiguration( - - ) { - - } -} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 4d9bdd310ae389..897af13faa2507 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -5,7 +5,9 @@ import java.io.File; import java.lang.reflect.Modifier; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -18,6 +20,7 @@ import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem; import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; +import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem; @@ -25,13 +28,17 @@ import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.configuration.BuildTimeConfigurationReader; +import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.deployment.recording.BytecodeRecorderImpl; import io.quarkus.gizmo.BytecodeCreator; import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FieldCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; @@ -65,7 +72,26 @@ MainClassBuildItem build(List staticInitTasks, List loaders, BuildProducer generatedClass, LaunchModeBuildItem launchMode, - ApplicationInfoBuildItem applicationInfo) { + ApplicationInfoBuildItem applicationInfo, + List runTimeDefaults, + ConfigurationBuildItem configItem) { + + BuildTimeConfigurationReader.ReadResult readResult = configItem.getReadResult(); + Map defaults = new HashMap<>(); + for (RunTimeConfigurationDefaultBuildItem item : runTimeDefaults) { + if (defaults.putIfAbsent(item.getKey(), item.getValue()) != null) { + throw new IllegalStateException("More than one default value for " + item.getKey() + " was produced"); + } + } + + ClassOutput classOutput = new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); + } + }; + + RunTimeConfigurationGenerator.generate(readResult, classOutput, defaults); appClassNameProducer.produce(new ApplicationClassNameBuildItem(APP_CLASS)); @@ -89,6 +115,10 @@ MainClassBuildItem build(List staticInitTasks, } mv.invokeStaticMethod(MethodDescriptor.ofMethod(Timing.class, "staticInitStarted", void.class)); + + // ensure that the config class is initialized + mv.invokeStaticMethod(RunTimeConfigurationGenerator.C_ENSURE_INITIALIZED); + ResultHandle startupContext = mv.newInstance(ofConstructor(StartupContext.class)); mv.writeStaticField(scField.getFieldDescriptor(), startupContext); TryBlock tryBlock = mv.tryBlock(); @@ -170,7 +200,7 @@ MainClassBuildItem build(List staticInitTasks, tryBlock = mv.tryBlock(); // Load the run time configuration - tryBlock.invokeStaticMethod(ConfigurationSetup.CREATE_RUN_TIME_CONFIG); + tryBlock.invokeStaticMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG); for (MainBytecodeRecorderBuildItem holder : mainMethod) { final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java index 0a65c6f706d999..3381cc95ebc7bf 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ReflectUtil.java @@ -1,10 +1,16 @@ package io.quarkus.deployment.util; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Parameter; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -74,6 +80,24 @@ public static Class rawTypeOf(final Type type) { } } + private static final Class[] NO_CLASSES = new Class[0]; + + public static Class[] rawTypesOfDestructive(final Type[] types) { + if (types.length == 0) { + return NO_CLASSES; + } + Type t; + Class r; + for (int i = 0; i < types.length; i++) { + t = types[i]; + r = rawTypeOf(t); + if (r != t) { + types[i] = r; + } + } + return Arrays.copyOf(types, types.length, Class[].class); + } + public static Type typeOfParameter(final Type type, final int paramIdx) { if (type instanceof ParameterizedType) { return ((ParameterizedType) type).getActualTypeArguments()[paramIdx]; @@ -94,6 +118,26 @@ public static void setFieldVal(Field field, Object obj, Object value) { } } + public static T newInstance(Class clazz) { + try { + return clazz.getConstructor().newInstance(); + } catch (InstantiationException e) { + throw toError(e); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } + } catch (NoSuchMethodException e) { + throw toError(e); + } catch (IllegalAccessException e) { + throw toError(e); + } + } + public static InstantiationError toError(final InstantiationException e) { final InstantiationError error = new InstantiationError(e.getMessage()); error.setStackTrace(e.getStackTrace()); @@ -117,4 +161,27 @@ public static NoSuchFieldError toError(final NoSuchFieldException e) { error.setStackTrace(e.getStackTrace()); return error; } + + public static UndeclaredThrowableException unwrapInvocationTargetException(InvocationTargetException original) { + try { + throw original.getCause(); + } catch (RuntimeException | Error e) { + throw e; + } catch (Throwable t) { + return new UndeclaredThrowableException(t); + } + } + + public static IllegalArgumentException reportError(AnnotatedElement e, String fmt, Object... args) { + if (e instanceof Member) { + return new IllegalArgumentException( + String.format(fmt, args) + " at " + e + " of " + ((Member) e).getDeclaringClass()); + } else if (e instanceof Parameter) { + return new IllegalArgumentException( + String.format(fmt, args) + " at " + e + " of " + ((Parameter) e).getDeclaringExecutable() + " of " + + ((Parameter) e).getDeclaringExecutable().getDeclaringClass()); + } else { + return new IllegalArgumentException(String.format(fmt, args) + " at " + e); + } + } } diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java index be8810f0f00a0b..05ac3ae9f210c9 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -8,10 +8,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; @@ -241,6 +244,7 @@ public void writeClass(boolean applicationClass, String className, byte[] data) debugPath.mkdir(); } File classFile = new File(debugPath, dotName + ".class"); + classFile.getParentFile().mkdirs(); try (FileOutputStream classWriter = new FileOutputStream(classFile)) { classWriter.write(data); } @@ -268,6 +272,25 @@ public void writeClass(boolean applicationClass, String className, byte[] data) } } + @Override + public Writer writeSource(final String className) { + if (DEBUG_CLASSES_DIR != null) { + try { + File debugPath = new File(DEBUG_CLASSES_DIR); + if (!debugPath.exists()) { + debugPath.mkdir(); + } + File classFile = new File(debugPath, className + ".zig"); + classFile.getParentFile().mkdirs(); + log.infof("Wrote %s", classFile.getAbsolutePath()); + return new OutputStreamWriter(new FileOutputStream(classFile), StandardCharsets.UTF_8); + } catch (Throwable t) { + t.printStackTrace(); + } + } + return ClassOutput.super.writeSource(className); + } + @Override public void setTransformers(Map>> functions) { this.bytecodeTransformers = functions; diff --git a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java index bae45a1b5e308f..5c1b2990866644 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java @@ -32,6 +32,7 @@ import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; +import io.quarkus.deployment.configuration.RunTimeConfigurationGenerator; import io.quarkus.runtime.Application; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ProfileManager; @@ -133,12 +134,26 @@ public void run() { } final Application application; - Class appClass = loader - .loadClass(result.consume(ApplicationClassNameBuildItem.class).getClassName()) - .asSubclass(Application.class); + final String className = result.consume(ApplicationClassNameBuildItem.class).getClassName(); ClassLoader old = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(loader); + Class appClass; + try { + // force init here + appClass = Class.forName(className, true, loader).asSubclass(Application.class); + } catch (Throwable t) { + // todo: dev mode expects run time config to be available immediately even if static init didn't complete. + try { + final Class configClass = Class.forName(RunTimeConfigurationGenerator.CONFIG_CLASS_NAME, true, + loader); + configClass.getDeclaredMethod(RunTimeConfigurationGenerator.C_CREATE_RUN_TIME_CONFIG.getName()) + .invoke(null); + } catch (Throwable t2) { + t.addSuppressed(t2); + } + throw t; + } application = appClass.newInstance(); application.start(null); } finally { diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java index 47e6b773f4b007..04ea9a369c325e 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java @@ -21,6 +21,7 @@ import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import io.quarkus.builder.BuildChainBuilder; @@ -32,7 +33,7 @@ import io.quarkus.runner.RuntimeRunner; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.Timing; -import io.smallrye.config.SmallRyeConfigProviderResolver; +import io.quarkus.runtime.configuration.QuarkusConfigFactory; /** * The main entry point for the dev mojo execution @@ -262,7 +263,13 @@ public void stop() { Thread.currentThread().setContextClassLoader(old); } } - SmallRyeConfigProviderResolver.instance().releaseConfig(SmallRyeConfigProviderResolver.instance().getConfig()); + QuarkusConfigFactory.setConfig(null); + final ConfigProviderResolver cpr = ConfigProviderResolver.instance(); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } DevModeMain.runner = null; } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java index 0db6079bb6a1e5..e1d5581b56f60b 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoItemFinder.java @@ -19,7 +19,9 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import io.quarkus.annotation.processor.Constants; @@ -261,6 +263,8 @@ private List recordConfigItemsFromConfigGroup(ConfigPhase configP private String simpleTypeToString(TypeMirror typeMirror) { if (typeMirror.getKind().isPrimitive()) { return typeMirror.toString(); + } else if (typeMirror.getKind() == TypeKind.ARRAY) { + return "list of " + simpleTypeToString(((ArrayType) typeMirror).getComponentType()); } final String knownGenericType = getKnownGenericType((DeclaredType) typeMirror); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Application.java b/core/runtime/src/main/java/io/quarkus/runtime/Application.java index 8cc773f7613241..b51146fc4dd0f8 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/Application.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/Application.java @@ -4,7 +4,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.LockSupport; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.graalvm.nativeimage.ImageInfo; import org.wildfly.common.Assert; import org.wildfly.common.lock.Locks; @@ -41,12 +40,6 @@ public abstract class Application { private volatile boolean shutdownRequested; private static volatile Application currentApplication; - /** - * The generated config code will install a new resolver, we save the original one here and make sure - * to restore it on shutdown. - */ - private final static ConfigProviderResolver originalResolver = ConfigProviderResolver.instance(); - /** * Construct a new instance. */ @@ -161,7 +154,6 @@ public final void stop() { doStop(); } finally { currentApplication = null; - ConfigProviderResolver.setInstance(originalResolver); stateLock.lock(); try { state = ST_STOPPED; diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java deleted file mode 100644 index 6f862d1c18fd9b..00000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.io.IOError; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Properties; - -import org.eclipse.microprofile.config.spi.ConfigSource; - -import io.smallrye.config.PropertiesConfigSource; - -/** - * - */ -public final class BuildTimeConfigFactory { - - public static final String BUILD_TIME_CONFIG_NAME = "META-INF/build-config.properties"; - - private BuildTimeConfigFactory() { - } - - public static ConfigSource getBuildTimeConfigSource() { - Properties properties = new Properties(); - final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - try { - final Enumeration resources = classLoader.getResources(BUILD_TIME_CONFIG_NAME); - if (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (InputStream is = url.openStream()) { - if (is != null) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - properties.load(isr); - } - } - } - } - return new PropertiesConfigSource(properties, "Build time configuration"); - } catch (IOException e) { - throw new IOError(e); - } - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java new file mode 100644 index 00000000000000..f92291dae31568 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -0,0 +1,79 @@ +package io.quarkus.runtime.configuration; + +import java.util.NoSuchElementException; + +import org.graalvm.nativeimage.ImageInfo; +import org.jboss.logging.Logger; + +import com.oracle.svm.core.annotate.RecomputeFieldValue; + +/** + * Utility methods to log configuration problems. + */ +public final class ConfigDiagnostic { + private static final Logger log = Logger.getLogger("io.quarkus.config"); + + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) + private static volatile boolean error = false; + + private ConfigDiagnostic() { + } + + public static void invalidValue(String name, IllegalArgumentException ex) { + final String message = ex.getMessage(); + log.errorf("An invalid value was given for configuration key \"%s\": %s", name, + message == null ? ex.toString() : message); + error = true; + } + + public static void missingValue(String name, NoSuchElementException ex) { + final String message = ex.getMessage(); + log.errorf("Configuration key \"%s\" is required, but its value is empty/missing: %s", name, + message == null ? ex.toString() : message); + error = true; + } + + public static void duplicate(String name) { + log.errorf("Configuration key \"%s\" was specified more than once", name); + error = true; + } + + public static void deprecated(String name) { + log.warnf("Configuration key \"%s\" is deprecated", name); + } + + public static void unknown(String name) { + log.warnf("Unrecognized configuration key \"%s\" was provided; it will be ignored", name); + } + + public static void unknown(NameIterator name) { + unknown(name.getName()); + } + + public static void unknownRunTime(String name) { + if (ImageInfo.inImageRuntimeCode()) { + // only warn at run time for native images, otherwise the user will get warned twice for every property + log.warnf("Unrecognized configuration key \"%s\" was provided; it will be ignored", name); + } + } + + public static void unknownRunTime(NameIterator name) { + unknownRunTime(name.getName()); + } + + /** + * Determine if a fatal configuration error has occurred. + * + * @return {@code true} if a fatal configuration error has occurred + */ + public static boolean isError() { + return error; + } + + /** + * Reset the config error status (for e.g. testing). + */ + public static void resetError() { + error = false; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java index ad3d262b6ffbb3..33daecaacfb36e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigInstantiator.java @@ -1,23 +1,24 @@ package io.quarkus.runtime.configuration; +import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalInt; -import java.util.OptionalLong; import java.util.Set; -import java.util.regex.Pattern; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.Converter; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; +import io.smallrye.config.Converters; import io.smallrye.config.SmallRyeConfig; /** @@ -30,7 +31,6 @@ */ public class ConfigInstantiator { - private static final Pattern COMMA_PATTERN = Pattern.compile(","); // certain well-known classname suffixes that we support private static Set supportedClassNameSuffix; @@ -71,54 +71,15 @@ private static void handleObject(String prefix, Object o, SmallRyeConfig config) String name = configItem.name(); if (name.equals(ConfigItem.HYPHENATED_ELEMENT_NAME)) { name = dashify(field.getName()); + } else if (name.equals(ConfigItem.ELEMENT_NAME)) { + name = field.getName(); } String fullName = prefix + "." + name; - String defaultValue = configItem.defaultValue(); - if (defaultValue.equals(ConfigItem.NO_DEFAULT)) { - defaultValue = null; - } final Type genericType = field.getGenericType(); - Optional val; - final boolean fieldIsOptional = fieldClass.equals(Optional.class); - final boolean fieldIsList = fieldClass.equals(List.class); - if (fieldIsOptional) { - Class actualType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - val = config.getOptionalValue(fullName, actualType); - } else if (fieldIsList) { - Class actualType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - val = config.getOptionalValues(fullName, actualType, ArrayList::new); - } else { - val = config.getOptionalValue(fullName, fieldClass); - } - if (val.isPresent()) { - field.set(o, fieldIsOptional ? val : val.get()); - } else if (defaultValue != null) { - if (fieldIsList) { - Class listType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - String[] parts = COMMA_PATTERN.split(defaultValue); - List list = new ArrayList<>(); - for (String i : parts) { - list.add(config.convert(i, listType)); - } - field.set(o, list); - } else if (fieldIsOptional) { - Class optionalType = (Class) ((ParameterizedType) genericType) - .getActualTypeArguments()[0]; - field.set(o, Optional.of(config.convert(defaultValue, optionalType))); - } else { - field.set(o, config.convert(defaultValue, fieldClass)); - } - } else if (fieldIsOptional) { - field.set(o, Optional.empty()); - } else if (fieldClass.equals(OptionalInt.class)) { - field.set(o, OptionalInt.empty()); - } else if (fieldClass.equals(OptionalDouble.class)) { - field.set(o, OptionalDouble.empty()); - } else if (fieldClass.equals(OptionalLong.class)) { - field.set(o, OptionalLong.empty()); + final Converter conv = getConverterFor(genericType); + try { + field.set(o, config.getValue(fullName, conv)); + } catch (NoSuchElementException ignored) { } } } @@ -127,6 +88,40 @@ private static void handleObject(String prefix, Object o, SmallRyeConfig config) } } + private static Converter getConverterFor(Type type) { + // hopefully this is enough + final SmallRyeConfig config = (SmallRyeConfig) ConfigProvider.getConfig(); + Class rawType = rawTypeOf(type); + if (rawType == Optional.class) { + return Converters.newOptionalConverter(getConverterFor(typeOfParameter(type, 0))); + } else if (rawType == List.class) { + return Converters.newCollectionConverter(getConverterFor(typeOfParameter(type, 0)), ArrayList::new); + } else { + return config.getConverter(rawTypeOf(type)); + } + } + + // cribbed from io.quarkus.deployment.util.ReflectUtil + private static Class rawTypeOf(final Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return rawTypeOf(((ParameterizedType) type).getRawType()); + } else if (type instanceof GenericArrayType) { + return Array.newInstance(rawTypeOf(((GenericArrayType) type).getGenericComponentType()), 0).getClass(); + } else { + throw new IllegalArgumentException("Type has no raw type class: " + type); + } + } + + static Type typeOfParameter(final Type type, final int paramIdx) { + if (type instanceof ParameterizedType) { + return ((ParameterizedType) type).getActualTypeArguments()[paramIdx]; + } else { + throw new IllegalArgumentException("Type is not parameterized: " + type); + } + } + // Configuration keys are normally derived from the field names that they are tied to. // This is done by de-camel-casing the name and then joining the segments with hyphens (-). // Some examples: diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java index 50295d1a0b01cc..cd9d9e4f5c7a6f 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigUtils.java @@ -1,196 +1,50 @@ package io.quarkus.runtime.configuration; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.function.IntFunction; -import java.util.stream.Collectors; -import org.eclipse.microprofile.config.spi.Converter; - -import io.smallrye.config.SmallRyeConfig; -import io.smallrye.config.StringUtil; +import io.smallrye.config.SmallRyeConfigBuilder; /** * */ public final class ConfigUtils { - private static final Map> EXPLICIT_RUNTIME_CONVERTERS_CACHE = new HashMap<>(); - private ConfigUtils() { } - /** - * This method replicates the logic of {@link SmallRyeConfig#getValues(String, Class, IntFunction)} for the given - * default value string. - * - * @param config the config instance (must not be {@code null}) - * @param defaultValue the default value string (must not be {@code null}) - * @param itemType the item type class (must not be {@code null}) - * @param converterClass - The converter class to use - * @param collectionFactory the collection factory (must not be {@code null}) - * @param the item type - * @param the collection type - * @return the collection (not {@code null}) - */ - public static > C getDefaults(SmallRyeConfig config, String defaultValue, Class itemType, - Class> converterClass, - IntFunction collectionFactory) { - final String[] items = Arrays.stream(StringUtil.split(defaultValue)).filter(s -> !s.isEmpty()).toArray(String[]::new); - final C collection = collectionFactory.apply(items.length); - for (String item : items) { - if (converterClass == null) { - collection.add(config.convert(item, itemType)); - } else { - final Converter converter = getConverterOfType(itemType, converterClass); - final String rawValue = config.convert(item, String.class); - collection.add(converter.convert(rawValue)); - } - } - - return collection; + public static IntFunction> listFactory() { + return ArrayList::new; } - /** - * Retrieve the value of a given config name from Configuration object. Converter the value to an appropriate type using the - * given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the value in appropriate type - */ - public static T getValue(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getValue(configName, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.getValue(configName, String.class); - return converter.convert(rawValue); + public static IntFunction> setFactory() { + return LinkedHashSet::new; } - /** - * Retrieve the Optional value of a property represented by the given config name. Converter the value to an appropriate - * type using the given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return Optional value of appropriate type - */ - public static Optional getOptionalValue(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getOptionalValue(configName, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.getValue(configName, String.class); - return Optional.ofNullable(converter.convert(rawValue)); + public static IntFunction> sortedSetFactory() { + return size -> new TreeSet<>(); } /** - * Retrieve the value of a given config name from Configuration object. Converter the value to an appropriate type using the - * given converter. + * Get the basic configuration builder. * - * @param config - Configuration object (must not be {@code null}) - * @param configName - the property name (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the values in appropriate type + * @return the configuration builder */ - public static ArrayList getValues(SmallRyeConfig config, String configName, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.getValues(configName, objectType, ArrayListFactory.getInstance()); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final ArrayList rawValues = config.getValues(configName, String.class, ArrayListFactory.getInstance()); - return rawValues.parallelStream().map(converter::convert).collect(Collectors.toCollection(ArrayList::new)); + public static SmallRyeConfigBuilder configBuilder() { + final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); + final ApplicationPropertiesConfigSource.InFileSystem inFileSystem = new ApplicationPropertiesConfigSource.InFileSystem(); + final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); + builder.withSources(inFileSystem, inJar); + final ExpandingConfigSource.Cache cache = new ExpandingConfigSource.Cache(); + builder.withWrapper(ExpandingConfigSource.wrapper(cache)); + builder.withWrapper(DeploymentProfileConfigSource.wrapper()); + builder.addDefaultSources(); + builder.addDiscoveredSources(); + builder.addDiscoveredConverters(); + return builder; } - - /** - * Converter the value to an appropriate type using the given converter. - * - * @param config - Configuration object (must not be {@code null}) - * @param value - the value (must not be {@code null}) - * @param objectType - the type of the object (must not be {@code null}) - * @param converterClass - The converter class to use - * @return the value - */ - public static T convert(SmallRyeConfig config, String value, Class objectType, - Class> converterClass) { - if (converterClass == null) { - return config.convert(value, objectType); - } - - final Converter converter = getConverterOfType(objectType, converterClass); - final String rawValue = config.convert(value, String.class); - return converter.convert(rawValue); - } - - private static Converter getConverterOfType(Class type, Class> converterType) { - @SuppressWarnings("unchecked") - final Converter converter = (Converter) EXPLICIT_RUNTIME_CONVERTERS_CACHE - .get(new ConverterClassHolder(type, converterType)); - if (converter != null) { - return converter; - } - - // build time converter no need to be cached - return newConverterInstance(type, converterType); - } - - public static Converter newConverterInstance(Class type, Class> converterClass) { - // todo: this gets cleaned up with the SmallRye Config update - if (HyphenateEnumConverter.class.equals(converterClass)) { - @SuppressWarnings("unchecked") - final Converter converter = new HyphenateEnumConverter(type); - return converter; - } - - try { - return converterClass.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { - throw new IllegalArgumentException(e); - } - } - - public static void populateExplicitRuntimeConverter(Class typeClass, Class> converterType, - Converter converter) { - final Class type = getWrapperClass(typeClass); - EXPLICIT_RUNTIME_CONVERTERS_CACHE.put(new ConverterClassHolder(type, converterType), converter); - } - - private static Class getWrapperClass(Class type) { - if (type == Integer.TYPE) { - return Integer.class; - } - - if (type == Long.TYPE) { - return Long.class; - } - if (type == Boolean.TYPE) { - return Boolean.class; - } - if (type == Float.TYPE) { - return Float.class; - } - - if (type == Double.TYPE) { - return Double.class; - } - - return type; - } - } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java new file mode 100644 index 00000000000000..cb5706fb9e3972 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java @@ -0,0 +1,46 @@ +package io.quarkus.runtime.configuration; + +/** + * An exception indicating that a configuration failure has occurred. + */ +public class ConfigurationException extends RuntimeException { + private static final long serialVersionUID = 4445679764085720090L; + + /** + * Constructs a new {@code ConfigurationException} instance. The message is left blank ({@code null}), and no + * cause is specified. + */ + public ConfigurationException() { + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial message. No + * cause is specified. + * + * @param msg the message + */ + public ConfigurationException(final String msg) { + super(msg); + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial cause. If + * a non-{@code null} cause is specified, its message is used to initialize the message of this + * {@code ConfigurationException}; otherwise the message is left blank ({@code null}). + * + * @param cause the cause + */ + public ConfigurationException(final Throwable cause) { + super(cause); + } + + /** + * Constructs a new {@code ConfigurationException} instance with an initial message and cause. + * + * @param msg the message + * @param cause the cause + */ + public ConfigurationException(final String msg, final Throwable cause) { + super(msg, cause); + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java index 5e66365e415062..3025974522450f 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterSupport.java @@ -19,6 +19,7 @@ * This small utility class is a tool which helps populating SmallRye {@link ConfigBuilder} with * {@link Converter} implementations loaded from {@link ServiceLoader}. */ +// todo: delete public class ConverterSupport { private static final Logger LOG = Logger.getLogger(ConverterSupport.class); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java deleted file mode 100644 index 40cbe8f7d02daa..00000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; -import java.util.Map; -import java.util.Properties; - -import io.smallrye.config.PropertiesConfigSource; - -/** - * The default values run time configuration source. - */ -public final class DefaultConfigSource extends PropertiesConfigSource { - private static final long serialVersionUID = -6482737535291300045L; - - public static final String DEFAULT_CONFIG_PROPERTIES_NAME = "META-INF/quarkus-default-config.properties"; - - /** - * Construct a new instance. - */ - public DefaultConfigSource() { - super(getMap(), "Default configuration values", 0); - } - - @SuppressWarnings("unchecked") - private static Map getMap() { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl == null) { - cl = DefaultConfigSource.class.getClassLoader(); - } - try { - final Properties p = new Properties(); - // work around #1477 - final Enumeration resources = cl == null ? ClassLoader.getSystemResources(DEFAULT_CONFIG_PROPERTIES_NAME) - : cl.getResources(DEFAULT_CONFIG_PROPERTIES_NAME); - if (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - try (InputStream is = url.openStream()) { - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - p.load(isr); - } - } - } - return (Map) p; - } catch (IOException e) { - throw new IllegalStateException("Cannot read default configuration", e); - } - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java index 1eb4059d753934..236286f6a4533a 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DeploymentProfileConfigSource.java @@ -61,4 +61,9 @@ public String getValue(final String name) { public String getName() { return delegate.getName(); } + + public String toString() { + return "DeploymentProfileConfigSource[profile=" + profilePrefix + ",delegate=" + getDelegate() + ",ord=" + getOrdinal() + + "]"; + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java index f356eb22343e0c..3746e210cf0d10 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DurationConverter.java @@ -32,6 +32,9 @@ public DurationConverter() { */ @Override public Duration convert(String value) { + if (value.isEmpty()) { + return null; + } if (DIGITS.asPredicate().test(value)) { return Duration.ofSeconds(Long.valueOf(value)); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java index aefcc7dfb77e32..86c28d94a8a229 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java @@ -52,6 +52,10 @@ public void flush() { cache.flush(); } + public String toString() { + return "ExpandingConfigSource[delegate=" + getDelegate() + ",ord=" + getOrdinal() + "]"; + } + private static boolean isExpanding() { return NO_EXPAND.get() != Boolean.TRUE; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java index a1108704612fca..45d88f8db37471 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/HyphenateEnumConverter.java @@ -7,10 +7,12 @@ import org.eclipse.microprofile.config.spi.Converter; +import io.quarkus.runtime.util.StringUtil; + /** - * A converter for hyphenated enums + * A converter for hyphenated enums. */ -final public class HyphenateEnumConverter> implements Converter { +public final class HyphenateEnumConverter> implements Converter { private static final String HYPHEN = "-"; private static final Pattern PATTERN = Pattern.compile("([-_]+)"); @@ -27,6 +29,10 @@ public HyphenateEnumConverter(Class enumType) { } } + public static > HyphenateEnumConverter of(Class enumType) { + return new HyphenateEnumConverter(enumType); + } + @Override public E convert(String value) { if (value == null || value.trim().isEmpty()) { @@ -46,7 +52,7 @@ public E convert(String value) { private String hyphenate(String value) { StringBuffer target = new StringBuffer(); - String hyphenate = io.quarkus.runtime.util.StringUtil.hyphenate(value); + String hyphenate = StringUtil.hyphenate(value); Matcher matcher = PATTERN.matcher(hyphenate); while (matcher.find()) { matcher.appendReplacement(target, HYPHEN); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java index eaa1858e691935..fbe8e5f95e3313 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/MemorySizeConverter.java @@ -42,6 +42,9 @@ public class MemorySizeConverter implements Converter { * @return {@link MemorySize} - a memory size represented by the given value */ public MemorySize convert(String value) { + if (value.isEmpty()) { + return null; + } Matcher matcher = MEMORY_SIZE_PATTERN.matcher(value); if (matcher.find()) { BigInteger number = new BigInteger(matcher.group(1)); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java index b641f262bd090b..0c68a3c2a2348f 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/NameIterator.java @@ -295,6 +295,22 @@ public String getPreviousSegment() { } } + public String getAllPreviousSegments() { + final int pos = getPosition(); + if (pos == -1) { + return ""; + } + return name.substring(0, pos); + } + + public String getAllPreviousSegmentsWith(String suffix) { + final int pos = getPosition(); + if (pos == -1) { + return suffix; + } + return name.substring(0, pos) + "." + suffix; + } + public boolean hasNext() { return pos < name.length(); } @@ -316,7 +332,12 @@ public String getName() { } public String toString() { - // generated code relies on this behavior - return getName(); + if (pos == -1) { + return "*" + name; + } else if (pos == name.length()) { + return name + "*"; + } else { + return name.substring(0, pos) + '*' + name.substring(pos + 1); + } } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java index 0b1aaf9872c60b..a783840069ad85 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/PathConverter.java @@ -17,6 +17,6 @@ public class PathConverter implements Converter { @Override public Path convert(String value) { - return Paths.get(value); + return value.isEmpty() ? null : Paths.get(value); } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java new file mode 100644 index 00000000000000..b359cd2aac0a4b --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/QuarkusConfigFactory.java @@ -0,0 +1,29 @@ +package io.quarkus.runtime.configuration; + +import io.smallrye.config.SmallRyeConfig; +import io.smallrye.config.SmallRyeConfigFactory; +import io.smallrye.config.SmallRyeConfigProviderResolver; + +/** + * The simple Quarkus implementation of {@link SmallRyeConfigFactory}. + */ +public final class QuarkusConfigFactory extends SmallRyeConfigFactory { + + private static volatile SmallRyeConfig config; + + /** + * Construct a new instance. Called by service loader. + */ + public QuarkusConfigFactory() { + // todo: replace with {@code provider()} post-Java 11 + } + + public SmallRyeConfig getConfigFor(final SmallRyeConfigProviderResolver configProviderResolver, + final ClassLoader classLoader) { + return config; + } + + public static void setConfig(SmallRyeConfig config) { + QuarkusConfigFactory.config = config; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java index eb395a0b945b8a..ad6e72093c48bd 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/RegexConverter.java @@ -21,6 +21,6 @@ public RegexConverter() { } public Pattern convert(final String value) { - return Pattern.compile(value); + return value.isEmpty() ? null : Pattern.compile(value); } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java deleted file mode 100644 index c41510ad7bb884..00000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.quarkus.runtime.configuration; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigBuilder; -import org.eclipse.microprofile.config.spi.ConfigProviderResolver; - -import io.smallrye.config.SmallRyeConfigBuilder; - -/** - * A simple configuration provider. - */ -public class SimpleConfigurationProviderResolver extends ConfigProviderResolver { - - // We use a shared config - private static volatile Config config; - - public Config getConfig() { - return config; - } - - public Config getConfig(final ClassLoader loader) { - return getConfig(); - } - - public ConfigBuilder getBuilder() { - return new SmallRyeConfigBuilder(); - } - - public void registerConfig(final Config config, final ClassLoader classLoader) { - SimpleConfigurationProviderResolver.config = config; - } - - public void releaseConfig(final Config config) { - SimpleConfigurationProviderResolver.config = null; - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java index fbf76fd31b8e20..c35da81757777c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/Substitutions.java @@ -1,7 +1,5 @@ package io.quarkus.runtime.configuration; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.wildfly.common.Assert; @@ -68,20 +66,4 @@ public static boolean setExpanding(boolean newValue) { return true; } } - - @TargetClass(ConfigProvider.class) - static final class Target_ConfigProvider { - @Delete - private static ConfigProviderResolver INSTANCE; - - @Substitute - public static Config getConfig() { - return ConfigProviderResolver.instance().getConfig(); - } - - @Substitute - public static Config getConfig(ClassLoader cl) { - return getConfig(); - } - } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java deleted file mode 100644 index b40200f6b81bfa..00000000000000 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/TemporaryConfigSourceProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.runtime.configuration; - -import java.util.Arrays; - -import org.eclipse.microprofile.config.spi.ConfigSource; -import org.eclipse.microprofile.config.spi.ConfigSourceProvider; - -/** - * This is a temporary hack until the class loader mess is worked out. - */ -public class TemporaryConfigSourceProvider implements ConfigSourceProvider { - public Iterable getConfigSources(final ClassLoader forClassLoader) { - return Arrays.asList( - new ApplicationPropertiesConfigSource.InJar(), - new ApplicationPropertiesConfigSource.InFileSystem()); - } -} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java index 31b994c477cfad..25358cb1af1b49 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java @@ -1,6 +1,8 @@ package io.quarkus.runtime.util; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Objects; @@ -97,6 +99,13 @@ public String next() { }; } + /** + * @deprecated Use {@link String#join} instead. + * @param delim delimiter + * @param it iterator + * @return the joined string + */ + @Deprecated public static String join(String delim, Iterator it) { final StringBuilder b = new StringBuilder(); if (it.hasNext()) { @@ -167,6 +176,34 @@ public String next() { }; } + @SafeVarargs + public static List withoutSuffix(List list, T... segments) { + if (list.size() < segments.length) { + return list; + } + for (int i = 0; i < segments.length; i++) { + if (!list.get(list.size() - i - 1).equals(segments[segments.length - i - 1])) { + return list; + } + } + return list.subList(0, list.size() - segments.length); + } + + public static List toList(Iterator orig) { + return toList(orig, 0); + } + + private static List toList(Iterator orig, int idx) { + if (orig.hasNext()) { + final String item = orig.next(); + final List list = toList(orig, idx + 1); + list.set(idx, item); + return list; + } else { + return Arrays.asList(new String[idx]); + } + } + @SafeVarargs private static boolean arrayContains(final T item, final T... array) { for (T arrayItem : array) { diff --git a/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory b/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory new file mode 100644 index 00000000000000..0900f32a78ef92 --- /dev/null +++ b/core/runtime/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigFactory @@ -0,0 +1 @@ +io.quarkus.runtime.configuration.QuarkusConfigFactory diff --git a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider b/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider deleted file mode 100644 index 778dff1e4a68cd..00000000000000 --- a/core/runtime/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSourceProvider +++ /dev/null @@ -1 +0,0 @@ -io.quarkus.runtime.configuration.TemporaryConfigSourceProvider diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java index 0b3605e20e5294..4c404b30c50f6a 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigExpanderTestCase.java @@ -34,7 +34,11 @@ public static void initConfig() { @AfterEach public void doAfter() { - cpr.releaseConfig(config); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } } private SmallRyeConfig buildConfig(Map configMap) { diff --git a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java index e09d72bb2fb0fc..48b668ef8c06a0 100644 --- a/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java +++ b/core/runtime/src/test/java/io/quarkus/runtime/configuration/ConfigProfileTestCase.java @@ -31,7 +31,11 @@ public static void initConfig() { @AfterEach public void doAfter() { - cpr.releaseConfig(config); + try { + cpr.releaseConfig(cpr.getConfig()); + } catch (IllegalStateException ignored) { + // just means no config was installed, which is fine + } } private SmallRyeConfig buildConfig(Map configMap) {