From 45f6668d03643ef2ad52b3a3fe36e0aebd913211 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 2 Oct 2019 12:03:46 +0200 Subject: [PATCH] Use @ConstructorBinding when generating meta-data Update the configuration processor to use the newly introduced `@ConstructorBinding` annotation to determine when meta data should be generated from constructor parameters. Prior to this commit, the processor had no good way to tell when constructor parameters should be used instead of getters/setters. Closes gh-17035 --- ...figurationMetadataAnnotationProcessor.java | 39 +++------ .../MetadataGenerationEnvironment.java | 51 ++++++++++- .../PropertyDescriptorResolver.java | 84 ++++++++++++++++--- .../MetadataGenerationEnvironmentFactory.java | 1 + .../PropertyDescriptorResolverTests.java | 40 ++++++++- ...figurationMetadataAnnotationProcessor.java | 7 ++ .../ConstructorBinding.java | 36 ++++++++ .../MetaConstructorBinding.java | 31 +++++++ ...ableClassConstructorBindingProperties.java | 39 +++++++++ .../ImmutableMultiConstructorProperties.java | 46 ++++++++++ .../immutable/ImmutableSimpleProperties.java | 2 + ...nvalidDefaultValueCharacterProperties.java | 2 + ...idDefaultValueFloatingPointProperties.java | 2 + .../InvalidDefaultValueNumberProperties.java | 2 + ...chingConstructorNoDirectiveProperties.java | 40 +++++++++ ...ructorsClassConstructorBindingExample.java | 50 +++++++++++ 16 files changed, 430 insertions(+), 42 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/MetaConstructorBinding.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableClassConstructorBindingProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableMultiConstructorProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/MatchingConstructorNoDirectiveProperties.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/TwoConstructorsClassConstructorBindingExample.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 1d0afe0b81..0f0a8dfcde 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -22,7 +22,6 @@ import java.io.StringWriter; import java.time.Duration; import java.util.Collections; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -39,7 +38,6 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; @@ -73,6 +71,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot." + "context.properties.DeprecatedConfigurationProperty"; + static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.ConstructorBinding"; + static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.context.properties.bind.DefaultValue"; static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.Endpoint"; @@ -101,6 +101,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION; } + protected String constructorBindingAnnotation() { + return CONSTRUCTOR_BINDING_ANNOTATION; + } + protected String defaultValueAnnotation() { return DEFAULT_VALUE_ANNOTATION; } @@ -130,7 +134,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata()); this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(), nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(), - defaultValueAnnotation(), endpointAnnotation(), readOperationAnnotation()); + constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(), + readOperationAnnotation()); } @Override @@ -159,38 +164,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private Map> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv, TypeElement annotation) { - DeclaredType annotationType = (DeclaredType) annotation.asType(); Map> result = new LinkedHashMap<>(); for (Element element : roundEnv.getRootElements()) { - LinkedList stack = new LinkedList<>(); - stack.push(element); - collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack); - stack.removeFirst(); - if (!stack.isEmpty()) { - result.put(element, Collections.unmodifiableList(stack)); + List annotations = this.metadataEnv.getElementsAnnotatedOrMetaAnnotatedWith(element, annotation); + if (!annotations.isEmpty()) { + result.put(element, annotations); } } return result; } - private boolean collectElementsAnnotatedOrMetaAnnotatedWith(DeclaredType annotationType, - LinkedList stack) { - Element element = stack.peekLast(); - for (AnnotationMirror annotation : this.processingEnv.getElementUtils().getAllAnnotationMirrors(element)) { - Element annotationElement = annotation.getAnnotationType().asElement(); - if (!stack.contains(annotationElement)) { - stack.addLast(annotationElement); - if (annotationElement.equals(annotationType.asElement())) { - return true; - } - if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) { - stack.removeLast(); - } - } - } - return false; - } - private void processElement(Element element) { try { AnnotationMirror annotation = this.metadataEnv.getConfigurationPropertiesAnnotation(element); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java index f3b787ae92..0fe689c165 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -86,6 +87,8 @@ class MetadataGenerationEnvironment { private final String deprecatedConfigurationPropertyAnnotation; + private final String constructorBindingAnnotation; + private final String defaultValueAnnotation; private final String endpointAnnotation; @@ -94,7 +97,8 @@ class MetadataGenerationEnvironment { MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation, String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation, - String defaultValueAnnotation, String endpointAnnotation, String readOperationAnnotation) { + String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation, + String readOperationAnnotation) { this.typeUtils = new TypeUtils(environment); this.elements = environment.getElementUtils(); this.messager = environment.getMessager(); @@ -102,6 +106,7 @@ class MetadataGenerationEnvironment { this.configurationPropertiesAnnotation = configurationPropertiesAnnotation; this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation; this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation; + this.constructorBindingAnnotation = constructorBindingAnnotation; this.defaultValueAnnotation = defaultValueAnnotation; this.endpointAnnotation = endpointAnnotation; this.readOperationAnnotation = readOperationAnnotation; @@ -170,6 +175,14 @@ class MetadataGenerationEnvironment { return new ItemDeprecation(reason, replacement); } + boolean hasConstructorBindingAnnotation(TypeElement typeElement) { + return hasAnnotationRecursive(typeElement, this.constructorBindingAnnotation); + } + + boolean hasConstructorBindingAnnotation(ExecutableElement element) { + return hasAnnotation(element, this.constructorBindingAnnotation); + } + boolean hasAnnotation(Element element, String type) { return getAnnotation(element, type) != null; } @@ -185,6 +198,42 @@ class MetadataGenerationEnvironment { return null; } + /** + * Collect the annotations that are annotated or meta-annotated with the specified + * {@link TypeElement annotation}. + * @param element the element to inspect + * @param annotationType the annotation to discover + * @return the annotations that are annotated or meta-annotated with this annotation + */ + List getElementsAnnotatedOrMetaAnnotatedWith(Element element, TypeElement annotationType) { + LinkedList stack = new LinkedList<>(); + stack.push(element); + collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack); + stack.removeFirst(); + return Collections.unmodifiableList(stack); + } + + private boolean hasAnnotationRecursive(Element element, String type) { + return !getElementsAnnotatedOrMetaAnnotatedWith(element, this.elements.getTypeElement(type)).isEmpty(); + } + + private boolean collectElementsAnnotatedOrMetaAnnotatedWith(TypeElement annotationType, LinkedList stack) { + Element element = stack.peekLast(); + for (AnnotationMirror annotation : this.elements.getAllAnnotationMirrors(element)) { + Element annotationElement = annotation.getAnnotationType().asElement(); + if (!stack.contains(annotationElement)) { + stack.addLast(annotationElement); + if (annotationElement.equals(annotationType)) { + return true; + } + if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) { + stack.removeLast(); + } + } + } + return false; + } + Map getAnnotationElementValues(AnnotationMirror annotation) { Map values = new LinkedHashMap<>(); annotation.getElementValues() diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java index 57e07c2a37..d12fb716b2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java @@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.lang.model.element.ExecutableElement; @@ -31,6 +32,7 @@ import javax.lang.model.util.ElementFilter; * Resolve {@link PropertyDescriptor} instances. * * @author Stephane Nicoll + * @author Phillip Webb */ class PropertyDescriptorResolver { @@ -54,13 +56,19 @@ class PropertyDescriptorResolver { if (factoryMethod != null) { return resolveJavaBeanProperties(type, factoryMethod, members); } - ExecutableElement constructor = resolveConstructor(type); - if (constructor != null) { - return resolveConstructorProperties(type, factoryMethod, members, constructor); - } - else { - return resolveJavaBeanProperties(type, factoryMethod, members); + return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), factoryMethod, members); + } + + private Stream> resolve(ConfigurationPropertiesTypeElement type, + ExecutableElement factoryMethod, TypeElementMembers members) { + if (type.isConstructorBindingEnabled()) { + ExecutableElement constructor = type.getBindConstructor(); + if (constructor != null) { + return resolveConstructorProperties(type.getType(), factoryMethod, members, constructor); + } + return Stream.empty(); } + return resolveJavaBeanProperties(type.getType(), factoryMethod, members); } Stream> resolveConstructorProperties(TypeElement type, ExecutableElement factoryMethod, @@ -108,12 +116,66 @@ class PropertyDescriptorResolver { return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment); } - private ExecutableElement resolveConstructor(TypeElement type) { - List constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); - if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0) { - return constructors.get(0); + /** + * Wrapper around a {@link TypeElement} that could be bound. + */ + private static class ConfigurationPropertiesTypeElement { + + private final TypeElement type; + + private final boolean constructorBoundType; + + private final List constructors; + + private final List boundConstructors; + + ConfigurationPropertiesTypeElement(TypeElement type, boolean constructorBoundType, + List constructors, List boundConstructors) { + this.type = type; + this.constructorBoundType = constructorBoundType; + this.constructors = constructors; + this.boundConstructors = boundConstructors; } - return null; + + TypeElement getType() { + return this.type; + } + + boolean isConstructorBindingEnabled() { + return this.constructorBoundType || !this.boundConstructors.isEmpty(); + } + + ExecutableElement getBindConstructor() { + if (this.constructorBoundType && this.boundConstructors.isEmpty()) { + return findBoundConstructor(); + } + if (this.boundConstructors.size() == 1) { + return this.boundConstructors.get(0); + } + return null; + } + + private ExecutableElement findBoundConstructor() { + ExecutableElement boundConstructor = null; + for (ExecutableElement canidate : this.constructors) { + if (!canidate.getParameters().isEmpty()) { + if (boundConstructor != null) { + return null; + } + boundConstructor = canidate; + } + } + return boundConstructor; + } + + static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) { + boolean constructorBoundType = env.hasConstructorBindingAnnotation(type); + List constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); + List boundConstructors = constructors.stream() + .filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList()); + return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors); + } + } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java index e42347fc08..c8cf16531e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java @@ -35,6 +35,7 @@ class MetadataGenerationEnvironmentFactory implements Function assertThat(stream).containsExactly("theName", "flag", "comparator", "counter"))); + process(ImmutableSimpleProperties.class, properties((stream) -> assertThat(stream) + .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); } @Test - void propertiesWithSeveralConstructors() throws IOException { + void propertiesWithConstructorAndClassConstructorBinding() throws IOException { + process(ImmutableClassConstructorBindingProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); + process(ImmutableClassConstructorBindingProperties.class, properties((stream) -> assertThat(stream) + .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); + } + + @Test + void propertiesWithConstructorAndClassConstructorBindingAndSeveralCandidates() throws IOException { + process(TwoConstructorsClassConstructorBindingExample.class, + propertyNames((stream) -> assertThat(stream).isEmpty())); + } + + @Test + void propertiesWithConstructorNoDirective() throws IOException { + process(MatchingConstructorNoDirectiveProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name"))); + process(MatchingConstructorNoDirectiveProperties.class, properties((stream) -> assertThat(stream) + .allMatch((predicate) -> predicate instanceof JavaBeanPropertyDescriptor))); + } + + @Test + void propertiesWithMultiConstructor() throws IOException { + process(ImmutableMultiConstructorProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name", "description"))); + process(ImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream) + .allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor))); + } + + @Test + void propertiesWithMultiConstructorNoDirective() throws IOException { process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name"))); process(TwoConstructorsExample.class, properties((stream) -> assertThat(stream).element(0).isInstanceOf(JavaBeanPropertyDescriptor.class))); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java index 17ebf47617..aeed80fdd7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java @@ -47,6 +47,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty"; + public static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.configurationsample.ConstructorBinding"; + public static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.configurationsample.DefaultValue"; public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint"; @@ -76,6 +78,11 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION; } + @Override + protected String constructorBindingAnnotation() { + return CONSTRUCTOR_BINDING_ANNOTATION; + } + @Override protected String defaultValueAnnotation() { return DEFAULT_VALUE_ANNOTATION; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java new file mode 100644 index 0000000000..48adcf7dc2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/ConstructorBinding.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2019 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Alternative to Spring Boot's {@code @ConstructorBinding} for testing (removes the need + * for a dependency on the real annotation). + * + * @author Stephane Nicoll + */ +@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ConstructorBinding { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/MetaConstructorBinding.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/MetaConstructorBinding.java new file mode 100644 index 0000000000..2b0df83ea4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/MetaConstructorBinding.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2019 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConstructorBinding +public @interface MetaConstructorBinding { + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableClassConstructorBindingProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableClassConstructorBindingProperties.java new file mode 100644 index 0000000000..e5adef8229 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableClassConstructorBindingProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2019 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.immutable; + +import org.springframework.boot.configurationsample.MetaConstructorBinding; + +/** + * Simple immutable properties with several constructors. + * + * @author Stephane Nicoll + */ +@SuppressWarnings("unused") +@MetaConstructorBinding +public class ImmutableClassConstructorBindingProperties { + + private final String name; + + private final String description; + + public ImmutableClassConstructorBindingProperties(String name, String description) { + this.name = name; + this.description = description; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableMultiConstructorProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableMultiConstructorProperties.java new file mode 100644 index 0000000000..0fae98700c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableMultiConstructorProperties.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2019 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.immutable; + +import org.springframework.boot.configurationsample.ConstructorBinding; + +/** + * Simple immutable properties with several constructors. + * + * @author Stephane Nicoll + */ +@SuppressWarnings("unused") +public class ImmutableMultiConstructorProperties { + + private final String name; + + /** + * Test description. + */ + private final String description; + + public ImmutableMultiConstructorProperties(String name) { + this(name, null); + } + + @ConstructorBinding + public ImmutableMultiConstructorProperties(String name, String description) { + this.name = name; + this.description = description; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableSimpleProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableSimpleProperties.java index 3e64f6fa32..bdd154f27c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableSimpleProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/immutable/ImmutableSimpleProperties.java @@ -19,6 +19,7 @@ package org.springframework.boot.configurationsample.immutable; import java.util.Comparator; import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.ConstructorBinding; import org.springframework.boot.configurationsample.DefaultValue; /** @@ -47,6 +48,7 @@ public class ImmutableSimpleProperties { @SuppressWarnings("unused") private final Long counter; + @ConstructorBinding public ImmutableSimpleProperties(@DefaultValue("boot") String theName, boolean flag, Comparator comparator, Long counter) { this.theName = theName; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueCharacterProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueCharacterProperties.java index b16d854036..2bced04453 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueCharacterProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueCharacterProperties.java @@ -17,6 +17,7 @@ package org.springframework.boot.configurationsample.specific; import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.ConstructorBinding; import org.springframework.boot.configurationsample.DefaultValue; /** @@ -29,6 +30,7 @@ public class InvalidDefaultValueCharacterProperties { private final char letter; + @ConstructorBinding public InvalidDefaultValueCharacterProperties(@DefaultValue("bad") char letter) { this.letter = letter; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueFloatingPointProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueFloatingPointProperties.java index b638e42169..32ce81d186 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueFloatingPointProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueFloatingPointProperties.java @@ -17,6 +17,7 @@ package org.springframework.boot.configurationsample.specific; import org.springframework.boot.configurationsample.ConfigurationProperties; +import org.springframework.boot.configurationsample.ConstructorBinding; import org.springframework.boot.configurationsample.DefaultValue; /** @@ -26,6 +27,7 @@ import org.springframework.boot.configurationsample.DefaultValue; * @author Stephane Nicoll */ @ConfigurationProperties("test") +@ConstructorBinding public class InvalidDefaultValueFloatingPointProperties { private final Double ratio; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueNumberProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueNumberProperties.java index c2747b72f7..b4c115c3a7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueNumberProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/InvalidDefaultValueNumberProperties.java @@ -18,6 +18,7 @@ package org.springframework.boot.configurationsample.specific; import org.springframework.boot.configurationsample.ConfigurationProperties; import org.springframework.boot.configurationsample.DefaultValue; +import org.springframework.boot.configurationsample.MetaConstructorBinding; /** * Demonstrates that an invalid default number value leads to a compilation failure. @@ -25,6 +26,7 @@ import org.springframework.boot.configurationsample.DefaultValue; * @author Stephane Nicoll */ @ConfigurationProperties("test") +@MetaConstructorBinding public class InvalidDefaultValueNumberProperties { private final int counter; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/MatchingConstructorNoDirectiveProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/MatchingConstructorNoDirectiveProperties.java new file mode 100644 index 0000000000..320c2b3bbc --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/MatchingConstructorNoDirectiveProperties.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2019 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; + +/** + * Simple properties with a constructor but no binding directive. + * + * @author Stephane Nicoll + */ +public class MatchingConstructorNoDirectiveProperties { + + private String name; + + public MatchingConstructorNoDirectiveProperties(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/TwoConstructorsClassConstructorBindingExample.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/TwoConstructorsClassConstructorBindingExample.java new file mode 100644 index 0000000000..6738f15d81 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/specific/TwoConstructorsClassConstructorBindingExample.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2019 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 org.springframework.boot.configurationsample.MetaConstructorBinding; + +/** + * A type that declares constructor binding but with two available constructors. + * + * @author Stephane Nicoll + */ +@MetaConstructorBinding +public class TwoConstructorsClassConstructorBindingExample { + + private String name; + + private String description; + + public TwoConstructorsClassConstructorBindingExample(String name) { + this(name, null); + } + + public TwoConstructorsClassConstructorBindingExample(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +}