diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 99bdb22e48..15f80b7928 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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": { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 78431ff4d2..4046881cfe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", diff --git a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 3d2b4e3738..0000000000 --- a/spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -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" - } - ] -} diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc index 976ef57035..ded7d7e7c1 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc @@ -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: diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java index 9f4b5a23de..365e16be04 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/ExpressionTree.java @@ -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 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) { + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java index c14bf74e73..d76fe5416e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/fieldvalues/javac/JavaCompilerFieldValuesParser.java @@ -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; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java index a062ba4ca9..3b8c1fd278 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadata.java @@ -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 SEPARATORS; - - static { - List chars = Arrays.asList('-', '_'); - SEPARATORS = Collections.unmodifiableSet(new HashSet<>(chars)); - } - private final Map> items; private final Map> 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 > List flattenValues(Map> map) { List content = new ArrayList<>(); for (List values : map.values()) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java index 1b07489c13..c44e22b89d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemHint.java @@ -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 { 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() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java index 70ec0f3dc7..33b7124859 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/ItemMetadata.java @@ -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 { 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 { } public static String newItemMetadataPrefix(String prefix, String suffix) { - return prefix.toLowerCase(Locale.ENGLISH) + ConfigurationMetadata.toDashedCase(suffix); + return prefix.toLowerCase(Locale.ENGLISH) + ConventionUtils.toDashedCase(suffix); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java new file mode 100644 index 0000000000..b9961e0bc7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java @@ -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 SEPARATORS; + + static { + List 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); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java new file mode 100644 index 0000000000..d94775642f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java @@ -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; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 070cc69bbd..ac87c915d8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -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); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java index 138d7db750..812617d06e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/fieldvalues/AbstractFieldValuesProcessorTests.java @@ -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" }) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/support/ConventionUtilsTests.java similarity index 88% rename from spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java rename to spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/support/ConventionUtilsTests.java index 2930088572..6d1065dc11 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/ConfigurationMetadataTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/support/ConventionUtilsTests.java @@ -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); } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java index ee8578c8a2..84fc184fc7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/fieldvalues/FieldValues.java @@ -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; + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java new file mode 100644 index 0000000000..4b9542f215 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java @@ -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; + } + +}