From 52eb3b468e15356b45586f9cc2dc17f635d3d8bc Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 14 Jan 2022 18:26:46 +0300 Subject: [PATCH 1/6] Enable configuration metadata generation for record component descriptions --- ...onstructorParameterPropertyDescriptor.java | 21 ++++++++-- .../PropertyDescriptor.java | 4 +- .../PropertyDescriptorResolver.java | 7 +++- .../TypeElementMembers.java | 20 +++++++++- .../configurationprocessor/TypeUtils.java | 26 +++++++++++- ...ationMetadataAnnotationProcessorTests.java | 40 ++++++++++++++++++- ...uctorParameterPropertyDescriptorTests.java | 6 +-- 7 files changed, 111 insertions(+), 13 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java index d2fdc0787d19..155d354b3d76 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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,6 +23,7 @@ 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 @@ * A {@link PropertyDescriptor} for a constructor parameter. * * @author Stephane Nicoll + * @author Pavel Anisimov */ class ConstructorParameterPropertyDescriptor extends PropertyDescriptor { + 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,16 @@ protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null); } + @Override + protected String resolveDescription(MetadataGenerationEnvironment environment) { + // record components descriptions are written using @param notation and require + // special processing + 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 defaultValue = getDefaultValue(environment, annotation); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java index 355ac02b1a84..113ce58f41cf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -155,7 +155,7 @@ private String resolveType(MetadataGenerationEnvironment environment) { return environment.getTypeUtils().getType(getOwnerElement(), getType()); } - private String resolveDescription(MetadataGenerationEnvironment environment) { + protected String resolveDescription(MetadataGenerationEnvironment environment) { return environment.getTypeUtils().getJavaDoc(getField()); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java index 084e61f2bc03..75c565d8a76e 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -25,6 +25,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; 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; @@ -35,6 +36,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Pavel Anisimov */ class PropertyDescriptorResolver { @@ -82,8 +84,9 @@ Stream> resolveConstructorProperties(TypeElement type, Exe 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, factoryMethod, parameter, name, - propertyType, field, getter, setter)); + propertyType, field, recordComponent, getter, setter)); }); return candidates.values().stream(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java index f51290c654b7..3c157e3c26a4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeElementMembers.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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. @@ -27,6 +27,7 @@ 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; @@ -38,6 +39,7 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Pavel Anisimov */ class TypeElementMembers { @@ -49,6 +51,8 @@ class TypeElementMembers { private final Map fields = new LinkedHashMap<>(); + private final Map recordComponents = new LinkedHashMap<>(); + private final Map> publicGetters = new LinkedHashMap<>(); private final Map> publicSetters = new LinkedHashMap<>(); @@ -66,6 +70,9 @@ private void process(TypeElement element) { for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) { processField(field); } + 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())) { process((TypeElement) superType); @@ -163,10 +170,21 @@ private void processField(VariableElement field) { } } + private void processRecordComponent(RecordComponentElement recordComponent) { + String name = recordComponent.getSimpleName().toString(); + if (!this.recordComponents.containsKey(name)) { + this.recordComponents.put(name, recordComponent); + } + } + Map getFields() { return Collections.unmodifiableMap(this.fields); } + Map getRecordComponents() { + return Collections.unmodifiableMap(this.recordComponents); + } + Map> getPublicGetters() { return Collections.unmodifiableMap(this.publicGetters); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index 3e2810e3185b..2b7a3ecd78de 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2022 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,11 +24,13 @@ 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 @@ * * @author Stephane Nicoll * @author Phillip Webb + * @author Pavel Anisimov */ class TypeUtils { @@ -176,6 +179,9 @@ boolean isCollectionOrMap(TypeMirror type) { } 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(); @@ -246,6 +252,24 @@ private void process(TypeDescriptor descriptor, TypeMirror type) { } } + 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. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 756d4b9a4ca5..a7a93d1d1470 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -74,6 +74,7 @@ * @author Andy Wilkinson * @author Kris De Volder * @author Jonas Keßler + * @author Pavel Anisimov */ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGenerationTests { @@ -460,4 +461,41 @@ void multiConstructorRecordProperties(@TempDir File temp) throws IOException { assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer")); } + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void recordPropertiesWithDescriptions(@TempDir File temp) throws IOException { + File exampleRecord = new File(temp, "ExampleRecord.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { + writer.println("/**"); + writer.println(" * ExampleRecord Javadoc sample"); + writer.println(" *"); + writer.println(" * @author Pavel Anisimov"); + writer.println(" * @param someString very long description that doesn't fit"); + writer.println(" * single line"); + writer.println(" * @param someInteger description with @param and @ pitfalls"); + writer.println(" * @param someBoolean description with extra spaces"); + writer.println(" *@param someLong description without space after asterisk"); + writer.println(" * @since 1.0.0"); + writer.println(" */"); + writer.println( + "@org.springframework.boot.configurationsample.ConfigurationProperties(\"record.descriptions\")"); + writer.println("public record ExampleRecord("); + writer.println("String someString,"); + writer.println("Integer someInteger,"); + writer.println("Boolean someBoolean,"); + writer.println("Long someLong"); + writer.println(") {"); + writer.println("}"); + } + ConfigurationMetadata metadata = compile(exampleRecord); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-string", String.class) + .withDescription("very long description that doesn't fit single line")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-integer", Integer.class) + .withDescription("description with @param and @ pitfalls")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-boolean", Boolean.class) + .withDescription("description with extra spaces")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-long", Long.class) + .withDescription("description without space after asterisk")); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java index d3d6589315c7..c664f333d5bf 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2022 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. @@ -126,7 +126,7 @@ void constructorParameterDeprecatedPropertyOnGetter() throws IOException { VariableElement field = getField(ownerElement, "flag"); VariableElement constructorParameter = getConstructorParameter(ownerElement, "flag"); ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor(ownerElement, - null, constructorParameter, "flag", field.asType(), field, getter, null); + null, constructorParameter, "flag", field.asType(), field, null, getter, null); assertItemMetadata(metadataEnv, property).isProperty().isDeprecatedWithNoInformation(); }); } @@ -213,7 +213,7 @@ protected ConstructorParameterPropertyDescriptor createPropertyDescriptor(TypeEl ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name)); ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name)); return new ConstructorParameterPropertyDescriptor(ownerElement, null, constructorParameter, name, - field.asType(), field, getter, setter); + field.asType(), field, null, getter, setter); } private VariableElement getConstructorParameter(TypeElement ownerElement, String name) { From e696104d2328d5aea4e81a8425a78b312695ac21 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 14 Jan 2022 19:01:22 +0300 Subject: [PATCH 2/6] Polish --- .../ConstructorParameterPropertyDescriptor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java index 155d354b3d76..7c737a10cc54 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java @@ -66,8 +66,7 @@ protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) @Override protected String resolveDescription(MetadataGenerationEnvironment environment) { - // record components descriptions are written using @param notation and require - // special processing + // record components descriptions are written using @param tag if (this.recordComponent != null) { return environment.getTypeUtils().getJavaDoc(this.recordComponent); } From 378dab71168ceef675d342d46bcebbdf3cdac162 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 14 Jan 2022 19:30:58 +0300 Subject: [PATCH 3/6] Enrich docs --- .../asciidoc/configuration-metadata/annotation-processor.adoc | 2 ++ .../docs/asciidoc/features/developing-auto-configuration.adoc | 2 ++ 2 files changed, 4 insertions(+) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc index 5219c77c14a4..2f00e998f9f4 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/configuration-metadata/annotation-processor.adoc @@ -79,6 +79,8 @@ The Javadoc on fields is used to populate the `description` attribute. For insta NOTE: You should only use plain text with `@ConfigurationProperties` field Javadoc, since they are not processed before being added to the JSON. +If you use `@ConfigurationProperties` with record class then record components' descriptions should be provided via class-level Javadoc tag `@param` (there are no explicit instance fields in record classes to put regular field-level Javadocs on). + The annotation processor applies a number of heuristics to extract the default value from the source model. Default values have to be provided statically. In particular, do not refer to a constant defined in another class. Also, the annotation processor cannot auto-detect default values for ``Enum``s and ``Collections``s. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc index 74a44e6aacbf..0be15d21c0db 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/developing-auto-configuration.adoc @@ -256,6 +256,8 @@ include::{docs-java}/features/developingautoconfiguration/customstarter/configur NOTE: You should only use plain text with `@ConfigurationProperties` field Javadoc, since they are not processed before being added to the JSON. +If you use `@ConfigurationProperties` with record class then record components' descriptions should be provided via class-level Javadoc tag `@param` (there are no explicit instance fields in record classes to put regular field-level Javadocs on). + Here are some rules we follow internally to make sure descriptions are consistent: * Do not start the description by "The" or "A". From efc201d74ffb644857eff66d6938d98e0ee51fe4 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 14 Jan 2022 22:02:06 +0300 Subject: [PATCH 4/6] Polish --- .../configurationprocessor/TypeUtils.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index 2b7a3ecd78de..f5d0166bafc0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -180,7 +180,7 @@ boolean isCollectionOrMap(TypeMirror type) { String getJavaDoc(Element element) { if (element instanceof RecordComponentElement) { - return getJavaDoc((RecordComponentElement) element); + return getJavadoc((RecordComponentElement) element); } String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; if (javadoc != null) { @@ -252,20 +252,20 @@ private void process(TypeDescriptor descriptor, TypeMirror type) { } } - 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; + 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) { + private Pattern paramJavadocPattern(String paramName) { String pattern = String.format("(?<=@param +%s).*?(?=([\r\n]+ *@)|$)", paramName); return Pattern.compile(pattern, Pattern.DOTALL); } From 1fea066acfc535a1e085b6e68b2152a369072e23 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 14 Jan 2022 22:51:34 +0300 Subject: [PATCH 5/6] Polish --- .../boot/configurationprocessor/TypeUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java index f5d0166bafc0..996e188d695f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java @@ -180,7 +180,7 @@ boolean isCollectionOrMap(TypeMirror type) { String getJavaDoc(Element element) { if (element instanceof RecordComponentElement) { - return getJavadoc((RecordComponentElement) element); + return getJavaDoc((RecordComponentElement) element); } String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; if (javadoc != null) { @@ -252,7 +252,7 @@ private void process(TypeDescriptor descriptor, TypeMirror type) { } } - private String getJavadoc(RecordComponentElement recordComponent) { + private String getJavaDoc(RecordComponentElement recordComponent) { String recordJavadoc = this.env.getElementUtils().getDocComment(recordComponent.getEnclosingElement()); if (recordJavadoc != null) { Pattern paramJavadocPattern = paramJavadocPattern(recordComponent.getSimpleName().toString()); From 9680a5d2550b47a7259454c6b53aa6befc95290a Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Sat, 15 Jan 2022 19:03:15 +0300 Subject: [PATCH 6/6] Improve test --- .../ConfigurationMetadataAnnotationProcessorTests.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index a7a93d1d1470..3729cd8be779 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -476,6 +476,7 @@ void recordPropertiesWithDescriptions(@TempDir File temp) throws IOException { writer.println(" * @param someBoolean description with extra spaces"); writer.println(" *@param someLong description without space after asterisk"); writer.println(" * @since 1.0.0"); + writer.println(" * @param someByte last description in Javadoc"); writer.println(" */"); writer.println( "@org.springframework.boot.configurationsample.ConfigurationProperties(\"record.descriptions\")"); @@ -483,7 +484,8 @@ void recordPropertiesWithDescriptions(@TempDir File temp) throws IOException { writer.println("String someString,"); writer.println("Integer someInteger,"); writer.println("Boolean someBoolean,"); - writer.println("Long someLong"); + writer.println("Long someLong,"); + writer.println("Byte someByte"); writer.println(") {"); writer.println("}"); } @@ -496,6 +498,8 @@ void recordPropertiesWithDescriptions(@TempDir File temp) throws IOException { .withDescription("description with extra spaces")); assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-long", Long.class) .withDescription("description without space after asterisk")); + assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-byte", Byte.class) + .withDescription("last description in Javadoc")); } }