diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 92e0cbbb125..1dd8854ac42 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -35,23 +35,17 @@ import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; import javax.tools.Diagnostic.Kind; -import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser; -import org.springframework.boot.configurationprocessor.fieldvalues.javac.JavaCompilerFieldValuesParser; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.InvalidConfigurationMetadataException; -import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; /** @@ -85,16 +79,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.actuate." + "endpoint.annotation.ReadOperation"; - static final String NULLABLE_ANNOTATION = "org.springframework.lang.Nullable"; - - static final String LOMBOK_DATA_ANNOTATION = "lombok.Data"; - - static final String LOMBOK_GETTER_ANNOTATION = "lombok.Getter"; - - static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter"; - - static final String LOMBOK_ACCESS_LEVEL_PUBLIC = "PUBLIC"; - private static final Set SUPPORTED_OPTIONS = Collections .unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION)); @@ -102,11 +86,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private MetadataCollector metadataCollector; - private TypeUtils typeUtils; - - private FieldValuesParser fieldValuesParser; - - private TypeExcludeFilter typeExcludeFilter = new TypeExcludeFilter(); + private MetadataGenerationEnvironment metadataEnv; protected String configurationPropertiesAnnotation() { return CONFIGURATION_PROPERTIES_ANNOTATION; @@ -141,33 +121,28 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); - this.typeUtils = new TypeUtils(env); this.metadataStore = new MetadataStore(env); this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata()); - try { - this.fieldValuesParser = new JavaCompilerFieldValuesParser(env); - } - catch (Throwable ex) { - this.fieldValuesParser = FieldValuesParser.NONE; - logWarning("Field value processing of @ConfigurationProperty meta-data is " - + "not supported"); - } + this.metadataEnv = new MetadataGenerationEnvironment(env, + configurationPropertiesAnnotation(), + nestedConfigurationPropertyAnnotation(), + deprecatedConfigurationPropertyAnnotation(), endpointAnnotation(), + readOperationAnnotation()); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { this.metadataCollector.processing(roundEnv); - Elements elementUtils = this.processingEnv.getElementUtils(); - TypeElement annotationType = elementUtils - .getTypeElement(configurationPropertiesAnnotation()); + TypeElement annotationType = this.metadataEnv + .getConfigurationPropertiesAnnotationElement(); if (annotationType != null) { // Is @ConfigurationProperties available for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) { processElement(element); } } - TypeElement endpointType = elementUtils.getTypeElement(endpointAnnotation()); + TypeElement endpointType = this.metadataEnv.getEndpointAnnotationElement(); if (endpointType != null) { // Is @Endpoint available getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType) .forEach(this::processEndpoint); @@ -220,8 +195,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private void processElement(Element element) { try { - AnnotationMirror annotation = getAnnotation(element, - configurationPropertiesAnnotation()); + AnnotationMirror annotation = this.metadataEnv + .getConfigurationPropertiesAnnotation(element); if (annotation != null) { String prefix = getPrefix(annotation); if (element instanceof TypeElement) { @@ -239,7 +214,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } private void processAnnotatedTypeElement(String prefix, TypeElement element) { - String type = this.typeUtils.getQualifiedName(element); + String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null)); processTypeElement(prefix, element, null); } @@ -250,10 +225,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor Element returns = this.processingEnv.getTypeUtils() .asElement(element.getReturnType()); if (returns instanceof TypeElement) { - ItemMetadata group = ItemMetadata.newGroup(prefix, - this.typeUtils.getQualifiedName(returns), - this.typeUtils.getQualifiedName(element.getEnclosingElement()), - element.toString()); + ItemMetadata group = ItemMetadata + .newGroup(prefix, + this.metadataEnv.getTypeUtils().getQualifiedName(returns), + this.metadataEnv.getTypeUtils() + .getQualifiedName(element.getEnclosingElement()), + element.toString()); if (this.metadataCollector.hasSimilarGroup(group)) { this.processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate `@ConfigurationProperties` definition for prefix '" @@ -270,164 +247,26 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private void processTypeElement(String prefix, TypeElement element, ExecutableElement source) { - TypeElementMembers members = new TypeElementMembers(this.processingEnv, - this.fieldValuesParser, element); - Map fieldValues = members.getFieldValues(); - processSimpleTypes(prefix, element, source, members, fieldValues); - processSimpleLombokTypes(prefix, element, source, members, fieldValues); - processNestedTypes(prefix, element, source, members); - processNestedLombokTypes(prefix, element, source, members); - } - - private void processSimpleTypes(String prefix, TypeElement element, - ExecutableElement source, TypeElementMembers members, - Map fieldValues) { - members.getPublicGetters().forEach((name, getter) -> { - TypeMirror returnType = getter.getReturnType(); - ExecutableElement setter = members.getPublicSetter(name, returnType); - VariableElement field = members.getFields().get(name); - Element returnTypeElement = this.processingEnv.getTypeUtils() - .asElement(returnType); - boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType); - boolean isNested = isNested(returnTypeElement, field, element); - boolean isCollection = this.typeUtils.isCollectionOrMap(returnType); - if (!isExcluded && !isNested && (setter != null || isCollection)) { - String dataType = this.typeUtils.getType(element, returnType); - String sourceType = this.typeUtils.getQualifiedName(element); - String description = this.typeUtils.getJavaDoc(field); - Object defaultValue = fieldValues.get(name); - boolean deprecated = isDeprecated(getter) || isDeprecated(setter) - || isDeprecated(source); - this.metadataCollector.add(ItemMetadata.newProperty(prefix, name, - dataType, sourceType, null, description, defaultValue, - deprecated ? getItemDeprecation(getter) : null)); - } - }); - } - - private ItemDeprecation getItemDeprecation(ExecutableElement getter) { - AnnotationMirror annotation = getAnnotation(getter, - deprecatedConfigurationPropertyAnnotation()); - String reason = null; - String replacement = null; - if (annotation != null) { - Map elementValues = getAnnotationElementValues(annotation); - reason = (String) elementValues.get("reason"); - replacement = (String) elementValues.get("replacement"); - } - reason = "".equals(reason) ? null : reason; - replacement = "".equals(replacement) ? null : replacement; - return new ItemDeprecation(reason, replacement); - } - - private void processSimpleLombokTypes(String prefix, TypeElement element, - ExecutableElement source, TypeElementMembers members, - Map fieldValues) { - members.getFields().forEach((name, field) -> { - if (!isLombokField(field, element)) { - return; - } - TypeMirror returnType = field.asType(); - Element returnTypeElement = this.processingEnv.getTypeUtils() - .asElement(returnType); - boolean isExcluded = this.typeExcludeFilter.isExcluded(returnType); - boolean isNested = isNested(returnTypeElement, field, element); - boolean isCollection = this.typeUtils.isCollectionOrMap(returnType); - boolean hasSetter = hasLombokSetter(field, element); - if (!isExcluded && !isNested && (hasSetter || isCollection)) { - String dataType = this.typeUtils.getType(element, returnType); - String sourceType = this.typeUtils.getQualifiedName(element); - String description = this.typeUtils.getJavaDoc(field); - Object defaultValue = fieldValues.get(name); - boolean deprecated = isDeprecated(field) || isDeprecated(source); - this.metadataCollector.add(ItemMetadata.newProperty(prefix, name, - dataType, sourceType, null, description, defaultValue, - deprecated ? new ItemDeprecation() : null)); - } - }); - } - - private void processNestedTypes(String prefix, TypeElement element, - ExecutableElement source, TypeElementMembers members) { - members.getPublicGetters().forEach((name, getter) -> { - VariableElement field = members.getFields().get(name); - processNestedType(prefix, element, source, name, getter, field, - getter.getReturnType()); - }); - } - - private void processNestedLombokTypes(String prefix, TypeElement element, - ExecutableElement source, TypeElementMembers members) { - members.getFields().forEach((name, field) -> { - if (isLombokField(field, element)) { - ExecutableElement getter = members.getPublicGetter(name, field.asType()); - processNestedType(prefix, element, source, name, getter, field, - field.asType()); - } - }); - } - - private boolean isLombokField(VariableElement field, TypeElement element) { - return hasLombokPublicAccessor(field, element, true); - } - - private boolean hasLombokSetter(VariableElement field, TypeElement element) { - return !field.getModifiers().contains(Modifier.FINAL) - && hasLombokPublicAccessor(field, element, false); - } - - /** - * Determine if the specified {@link VariableElement field} defines a public accessor - * using lombok annotations. - * @param field the field to inspect - * @param element the parent element of the field (i.e. its holding class) - * @param getter {@code true} to look for the read accessor, {@code false} for the - * write accessor - * @return {@code true} if this field has a public accessor of the specified type - */ - private boolean hasLombokPublicAccessor(VariableElement field, TypeElement element, - boolean getter) { - String annotation = (getter ? LOMBOK_GETTER_ANNOTATION - : LOMBOK_SETTER_ANNOTATION); - AnnotationMirror lombokMethodAnnotationOnField = getAnnotation(field, annotation); - if (lombokMethodAnnotationOnField != null) { - return isAccessLevelPublic(lombokMethodAnnotationOnField); - } - AnnotationMirror lombokMethodAnnotationOnElement = getAnnotation(element, - annotation); - if (lombokMethodAnnotationOnElement != null) { - return isAccessLevelPublic(lombokMethodAnnotationOnElement); - } - return hasAnnotation(element, LOMBOK_DATA_ANNOTATION); - } - - private boolean isAccessLevelPublic(AnnotationMirror lombokAnnotation) { - Map values = getAnnotationElementValues(lombokAnnotation); - Object value = values.get("value"); - return (value == null || value.toString().equals(LOMBOK_ACCESS_LEVEL_PUBLIC)); - } - - private void processNestedType(String prefix, TypeElement element, - ExecutableElement source, String name, ExecutableElement getter, - VariableElement field, TypeMirror returnType) { - Element returnElement = this.processingEnv.getTypeUtils().asElement(returnType); - boolean isNested = isNested(returnElement, field, element); - AnnotationMirror annotation = getAnnotation(getter, - configurationPropertiesAnnotation()); - if (returnElement instanceof TypeElement && annotation == null && isNested) { - String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, name); - this.metadataCollector.add(ItemMetadata.newGroup(nestedPrefix, - this.typeUtils.getQualifiedName(returnElement), - this.typeUtils.getQualifiedName(element), - (getter != null) ? getter.toString() : null)); - processTypeElement(nestedPrefix, (TypeElement) returnElement, source); - } + new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source) + .forEach((descriptor) -> { + this.metadataCollector.add( + descriptor.resolveItemMetadata(prefix, this.metadataEnv)); + if (descriptor.isNested(this.metadataEnv)) { + TypeElement nestedTypeElement = (TypeElement) this.metadataEnv + .getTypeUtils().asElement(descriptor.getType()); + String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, + descriptor.getName()); + processTypeElement(nestedPrefix, nestedTypeElement, source); + } + }); } private void processEndpoint(Element element, List annotations) { try { - String annotationName = this.typeUtils.getQualifiedName(annotations.get(0)); - AnnotationMirror annotation = getAnnotation(element, annotationName); + String annotationName = this.metadataEnv.getTypeUtils() + .getQualifiedName(annotations.get(0)); + AnnotationMirror annotation = this.metadataEnv.getAnnotation(element, + annotationName); if (element instanceof TypeElement) { processEndpoint(annotation, (TypeElement) element); } @@ -439,7 +278,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } private void processEndpoint(AnnotationMirror annotation, TypeElement element) { - Map elementValues = getAnnotationElementValues(annotation); + Map elementValues = this.metadataEnv + .getAnnotationElementValues(annotation); String endpointId = (String) elementValues.get("id"); if (endpointId == null || "".equals(endpointId)) { return; // Can't process that endpoint @@ -447,7 +287,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor String endpointKey = ItemMetadata.newItemMetadataPrefix("management.endpoint.", endpointId); Boolean enabledByDefault = (Boolean) elementValues.get("enableByDefault"); - String type = this.typeUtils.getQualifiedName(element); + String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); this.metadataCollector.add(ItemMetadata.newGroup(endpointKey, type, type, null)); this.metadataCollector.add(ItemMetadata.newProperty(endpointKey, "enabled", Boolean.class.getName(), type, null, @@ -463,7 +303,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private boolean hasMainReadOperation(TypeElement element) { for (ExecutableElement method : ElementFilter .methodsIn(element.getEnclosedElements())) { - if (hasAnnotation(method, readOperationAnnotation()) + if (this.metadataEnv.getReadOperationAnnotation(method) != null && (TypeKind.VOID != method.getReturnType().getKind()) && hasNoOrOptionalParameters(method)) { return true; @@ -474,81 +314,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor private boolean hasNoOrOptionalParameters(ExecutableElement method) { for (VariableElement parameter : method.getParameters()) { - if (!hasAnnotation(parameter, NULLABLE_ANNOTATION)) { + if (!this.metadataEnv.hasNullableAnnotation(parameter)) { return false; } } return true; } - private boolean isNested(Element returnType, VariableElement field, - TypeElement element) { - if (hasAnnotation(field, nestedConfigurationPropertyAnnotation())) { - return true; - } - if (isCyclePresent(returnType, element)) { - return false; - } - return (isParentTheSame(returnType, element)) - && returnType.getKind() != ElementKind.ENUM; - } - - private boolean isCyclePresent(Element returnType, Element element) { - if (!(element.getEnclosingElement() instanceof TypeElement)) { - return false; - } - if (element.getEnclosingElement().equals(returnType)) { - return true; - } - return isCyclePresent(returnType, element.getEnclosingElement()); - } - - private boolean isParentTheSame(Element returnType, TypeElement element) { - if (returnType == null || element == null) { - return false; - } - return getTopLevelType(returnType).equals(getTopLevelType(element)); - } - - private Element getTopLevelType(Element element) { - if (!(element.getEnclosingElement() instanceof TypeElement)) { - return element; - } - return getTopLevelType(element.getEnclosingElement()); - } - - private boolean isDeprecated(Element element) { - if (isElementDeprecated(element)) { - return true; - } - if (element instanceof VariableElement || element instanceof ExecutableElement) { - return isElementDeprecated(element.getEnclosingElement()); - } - return false; - } - - private boolean isElementDeprecated(Element element) { - return hasAnnotation(element, "java.lang.Deprecated") - || hasAnnotation(element, deprecatedConfigurationPropertyAnnotation()); - } - - private boolean hasAnnotation(Element element, String type) { - return getAnnotation(element, type) != null; - } - - private AnnotationMirror getAnnotation(Element element, String type) { - if (element != null) { - for (AnnotationMirror annotation : element.getAnnotationMirrors()) { - if (type.equals(annotation.getAnnotationType().toString())) { - return annotation; - } - } - } - return null; - } - private String getPrefix(AnnotationMirror annotation) { - Map elementValues = getAnnotationElementValues(annotation); + Map elementValues = this.metadataEnv + .getAnnotationElementValues(annotation); Object prefix = elementValues.get("prefix"); if (prefix != null && !"".equals(prefix)) { return (String) prefix; @@ -560,13 +335,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor return null; } - private Map getAnnotationElementValues(AnnotationMirror annotation) { - Map values = new LinkedHashMap<>(); - annotation.getElementValues().forEach((name, value) -> values - .put(name.getSimpleName().toString(), value.getValue())); - return values; - } - protected ConfigurationMetadata writeMetaData() throws Exception { ConfigurationMetadata metadata = this.metadataCollector.getMetadata(); metadata = mergeAdditionalMetadata(metadata); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java new file mode 100644 index 00000000000..ab876524b5f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java @@ -0,0 +1,62 @@ +/* + * 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 + * + * http://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 javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; + +/** + * A {@link PropertyDescriptor} for a standard JavaBean property. + * + * @author Stephane Nicoll + */ +class JavaBeanPropertyDescriptor extends PropertyDescriptor { + + private final ExecutableElement setter; + + JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, + ExecutableElement getter, String name, TypeMirror type, VariableElement field, + ExecutableElement setter) { + super(ownerElement, factoryMethod, getter, name, type, field, getter); + this.setter = setter; + } + + public ExecutableElement getSetter() { + return this.setter; + } + + @Override + protected boolean isProperty(MetadataGenerationEnvironment env) { + boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); + return !env.isExcluded(getType()) && getGetter() != null + && (getSetter() != null || isCollection); + } + + @Override + protected ItemDeprecation resolveItemDeprecation( + MetadataGenerationEnvironment environment) { + boolean deprecated = environment.isDeprecated(getGetter()) + || environment.isDeprecated(getSetter()) + || environment.isDeprecated(getFactoryMethod()); + return deprecated ? environment.resolveItemDeprecation(getGetter()) : null; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java new file mode 100644 index 00000000000..93848ba4fa1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptor.java @@ -0,0 +1,114 @@ +/* + * 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 + * + * http://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.Map; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; + +/** + * A {@link PropertyDescriptor} for a Lombok field. + * + * @author Stephane Nicoll + */ +class LombokPropertyDescriptor extends PropertyDescriptor { + + private static final String LOMBOK_DATA_ANNOTATION = "lombok.Data"; + + private static final String LOMBOK_GETTER_ANNOTATION = "lombok.Getter"; + + private static final String LOMBOK_SETTER_ANNOTATION = "lombok.Setter"; + + private static final String LOMBOK_ACCESS_LEVEL_PUBLIC = "PUBLIC"; + + LombokPropertyDescriptor(TypeElement typeElement, ExecutableElement factoryMethod, + VariableElement field, String name, TypeMirror type, + ExecutableElement getter) { + super(typeElement, factoryMethod, field, name, type, field, getter); + } + + @Override + protected boolean isProperty(MetadataGenerationEnvironment env) { + if (!hasLombokPublicAccessor(env, true)) { + return false; + } + boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); + return !env.isExcluded(getType()) && (hasSetter(env) || isCollection); + } + + @Override + protected boolean isNested(MetadataGenerationEnvironment environment) { + if (!hasLombokPublicAccessor(environment, true)) { + return false; + } + return super.isNested(environment); + } + + @Override + protected ItemDeprecation resolveItemDeprecation( + MetadataGenerationEnvironment environment) { + boolean deprecated = environment.isDeprecated(getField()) + || environment.isDeprecated(getFactoryMethod()); + return deprecated ? new ItemDeprecation() : null; + + } + + private boolean hasSetter(MetadataGenerationEnvironment env) { + return !getField().getModifiers().contains(Modifier.FINAL) + && hasLombokPublicAccessor(env, false); + } + + /** + * Determine if the current {@link #getField() field} defines a public accessor using + * lombok annotations. + * @param env the {@link MetadataGenerationEnvironment} + * @param getter {@code true} to look for the read accessor, {@code false} for the + * write accessor + * @return {@code true} if this field has a public accessor of the specified type + */ + private boolean hasLombokPublicAccessor(MetadataGenerationEnvironment env, + boolean getter) { + String annotation = (getter ? LOMBOK_GETTER_ANNOTATION + : LOMBOK_SETTER_ANNOTATION); + AnnotationMirror lombokMethodAnnotationOnField = env.getAnnotation(getField(), + annotation); + if (lombokMethodAnnotationOnField != null) { + return isAccessLevelPublic(env, lombokMethodAnnotationOnField); + } + AnnotationMirror lombokMethodAnnotationOnElement = env + .getAnnotation(getOwnerElement(), annotation); + if (lombokMethodAnnotationOnElement != null) { + return isAccessLevelPublic(env, lombokMethodAnnotationOnElement); + } + return (env.getAnnotation(getOwnerElement(), LOMBOK_DATA_ANNOTATION) != null); + } + + private boolean isAccessLevelPublic(MetadataGenerationEnvironment env, + AnnotationMirror lombokAnnotation) { + Map values = env.getAnnotationElementValues(lombokAnnotation); + Object value = values.get("value"); + return (value == null || value.toString().equals(LOMBOK_ACCESS_LEVEL_PUBLIC)); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java new file mode 100644 index 00000000000..4c3c5f53406 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java @@ -0,0 +1,231 @@ +/* + * 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 + * + * http://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.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.ProcessingEnvironment; +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.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + +import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser; +import org.springframework.boot.configurationprocessor.fieldvalues.javac.JavaCompilerFieldValuesParser; +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; + +/** + * Provide utilities to detect and validate configuration properties. + * + * @author Stephane Nicoll + */ +class MetadataGenerationEnvironment { + + private static final String NULLABLE_ANNOTATION = "org.springframework.lang.Nullable"; + + private final Set typeExcludes; + + private final TypeUtils typeUtils; + + private final Elements elements; + + private final FieldValuesParser fieldValuesParser; + + private final Map> defaultValues = new HashMap<>(); + + private final String configurationPropertiesAnnotation; + + private final String nestedConfigurationPropertyAnnotation; + + private final String deprecatedConfigurationPropertyAnnotation; + + private final String endpointAnnotation; + + private final String readOperationAnnotation; + + MetadataGenerationEnvironment(ProcessingEnvironment environment, + String configurationPropertiesAnnotation, + String nestedConfigurationPropertyAnnotation, + String deprecatedConfigurationPropertyAnnotation, String endpointAnnotation, + String readOperationAnnotation) { + this.typeExcludes = determineTypeExcludes(); + this.typeUtils = new TypeUtils(environment); + this.elements = environment.getElementUtils(); + this.fieldValuesParser = resolveFieldValuesParser(environment); + this.configurationPropertiesAnnotation = configurationPropertiesAnnotation; + this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation; + this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation; + this.endpointAnnotation = endpointAnnotation; + this.readOperationAnnotation = readOperationAnnotation; + } + + private static Set determineTypeExcludes() { + Set excludes = new HashSet<>(); + excludes.add("com.zaxxer.hikari.IConnectionCustomizer"); + excludes.add("groovy.text.markup.MarkupTemplateEngine"); + excludes.add("java.io.Writer"); + excludes.add("java.io.PrintWriter"); + excludes.add("java.lang.ClassLoader"); + excludes.add("java.util.concurrent.ThreadFactory"); + excludes.add("javax.jms.XAConnectionFactory"); + excludes.add("javax.sql.DataSource"); + excludes.add("javax.sql.XADataSource"); + excludes.add("org.apache.tomcat.jdbc.pool.PoolConfiguration"); + excludes.add("org.apache.tomcat.jdbc.pool.Validator"); + excludes.add("org.flywaydb.core.api.callback.FlywayCallback"); + excludes.add("org.flywaydb.core.api.resolver.MigrationResolver"); + return excludes; + } + + private static FieldValuesParser resolveFieldValuesParser(ProcessingEnvironment env) { + try { + return new JavaCompilerFieldValuesParser(env); + } + catch (Throwable ex) { + return FieldValuesParser.NONE; + } + } + + public TypeUtils getTypeUtils() { + return this.typeUtils; + } + + public Object getDefaultValue(TypeElement type, String name) { + return this.defaultValues.computeIfAbsent(type, this::resolveFieldValues) + .get(name); + } + + public boolean isExcluded(TypeMirror type) { + if (type == null) { + return false; + } + String typeName = type.toString(); + if (typeName.endsWith("[]")) { + typeName = typeName.substring(0, typeName.length() - 2); + } + return this.typeExcludes.contains(typeName); + } + + public boolean isDeprecated(Element element) { + if (isElementDeprecated(element)) { + return true; + } + if (element instanceof VariableElement || element instanceof ExecutableElement) { + return isElementDeprecated(element.getEnclosingElement()); + } + return false; + } + + public ItemDeprecation resolveItemDeprecation(Element element) { + AnnotationMirror annotation = getAnnotation(element, + this.deprecatedConfigurationPropertyAnnotation); + String reason = null; + String replacement = null; + if (annotation != null) { + Map elementValues = getAnnotationElementValues(annotation); + reason = (String) elementValues.get("reason"); + replacement = (String) elementValues.get("replacement"); + } + reason = "".equals(reason) ? null : reason; + replacement = "".equals(replacement) ? null : replacement; + return new ItemDeprecation(reason, replacement); + } + + public boolean hasAnnotation(Element element, String type) { + return getAnnotation(element, type) != null; + } + + public AnnotationMirror getAnnotation(Element element, String type) { + if (element != null) { + for (AnnotationMirror annotation : element.getAnnotationMirrors()) { + if (type.equals(annotation.getAnnotationType().toString())) { + return annotation; + } + } + } + return null; + } + + public Map getAnnotationElementValues(AnnotationMirror annotation) { + Map values = new LinkedHashMap<>(); + annotation.getElementValues().forEach((name, value) -> values + .put(name.getSimpleName().toString(), value.getValue())); + return values; + } + + public TypeElement getConfigurationPropertiesAnnotationElement() { + return this.elements.getTypeElement(this.configurationPropertiesAnnotation); + } + + public AnnotationMirror getConfigurationPropertiesAnnotation(Element element) { + return getAnnotation(element, this.configurationPropertiesAnnotation); + } + + public AnnotationMirror getNestedConfigurationPropertyAnnotation(Element element) { + return getAnnotation(element, this.nestedConfigurationPropertyAnnotation); + } + + public TypeElement getEndpointAnnotationElement() { + return this.elements.getTypeElement(this.endpointAnnotation); + } + + public AnnotationMirror getReadOperationAnnotation(Element element) { + return getAnnotation(element, this.readOperationAnnotation); + } + + public boolean hasNullableAnnotation(Element element) { + return getAnnotation(element, NULLABLE_ANNOTATION) != null; + } + + private boolean isElementDeprecated(Element element) { + return hasAnnotation(element, "java.lang.Deprecated") + || hasAnnotation(element, this.deprecatedConfigurationPropertyAnnotation); + } + + private Map resolveFieldValues(TypeElement element) { + Map values = new LinkedHashMap<>(); + resolveFieldValuesFor(values, element); + return values; + } + + private void resolveFieldValuesFor(Map values, TypeElement element) { + try { + this.fieldValuesParser.getFieldValues(element).forEach((name, value) -> { + if (!values.containsKey(name)) { + values.put(name, value); + } + }); + } + catch (Exception ex) { + // continue + } + Element superType = this.typeUtils.asElement(element.getSuperclass()); + if (superType instanceof TypeElement + && superType.asType().getKind() != TypeKind.NONE) { + resolveFieldValuesFor(values, (TypeElement) superType); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java new file mode 100644 index 00000000000..8607220570a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java @@ -0,0 +1,183 @@ +/* + * 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 + * + * http://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 javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; + +/** + * Description of a property that can be candidate for metadata generation. + * + * @param the type of the source element that determines the property + * @author Stephane Nicoll + */ +abstract class PropertyDescriptor { + + private final TypeElement ownerElement; + + private final ExecutableElement factoryMethod; + + private final S source; + + private final String name; + + private final TypeMirror type; + + private final VariableElement field; + + private final ExecutableElement getter; + + protected PropertyDescriptor(TypeElement ownerElement, + ExecutableElement factoryMethod, S source, String name, TypeMirror type, + VariableElement field, ExecutableElement getter) { + this.ownerElement = ownerElement; + this.factoryMethod = factoryMethod; + this.source = source; + this.name = name; + this.type = type; + this.field = field; + this.getter = getter; + } + + public TypeElement getOwnerElement() { + return this.ownerElement; + } + + public ExecutableElement getFactoryMethod() { + return this.factoryMethod; + } + + public S getSource() { + return this.source; + } + + public String getName() { + return this.name; + } + + public TypeMirror getType() { + return this.type; + } + + public VariableElement getField() { + return this.field; + } + + public ExecutableElement getGetter() { + return this.getter; + } + + protected abstract ItemDeprecation resolveItemDeprecation( + MetadataGenerationEnvironment environment); + + protected abstract boolean isProperty(MetadataGenerationEnvironment environment); + + protected boolean isNested(MetadataGenerationEnvironment environment) { + Element typeElement = environment.getTypeUtils().asElement(getType()); + if (!(typeElement instanceof TypeElement) + || typeElement.getKind() == ElementKind.ENUM) { + return false; + } + if (environment.getConfigurationPropertiesAnnotation(getGetter()) != null) { + return false; + } + if (environment.getNestedConfigurationPropertyAnnotation(getField()) != null) { + return true; + } + if (isCyclePresent(typeElement, getOwnerElement())) { + return false; + } + return isParentTheSame(typeElement, getOwnerElement()); + } + + public ItemMetadata resolveItemMetadata(String prefix, + MetadataGenerationEnvironment environment) { + if (isNested(environment)) { + return resolveItemMetadataGroup(prefix, environment); + } + else if (isProperty(environment)) { + return resolveItemMetadataProperty(prefix, environment); + } + return null; + } + + private ItemMetadata resolveItemMetadataProperty(String prefix, + MetadataGenerationEnvironment environment) { + String dataType = resolveType(environment); + String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); + String description = resolveDescription(environment); + Object defaultValue = resolveDefaultValue(environment); + ItemDeprecation deprecation = resolveItemDeprecation(environment); + return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, + description, defaultValue, deprecation); + } + + private ItemMetadata resolveItemMetadataGroup(String prefix, + MetadataGenerationEnvironment environment) { + Element propertyElement = environment.getTypeUtils().asElement(getType()); + String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, getName()); + String dataType = environment.getTypeUtils().getQualifiedName(propertyElement); + String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); + String sourceMethod = (getGetter() != null) ? getGetter().toString() : null; + return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod); + } + + private String resolveType(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getType(getOwnerElement(), getType()); + } + + private String resolveDescription(MetadataGenerationEnvironment environment) { + return environment.getTypeUtils().getJavaDoc(getField()); + } + + private Object resolveDefaultValue(MetadataGenerationEnvironment environment) { + return environment.getDefaultValue(getOwnerElement(), getName()); + } + + private boolean isCyclePresent(Element returnType, Element element) { + if (!(element.getEnclosingElement() instanceof TypeElement)) { + return false; + } + if (element.getEnclosingElement().equals(returnType)) { + return true; + } + return isCyclePresent(returnType, element.getEnclosingElement()); + } + + private boolean isParentTheSame(Element returnType, TypeElement element) { + if (returnType == null || element == null) { + return false; + } + return getTopLevelType(returnType).equals(getTopLevelType(element)); + } + + private Element getTopLevelType(Element element) { + if (!(element.getEnclosingElement() instanceof TypeElement)) { + return element; + } + return getTopLevelType(element.getEnclosingElement()); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java new file mode 100644 index 00000000000..c5f81fc909f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java @@ -0,0 +1,75 @@ +/* + * 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 + * + * http://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.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** + * Resolve {@link PropertyDescriptor} instances. + * + * @author Stephane Nicoll + */ +class PropertyDescriptorResolver { + + private final MetadataGenerationEnvironment environment; + + PropertyDescriptorResolver(MetadataGenerationEnvironment environment) { + this.environment = environment; + } + + /** + * Return the {@link PropertyDescriptor} instances that are valid candidates for the + * specified {@link TypeElement type} based on the specified {@link ExecutableElement + * factory method}, if any. + * @param type the target type + * @param factoryMethod the method that triggered the metadata for that {@code type} + * or {@code null} + * @return the candidate properties for metadata generation + */ + public Stream> resolve(TypeElement type, + ExecutableElement factoryMethod) { + TypeElementMembers members = new TypeElementMembers(this.environment, type); + List> candidates = new ArrayList<>(); + // First check if we have regular java bean properties there + members.getPublicGetters().forEach((name, getter) -> { + TypeMirror returnType = getter.getReturnType(); + candidates.add(new JavaBeanPropertyDescriptor(type, factoryMethod, getter, + name, returnType, members.getFields().get(name), + members.getPublicSetter(name, returnType))); + }); + // Then check for Lombok ones + members.getFields().forEach((name, field) -> { + TypeMirror returnType = field.asType(); + ExecutableElement getter = members.getPublicGetter(name, returnType); + candidates.add(new LombokPropertyDescriptor(type, factoryMethod, field, name, + returnType, getter)); + }); + return candidates.stream().filter(this::isCandidate); + } + + private boolean isCandidate(PropertyDescriptor descriptor) { + return descriptor.isProperty(this.environment) + || descriptor.isNested(this.environment); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java index e39018a532c..91cd36ef4da 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -33,8 +32,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import org.springframework.boot.configurationprocessor.fieldvalues.FieldValuesParser; - /** * Provides access to relevant {@link TypeElement} members. * @@ -46,9 +43,7 @@ class TypeElementMembers { private static final String OBJECT_CLASS_NAME = Object.class.getName(); - private final ProcessingEnvironment env; - - private final TypeUtils typeUtils; + private final MetadataGenerationEnvironment env; private final Map fields = new LinkedHashMap<>(); @@ -56,15 +51,8 @@ class TypeElementMembers { private final Map> publicSetters = new LinkedHashMap<>(); - private final Map fieldValues = new LinkedHashMap<>(); - - private final FieldValuesParser fieldValuesParser; - - TypeElementMembers(ProcessingEnvironment env, FieldValuesParser fieldValuesParser, - TypeElement element) { + TypeElementMembers(MetadataGenerationEnvironment env, TypeElement element) { this.env = env; - this.typeUtils = new TypeUtils(this.env); - this.fieldValuesParser = fieldValuesParser; process(element); } @@ -77,17 +65,6 @@ class TypeElementMembers { .fieldsIn(element.getEnclosedElements())) { processField(field); } - try { - this.fieldValuesParser.getFieldValues(element).forEach((name, value) -> { - if (!this.fieldValues.containsKey(name)) { - this.fieldValues.put(name, value); - } - }); - } - catch (Exception ex) { - // continue - } - Element superType = this.env.getTypeUtils().asElement(element.getSuperclass()); if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())) { @@ -180,7 +157,8 @@ class TypeElementMembers { if (this.env.getTypeUtils().isSameType(returnType, type)) { return candidate; } - TypeMirror alternative = this.typeUtils.getWrapperOrPrimitiveFor(type); + TypeMirror alternative = this.env.getTypeUtils() + .getWrapperOrPrimitiveFor(type); if (alternative != null && this.env.getTypeUtils().isSameType(returnType, alternative)) { return candidate; @@ -196,7 +174,8 @@ class TypeElementMembers { if (matching != null) { return matching; } - TypeMirror alternative = this.typeUtils.getWrapperOrPrimitiveFor(type); + TypeMirror alternative = this.env.getTypeUtils() + .getWrapperOrPrimitiveFor(type); if (alternative != null) { return getMatchingSetter(candidates, alternative); } @@ -204,8 +183,4 @@ class TypeElementMembers { return null; } - public Map getFieldValues() { - return Collections.unmodifiableMap(this.fieldValues); - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeExcludeFilter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeExcludeFilter.java deleted file mode 100644 index 39038d81055..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeExcludeFilter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2012-2018 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 - * - * http://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.HashSet; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; - -/** - * Filter to exclude elements that don't make sense to process. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - * @since 1.2.0 - */ -class TypeExcludeFilter { - - private final Set excludes = new HashSet<>(); - - TypeExcludeFilter() { - add("com.zaxxer.hikari.IConnectionCustomizer"); - add("groovy.text.markup.MarkupTemplateEngine"); - add("java.io.Writer"); - add("java.io.PrintWriter"); - add("java.lang.ClassLoader"); - add("java.util.concurrent.ThreadFactory"); - add("javax.jms.XAConnectionFactory"); - add("javax.sql.DataSource"); - add("javax.sql.XADataSource"); - add("org.apache.tomcat.jdbc.pool.PoolConfiguration"); - add("org.apache.tomcat.jdbc.pool.Validator"); - add("org.flywaydb.core.api.callback.FlywayCallback"); - add("org.flywaydb.core.api.resolver.MigrationResolver"); - } - - private void add(String className) { - this.excludes.add(className); - } - - public boolean isExcluded(TypeMirror type) { - if (type == null) { - return false; - } - String typeName = type.toString(); - if (typeName.endsWith("[]")) { - typeName = typeName.substring(0, typeName.length() - 2); - } - return this.excludes.contains(typeName); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index d71fdbfc156..7690b797e6d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -110,6 +110,14 @@ class TypeUtils { } } + public boolean isSameType(TypeMirror t1, TypeMirror t2) { + return this.types.isSameType(t1, t2); + } + + public Element asElement(TypeMirror type) { + return this.types.asElement(type); + } + /** * Return the qualified name of the specified element. * @param element the element to handle @@ -294,7 +302,7 @@ class TypeUtils { if (type instanceof DeclaredType) { DeclaredType declaredType = (DeclaredType) type; Element enclosingElement = declaredType.asElement().getEnclosingElement(); - if (enclosingElement != null && enclosingElement instanceof TypeElement) { + if (enclosingElement instanceof TypeElement) { return (TypeElement) enclosingElement; } } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 441b154eb35..0c215f827ab 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -24,6 +24,8 @@ import org.springframework.boot.configurationsample.simple.ClassWithNestedProper import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty; import org.springframework.boot.configurationsample.simple.DescriptionProperties; import org.springframework.boot.configurationsample.simple.HierarchicalProperties; +import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent; +import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent; import org.springframework.boot.configurationsample.simple.NotAnnotated; import org.springframework.boot.configurationsample.simple.SimpleArrayProperties; import org.springframework.boot.configurationsample.simple.SimpleCollectionProperties; @@ -141,16 +143,18 @@ public class ConfigurationMetadataAnnotationProcessorTests @Test public void hierarchicalProperties() { - ConfigurationMetadata metadata = compile(HierarchicalProperties.class); + ConfigurationMetadata metadata = compile(HierarchicalProperties.class, + HierarchicalPropertiesParent.class, + HierarchicalPropertiesGrandparent.class); assertThat(metadata).has(Metadata.withGroup("hierarchical") .fromSource(HierarchicalProperties.class)); assertThat(metadata).has(Metadata.withProperty("hierarchical.first", String.class) + .withDefaultValue("one").fromSource(HierarchicalProperties.class)); + assertThat(metadata).has(Metadata + .withProperty("hierarchical.second", String.class).withDefaultValue("two") .fromSource(HierarchicalProperties.class)); - assertThat(metadata) - .has(Metadata.withProperty("hierarchical.second", String.class) - .fromSource(HierarchicalProperties.class)); assertThat(metadata).has(Metadata.withProperty("hierarchical.third", String.class) - .fromSource(HierarchicalProperties.class)); + .withDefaultValue("three").fromSource(HierarchicalProperties.class)); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java new file mode 100644 index 00000000000..1a9acdb7635 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptorTests.java @@ -0,0 +1,287 @@ +/* + * 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 + * + * http://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 javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import org.junit.Test; + +import org.springframework.boot.configurationsample.simple.DeprecatedProperties; +import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty; +import org.springframework.boot.configurationsample.simple.SimpleCollectionProperties; +import org.springframework.boot.configurationsample.simple.SimpleProperties; +import org.springframework.boot.configurationsample.simple.SimpleTypeProperties; +import org.springframework.boot.configurationsample.specific.InnerClassProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link JavaBeanPropertyDescriptor}. + * + * @author Stephane Nicoll + */ +public class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests { + + @Test + public void javaBeanSimpleProperty() throws IOException { + process(SimpleTypeProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(SimpleTypeProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "myString"); + assertThat(property.getName()).isEqualTo("myString"); + assertThat(property.getSource()).isSameAs(property.getGetter()); + assertThat(property.getGetter().getSimpleName()).hasToString("getMyString"); + assertThat(property.getSetter().getSimpleName()).hasToString("setMyString"); + assertThat(property.isProperty(metadataEnv)).isTrue(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void javaBeanCollectionProperty() throws IOException { + process(SimpleCollectionProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(SimpleCollectionProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "doubles"); + assertThat(property.getName()).isEqualTo("doubles"); + assertThat(property.getGetter().getSimpleName()).hasToString("getDoubles"); + assertThat(property.getSetter()).isNull(); + assertThat(property.isProperty(metadataEnv)).isTrue(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void javaBeanNestedPropertySameClass() throws IOException { + process(InnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(InnerClassProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "first"); + assertThat(property.getName()).isEqualTo("first"); + assertThat(property.getGetter().getSimpleName()).hasToString("getFirst"); + assertThat(property.getSetter()).isNull(); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isTrue(); + }); + } + + @Test + public void javaBeanNestedPropertyWithAnnotation() throws IOException { + process(InnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(InnerClassProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "third"); + assertThat(property.getName()).isEqualTo("third"); + assertThat(property.getGetter().getSimpleName()).hasToString("getThird"); + assertThat(property.getSetter()).isNull(); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isTrue(); + }); + } + + @Test + public void javaBeanSimplePropertyWithOnlyGetterShouldNotBeExposed() + throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + ExecutableElement getter = getMethod(ownerElement, "getSize"); + VariableElement field = getField(ownerElement, "size"); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor( + ownerElement, getter, getter, "size", field.asType(), field, null); + assertThat(property.getName()).isEqualTo("size"); + assertThat(property.getSource()).isSameAs(property.getGetter()); + assertThat(property.getGetter().getSimpleName()).hasToString("getSize"); + assertThat(property.getSetter()).isNull(); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void javaBeanSimplePropertyWithOnlySetterShouldNotBeExposed() + throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + VariableElement field = getField(ownerElement, "counter"); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor( + ownerElement, null, null, "counter", field.asType(), field, + getMethod(ownerElement, "setCounter")); + assertThat(property.getName()).isEqualTo("counter"); + assertThat(property.getSource()).isSameAs(property.getGetter()); + assertThat(property.getGetter()).isNull(); + assertThat(property.getSetter().getSimpleName()).hasToString("setCounter"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void javaBeanMetadataSimpleProperty() throws IOException { + process(SimpleTypeProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(SimpleTypeProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "myString"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasName("test.my-string").hasType(String.class) + .hasSourceType(SimpleTypeProperties.class).hasNoDescription() + .isNotDeprecated(); + }); + } + + @Test + public void javaBeanMetadataCollectionProperty() throws IOException { + process(SimpleCollectionProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(SimpleCollectionProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "doubles"); + assertItemMetadata(metadataEnv, property).isProperty().hasName("test.doubles") + .hasType("java.util.List") + .hasSourceType(SimpleCollectionProperties.class).hasNoDescription() + .isNotDeprecated(); + }); + } + + @Test + public void javaBeanMetadataNestedGroup() throws IOException { + process(InnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(InnerClassProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "first"); + assertItemMetadata(metadataEnv, property).isGroup().hasName("test.first") + .hasType( + "org.springframework.boot.configurationsample.specific.InnerClassProperties$Foo") + .hasSourceType(InnerClassProperties.class) + .hasSourceMethod("getFirst()").hasNoDescription().isNotDeprecated(); + }); + } + + @Test + public void javaBeanMetadataNotACandidatePropertyShouldReturnNull() + throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + VariableElement field = getField(ownerElement, "counter"); + JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor( + ownerElement, null, null, "counter", field.asType(), field, + getMethod(ownerElement, "setCounter")); + assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull(); + }); + } + + @Test + @SuppressWarnings("deprecation") + public void javaBeanDeprecatedPropertyOnClass() throws IOException { + process(DeprecatedProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(DeprecatedProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithNoInformation(); + }); + } + + @Test + public void javaBeanMetadataDeprecatedPropertyWithAnnotation() throws IOException { + process(DeprecatedSingleProperty.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(DeprecatedSingleProperty.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithReason("renamed") + .isDeprecatedWithReplacement("singledeprecated.new-name"); + }); + } + + @Test + public void javaBeanDeprecatedPropertyOnGetter() throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "flag", "isFlag", "setFlag"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithNoInformation(); + }); + } + + @Test + public void javaBeanDeprecatedPropertyOnSetter() throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "theName"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithNoInformation(); + }); + } + + @Test + public void javaBeanPropertyWithDescription() throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "theName"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasDescription("The name of this simple properties."); + }); + } + + @Test + public void javaBeanPropertyWithDefaultValue() throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "theName"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasDefaultValue("boot"); + }); + } + + protected JavaBeanPropertyDescriptor createPropertyDescriptor( + TypeElement ownerElement, String name) { + return createPropertyDescriptor(ownerElement, name, + createAccessorMethod("get", name), createAccessorMethod("set", name)); + } + + protected JavaBeanPropertyDescriptor createPropertyDescriptor( + TypeElement ownerElement, String name, String getterName, String setterName) { + ExecutableElement getter = getMethod(ownerElement, getterName); + ExecutableElement setter = getMethod(ownerElement, setterName); + VariableElement field = getField(ownerElement, name); + return new JavaBeanPropertyDescriptor(ownerElement, null, getter, name, + getter.getReturnType(), field, setter); + } + + private String createAccessorMethod(String prefix, String name) { + char[] chars = name.toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + return prefix + new String(chars, 0, chars.length); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java new file mode 100644 index 00000000000..9ab994ae66e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/LombokPropertyDescriptorTests.java @@ -0,0 +1,308 @@ +/* + * 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 + * + * http://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 javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; + +import org.junit.Test; + +import org.springframework.boot.configurationsample.lombok.LombokDefaultValueProperties; +import org.springframework.boot.configurationsample.lombok.LombokDeprecatedProperties; +import org.springframework.boot.configurationsample.lombok.LombokDeprecatedSingleProperty; +import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; +import org.springframework.boot.configurationsample.lombok.LombokInnerClassProperties; +import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties; +import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; +import org.springframework.boot.configurationsample.simple.SimpleProperties; +import org.springframework.boot.configurationsample.specific.InnerClassProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LombokPropertyDescriptor}. + * + * @author Stephane Nicoll + */ +public class LombokPropertyDescriptorTests extends PropertyDescriptorTests { + + @Test + public void lombokSimpleProperty() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertThat(property.getName()).isEqualTo("name"); + assertThat(property.getSource()).isSameAs(property.getField()); + assertThat(property.getField().getSimpleName()).hasToString("name"); + assertThat(property.isProperty(metadataEnv)).isTrue(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokCollectionProperty() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "items"); + assertThat(property.getName()).isEqualTo("items"); + assertThat(property.getSource()).isSameAs(property.getField()); + assertThat(property.getField().getSimpleName()).hasToString("items"); + assertThat(property.isProperty(metadataEnv)).isTrue(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokNestedPropertySameClass() throws IOException { + process(LombokInnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokInnerClassProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "first"); + assertThat(property.getName()).isEqualTo("first"); + assertThat(property.getSource()).isSameAs(property.getField()); + assertThat(property.getField().getSimpleName()).hasToString("first"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isTrue(); + }); + } + + @Test + public void lombokNestedPropertyWithAnnotation() throws IOException { + process(LombokInnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokInnerClassProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "third"); + assertThat(property.getName()).isEqualTo("third"); + assertThat(property.getSource()).isSameAs(property.getField()); + assertThat(property.getField().getSimpleName()).hasToString("third"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isTrue(); + }); + } + + @Test + public void lombokSimplePropertyWithOnlyGetterOnClassShouldNotBeExposed() + throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "ignored"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokSimplePropertyWithOnlyGetterOnDataClassShouldNotBeExposed() + throws IOException { + process(LombokSimpleDataProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleDataProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "ignored"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokSimplePropertyWithOnlyGetterOnFieldShouldNotBeExposed() + throws IOException { + process(LombokExplicitProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokExplicitProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "ignoredOnlyGetter"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokSimplePropertyWithOnlySetterOnFieldShouldNotBeExposed() + throws IOException { + process(LombokExplicitProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokExplicitProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "ignoredOnlySetter"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokMetadataSimpleProperty() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "description"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasName("test.description").hasType(String.class) + .hasSourceType(LombokSimpleProperties.class).hasNoDescription() + .isNotDeprecated(); + }); + } + + @Test + public void lombokMetadataCollectionProperty() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "items"); + assertItemMetadata(metadataEnv, property).isProperty().hasName("test.items") + .hasType("java.util.List") + .hasSourceType(LombokSimpleProperties.class).hasNoDescription() + .isNotDeprecated(); + }); + } + + @Test + public void lombokMetadataNestedGroup() throws IOException { + process(LombokInnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokInnerClassProperties.class); + VariableElement field = getField(ownerElement, "third"); + ExecutableElement getter = getMethod(ownerElement, "getThird"); + LombokPropertyDescriptor property = new LombokPropertyDescriptor(ownerElement, + null, field, "third", field.asType(), getter); + assertItemMetadata(metadataEnv, property).isGroup().hasName("test.third") + .hasType( + "org.springframework.boot.configurationsample.lombok.SimpleLombokPojo") + .hasSourceType(LombokInnerClassProperties.class) + .hasSourceMethod("getThird()").hasNoDescription().isNotDeprecated(); + }); + } + + @Test + public void lombokMetadataNestedGroupNoGetter() throws IOException { + process(LombokInnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokInnerClassProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "first"); + assertItemMetadata(metadataEnv, property).isGroup().hasName("test.first") + .hasType( + "org.springframework.boot.configurationsample.lombok.LombokInnerClassProperties$Foo") + .hasSourceType(LombokInnerClassProperties.class).hasSourceMethod(null) + .hasNoDescription().isNotDeprecated(); + }); + } + + @Test + public void lombokMetadataNotACandidatePropertyShouldReturnNull() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "ignored"); + assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull(); + }); + } + + @Test + @SuppressWarnings("deprecation") + public void lombokDeprecatedPropertyOnClass() throws IOException { + process(LombokDeprecatedProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokDeprecatedProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithNoInformation(); + }); + } + + @Test + public void lombokDeprecatedPropertyOnField() throws IOException { + process(LombokDeprecatedSingleProperty.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokDeprecatedSingleProperty.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertItemMetadata(metadataEnv, property).isProperty() + .isDeprecatedWithNoInformation(); + }); + } + + @Test + public void lombokPropertyWithDescription() throws IOException { + process(LombokSimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokSimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "name"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasDescription("Name description."); + }); + } + + @Test + public void lombokPropertyWithDefaultValue() throws IOException { + process(LombokDefaultValueProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(LombokDefaultValueProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "description"); + assertItemMetadata(metadataEnv, property).isProperty() + .hasDefaultValue("my description"); + }); + } + + @Test + public void lombokPropertyNotCandidate() throws IOException { + process(SimpleProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "theName"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + @Test + public void lombokNestedPropertyNotCandidate() throws IOException { + process(InnerClassProperties.class, (roundEnv, metadataEnv) -> { + TypeElement ownerElement = roundEnv + .getRootElement(InnerClassProperties.class); + LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, + "first"); + assertThat(property.isProperty(metadataEnv)).isFalse(); + assertThat(property.isNested(metadataEnv)).isFalse(); + }); + } + + protected LombokPropertyDescriptor createPropertyDescriptor(TypeElement ownerElement, + String name) { + VariableElement field = getField(ownerElement, name); + return new LombokPropertyDescriptor(ownerElement, null, field, name, + field.asType(), null); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java new file mode 100644 index 00000000000..bbf74d9ce8a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java @@ -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 + * + * http://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.function.Function; + +import javax.annotation.processing.ProcessingEnvironment; + +import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor; + +/** + * A factory for {@link MetadataGenerationEnvironment} against test annotations. + * + * @author Stephane Nicoll + */ +class MetadataGenerationEnvironmentFactory + implements Function { + + @Override + public MetadataGenerationEnvironment apply(ProcessingEnvironment environment) { + return new MetadataGenerationEnvironment(environment, + TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION, + TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION, + TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION, + TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION, + TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java new file mode 100644 index 00000000000..c129f11b5a3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolverTests.java @@ -0,0 +1,141 @@ +/* + * 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 + * + * http://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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import javax.lang.model.element.TypeElement; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; +import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; +import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; +import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties; +import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties; +import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties; +import org.springframework.boot.configurationsample.simple.HierarchicalProperties; +import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent; +import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent; +import org.springframework.boot.configurationsample.simple.SimpleProperties; +import org.springframework.boot.testsupport.compiler.TestCompiler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PropertyDescriptorResolver}. + * + * @author Stephane Nicoll + */ +public class PropertyDescriptorResolverTests { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void propertiesWithJavaBeanProperties() throws IOException { + process(SimpleProperties.class, propertyNames((stream) -> assertThat(stream) + .containsExactly("theName", "flag", "comparator"))); + } + + @Test + public void propertiesWithJavaBeanHierarchicalProperties() throws IOException { + process(HierarchicalProperties.class, + Arrays.asList(HierarchicalPropertiesParent.class, + HierarchicalPropertiesGrandparent.class), + (type, metadataEnv) -> { + PropertyDescriptorResolver resolver = new PropertyDescriptorResolver( + metadataEnv); + assertThat( + resolver.resolve(type, null).map(PropertyDescriptor::getName)) + .containsExactly("third", "second", "first"); + assertThat(resolver.resolve(type, null) + .map((descriptor) -> descriptor.resolveItemMetadata("test", + metadataEnv)) + .map(ItemMetadata::getDefaultValue)).containsExactly("three", + "two", "one"); + }); + } + + @Test + public void propertiesWithLombokGetterSetterAtClassLevel() throws IOException { + process(LombokSimpleProperties.class, propertyNames((stream) -> assertThat(stream) + .containsExactly("name", "description", "counter", "number", "items"))); + } + + @Test + public void propertiesWithLombokGetterSetterAtFieldLevel() throws IOException { + process(LombokExplicitProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name", + "description", "counter", "number", "items"))); + } + + @Test + public void propertiesWithLombokDataClass() throws IOException { + process(LombokSimpleDataProperties.class, + propertyNames((stream) -> assertThat(stream).containsExactly("name", + "description", "counter", "number", "items"))); + } + + private BiConsumer properties( + Consumer>> stream) { + return (element, metadataEnv) -> { + PropertyDescriptorResolver resolver = new PropertyDescriptorResolver( + metadataEnv); + stream.accept(resolver.resolve(element, null)); + }; + } + + private BiConsumer propertyNames( + Consumer> stream) { + return properties( + (result) -> stream.accept(result.map(PropertyDescriptor::getName))); + } + + private void process(Class target, + BiConsumer consumer) + throws IOException { + process(target, Collections.emptyList(), consumer); + } + + private void process(Class target, Collection> additionalClasses, + BiConsumer consumer) + throws IOException { + BiConsumer internalConsumer = ( + roundEnv, metadataEnv) -> { + TypeElement element = roundEnv.getRootElement(target); + consumer.accept(element, metadataEnv); + }; + TestableAnnotationProcessor processor = new TestableAnnotationProcessor<>( + internalConsumer, new MetadataGenerationEnvironmentFactory()); + TestCompiler compiler = new TestCompiler(this.temporaryFolder); + ArrayList> allClasses = new ArrayList<>(); + allClasses.add(target); + allClasses.addAll(additionalClasses); + compiler.getTask(allClasses.toArray(new Class[0])).call(processor); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java new file mode 100644 index 00000000000..04da993da1f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/PropertyDescriptorTests.java @@ -0,0 +1,72 @@ +/* + * 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 + * + * http://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.function.BiConsumer; + +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.util.ElementFilter; + +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import org.springframework.boot.configurationprocessor.test.ItemMetadataAssert; +import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; +import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; +import org.springframework.boot.testsupport.compiler.TestCompiler; + +/** + * Base test infrastructure to test {@link PropertyDescriptor} implementations. + * + * @author Stephane Nicoll + */ +public abstract class PropertyDescriptorTests { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + protected ExecutableElement getMethod(TypeElement element, String name) { + return ElementFilter.methodsIn(element.getEnclosedElements()).stream().filter( + (method) -> ((Element) method).getSimpleName().toString().equals(name)) + .findFirst().orElse(null); + } + + protected VariableElement getField(TypeElement element, String name) { + return ElementFilter.fieldsIn(element.getEnclosedElements()).stream().filter( + (method) -> ((Element) method).getSimpleName().toString().equals(name)) + .findFirst().orElse(null); + } + + protected ItemMetadataAssert assertItemMetadata( + MetadataGenerationEnvironment metadataEnv, PropertyDescriptor property) { + return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv)); + } + + protected void process(Class target, + BiConsumer consumer) + throws IOException { + TestableAnnotationProcessor processor = new TestableAnnotationProcessor<>( + consumer, new MetadataGenerationEnvironmentFactory()); + TestCompiler compiler = new TestCompiler(this.temporaryFolder); + compiler.getTask(target).call(processor); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/ItemMetadataAssert.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/ItemMetadataAssert.java new file mode 100644 index 00000000000..ccc659cd417 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/ItemMetadataAssert.java @@ -0,0 +1,128 @@ +/* + * 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 + * + * http://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.test; + +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.AssertProvider; +import org.assertj.core.internal.Objects; + +import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; +import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.ItemType; + +/** + * AssertJ assert for {@link ItemMetadata}. + * + * @author Stephane Nicoll + */ +public class ItemMetadataAssert extends AbstractAssert + implements AssertProvider { + + private static final Objects objects = Objects.instance(); + + public ItemMetadataAssert(ItemMetadata itemMetadata) { + super(itemMetadata, ItemMetadataAssert.class); + objects.assertNotNull(this.info, itemMetadata); + } + + public ItemMetadataAssert isProperty() { + objects.assertEqual(this.info, this.actual.isOfItemType(ItemType.PROPERTY), true); + return this; + } + + public ItemMetadataAssert isGroup() { + objects.assertEqual(this.info, this.actual.isOfItemType(ItemType.GROUP), true); + return this; + } + + public ItemMetadataAssert hasName(String name) { + objects.assertEqual(this.info, this.actual.getName(), name); + return this; + } + + public ItemMetadataAssert hasType(String type) { + objects.assertEqual(this.info, this.actual.getType(), type); + return this; + } + + public ItemMetadataAssert hasType(Class type) { + return hasType(type.getName()); + } + + public ItemMetadataAssert hasDescription(String description) { + objects.assertEqual(this.info, this.actual.getDescription(), description); + return this; + } + + public ItemMetadataAssert hasNoDescription() { + return hasDescription(null); + } + + public ItemMetadataAssert hasSourceType(String type) { + objects.assertEqual(this.info, this.actual.getSourceType(), type); + return this; + } + + public ItemMetadataAssert hasSourceType(Class type) { + return hasSourceType(type.getName()); + } + + public ItemMetadataAssert hasSourceMethod(String type) { + objects.assertEqual(this.info, this.actual.getSourceMethod(), type); + return this; + } + + public ItemMetadataAssert hasDefaultValue(Object defaultValue) { + objects.assertEqual(this.info, this.actual.getDefaultValue(), defaultValue); + return this; + } + + public ItemMetadataAssert isDeprecatedWithNoInformation() { + assertItemDeprecation(); + return this; + } + + public ItemMetadataAssert isDeprecatedWithReason(String reason) { + ItemDeprecation deprecation = assertItemDeprecation(); + objects.assertEqual(this.info, deprecation.getReason(), reason); + return this; + } + + public ItemMetadataAssert isDeprecatedWithReplacement(String replacement) { + ItemDeprecation deprecation = assertItemDeprecation(); + objects.assertEqual(this.info, deprecation.getReplacement(), replacement); + return this; + } + + public ItemMetadataAssert isNotDeprecated() { + objects.assertNull(this.info, this.actual.getDeprecation()); + return this; + } + + private ItemDeprecation assertItemDeprecation() { + ItemDeprecation deprecation = this.actual.getDeprecation(); + objects.assertNotNull(this.info, deprecation); + objects.assertNull(this.info, deprecation.getLevel()); + return deprecation; + } + + @Override + public ItemMetadataAssert assertThat() { + return this; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java index 58fd62a8923..1306064d695 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java @@ -41,15 +41,15 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller; public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationMetadataAnnotationProcessor { - static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties"; + public static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.configurationsample.ConfigurationProperties"; - static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty"; + public static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.NestedConfigurationProperty"; - 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"; - static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint"; + public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint"; - static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation"; + public static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation"; private ConfigurationMetadata metadata; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDefaultValueProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDefaultValueProperties.java new file mode 100644 index 00000000000..2e577bb6f15 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDefaultValueProperties.java @@ -0,0 +1,34 @@ +/* + * 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 + * + * http://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.lombok; + +import lombok.Data; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Configuration properties with default values. + * + * @author Stephane Nicoll + */ +@Data +@ConfigurationProperties("default") +public class LombokDefaultValueProperties { + + private String description = "my description"; + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedProperties.java new file mode 100644 index 00000000000..09f5acd270b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://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.lombok; + +import lombok.Getter; +import lombok.Setter; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Deprecated configuration properties. + * + * @author Stephane Nicoll + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "deprecated") +@Deprecated +public class LombokDeprecatedProperties { + + private String name; + + private String description; + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedSingleProperty.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedSingleProperty.java new file mode 100644 index 00000000000..c5cef3809cf --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokDeprecatedSingleProperty.java @@ -0,0 +1,37 @@ +/* + * 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 + * + * http://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.lombok; + +import lombok.Data; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +/** + * Configuration properties with a single deprecated element. + * + * @author Stephane Nicoll + */ +@Data +@ConfigurationProperties("singledeprecated") +public class LombokDeprecatedSingleProperty { + + @Deprecated + private String name; + + private String description; + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassProperties.java index da07e3f1240..1870b9a8188 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/lombok/LombokInnerClassProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * 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. @@ -40,6 +40,11 @@ public class LombokInnerClassProperties { private Fourth fourth; + // Only there to record the source method + public SimpleLombokPojo getThird() { + return this.third; + } + @Data public static class Foo { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalProperties.java index 46aef227edb..884eb2a2ae5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * 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. @@ -26,7 +26,7 @@ import org.springframework.boot.configurationsample.ConfigurationProperties; @ConfigurationProperties(prefix = "hierarchical") public class HierarchicalProperties extends HierarchicalPropertiesParent { - private String third; + private String third = "three"; public String getThird() { return this.third; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesGrandparent.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesGrandparent.java index bc2e678f513..a77d2f4970e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesGrandparent.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesGrandparent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * 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. @@ -23,7 +23,7 @@ package org.springframework.boot.configurationsample.simple; */ public abstract class HierarchicalPropertiesGrandparent { - private String first; + private String first = "one"; public String getFirst() { return this.first; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesParent.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesParent.java index 84345d81ac3..ef1009b6ec3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesParent.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/simple/HierarchicalPropertiesParent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * 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. @@ -24,7 +24,7 @@ package org.springframework.boot.configurationsample.simple; public abstract class HierarchicalPropertiesParent extends HierarchicalPropertiesGrandparent { - private String second; + private String second = "two"; public String getSecond() { return this.second;