Commit 3125f424 authored by Stephane Nicoll's avatar Stephane Nicoll

Add metadata support for immutable ConfigurationProperties type

Closes gh-16071
parent 35d7fccb
...@@ -809,8 +809,11 @@ is used to populate the `description` attribute. ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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
......
/*
* 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> { ...@@ -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> { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -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> { ...@@ -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> { ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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");
}
} }
/*
* 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 ...@@ -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; ...@@ -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 ...@@ -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 { ...@@ -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 ...@@ -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 ...@@ -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;
......
/*
* 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();
}
/*
* 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;
}
}
/*
* 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
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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 @@ ...@@ -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" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment