Generate configuration metadata for records
Update `spring-boot-configuration-processor` to support generating configuration metadata from record parameter javadoc. See gh-29403
This commit is contained in:
committed by
Phillip Webb
parent
cde9166d50
commit
af976caec9
@@ -23,6 +23,7 @@ import java.util.function.Function;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.PrimitiveType;
|
||||
@@ -34,13 +35,17 @@ import javax.tools.Diagnostic.Kind;
|
||||
* A {@link PropertyDescriptor} for a constructor parameter.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
|
||||
private final RecordComponentElement recordComponent;
|
||||
|
||||
ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod,
|
||||
VariableElement source, String name, TypeMirror type, VariableElement field, ExecutableElement getter,
|
||||
ExecutableElement setter) {
|
||||
VariableElement source, String name, TypeMirror type, VariableElement field,
|
||||
RecordComponentElement recordComponent, ExecutableElement getter, ExecutableElement setter) {
|
||||
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
|
||||
this.recordComponent = recordComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +64,15 @@ class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<Variable
|
||||
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
// record components descriptions are written using @param tag
|
||||
if (this.recordComponent != null) {
|
||||
return environment.getTypeUtils().getJavaDoc(this.recordComponent);
|
||||
}
|
||||
return super.resolveDescription(environment);
|
||||
}
|
||||
|
||||
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) {
|
||||
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element);
|
||||
List<String> defaultValue = getDefaultValue(environment, annotation);
|
||||
|
||||
@@ -155,7 +155,7 @@ abstract class PropertyDescriptor<S extends Element> {
|
||||
return environment.getTypeUtils().getType(getOwnerElement(), getType());
|
||||
}
|
||||
|
||||
private String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getJavaDoc(getField());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
@@ -36,6 +37,7 @@ import javax.lang.model.util.ElementFilter;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class PropertyDescriptorResolver {
|
||||
|
||||
@@ -82,8 +84,9 @@ class PropertyDescriptorResolver {
|
||||
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||
VariableElement field = members.getFields().get(name);
|
||||
RecordComponentElement recordComponent = members.getRecordComponents().get(name);
|
||||
register(candidates, new ConstructorParameterPropertyDescriptor(type, null, parameter, name, propertyType,
|
||||
field, getter, setter));
|
||||
field, recordComponent, getter, setter));
|
||||
});
|
||||
return candidates.values().stream();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.function.Function;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
@@ -39,6 +40,7 @@ import javax.lang.model.util.ElementFilter;
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Moritz Halbritter
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class TypeElementMembers {
|
||||
|
||||
@@ -54,6 +56,8 @@ class TypeElementMembers {
|
||||
|
||||
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, RecordComponentElement> recordComponents = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, List<ExecutableElement>> publicGetters = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
|
||||
@@ -72,6 +76,9 @@ class TypeElementMembers {
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
|
||||
processMethod(method);
|
||||
}
|
||||
for (RecordComponentElement recordComponent : ElementFilter.recordComponentsIn(element.getEnclosedElements())) {
|
||||
processRecordComponent(recordComponent);
|
||||
}
|
||||
Element superType = this.env.getTypeUtils().asElement(element.getSuperclass());
|
||||
if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())
|
||||
&& !RECORD_CLASS_NAME.equals(superType.toString())) {
|
||||
@@ -189,10 +196,21 @@ class TypeElementMembers {
|
||||
this.fields.putIfAbsent(name, field);
|
||||
}
|
||||
|
||||
private void processRecordComponent(RecordComponentElement recordComponent) {
|
||||
String name = recordComponent.getSimpleName().toString();
|
||||
if (!this.recordComponents.containsKey(name)) {
|
||||
this.recordComponents.put(name, recordComponent);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, VariableElement> getFields() {
|
||||
return Collections.unmodifiableMap(this.fields);
|
||||
}
|
||||
|
||||
Map<String, RecordComponentElement> getRecordComponents() {
|
||||
return Collections.unmodifiableMap(this.recordComponents);
|
||||
}
|
||||
|
||||
Map<String, List<ExecutableElement>> getPublicGetters() {
|
||||
return Collections.unmodifiableMap(this.publicGetters);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
@@ -44,6 +46,7 @@ import javax.lang.model.util.Types;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class TypeUtils {
|
||||
|
||||
@@ -176,6 +179,9 @@ class TypeUtils {
|
||||
}
|
||||
|
||||
String getJavaDoc(Element element) {
|
||||
if (element instanceof RecordComponentElement) {
|
||||
return getJavaDoc((RecordComponentElement) element);
|
||||
}
|
||||
String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null;
|
||||
if (javadoc != null) {
|
||||
javadoc = NEW_LINE_PATTERN.matcher(javadoc).replaceAll("").trim();
|
||||
@@ -247,6 +253,24 @@ class TypeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private String getJavaDoc(RecordComponentElement recordComponent) {
|
||||
String recordJavadoc = this.env.getElementUtils().getDocComment(recordComponent.getEnclosingElement());
|
||||
if (recordJavadoc != null) {
|
||||
Pattern paramJavadocPattern = paramJavadocPattern(recordComponent.getSimpleName().toString());
|
||||
Matcher paramJavadocMatcher = paramJavadocPattern.matcher(recordJavadoc);
|
||||
if (paramJavadocMatcher.find()) {
|
||||
String paramJavadoc = NEW_LINE_PATTERN.matcher(paramJavadocMatcher.group()).replaceAll("").trim();
|
||||
return paramJavadoc.isEmpty() ? null : paramJavadoc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Pattern paramJavadocPattern(String paramName) {
|
||||
String pattern = String.format("(?<=@param +%s).*?(?=([\r\n]+ *@)|$)", paramName);
|
||||
return Pattern.compile(pattern, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor that extracts the fully qualified name of a type, including generic
|
||||
* information.
|
||||
|
||||
Reference in New Issue
Block a user