mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Restructure metadata generation
This commit restructures the annotation processor to off-load most of its logic in a PropertyDescriptor abstraction that is consumed to generate the relevant metadata. This has the benefit to isolate the various way properties can be identified (java bean and lombok for now). Closes gh-16036
This commit is contained in:
parent
99c0b4561d
commit
00a18c32ab
@ -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<String> 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<? extends TypeElement> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<Element> 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<String, Object> elementValues = getAnnotationElementValues(annotation);
|
||||
Map<String, Object> 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<String, Object> elementValues = getAnnotationElementValues(annotation);
|
||||
Map<String, Object> 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<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
||||
Map<String, Object> 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);
|
||||
|
@ -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<ExecutableElement> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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<VariableElement> {
|
||||
|
||||
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<String, Object> values = env.getAnnotationElementValues(lombokAnnotation);
|
||||
Object value = values.get("value");
|
||||
return (value == null || value.toString().equals(LOMBOK_ACCESS_LEVEL_PUBLIC));
|
||||
}
|
||||
|
||||
}
|
@ -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<String> typeExcludes;
|
||||
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
private final Elements elements;
|
||||
|
||||
private final FieldValuesParser fieldValuesParser;
|
||||
|
||||
private final Map<TypeElement, Map<String, Object>> 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<String> determineTypeExcludes() {
|
||||
Set<String> 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<String, Object> 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<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
|
||||
Map<String, Object> 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<String, Object> resolveFieldValues(TypeElement element) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
resolveFieldValuesFor(values, element);
|
||||
return values;
|
||||
}
|
||||
|
||||
private void resolveFieldValuesFor(Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 <S> the type of the source element that determines the property
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
abstract class PropertyDescriptor<S> {
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@ -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<PropertyDescriptor<?>> resolve(TypeElement type,
|
||||
ExecutableElement factoryMethod) {
|
||||
TypeElementMembers members = new TypeElementMembers(this.environment, type);
|
||||
List<PropertyDescriptor<?>> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<String, VariableElement> fields = new LinkedHashMap<>();
|
||||
|
||||
@ -56,15 +51,8 @@ class TypeElementMembers {
|
||||
|
||||
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, Object> 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<String, Object> getFieldValues() {
|
||||
return Collections.unmodifiableMap(this.fieldValues);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<java.lang.Double>")
|
||||
.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);
|
||||
}
|
||||
|
||||
}
|
@ -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<java.lang.String>")
|
||||
.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);
|
||||
}
|
||||
|
||||
}
|
@ -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<ProcessingEnvironment, MetadataGenerationEnvironment> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
@ -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<TypeElement, MetadataGenerationEnvironment> properties(
|
||||
Consumer<Stream<PropertyDescriptor<?>>> stream) {
|
||||
return (element, metadataEnv) -> {
|
||||
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(
|
||||
metadataEnv);
|
||||
stream.accept(resolver.resolve(element, null));
|
||||
};
|
||||
}
|
||||
|
||||
private BiConsumer<TypeElement, MetadataGenerationEnvironment> propertyNames(
|
||||
Consumer<Stream<String>> stream) {
|
||||
return properties(
|
||||
(result) -> stream.accept(result.map(PropertyDescriptor::getName)));
|
||||
}
|
||||
|
||||
private void process(Class<?> target,
|
||||
BiConsumer<TypeElement, MetadataGenerationEnvironment> consumer)
|
||||
throws IOException {
|
||||
process(target, Collections.emptyList(), consumer);
|
||||
}
|
||||
|
||||
private void process(Class<?> target, Collection<Class<?>> additionalClasses,
|
||||
BiConsumer<TypeElement, MetadataGenerationEnvironment> consumer)
|
||||
throws IOException {
|
||||
BiConsumer<RoundEnvironmentTester, MetadataGenerationEnvironment> internalConsumer = (
|
||||
roundEnv, metadataEnv) -> {
|
||||
TypeElement element = roundEnv.getRootElement(target);
|
||||
consumer.accept(element, metadataEnv);
|
||||
};
|
||||
TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>(
|
||||
internalConsumer, new MetadataGenerationEnvironmentFactory());
|
||||
TestCompiler compiler = new TestCompiler(this.temporaryFolder);
|
||||
ArrayList<Class<?>> allClasses = new ArrayList<>();
|
||||
allClasses.add(target);
|
||||
allClasses.addAll(additionalClasses);
|
||||
compiler.getTask(allClasses.toArray(new Class<?>[0])).call(processor);
|
||||
}
|
||||
|
||||
}
|
@ -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<RoundEnvironmentTester, MetadataGenerationEnvironment> consumer)
|
||||
throws IOException {
|
||||
TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>(
|
||||
consumer, new MetadataGenerationEnvironmentFactory());
|
||||
TestCompiler compiler = new TestCompiler(this.temporaryFolder);
|
||||
compiler.getTask(target).call(processor);
|
||||
}
|
||||
|
||||
}
|
@ -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<ItemMetadataAssert, ItemMetadata>
|
||||
implements AssertProvider<ItemMetadataAssert> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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";
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user