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; + } + +}