From 477bd7d15af6c2f7d75020d6199dac777a11e182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 1 Aug 2024 13:19:34 +0200 Subject: [PATCH 1/2] Detect default enum value This commit improves the configuration metadata annotation processor to detect a default enum value. The algorithm is best-effort, similarly to what it already does for well known prefixes (period, duration, etc). Based on an expression and an identifier, the default value is inferred if the expression matches the declaration of the property type. See gh-7562 --- .../annotation-processor.adoc | 2 +- .../fieldvalues/javac/ExpressionTree.java | 22 +++++- .../javac/JavaCompilerFieldValuesParser.java | 20 ++++-- .../metadata/ConfigurationMetadata.java | 35 +--------- .../metadata/ItemHint.java | 8 ++- .../metadata/ItemMetadata.java | 6 +- .../support/ConventionUtils.java | 67 +++++++++++++++++++ .../support/package-info.java | 20 ++++++ ...ationMetadataAnnotationProcessorTests.java | 13 ++++ .../AbstractFieldValuesProcessorTests.java | 7 +- .../ConventionUtilsTests.java} | 10 +-- .../fieldvalues/FieldValues.java | 21 +++++- .../specific/EnumValuesPojo.java | 52 ++++++++++++++ 13 files changed, 233 insertions(+), 50 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/ConventionUtils.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/support/package-info.java rename spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/{metadata/ConfigurationMetadataTests.java => support/ConventionUtilsTests.java} (88%) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/EnumValuesPojo.java 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; + } + +} From 788417df7f62c79fc5ab5acdc18a5519a28ca212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Thu, 1 Aug 2024 15:16:23 +0200 Subject: [PATCH 2/2] Remove duplicate metadata for Enum default values See gh-7562 --- ...itional-spring-configuration-metadata.json | 88 --------- ...itional-spring-configuration-metadata.json | 184 ------------------ ...itional-spring-configuration-metadata.json | 30 --- 3 files changed, 302 deletions(-) delete mode 100644 spring-boot-project/spring-boot-docker-compose/src/main/resources/META-INF/additional-spring-configuration-metadata.json 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" - } - ] -}