Detect default enum value in configuration metadata

Closes gh-7562
This commit is contained in:
Stéphane Nicoll
2024-08-01 15:43:44 +02:00
16 changed files with 233 additions and 352 deletions

View File

@@ -30,14 +30,6 @@
"description": "Whether to enable default metrics exporters.",
"defaultValue": true
},
{
"name": "management.endpoint.configprops.show-values",
"defaultValue": "never"
},
{
"name": "management.endpoint.env.show-values",
"defaultValue": "never"
},
{
"name": "management.endpoint.health.probes.add-additional-paths",
"type": "java.lang.Boolean",
@@ -50,10 +42,6 @@
"description": "Whether to enable liveness and readiness probes.",
"defaultValue": false
},
{
"name": "management.endpoint.health.show-details",
"defaultValue": "never"
},
{
"name": "management.endpoint.health.status.order",
"defaultValue": [
@@ -69,10 +57,6 @@
"description": "Whether to validate health group membership on startup. Validation fails if a group includes or excludes a health contributor that does not exist.",
"defaultValue": true
},
{
"name": "management.endpoint.quartz.show-values",
"defaultValue": "never"
},
{
"name": "management.endpoints.enabled-by-default",
"type": "java.lang.Boolean",
@@ -107,26 +91,6 @@
"health"
]
},
{
"name": "management.ganglia.metrics.export.addressing-mode",
"defaultValue": "multicast"
},
{
"name": "management.ganglia.metrics.export.duration-units",
"defaultValue": "milliseconds"
},
{
"name": "management.graphite.metrics.export.duration-units",
"defaultValue": "milliseconds"
},
{
"name": "management.graphite.metrics.export.protocol",
"defaultValue": "pickled"
},
{
"name": "management.graphite.metrics.export.rate-units",
"defaultValue": "seconds"
},
{
"name": "management.health.cassandra.enabled",
"type": "java.lang.Boolean",
@@ -277,10 +241,6 @@
"errors"
]
},
{
"name": "management.influx.metrics.export.consistency",
"defaultValue": "one"
},
{
"name": "management.info.build.enabled",
"type": "java.lang.Boolean",
@@ -305,10 +265,6 @@
"description": "Whether to enable git info.",
"defaultValue": true
},
{
"name": "management.info.git.mode",
"defaultValue": "simple"
},
{
"name": "management.info.java.enabled",
"type": "java.lang.Boolean",
@@ -2079,45 +2035,17 @@
"level": "error"
}
},
{
"name": "management.newrelic.metrics.export.client-provider-type",
"defaultValue": "insights-api"
},
{
"name": "management.observations.annotations.enabled",
"type": "java.lang.Boolean",
"description": "Whether auto-configuration of Micrometer annotations is enabled.",
"defaultValue": false
},
{
"name": "management.otlp.logging.compression",
"defaultValue": "none"
},
{
"name": "management.otlp.metrics.export.aggregation-temporality",
"defaultValue": "cumulative"
},
{
"name": "management.otlp.metrics.export.base-time-unit",
"defaultValue": "milliseconds"
},
{
"name": "management.otlp.tracing.compression",
"defaultValue": "none"
},
{
"name": "management.otlp.tracing.export.enabled",
"type": "java.lang.Boolean",
"description": "Whether auto-configuration of tracing is enabled to export OTLP traces."
},
{
"name": "management.prometheus.metrics.export.histogram-flavor",
"defaultValue": "prometheus"
},
{
"name": "management.prometheus.metrics.export.pushgateway.shutdown-operation",
"defaultValue": "none"
},
{
"name": "management.server.add-application-context-header",
"type": "java.lang.Boolean",
@@ -2218,22 +2146,6 @@
"name": "management.server.ssl.trust-store-type",
"description": "Type of the trust store."
},
{
"name": "management.signalfx.metrics.export.published-histogram-type",
"defaultValue": "default"
},
{
"name": "management.simple.metrics.export.mode",
"defaultValue": "cumulative"
},
{
"name": "management.statsd.metrics.export.flavor",
"defaultValue": "datadog"
},
{
"name": "management.statsd.metrics.export.protocol",
"defaultValue": "udp"
},
{
"name": "management.trace.http.enabled",
"deprecation": {

View File

@@ -37,22 +37,6 @@
"level": "error"
}
},
{
"name": "server.error.include-binding-errors",
"defaultValue": "never"
},
{
"name": "server.error.include-message",
"defaultValue": "never"
},
{
"name": "server.error.include-path",
"defaultValue": "always"
},
{
"name": "server.error.include-stacktrace",
"defaultValue": "never"
},
{
"name": "server.http2.enabled",
"description": "Whether to enable HTTP/2 support, if the current environment supports it.",
@@ -72,10 +56,6 @@
"level": "error"
}
},
{
"name": "server.jetty.accesslog.format",
"defaultValue": "ncsa"
},
{
"name": "server.jetty.accesslog.locale",
"deprecation": {
@@ -279,10 +259,6 @@
"name": "server.servlet.session.tracking-modes",
"description": "Session tracking modes."
},
{
"name": "server.shutdown",
"defaultValue": "immediate"
},
{
"name": "server.ssl.bundle",
"description": "The name of a configured SSL bundle."
@@ -499,10 +475,6 @@
"level": "error"
}
},
{
"name": "spring.batch.jdbc.initialize-schema",
"defaultValue": "embedded"
},
{
"name": "spring.batch.job.enabled",
"type": "java.lang.Boolean",
@@ -1055,10 +1027,6 @@
"name": "spring.data.mongodb.uri",
"defaultValue": "mongodb://localhost/test"
},
{
"name": "spring.data.mongodb.uuid-representation",
"defaultValue": "java-legacy"
},
{
"name": "spring.data.neo4j.auto-index",
"description": "Auto index mode.",
@@ -1157,10 +1125,6 @@
"level": "error"
}
},
{
"name": "spring.data.rest.detection-strategy",
"defaultValue": "default"
},
{
"name" : "spring.datasource.continue-on-error",
"type" : "java.lang.Boolean",
@@ -1619,10 +1583,6 @@
"name": "spring.info.git.location",
"defaultValue": "classpath:git.properties"
},
{
"name": "spring.integration.jdbc.initialize-schema",
"defaultValue": "embedded"
},
{
"name": "spring.jackson.constructor-detector",
"defaultValue": "default"
@@ -1639,22 +1599,6 @@
"level": "error"
}
},
{
"name": "spring.jersey.type",
"defaultValue": "servlet"
},
{
"name": "spring.jms.listener.session.acknowledge-mode",
"defaultValue": "auto"
},
{
"name": "spring.jms.template.session.acknowledge-mode",
"defaultValue": "auto"
},
{
"name": "spring.jmx.registration-policy",
"defaultValue": "fail-on-existing"
},
{
"name": "spring.jpa.hibernate.use-new-id-generator-mappings",
"type": "java.lang.Boolean",
@@ -1835,10 +1779,6 @@
"level": "error"
}
},
{
"name": "spring.kafka.consumer.isolation-level",
"defaultValue": "read-uncommitted"
},
{
"name": "spring.kafka.consumer.ssl.keystore-location",
"type": "org.springframework.core.io.Resource",
@@ -1875,10 +1815,6 @@
"level": "error"
}
},
{
"name": "spring.kafka.jaas.control-flag",
"defaultValue": "required"
},
{
"name": "spring.kafka.listener.only-log-record-metadata",
"type": "java.lang.Boolean",
@@ -1889,10 +1825,6 @@
"level": "error"
}
},
{
"name": "spring.kafka.listener.type",
"defaultValue": "single"
},
{
"name": "spring.kafka.producer.ssl.keystore-location",
"type": "org.springframework.core.io.Resource",
@@ -2097,10 +2029,6 @@
"level": "error"
}
},
{
"name": "spring.mvc.pathmatch.matching-strategy",
"defaultValue": "path-pattern-parser"
},
{
"name": "spring.mvc.throw-exception-if-no-handler-found",
"deprecation": {
@@ -2108,58 +2036,22 @@
"level": "error"
}
},
{
"name": "spring.neo4j.security.trust-strategy",
"defaultValue": "trust-system-ca-signed-certificates"
},
{
"name": "spring.neo4j.uri",
"defaultValue": "bolt://localhost:7687"
},
{
"name": "spring.pulsar.client.failover.policy",
"defaultValue": "order"
},
{
"name": "spring.pulsar.consumer.subscription.initial-position",
"defaultValue": "latest"
},
{
"name": "spring.pulsar.consumer.subscription.mode",
"defaultValue": "durable"
},
{
"name": "spring.pulsar.consumer.subscription.topics-mode",
"defaultValue": "persistentonly"
},
{
"name": "spring.pulsar.consumer.subscription.type",
"defaultValue": "exclusive"
},
{
"name": "spring.pulsar.function.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable function support.",
"defaultValue": true
},
{
"name": "spring.pulsar.producer.access-mode",
"defaultValue": "shared"
},
{
"name": "spring.pulsar.producer.cache.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable caching in the PulsarProducerFactory.",
"defaultValue": true
},
{
"name": "spring.pulsar.producer.hashing-scheme",
"defaultValue": "javastringhash"
},
{
"name": "spring.pulsar.producer.message-routing-mode",
"defaultValue": "roundrobinpartition"
},
{
"name": "spring.quartz.jdbc.comment-prefix",
"defaultValue": [
@@ -2167,30 +2059,10 @@
"--"
]
},
{
"name": "spring.quartz.jdbc.initialize-schema",
"defaultValue": "embedded"
},
{
"name": "spring.quartz.job-store-type",
"defaultValue": "memory"
},
{
"name": "spring.quartz.scheduler-name",
"defaultValue": "quartzScheduler"
},
{
"name": "spring.r2dbc.pool.validation-depth",
"defaultValue": "local"
},
{
"name": "spring.rabbitmq.address-shuffle-mode",
"defaultValue": "none"
},
{
"name": "spring.rabbitmq.cache.connection.mode",
"defaultValue": "channel"
},
{
"name": "spring.rabbitmq.dynamic",
"type": "java.lang.Boolean",
@@ -2204,10 +2076,6 @@
"level": "error"
}
},
{
"name": "spring.rabbitmq.listener.type",
"defaultValue": "simple"
},
{
"name": "spring.rabbitmq.publisher-confirms",
"type": "java.lang.Boolean",
@@ -2223,10 +2091,6 @@
"level": "error"
}
},
{
"name": "spring.reactor.context-propagation",
"defaultValue": "limited"
},
{
"name": "spring.reactor.stacktrace-mode.enabled",
"description": "Whether Reactor should collect stacktrace information at runtime.",
@@ -2772,10 +2636,6 @@
"name": "spring.rsocket.server.ssl.trust-store-type",
"description": "Type of the trust store."
},
{
"name": "spring.rsocket.server.transport",
"defaultValue": "tcp"
},
{
"name": "spring.security.filter.dispatcher-types",
"defaultValue": [
@@ -2798,46 +2658,10 @@
"level": "error"
}
},
{
"name": "spring.session.hazelcast.flush-mode",
"defaultValue": "on-save"
},
{
"name": "spring.session.hazelcast.save-mode",
"defaultValue": "on-set-attribute"
},
{
"name": "spring.session.jdbc.flush-mode",
"defaultValue": "on-save"
},
{
"name": "spring.session.jdbc.initialize-schema",
"defaultValue": "embedded"
},
{
"name": "spring.session.jdbc.save-mode",
"defaultValue": "on-set-attribute"
},
{
"name": "spring.session.redis.cleanup-cron",
"defaultValue": "0 * * * * *"
},
{
"name": "spring.session.redis.configure-action",
"defaultValue": "notify-keyspace-events"
},
{
"name": "spring.session.redis.flush-mode",
"defaultValue": "on-save"
},
{
"name": "spring.session.redis.repository-type",
"defaultValue": "default"
},
{
"name": "spring.session.redis.save-mode",
"defaultValue": "on-set-attribute"
},
{
"name": "spring.session.servlet.filter-dispatcher-types",
"defaultValue": [
@@ -2856,10 +2680,6 @@
"level": "warning"
}
},
{
"name": "spring.sql.init.mode",
"defaultValue": "embedded"
},
{
"name": "spring.threads.virtual.enabled",
"type": "java.lang.Boolean",
@@ -2893,10 +2713,6 @@
"name": "spring.thymeleaf.suffix",
"defaultValue": ".html"
},
{
"name": "spring.web.locale-resolver",
"defaultValue": "accept-header"
},
{
"name": "spring.webflux.hiddenmethod.filter.enabled",
"type": "java.lang.Boolean",

View File

@@ -1,30 +0,0 @@
{
"groups": [],
"hints": [],
"properties": [
{
"name": "spring.docker.compose.lifecycle-management",
"defaultValue": "start-and-stop"
},
{
"name": "spring.docker.compose.readiness.wait",
"defaultValue": "always"
},
{
"name": "spring.docker.compose.start.command",
"defaultValue": "up"
},
{
"name": "spring.docker.compose.start.log-level",
"defaultValue": "info"
},
{
"name": "spring.docker.compose.start.skip",
"defaultValue": "if-running"
},
{
"name": "spring.docker.compose.stop.command",
"defaultValue": "stop"
}
]
}

View File

@@ -93,7 +93,7 @@ If you use `@ConfigurationProperties` with record class then record components'
The annotation processor applies a number of heuristics to extract the default value from the source model.
Default values have to be provided statically. In particular, do not refer to a constant defined in another class.
Also, the annotation processor cannot auto-detect default values for ``Enum``s and ``Collections``s.
Also, the annotation processor cannot auto-detect default values for ``Collections``s.
For cases where the default value could not be detected, xref:configuration-metadata/annotation-processor.adoc#appendix.configuration-metadata.annotation-processor.adding-additional-metadata[manual metadata] should be provided.
Consider the following example:

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,6 +36,12 @@ class ExpressionTree extends ReflectionWrapper {
private final Method methodInvocationArgumentsMethod = findMethod(this.methodInvocationTreeType, "getArguments");
private final Class<?> memberSelectTreeType = findClass("com.sun.source.tree.MemberSelectTree");
private final Method memberSelectTreeExpressionMethod = findMethod(this.memberSelectTreeType, "getExpression");
private final Method memberSelectTreeIdentifierMethod = findMethod(this.memberSelectTreeType, "getIdentifier");
private final Class<?> newArrayTreeType = findClass("com.sun.source.tree.NewArrayTree");
private final Method arrayValueMethod = findMethod(this.newArrayTreeType, "getInitializers");
@@ -65,6 +71,17 @@ class ExpressionTree extends ReflectionWrapper {
return null;
}
Member getSelectedMember() throws Exception {
if (this.memberSelectTreeType.isAssignableFrom(getInstance().getClass())) {
String expression = this.memberSelectTreeExpressionMethod.invoke(getInstance()).toString();
String identifier = this.memberSelectTreeIdentifierMethod.invoke(getInstance()).toString();
if (expression != null && identifier != null) {
return new Member(expression, identifier);
}
}
return null;
}
List<? extends ExpressionTree> getArrayExpression() throws Exception {
if (this.newArrayTreeType.isAssignableFrom(getInstance().getClass())) {
List<?> elements = (List<?>) this.arrayValueMethod.invoke(getInstance());
@@ -80,4 +97,7 @@ class ExpressionTree extends ReflectionWrapper {
return null;
}
record Member(String expression, String identifier) {
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor.fieldvalues.javac;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -27,6 +28,8 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser;
import org.springframework.boot.configurationprocessor.fieldvalues.javac.ExpressionTree.Member;
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
/**
* {@link FieldValuesParser} implementation for the standard Java compiler.
@@ -165,12 +168,12 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser {
Class<?> wrapperType = WRAPPER_TYPES.get(variable.getType());
Object defaultValue = DEFAULT_TYPE_VALUES.get(wrapperType);
if (initializer != null) {
return getValue(initializer, defaultValue);
return getValue(variable.getType(), initializer, defaultValue);
}
return defaultValue;
}
private Object getValue(ExpressionTree expression, Object defaultValue) throws Exception {
private Object getValue(String variableType, ExpressionTree expression, Object defaultValue) throws Exception {
Object literalValue = expression.getLiteralValue();
if (literalValue != null) {
return literalValue;
@@ -183,7 +186,7 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser {
if (arrayValues != null) {
Object[] result = new Object[arrayValues.size()];
for (int i = 0; i < arrayValues.size(); i++) {
Object value = getValue(arrayValues.get(i), null);
Object value = getValue(variableType, arrayValues.get(i), null);
if (value == null) { // One of the elements could not be resolved
return defaultValue;
}
@@ -195,7 +198,16 @@ public class JavaCompilerFieldValuesParser implements FieldValuesParser {
return this.staticFinals.get(expression.toString());
}
if (expression.getKind().equals("MEMBER_SELECT")) {
return WELL_KNOWN_STATIC_FINALS.get(expression.toString());
Object value = WELL_KNOWN_STATIC_FINALS.get(expression.toString());
if (value != null) {
return value;
}
Member selectedMember = expression.getSelectedMember();
// Type matching the expression, assuming an enum
if (selectedMember != null && selectedMember.expression().equals(variableType)) {
return ConventionUtils.toDashedCase(selectedMember.identifier().toLowerCase(Locale.ENGLISH));
}
return null;
}
return defaultValue;
}

View File

@@ -17,14 +17,12 @@
package org.springframework.boot.configurationprocessor.metadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
/**
* Configuration meta-data.
@@ -36,13 +34,6 @@ import java.util.Set;
*/
public class ConfigurationMetadata {
private static final Set<Character> SEPARATORS;
static {
List<Character> chars = Arrays.asList('-', '_');
SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars));
}
private final Map<String, List<ItemMetadata>> items;
private final Map<String, List<ItemHint>> hints;
@@ -184,31 +175,11 @@ public class ConfigurationMetadata {
public static String nestedPrefix(String prefix, String name) {
String nestedPrefix = (prefix != null) ? prefix : "";
String dashedName = toDashedCase(name);
String dashedName = ConventionUtils.toDashedCase(name);
nestedPrefix += nestedPrefix.isEmpty() ? dashedName : "." + dashedName;
return nestedPrefix;
}
static String toDashedCase(String name) {
StringBuilder dashed = new StringBuilder();
Character previous = null;
for (int i = 0; i < name.length(); i++) {
char current = name.charAt(i);
if (SEPARATORS.contains(current)) {
dashed.append("-");
}
else if (Character.isUpperCase(current) && previous != null && !SEPARATORS.contains(previous)) {
dashed.append("-").append(current);
}
else {
dashed.append(current);
}
previous = current;
}
return dashed.toString().toLowerCase(Locale.ENGLISH);
}
private static <T extends Comparable<T>> List<T> flattenValues(Map<?, List<T>> map) {
List<T> content = new ArrayList<>();
for (List<T> values : map.values()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
/**
* Provide hints on an {@link ItemMetadata}. Defines the list of possible values for a
* particular item as {@link ItemHint.ValueHint} instances.
@@ -53,9 +55,9 @@ public class ItemHint implements Comparable<ItemHint> {
if (dot != -1) {
String prefix = name.substring(0, dot);
String originalName = name.substring(dot);
return prefix + ConfigurationMetadata.toDashedCase(originalName);
return prefix + ConventionUtils.toDashedCase(originalName);
}
return ConfigurationMetadata.toDashedCase(name);
return ConventionUtils.toDashedCase(name);
}
public String getName() {

View File

@@ -18,6 +18,8 @@ package org.springframework.boot.configurationprocessor.metadata;
import java.util.Locale;
import org.springframework.boot.configurationprocessor.support.ConventionUtils;
/**
* A group or property meta-data item from some {@link ConfigurationMetadata}.
*
@@ -68,7 +70,7 @@ public final class ItemMetadata implements Comparable<ItemMetadata> {
if (!fullName.isEmpty()) {
fullName.append('.');
}
fullName.append(ConfigurationMetadata.toDashedCase(name));
fullName.append(ConventionUtils.toDashedCase(name));
}
return fullName.toString();
}
@@ -218,7 +220,7 @@ public final class ItemMetadata implements Comparable<ItemMetadata> {
}
public static String newItemMetadataPrefix(String prefix, String suffix) {
return prefix.toLowerCase(Locale.ENGLISH) + ConfigurationMetadata.toDashedCase(suffix);
return prefix.toLowerCase(Locale.ENGLISH) + ConventionUtils.toDashedCase(suffix);
}
/**

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Convention utilities.
*
* @author Stephane Nicoll
* @since 3.4.0
*/
public abstract class ConventionUtils {
private static final Set<Character> SEPARATORS;
static {
List<Character> chars = Arrays.asList('-', '_');
SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars));
}
/**
* Return the idiomatic metadata format for the given {@code value}.
* @param value a value
* @return the idiomatic format for the value, or the value itself if it already
* complies with the idiomatic metadata format.
*/
public static String toDashedCase(String value) {
StringBuilder dashed = new StringBuilder();
Character previous = null;
for (int i = 0; i < value.length(); i++) {
char current = value.charAt(i);
if (SEPARATORS.contains(current)) {
dashed.append("-");
}
else if (Character.isUpperCase(current) && previous != null && !SEPARATORS.contains(previous)) {
dashed.append("-").append(current);
}
else {
dashed.append(current);
}
previous = current;
}
return dashed.toString().toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support classes for configuration metadata processing.
*/
package org.springframework.boot.configurationprocessor.support;

View File

@@ -16,6 +16,9 @@
package org.springframework.boot.configurationprocessor;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import org.junit.jupiter.api.Test;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
@@ -50,6 +53,7 @@ import org.springframework.boot.configurationsample.specific.DeprecatedSimplePoj
import org.springframework.boot.configurationsample.specific.DeprecatedUnrelatedMethodPojo;
import org.springframework.boot.configurationsample.specific.DoubleRegistrationProperties;
import org.springframework.boot.configurationsample.specific.EmptyDefaultValueProperties;
import org.springframework.boot.configurationsample.specific.EnumValuesPojo;
import org.springframework.boot.configurationsample.specific.ExcludedTypesPojo;
import org.springframework.boot.configurationsample.specific.InnerClassAnnotatedGetterConfig;
import org.springframework.boot.configurationsample.specific.InnerClassHierarchicalProperties;
@@ -173,6 +177,15 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
.fromSource(HierarchicalProperties.class));
}
@Test
void enumValues() {
ConfigurationMetadata metadata = compile(EnumValuesPojo.class);
assertThat(metadata).has(Metadata.withGroup("test").fromSource(EnumValuesPojo.class));
assertThat(metadata).has(Metadata.withProperty("test.seconds", ChronoUnit.class).withDefaultValue("seconds"));
assertThat(metadata)
.has(Metadata.withProperty("test.hour-of-day", ChronoField.class).withDefaultValue("hour-of-day"));
}
@Test
void descriptionProperties() {
ConfigurationMetadata metadata = compile(DescriptionProperties.class);

View File

@@ -48,7 +48,7 @@ public abstract class AbstractFieldValuesProcessorTests {
protected abstract FieldValuesParser createProcessor(ProcessingEnvironment env);
@Test
void getFieldValues() throws Exception {
void getFieldValues() {
TestProcessor processor = new TestProcessor();
TestCompiler compiler = TestCompiler.forSystem()
.withProcessors(processor)
@@ -105,6 +105,11 @@ public abstract class AbstractFieldValuesProcessorTests {
assertThat(values.get("periodMonths")).isEqualTo("10m");
assertThat(values.get("periodYears")).isEqualTo("15y");
assertThat(values.get("periodZero")).isEqualTo(0);
assertThat(values.get("enumNone")).isNull();
assertThat(values.get("enumSimple")).isEqualTo("seconds");
assertThat(values.get("enumQualified")).isEqualTo("hour-of-day");
assertThat(values.get("enumWithIndirection")).isNull();
assertThat(values.get("memberSelectInt")).isNull();
}
@SupportedAnnotationTypes({ "org.springframework.boot.configurationsample.ConfigurationProperties" })

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,18 @@
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor.metadata;
package org.springframework.boot.configurationprocessor.support;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationMetadata}.
* Tests for {@link ConventionUtils}.
*
* @author Stephane Nicoll
*/
class ConfigurationMetadataTests {
class ConventionUtilsTests {
@Test
void toDashedCaseCamelCase() {
@@ -78,7 +78,7 @@ class ConfigurationMetadataTests {
}
private String toDashedCase(String name) {
return ConfigurationMetadata.toDashedCase(name);
return ConventionUtils.toDashedCase(name);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.util.MimeType;
@@ -151,4 +152,22 @@ public class FieldValues {
private Period periodZero = Period.ZERO;
private ChronoUnit enumNone;
private ChronoUnit enumSimple = ChronoUnit.SECONDS;
private java.time.temporal.ChronoField enumQualified = java.time.temporal.ChronoField.HOUR_OF_DAY;
private ChronoUnit enumWithIndirection = SampleOptions.DEFAULT_UNIT;
private int memberSelectInt = SampleOptions.DEFAULT_MAX_RETRIES;
public static class SampleOptions {
static final Integer DEFAULT_MAX_RETRIES = 20;
static final ChronoUnit DEFAULT_UNIT = ChronoUnit.SECONDS;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.specific;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.configurationsample.ConfigurationProperties;
/**
* Sample config for enum and default values.
*
* @author Stephane Nicoll
*/
@ConfigurationProperties("test")
public class EnumValuesPojo {
private ChronoUnit seconds = ChronoUnit.SECONDS;
private ChronoField hourOfDay = ChronoField.HOUR_OF_DAY;
public ChronoUnit getSeconds() {
return this.seconds;
}
public void setSeconds(ChronoUnit seconds) {
this.seconds = seconds;
}
public ChronoField getHourOfDay() {
return this.hourOfDay;
}
public void setHourOfDay(ChronoField hourOfDay) {
this.hourOfDay = hourOfDay;
}
}