Add metadata support for immutable ConfigurationProperties type
Closes gh-16071
This commit is contained in:
parent
35d7fccb33
commit
3125f424ce
|
@ -809,8 +809,11 @@ is used to populate the `description` attribute.
|
||||||
NOTE: You should only use simple text with `@ConfigurationProperties` field Javadoc, since
|
NOTE: You should only use simple text with `@ConfigurationProperties` field Javadoc, since
|
||||||
they are not processed before being added to the JSON.
|
they are not processed before being added to the JSON.
|
||||||
|
|
||||||
Properties are discovered through the presence of standard getters and setters with
|
If the class has a single constructor with at least one parameters, one property is
|
||||||
special handling for collection types (that is detected even if only a getter is present).
|
created per constructor parameter. Otherwise, properties are discovered through the
|
||||||
|
presence of standard getters and setters with special handling for collection types (that
|
||||||
|
is detected even if only a getter is present).
|
||||||
|
|
||||||
The annotation processor also supports the use of the `@Data`, `@Getter`, and `@Setter`
|
The annotation processor also supports the use of the `@Data`, `@Getter`, and `@Setter`
|
||||||
lombok annotations.
|
lombok annotations.
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,9 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
|
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
|
||||||
+ "context.properties.DeprecatedConfigurationProperty";
|
+ "context.properties.DeprecatedConfigurationProperty";
|
||||||
|
|
||||||
|
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot."
|
||||||
|
+ "context.properties.bind.DefaultValue";
|
||||||
|
|
||||||
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate."
|
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate."
|
||||||
+ "endpoint.annotation.Endpoint";
|
+ "endpoint.annotation.Endpoint";
|
||||||
|
|
||||||
|
@ -100,6 +103,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String defaultValueAnnotation() {
|
||||||
|
return DEFAULT_VALUE_ANNOTATION;
|
||||||
|
}
|
||||||
|
|
||||||
protected String endpointAnnotation() {
|
protected String endpointAnnotation() {
|
||||||
return ENDPOINT_ANNOTATION;
|
return ENDPOINT_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
@ -127,8 +134,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||||
this.metadataEnv = new MetadataGenerationEnvironment(env,
|
this.metadataEnv = new MetadataGenerationEnvironment(env,
|
||||||
configurationPropertiesAnnotation(),
|
configurationPropertiesAnnotation(),
|
||||||
nestedConfigurationPropertyAnnotation(),
|
nestedConfigurationPropertyAnnotation(),
|
||||||
deprecatedConfigurationPropertyAnnotation(), endpointAnnotation(),
|
deprecatedConfigurationPropertyAnnotation(), defaultValueAnnotation(),
|
||||||
readOperationAnnotation());
|
endpointAnnotation(), readOperationAnnotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
/*
|
||||||
|
* 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.configurationprocessor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.element.VariableElement;
|
||||||
|
import javax.lang.model.type.PrimitiveType;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.lang.model.util.TypeKindVisitor8;
|
||||||
|
import javax.tools.Diagnostic.Kind;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link PropertyDescriptor} for a constructor parameter.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||||
|
|
||||||
|
ConstructorParameterPropertyDescriptor(TypeElement ownerElement,
|
||||||
|
ExecutableElement factoryMethod, VariableElement source, String name,
|
||||||
|
TypeMirror type, VariableElement field, ExecutableElement getter,
|
||||||
|
ExecutableElement setter) {
|
||||||
|
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isProperty(MetadataGenerationEnvironment env) {
|
||||||
|
// If it's a constructor parameter, it doesn't matter as we must be able to bind
|
||||||
|
// it to build the object.
|
||||||
|
return !isNested(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||||
|
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource());
|
||||||
|
if (defaultValue != null) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getDefaultValueFromAnnotation(
|
||||||
|
MetadataGenerationEnvironment environment, Element element) {
|
||||||
|
AnnotationMirror defaultValueAnnotation = environment
|
||||||
|
.getDefaultValueAnnotation(element);
|
||||||
|
if (defaultValueAnnotation != null) {
|
||||||
|
List<String> defaultValue = (List<String>) environment
|
||||||
|
.getAnnotationElementValues(defaultValueAnnotation).get("value");
|
||||||
|
if (defaultValue != null) {
|
||||||
|
try {
|
||||||
|
TypeMirror specificType = determineSpecificType(environment);
|
||||||
|
if (defaultValue.size() == 1) {
|
||||||
|
return coerceValue(specificType, defaultValue.get(0));
|
||||||
|
}
|
||||||
|
return defaultValue.stream()
|
||||||
|
.map((value) -> coerceValue(specificType, value))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException ex) {
|
||||||
|
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(),
|
||||||
|
element, defaultValueAnnotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) {
|
||||||
|
TypeMirror candidate = getSource().asType();
|
||||||
|
TypeMirror elementCandidate = environment.getTypeUtils()
|
||||||
|
.extractElementType(candidate);
|
||||||
|
if (elementCandidate != null) {
|
||||||
|
candidate = elementCandidate;
|
||||||
|
}
|
||||||
|
PrimitiveType primitiveType = environment.getTypeUtils()
|
||||||
|
.getPrimitiveType(candidate);
|
||||||
|
return (primitiveType != null) ? primitiveType : candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object coerceValue(TypeMirror type, String value) {
|
||||||
|
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE,
|
||||||
|
value);
|
||||||
|
return (coercedValue != null) ? coercedValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultValueCoercionTypeVisitor
|
||||||
|
extends TypeKindVisitor8<Object, String> {
|
||||||
|
|
||||||
|
private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();
|
||||||
|
|
||||||
|
private Integer parseInteger(String value) {
|
||||||
|
try {
|
||||||
|
return Integer.valueOf(value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Invalid number representation '%s'", value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double parseFloatingPoint(String value) {
|
||||||
|
try {
|
||||||
|
return Double.valueOf(value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
throw new IllegalArgumentException(String
|
||||||
|
.format("Invalid floating point representation '%s'", value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) {
|
||||||
|
return Boolean.parseBoolean(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsByte(PrimitiveType t, String value) {
|
||||||
|
return parseInteger(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsShort(PrimitiveType t, String value) {
|
||||||
|
return parseInteger(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsInt(PrimitiveType t, String value) {
|
||||||
|
return parseInteger(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsLong(PrimitiveType t, String value) {
|
||||||
|
return parseInteger(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsChar(PrimitiveType t, String value) {
|
||||||
|
if (value.length() > 1) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
String.format("Invalid character representation '%s'", value));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsFloat(PrimitiveType t, String value) {
|
||||||
|
return parseFloatingPoint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsDouble(PrimitiveType t, String value) {
|
||||||
|
return parseFloatingPoint(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DefaultPrimitiveTypeVisitor
|
||||||
|
extends TypeKindVisitor8<Object, Void> {
|
||||||
|
|
||||||
|
private static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsBoolean(PrimitiveType t, Void ignore) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsByte(PrimitiveType t, Void ignore) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsInt(PrimitiveType t, Void ignore) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsLong(PrimitiveType t, Void ignore) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsChar(PrimitiveType t, Void ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsFloat(PrimitiveType t, Void ignore) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) {
|
||||||
|
return 0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,4 +41,9 @@ class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> {
|
||||||
&& (getSetter() != null || isCollection);
|
&& (getSetter() != null || isCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||||
|
return environment.getFieldDefaultValue(getOwnerElement(), getName());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,11 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||||
return !env.isExcluded(getType()) && (hasSetter(env) || isCollection);
|
return !env.isExcluded(getType()) && (hasSetter(env) || isCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||||
|
return environment.getFieldDefaultValue(getOwnerElement(), getName());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isNested(MetadataGenerationEnvironment environment) {
|
protected boolean isNested(MetadataGenerationEnvironment environment) {
|
||||||
if (!hasLombokPublicAccessor(environment, true)) {
|
if (!hasLombokPublicAccessor(environment, true)) {
|
||||||
|
|
|
@ -16,14 +16,18 @@
|
||||||
|
|
||||||
package org.springframework.boot.configurationprocessor;
|
package org.springframework.boot.configurationprocessor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.processing.Messager;
|
||||||
import javax.annotation.processing.ProcessingEnvironment;
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
import javax.lang.model.element.AnnotationMirror;
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
|
import javax.lang.model.element.AnnotationValue;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
|
@ -51,6 +55,8 @@ class MetadataGenerationEnvironment {
|
||||||
|
|
||||||
private final Elements elements;
|
private final Elements elements;
|
||||||
|
|
||||||
|
private final Messager messager;
|
||||||
|
|
||||||
private final FieldValuesParser fieldValuesParser;
|
private final FieldValuesParser fieldValuesParser;
|
||||||
|
|
||||||
private final Map<TypeElement, Map<String, Object>> defaultValues = new HashMap<>();
|
private final Map<TypeElement, Map<String, Object>> defaultValues = new HashMap<>();
|
||||||
|
@ -61,6 +67,8 @@ class MetadataGenerationEnvironment {
|
||||||
|
|
||||||
private final String deprecatedConfigurationPropertyAnnotation;
|
private final String deprecatedConfigurationPropertyAnnotation;
|
||||||
|
|
||||||
|
private final String defaultValueAnnotation;
|
||||||
|
|
||||||
private final String endpointAnnotation;
|
private final String endpointAnnotation;
|
||||||
|
|
||||||
private final String readOperationAnnotation;
|
private final String readOperationAnnotation;
|
||||||
|
@ -68,15 +76,18 @@ class MetadataGenerationEnvironment {
|
||||||
MetadataGenerationEnvironment(ProcessingEnvironment environment,
|
MetadataGenerationEnvironment(ProcessingEnvironment environment,
|
||||||
String configurationPropertiesAnnotation,
|
String configurationPropertiesAnnotation,
|
||||||
String nestedConfigurationPropertyAnnotation,
|
String nestedConfigurationPropertyAnnotation,
|
||||||
String deprecatedConfigurationPropertyAnnotation, String endpointAnnotation,
|
String deprecatedConfigurationPropertyAnnotation,
|
||||||
|
String defaultValueAnnotation, String endpointAnnotation,
|
||||||
String readOperationAnnotation) {
|
String readOperationAnnotation) {
|
||||||
this.typeExcludes = determineTypeExcludes();
|
this.typeExcludes = determineTypeExcludes();
|
||||||
this.typeUtils = new TypeUtils(environment);
|
this.typeUtils = new TypeUtils(environment);
|
||||||
this.elements = environment.getElementUtils();
|
this.elements = environment.getElementUtils();
|
||||||
|
this.messager = environment.getMessager();
|
||||||
this.fieldValuesParser = resolveFieldValuesParser(environment);
|
this.fieldValuesParser = resolveFieldValuesParser(environment);
|
||||||
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
|
||||||
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
|
||||||
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
|
||||||
|
this.defaultValueAnnotation = defaultValueAnnotation;
|
||||||
this.endpointAnnotation = endpointAnnotation;
|
this.endpointAnnotation = endpointAnnotation;
|
||||||
this.readOperationAnnotation = readOperationAnnotation;
|
this.readOperationAnnotation = readOperationAnnotation;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +123,18 @@ class MetadataGenerationEnvironment {
|
||||||
return this.typeUtils;
|
return this.typeUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getDefaultValue(TypeElement type, String name) {
|
public Messager getMessager() {
|
||||||
|
return this.messager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the default value of the field with the specified {@code name}.
|
||||||
|
* @param type the type to consider
|
||||||
|
* @param name the name of the field
|
||||||
|
* @return the default value or {@code null} if the field does not exist or no default
|
||||||
|
* value has been detected
|
||||||
|
*/
|
||||||
|
public Object getFieldDefaultValue(TypeElement type, String name) {
|
||||||
return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues)
|
return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues)
|
||||||
.get(name);
|
.get(name);
|
||||||
}
|
}
|
||||||
|
@ -171,10 +193,21 @@ class MetadataGenerationEnvironment {
|
||||||
public Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
public Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
||||||
Map<String, Object> values = new LinkedHashMap<>();
|
Map<String, Object> values = new LinkedHashMap<>();
|
||||||
annotation.getElementValues().forEach((name, value) -> values
|
annotation.getElementValues().forEach((name, value) -> values
|
||||||
.put(name.getSimpleName().toString(), value.getValue()));
|
.put(name.getSimpleName().toString(), getAnnotationValue(value)));
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object getAnnotationValue(AnnotationValue annotationValue) {
|
||||||
|
Object value = annotationValue.getValue();
|
||||||
|
if (value instanceof List) {
|
||||||
|
List<Object> values = new ArrayList<>();
|
||||||
|
((List<?>) value)
|
||||||
|
.forEach((v) -> values.add(((AnnotationValue) v).getValue()));
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public TypeElement getConfigurationPropertiesAnnotationElement() {
|
public TypeElement getConfigurationPropertiesAnnotationElement() {
|
||||||
return this.elements.getTypeElement(this.configurationPropertiesAnnotation);
|
return this.elements.getTypeElement(this.configurationPropertiesAnnotation);
|
||||||
}
|
}
|
||||||
|
@ -187,6 +220,10 @@ class MetadataGenerationEnvironment {
|
||||||
return getAnnotation(element, this.nestedConfigurationPropertyAnnotation);
|
return getAnnotation(element, this.nestedConfigurationPropertyAnnotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AnnotationMirror getDefaultValueAnnotation(Element element) {
|
||||||
|
return getAnnotation(element, this.defaultValueAnnotation);
|
||||||
|
}
|
||||||
|
|
||||||
public TypeElement getEndpointAnnotationElement() {
|
public TypeElement getEndpointAnnotationElement() {
|
||||||
return this.elements.getTypeElement(this.endpointAnnotation);
|
return this.elements.getTypeElement(this.endpointAnnotation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||||
* @param <S> the type of the source element that determines the property
|
* @param <S> the type of the source element that determines the property
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
abstract class PropertyDescriptor<S> {
|
abstract class PropertyDescriptor<S extends Element> {
|
||||||
|
|
||||||
private final TypeElement ownerElement;
|
private final TypeElement ownerElement;
|
||||||
|
|
||||||
|
@ -98,6 +98,9 @@ abstract class PropertyDescriptor<S> {
|
||||||
|
|
||||||
protected abstract boolean isProperty(MetadataGenerationEnvironment environment);
|
protected abstract boolean isProperty(MetadataGenerationEnvironment environment);
|
||||||
|
|
||||||
|
protected abstract Object resolveDefaultValue(
|
||||||
|
MetadataGenerationEnvironment environment);
|
||||||
|
|
||||||
protected ItemDeprecation resolveItemDeprecation(
|
protected ItemDeprecation resolveItemDeprecation(
|
||||||
MetadataGenerationEnvironment environment) {
|
MetadataGenerationEnvironment environment) {
|
||||||
boolean deprecated = environment.isDeprecated(getGetter())
|
boolean deprecated = environment.isDeprecated(getGetter())
|
||||||
|
@ -164,10 +167,6 @@ abstract class PropertyDescriptor<S> {
|
||||||
return environment.getTypeUtils().getJavaDoc(getField());
|
return environment.getTypeUtils().getJavaDoc(getField());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
|
||||||
return environment.getDefaultValue(getOwnerElement(), getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCyclePresent(Element returnType, Element element) {
|
private boolean isCyclePresent(Element returnType, Element element) {
|
||||||
if (!(element.getEnclosingElement() instanceof TypeElement)) {
|
if (!(element.getEnclosingElement() instanceof TypeElement)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -16,13 +16,16 @@
|
||||||
|
|
||||||
package org.springframework.boot.configurationprocessor;
|
package org.springframework.boot.configurationprocessor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import javax.lang.model.element.ExecutableElement;
|
import javax.lang.model.element.ExecutableElement;
|
||||||
import javax.lang.model.element.TypeElement;
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.element.VariableElement;
|
||||||
import javax.lang.model.type.TypeMirror;
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.lang.model.util.ElementFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve {@link PropertyDescriptor} instances.
|
* Resolve {@link PropertyDescriptor} instances.
|
||||||
|
@ -49,23 +52,59 @@ class PropertyDescriptorResolver {
|
||||||
public Stream<PropertyDescriptor<?>> resolve(TypeElement type,
|
public Stream<PropertyDescriptor<?>> resolve(TypeElement type,
|
||||||
ExecutableElement factoryMethod) {
|
ExecutableElement factoryMethod) {
|
||||||
TypeElementMembers members = new TypeElementMembers(this.environment, type);
|
TypeElementMembers members = new TypeElementMembers(this.environment, type);
|
||||||
List<PropertyDescriptor<?>> candidates = new ArrayList<>();
|
ExecutableElement constructor = resolveConstructor(type);
|
||||||
|
if (constructor != null) {
|
||||||
|
return resolveConstructorProperties(type, factoryMethod, members,
|
||||||
|
constructor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return resolveJavaBeanProperties(type, factoryMethod, members);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type,
|
||||||
|
ExecutableElement factoryMethod, TypeElementMembers members,
|
||||||
|
ExecutableElement constructor) {
|
||||||
|
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
|
||||||
|
constructor.getParameters().forEach((parameter) -> {
|
||||||
|
String name = parameter.getSimpleName().toString();
|
||||||
|
TypeMirror propertyType = parameter.asType();
|
||||||
|
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||||
|
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||||
|
VariableElement field = members.getFields().get(name);
|
||||||
|
register(candidates, new ConstructorParameterPropertyDescriptor(type,
|
||||||
|
factoryMethod, parameter, name, propertyType, field, getter, setter));
|
||||||
|
});
|
||||||
|
return candidates.values().stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type,
|
||||||
|
ExecutableElement factoryMethod, TypeElementMembers members) {
|
||||||
// First check if we have regular java bean properties there
|
// First check if we have regular java bean properties there
|
||||||
|
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
|
||||||
members.getPublicGetters().forEach((name, getter) -> {
|
members.getPublicGetters().forEach((name, getter) -> {
|
||||||
TypeMirror returnType = getter.getReturnType();
|
TypeMirror propertyType = getter.getReturnType();
|
||||||
candidates.add(new JavaBeanPropertyDescriptor(type, factoryMethod, getter,
|
register(candidates,
|
||||||
name, returnType, members.getFields().get(name),
|
new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name,
|
||||||
members.getPublicSetter(name, returnType)));
|
propertyType, members.getFields().get(name),
|
||||||
|
members.getPublicSetter(name, propertyType)));
|
||||||
});
|
});
|
||||||
// Then check for Lombok ones
|
// Then check for Lombok ones
|
||||||
members.getFields().forEach((name, field) -> {
|
members.getFields().forEach((name, field) -> {
|
||||||
TypeMirror returnType = field.asType();
|
TypeMirror propertyType = field.asType();
|
||||||
ExecutableElement getter = members.getPublicGetter(name, returnType);
|
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||||
ExecutableElement setter = members.getPublicSetter(name, returnType);
|
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||||
candidates.add(new LombokPropertyDescriptor(type, factoryMethod, field, name,
|
register(candidates, new LombokPropertyDescriptor(type, factoryMethod, field,
|
||||||
returnType, getter, setter));
|
name, propertyType, getter, setter));
|
||||||
});
|
});
|
||||||
return candidates.stream().filter(this::isCandidate);
|
return candidates.values().stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void register(Map<String, PropertyDescriptor<?>> candidates,
|
||||||
|
PropertyDescriptor<?> descriptor) {
|
||||||
|
if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) {
|
||||||
|
candidates.put(descriptor.getName(), descriptor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCandidate(PropertyDescriptor<?> descriptor) {
|
private boolean isCandidate(PropertyDescriptor<?> descriptor) {
|
||||||
|
@ -73,4 +112,13 @@ class PropertyDescriptorResolver {
|
||||||
|| descriptor.isNested(this.environment);
|
|| descriptor.isNested(this.environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ExecutableElement resolveConstructor(TypeElement type) {
|
||||||
|
List<ExecutableElement> constructors = ElementFilter
|
||||||
|
.constructorsIn(type.getEnclosedElements());
|
||||||
|
if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0) {
|
||||||
|
return constructors.get(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,42 @@ class TypeUtils {
|
||||||
return type.accept(this.typeExtractor, createTypeDescriptor(element));
|
return type.accept(this.typeExtractor, createTypeDescriptor(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the target element type from the specified container type or {@code null}
|
||||||
|
* if no element type was found.
|
||||||
|
* @param type a type, potentially wrapping an element type
|
||||||
|
* @return the element type or {@code null} if no specific type was found
|
||||||
|
*/
|
||||||
|
public TypeMirror extractElementType(TypeMirror type) {
|
||||||
|
if (!this.env.getTypeUtils().isAssignable(type, this.collectionType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getCollectionElementType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TypeMirror getCollectionElementType(TypeMirror type) {
|
||||||
|
if (((TypeElement) this.types.asElement(type)).getQualifiedName()
|
||||||
|
.contentEquals(Collection.class.getName())) {
|
||||||
|
DeclaredType declaredType = (DeclaredType) type;
|
||||||
|
// raw type, just "Collection"
|
||||||
|
if (declaredType.getTypeArguments().size() == 0) {
|
||||||
|
return this.types.getDeclaredType(this.env.getElementUtils()
|
||||||
|
.getTypeElement(Object.class.getName()));
|
||||||
|
}
|
||||||
|
else { // return type argument to Collection<...>
|
||||||
|
return declaredType.getTypeArguments().get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursively walk the supertypes, looking for Collection<...>
|
||||||
|
for (TypeMirror superType : this.env.getTypeUtils().directSupertypes(type)) {
|
||||||
|
if (this.types.isAssignable(superType, this.collectionType)) {
|
||||||
|
return getCollectionElementType(superType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCollectionOrMap(TypeMirror type) {
|
public boolean isCollectionOrMap(TypeMirror type) {
|
||||||
return this.env.getTypeUtils().isAssignable(type, this.collectionType)
|
return this.env.getTypeUtils().isAssignable(type, this.collectionType)
|
||||||
|| this.env.getTypeUtils().isAssignable(type, this.mapType);
|
|| this.env.getTypeUtils().isAssignable(type, this.mapType);
|
||||||
|
@ -156,6 +192,19 @@ class TypeUtils {
|
||||||
return "".equals(javadoc) ? null : javadoc;
|
return "".equals(javadoc) ? null : javadoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link PrimitiveType} of the specified type or {@code null} if the type
|
||||||
|
* does not represent a valid wrapper type.
|
||||||
|
* @param typeMirror a type
|
||||||
|
* @return the primitive type or {@code null} if the type is not a wrapper type
|
||||||
|
*/
|
||||||
|
public PrimitiveType getPrimitiveType(TypeMirror typeMirror) {
|
||||||
|
if (getPrimitiveFor(typeMirror) != null) {
|
||||||
|
return this.types.unboxedType(typeMirror);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public TypeMirror getWrapperOrPrimitiveFor(TypeMirror typeMirror) {
|
public TypeMirror getWrapperOrPrimitiveFor(TypeMirror typeMirror) {
|
||||||
Class<?> candidate = getWrapperFor(typeMirror);
|
Class<?> candidate = getWrapperFor(typeMirror);
|
||||||
if (candidate != null) {
|
if (candidate != null) {
|
||||||
|
|
|
@ -43,6 +43,9 @@ import org.springframework.boot.configurationsample.specific.InnerClassHierarchi
|
||||||
import org.springframework.boot.configurationsample.specific.InnerClassProperties;
|
import org.springframework.boot.configurationsample.specific.InnerClassProperties;
|
||||||
import org.springframework.boot.configurationsample.specific.InnerClassRootConfig;
|
import org.springframework.boot.configurationsample.specific.InnerClassRootConfig;
|
||||||
import org.springframework.boot.configurationsample.specific.InvalidAccessorProperties;
|
import org.springframework.boot.configurationsample.specific.InvalidAccessorProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.InvalidDefaultValueCharacterProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.InvalidDefaultValueFloatingPointProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.InvalidDefaultValueNumberProperties;
|
||||||
import org.springframework.boot.configurationsample.specific.InvalidDoubleRegistrationProperties;
|
import org.springframework.boot.configurationsample.specific.InvalidDoubleRegistrationProperties;
|
||||||
import org.springframework.boot.configurationsample.specific.SimplePojo;
|
import org.springframework.boot.configurationsample.specific.SimplePojo;
|
||||||
import org.springframework.boot.configurationsample.specific.StaticAccessor;
|
import org.springframework.boot.configurationsample.specific.StaticAccessor;
|
||||||
|
@ -385,4 +388,26 @@ public class ConfigurationMetadataAnnotationProcessorTests
|
||||||
.withMessageContaining("Compilation failed");
|
.withMessageContaining("Compilation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithInvalidDefaultValueOnNumber() {
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> compile(InvalidDefaultValueNumberProperties.class))
|
||||||
|
.withMessageContaining("Compilation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithInvalidDefaultValueOnFloatingPoint() {
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(
|
||||||
|
() -> compile(InvalidDefaultValueFloatingPointProperties.class))
|
||||||
|
.withMessageContaining("Compilation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithInvalidDefaultValueOnCharacter() {
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> compile(InvalidDefaultValueCharacterProperties.class))
|
||||||
|
.withMessageContaining("Compilation failed");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,318 @@
|
||||||
|
/*
|
||||||
|
* 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.configurationprocessor;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.element.VariableElement;
|
||||||
|
import javax.lang.model.util.ElementFilter;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableCollectionProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableInnerClassProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutablePrimitiveProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutablePrimitiveWithDefaultsProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutablePrimitiveWrapperWithDefaultsProperties;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ConstructorParameterPropertyDescriptor}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterSimpleProperty() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "theName");
|
||||||
|
assertThat(property.getName()).isEqualTo("theName");
|
||||||
|
assertThat(property.getSource()).hasToString("theName");
|
||||||
|
assertThat(property.getGetter().getSimpleName()).hasToString("getTheName");
|
||||||
|
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||||
|
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterNestedPropertySameClass() throws IOException {
|
||||||
|
process(ImmutableInnerClassProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableInnerClassProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "first");
|
||||||
|
assertThat(property.getName()).isEqualTo("first");
|
||||||
|
assertThat(property.getSource()).hasToString("first");
|
||||||
|
assertThat(property.getGetter().getSimpleName()).hasToString("getFirst");
|
||||||
|
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||||
|
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterNestedPropertyWithAnnotation() throws IOException {
|
||||||
|
process(ImmutableInnerClassProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableInnerClassProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "third");
|
||||||
|
assertThat(property.getName()).isEqualTo("third");
|
||||||
|
assertThat(property.getSource()).hasToString("third");
|
||||||
|
assertThat(property.getGetter().getSimpleName()).hasToString("getThird");
|
||||||
|
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||||
|
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterSimplePropertyWithNoAccessorShouldBeExposed()
|
||||||
|
throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "counter");
|
||||||
|
assertThat(property.getName()).isEqualTo("counter");
|
||||||
|
assertThat(property.getSource()).hasToString("counter");
|
||||||
|
assertThat(property.getGetter()).isNull();
|
||||||
|
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||||
|
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterMetadataSimpleProperty() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "counter");
|
||||||
|
assertItemMetadata(metadataEnv, property).isProperty().hasName("test.counter")
|
||||||
|
.hasType(Long.class).hasSourceType(ImmutableSimpleProperties.class)
|
||||||
|
.hasNoDescription().isNotDeprecated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterMetadataNestedGroup() throws IOException {
|
||||||
|
process(ImmutableInnerClassProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableInnerClassProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "first");
|
||||||
|
assertItemMetadata(metadataEnv, property).isGroup().hasName("test.first")
|
||||||
|
.hasType(
|
||||||
|
"org.springframework.boot.configurationsample.immutable.ImmutableInnerClassProperties$Foo")
|
||||||
|
.hasSourceType(ImmutableInnerClassProperties.class)
|
||||||
|
.hasSourceMethod("getFirst()").hasNoDescription().isNotDeprecated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterDeprecatedPropertyOnGetter() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ExecutableElement getter = getMethod(ownerElement, "isFlag");
|
||||||
|
VariableElement field = getField(ownerElement, "flag");
|
||||||
|
VariableElement constructorParameter = getConstructorParameter(ownerElement,
|
||||||
|
"flag");
|
||||||
|
ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor(
|
||||||
|
ownerElement, null, constructorParameter, "flag", field.asType(),
|
||||||
|
field, getter, null);
|
||||||
|
assertItemMetadata(metadataEnv, property).isProperty()
|
||||||
|
.isDeprecatedWithNoInformation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithDescription() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "theName");
|
||||||
|
assertItemMetadata(metadataEnv, property).isProperty()
|
||||||
|
.hasDescription("The name of this simple properties.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithDefaultValue() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableSimpleProperties.class);
|
||||||
|
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(
|
||||||
|
ownerElement, "theName");
|
||||||
|
assertItemMetadata(metadataEnv, property).isProperty()
|
||||||
|
.hasDefaultValue("boot");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithPrimitiveTypes() throws IOException {
|
||||||
|
process(ImmutablePrimitiveProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutablePrimitiveProperties.class);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "flag"))
|
||||||
|
.hasDefaultValue(false);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "octet")).hasDefaultValue(0);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "letter"))
|
||||||
|
.hasDefaultValue(null);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "number")).hasDefaultValue(0);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "counter")).hasDefaultValue(0);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "value")).hasDefaultValue(0L);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "percentage"))
|
||||||
|
.hasDefaultValue(0);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "ratio")).hasDefaultValue(0D);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithPrimitiveTypesAndDefaultValues()
|
||||||
|
throws IOException {
|
||||||
|
process(ImmutablePrimitiveWithDefaultsProperties.class,
|
||||||
|
(roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv.getRootElement(
|
||||||
|
ImmutablePrimitiveWithDefaultsProperties.class);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "flag"))
|
||||||
|
.hasDefaultValue(true);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "octet"))
|
||||||
|
.hasDefaultValue(120);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "letter"))
|
||||||
|
.hasDefaultValue("a");
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "number"))
|
||||||
|
.hasDefaultValue(1000);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "counter"))
|
||||||
|
.hasDefaultValue(42);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "value"))
|
||||||
|
.hasDefaultValue(2000);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "percentage"))
|
||||||
|
.hasDefaultValue(0.5);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "ratio"))
|
||||||
|
.hasDefaultValue(42.42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithPrimitiveWrapperTypesAndDefaultValues()
|
||||||
|
throws IOException {
|
||||||
|
process(ImmutablePrimitiveWrapperWithDefaultsProperties.class,
|
||||||
|
(roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv.getRootElement(
|
||||||
|
ImmutablePrimitiveWrapperWithDefaultsProperties.class);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "flag"))
|
||||||
|
.hasDefaultValue(true);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "octet"))
|
||||||
|
.hasDefaultValue(120);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "letter"))
|
||||||
|
.hasDefaultValue("a");
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "number"))
|
||||||
|
.hasDefaultValue(1000);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "counter"))
|
||||||
|
.hasDefaultValue(42);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "value"))
|
||||||
|
.hasDefaultValue(2000);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "percentage"))
|
||||||
|
.hasDefaultValue(0.5);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "ratio"))
|
||||||
|
.hasDefaultValue(42.42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorParameterPropertyWithCollectionTypesAndDefaultValues()
|
||||||
|
throws IOException {
|
||||||
|
process(ImmutableCollectionProperties.class, (roundEnv, metadataEnv) -> {
|
||||||
|
TypeElement ownerElement = roundEnv
|
||||||
|
.getRootElement(ImmutableCollectionProperties.class);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "names"))
|
||||||
|
.hasDefaultValue(null);
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "flags"))
|
||||||
|
.hasDefaultValue(Arrays.asList(true, false));
|
||||||
|
assertItemMetadata(metadataEnv,
|
||||||
|
createPropertyDescriptor(ownerElement, "durations"))
|
||||||
|
.hasDefaultValue(Arrays.asList("10s", "1m", "1h"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ConstructorParameterPropertyDescriptor createPropertyDescriptor(
|
||||||
|
TypeElement ownerElement, String name) {
|
||||||
|
VariableElement constructorParameter = getConstructorParameter(ownerElement,
|
||||||
|
name);
|
||||||
|
VariableElement field = getField(ownerElement, name);
|
||||||
|
ExecutableElement getter = getMethod(ownerElement,
|
||||||
|
createAccessorMethodName("get", name));
|
||||||
|
ExecutableElement setter = getMethod(ownerElement,
|
||||||
|
createAccessorMethodName("set", name));
|
||||||
|
return new ConstructorParameterPropertyDescriptor(ownerElement, null,
|
||||||
|
constructorParameter, name, field.asType(), field, getter, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VariableElement getConstructorParameter(TypeElement ownerElement,
|
||||||
|
String name) {
|
||||||
|
List<ExecutableElement> constructors = ElementFilter
|
||||||
|
.constructorsIn(ownerElement.getEnclosedElements()).stream()
|
||||||
|
.filter((constructor) -> !constructor.getParameters().isEmpty())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (constructors.size() != 1) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"No candidate constructor for " + ownerElement);
|
||||||
|
}
|
||||||
|
return constructors.get(0).getParameters().stream()
|
||||||
|
.filter((parameter) -> parameter.getSimpleName().toString().equals(name))
|
||||||
|
.findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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.configurationprocessor;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||||
|
import org.springframework.boot.configurationprocessor.metadata.Metadata;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata generation tests for immutable properties.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ImmutablePropertiesMetadataGenerationTests
|
||||||
|
extends AbstractMetadataGenerationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void immutableSimpleProperties() {
|
||||||
|
ConfigurationMetadata metadata = compile(ImmutableSimpleProperties.class);
|
||||||
|
assertThat(metadata).has(Metadata.withGroup("immutable")
|
||||||
|
.fromSource(ImmutableSimpleProperties.class));
|
||||||
|
assertThat(metadata).has(Metadata.withProperty("immutable.the-name", String.class)
|
||||||
|
.fromSource(ImmutableSimpleProperties.class)
|
||||||
|
.withDescription("The name of this simple properties.")
|
||||||
|
.withDefaultValue("boot"));
|
||||||
|
assertThat(metadata).has(Metadata.withProperty("immutable.flag", Boolean.class)
|
||||||
|
.withDefaultValue(false).fromSource(ImmutableSimpleProperties.class)
|
||||||
|
.withDescription("A simple flag.").withDeprecation(null, null));
|
||||||
|
assertThat(metadata).has(Metadata.withProperty("immutable.comparator"));
|
||||||
|
assertThat(metadata).has(Metadata.withProperty("immutable.counter"));
|
||||||
|
assertThat(metadata.getItems()).hasSize(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ class MetadataGenerationEnvironmentFactory
|
||||||
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
|
||||||
|
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
|
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
|
||||||
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION);
|
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.junit.rules.TemporaryFolder;
|
||||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||||
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
|
||||||
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
|
||||||
|
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
|
||||||
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
|
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
|
||||||
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
|
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
|
||||||
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
|
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
|
||||||
|
@ -41,6 +42,7 @@ import org.springframework.boot.configurationsample.simple.HierarchicalPropertie
|
||||||
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
|
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
|
||||||
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
|
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
|
||||||
import org.springframework.boot.configurationsample.simple.SimpleProperties;
|
import org.springframework.boot.configurationsample.simple.SimpleProperties;
|
||||||
|
import org.springframework.boot.configurationsample.specific.TwoConstructorsExample;
|
||||||
import org.springframework.boot.testsupport.compiler.TestCompiler;
|
import org.springframework.boot.testsupport.compiler.TestCompiler;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -100,6 +102,21 @@ public class PropertyDescriptorResolverTests {
|
||||||
"description", "counter", "number", "items")));
|
"description", "counter", "number", "items")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWithConstructorParameters() throws IOException {
|
||||||
|
process(ImmutableSimpleProperties.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).containsExactly("theName",
|
||||||
|
"flag", "comparator", "counter")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void propertiesWithSeveralConstructors() throws IOException {
|
||||||
|
process(TwoConstructorsExample.class,
|
||||||
|
propertyNames((stream) -> assertThat(stream).containsExactly("name")));
|
||||||
|
process(TwoConstructorsExample.class, properties((stream) -> assertThat(stream)
|
||||||
|
.element(0).isInstanceOf(JavaBeanPropertyDescriptor.class)));
|
||||||
|
}
|
||||||
|
|
||||||
private BiConsumer<TypeElement, MetadataGenerationEnvironment> properties(
|
private BiConsumer<TypeElement, MetadataGenerationEnvironment> properties(
|
||||||
Consumer<Stream<PropertyDescriptor<?>>> stream) {
|
Consumer<Stream<PropertyDescriptor<?>>> stream) {
|
||||||
return (element, metadataEnv) -> {
|
return (element, metadataEnv) -> {
|
||||||
|
|
|
@ -47,6 +47,8 @@ public class TestConfigurationMetadataAnnotationProcessor
|
||||||
|
|
||||||
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
|
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
|
||||||
|
|
||||||
|
public static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.configurationsample.DefaultValue";
|
||||||
|
|
||||||
public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint";
|
public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint";
|
||||||
|
|
||||||
public static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation";
|
public static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation";
|
||||||
|
@ -74,6 +76,11 @@ public class TestConfigurationMetadataAnnotationProcessor
|
||||||
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String defaultValueAnnotation() {
|
||||||
|
return DEFAULT_VALUE_ANNOTATION;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String endpointAnnotation() {
|
protected String endpointAnnotation() {
|
||||||
return ENDPOINT_ANNOTATION;
|
return ENDPOINT_ANNOTATION;
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 @DefaultValue} for testing (removes the need for a
|
||||||
|
* dependency on the real annotation).
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@Target({ ElementType.PARAMETER })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface DefaultValue {
|
||||||
|
|
||||||
|
String[] value();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with collections types and defaults.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ImmutableCollectionProperties {
|
||||||
|
|
||||||
|
private final List<String> names;
|
||||||
|
|
||||||
|
private final List<Boolean> flags;
|
||||||
|
|
||||||
|
private final List<Duration> durations;
|
||||||
|
|
||||||
|
public ImmutableCollectionProperties(List<String> names,
|
||||||
|
@DefaultValue({ "true", "false" }) List<Boolean> flags,
|
||||||
|
@DefaultValue({ "10s", "1m", "1h" }) List<Duration> durations) {
|
||||||
|
this.names = names;
|
||||||
|
this.flags = flags;
|
||||||
|
this.durations = durations;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* 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.NestedConfigurationProperty;
|
||||||
|
import org.springframework.boot.configurationsample.specific.SimplePojo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner properties, in immutable format.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class ImmutableInnerClassProperties {
|
||||||
|
|
||||||
|
private final Foo first;
|
||||||
|
|
||||||
|
private Foo second;
|
||||||
|
|
||||||
|
@NestedConfigurationProperty
|
||||||
|
private final SimplePojo third;
|
||||||
|
|
||||||
|
private final Fourth fourth;
|
||||||
|
|
||||||
|
public ImmutableInnerClassProperties(Foo first, Foo second, SimplePojo third,
|
||||||
|
Fourth fourth) {
|
||||||
|
this.first = first;
|
||||||
|
this.second = second;
|
||||||
|
this.third = third;
|
||||||
|
this.fourth = fourth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo getFirst() {
|
||||||
|
return this.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo getTheSecond() {
|
||||||
|
return this.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTheSecond(Foo second) {
|
||||||
|
this.second = second;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimplePojo getThird() {
|
||||||
|
return this.third;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fourth getFourth() {
|
||||||
|
return this.fourth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Foo {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private final Bar bar = new Bar();
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bar getBar() {
|
||||||
|
return this.bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Bar {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Fourth {
|
||||||
|
|
||||||
|
YES, NO
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with primitive types.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ImmutablePrimitiveProperties {
|
||||||
|
|
||||||
|
private final boolean flag;
|
||||||
|
|
||||||
|
private final byte octet;
|
||||||
|
|
||||||
|
private final char letter;
|
||||||
|
|
||||||
|
private final short number;
|
||||||
|
|
||||||
|
private final int counter;
|
||||||
|
|
||||||
|
private final long value;
|
||||||
|
|
||||||
|
private final float percentage;
|
||||||
|
|
||||||
|
private final double ratio;
|
||||||
|
|
||||||
|
public ImmutablePrimitiveProperties(boolean flag, byte octet, char letter,
|
||||||
|
short number, int counter, long value, float percentage, double ratio) {
|
||||||
|
this.flag = flag;
|
||||||
|
this.octet = octet;
|
||||||
|
this.letter = letter;
|
||||||
|
this.number = number;
|
||||||
|
this.counter = counter;
|
||||||
|
this.value = value;
|
||||||
|
this.percentage = percentage;
|
||||||
|
this.ratio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with primitive types and defaults.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ImmutablePrimitiveWithDefaultsProperties {
|
||||||
|
|
||||||
|
private final boolean flag;
|
||||||
|
|
||||||
|
private final byte octet;
|
||||||
|
|
||||||
|
private final char letter;
|
||||||
|
|
||||||
|
private final short number;
|
||||||
|
|
||||||
|
private final int counter;
|
||||||
|
|
||||||
|
private final long value;
|
||||||
|
|
||||||
|
private final float percentage;
|
||||||
|
|
||||||
|
private final double ratio;
|
||||||
|
|
||||||
|
public ImmutablePrimitiveWithDefaultsProperties(@DefaultValue("true") boolean flag,
|
||||||
|
@DefaultValue("120") byte octet, @DefaultValue("a") char letter,
|
||||||
|
@DefaultValue("1000") short number, @DefaultValue("42") int counter,
|
||||||
|
@DefaultValue("2000") long value, @DefaultValue("0.5") float percentage,
|
||||||
|
@DefaultValue("42.42") double ratio) {
|
||||||
|
this.flag = flag;
|
||||||
|
this.octet = octet;
|
||||||
|
this.letter = letter;
|
||||||
|
this.number = number;
|
||||||
|
this.counter = counter;
|
||||||
|
this.value = value;
|
||||||
|
this.percentage = percentage;
|
||||||
|
this.ratio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple immutable properties with primitive wrapper types and defaults.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ImmutablePrimitiveWrapperWithDefaultsProperties {
|
||||||
|
|
||||||
|
private final Boolean flag;
|
||||||
|
|
||||||
|
private final Byte octet;
|
||||||
|
|
||||||
|
private final Character letter;
|
||||||
|
|
||||||
|
private final Short number;
|
||||||
|
|
||||||
|
private final Integer counter;
|
||||||
|
|
||||||
|
private final Long value;
|
||||||
|
|
||||||
|
private final Float percentage;
|
||||||
|
|
||||||
|
private final Double ratio;
|
||||||
|
|
||||||
|
public ImmutablePrimitiveWrapperWithDefaultsProperties(
|
||||||
|
@DefaultValue("true") Boolean flag, @DefaultValue("120") Byte octet,
|
||||||
|
@DefaultValue("a") Character letter, @DefaultValue("1000") Short number,
|
||||||
|
@DefaultValue("42") Integer counter, @DefaultValue("2000") Long value,
|
||||||
|
@DefaultValue("0.5") Float percentage, @DefaultValue("42.42") Double ratio) {
|
||||||
|
this.flag = flag;
|
||||||
|
this.octet = octet;
|
||||||
|
this.letter = letter;
|
||||||
|
this.number = number;
|
||||||
|
this.counter = counter;
|
||||||
|
this.value = value;
|
||||||
|
this.percentage = percentage;
|
||||||
|
this.ratio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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 java.util.Comparator;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple properties, in immutable format.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("immutable")
|
||||||
|
public class ImmutableSimpleProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this simple properties.
|
||||||
|
*/
|
||||||
|
private final String theName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple flag.
|
||||||
|
*/
|
||||||
|
private final boolean flag;
|
||||||
|
|
||||||
|
// An interface can still be injected because it might have a converter
|
||||||
|
private final Comparator<?> comparator;
|
||||||
|
|
||||||
|
// Even if it is not exposed, we're still offering a way to bind the value via the
|
||||||
|
// constructor so it should be present in the metadata
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private final Long counter;
|
||||||
|
|
||||||
|
public ImmutableSimpleProperties(@DefaultValue("boot") String theName, boolean flag,
|
||||||
|
Comparator<?> comparator, Long counter) {
|
||||||
|
this.theName = theName;
|
||||||
|
this.flag = flag;
|
||||||
|
this.comparator = comparator;
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTheName() {
|
||||||
|
return this.theName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public boolean isFlag() {
|
||||||
|
return this.flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparator<?> getComparator() {
|
||||||
|
return this.comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates that an invalid default character value leads to a compilation failure.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
public class InvalidDefaultValueCharacterProperties {
|
||||||
|
|
||||||
|
private final char letter;
|
||||||
|
|
||||||
|
public InvalidDefaultValueCharacterProperties(@DefaultValue("bad") char letter) {
|
||||||
|
this.letter = letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char getLetter() {
|
||||||
|
return this.letter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates that an invalid default floating point value leads to a compilation
|
||||||
|
* failure.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
public class InvalidDefaultValueFloatingPointProperties {
|
||||||
|
|
||||||
|
private final Double ratio;
|
||||||
|
|
||||||
|
public InvalidDefaultValueFloatingPointProperties(
|
||||||
|
@DefaultValue("55.55.33") Double ratio) {
|
||||||
|
this.ratio = ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getRatio() {
|
||||||
|
return this.ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.configurationsample.DefaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demonstrates that an invalid default number value leads to a compilation failure.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
public class InvalidDefaultValueNumberProperties {
|
||||||
|
|
||||||
|
private final int counter;
|
||||||
|
|
||||||
|
public InvalidDefaultValueNumberProperties(@DefaultValue("invalid") int counter) {
|
||||||
|
this.counter = counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCounter() {
|
||||||
|
return this.counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type with more than one constructor.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
public class TwoConstructorsExample {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public TwoConstructorsExample() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public TwoConstructorsExample(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]sample[\\/]" checks="ImportControl" />
|
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]sample[\\/]" checks="ImportControl" />
|
||||||
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]sample[\\/]" checks="ImportControl" />
|
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]sample[\\/]" checks="ImportControl" />
|
||||||
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc*" />
|
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="Javadoc*" />
|
||||||
|
<suppress files="[\\/]src[\\/]test[\\/]java[\\/]" checks="NonEmptyAtclauseDescription" />
|
||||||
<suppress files="[\\/]autoconfigure[\\/]" checks="JavadocType" />
|
<suppress files="[\\/]autoconfigure[\\/]" checks="JavadocType" />
|
||||||
<suppress files="[\\/]autoconfigure[\\/]" checks="JavadocVariable" />
|
<suppress files="[\\/]autoconfigure[\\/]" checks="JavadocVariable" />
|
||||||
<suppress files="[\\/]spring-boot-docs[\\/]" checks="JavadocType" />
|
<suppress files="[\\/]spring-boot-docs[\\/]" checks="JavadocType" />
|
||||||
|
|
Loading…
Reference in New Issue