Merge pull request #29403 from An1s9n

* pr/29403:
  Polish 'Generate configuration metadata for records'
  Generate configuration metadata for records

Closes gh-29403
This commit is contained in:
Phillip Webb
2024-04-22 10:29:57 -07:00
18 changed files with 676 additions and 368 deletions

View File

@@ -255,6 +255,8 @@ include-code::AcmeProperties[]
NOTE: You should only use plain text with `@ConfigurationProperties` field Javadoc, since they are not processed before being added to the JSON. 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: Here are some rules we follow internally to make sure descriptions are consistent:
* Do not start the description by "The" or "A". * Do not start the description by "The" or "A".

View File

@@ -89,6 +89,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. 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. 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. 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. Also, the annotation processor cannot auto-detect default values for ``Enum``s and ``Collections``s.

View File

@@ -16,195 +16,47 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.TypeKindVisitor8;
import javax.tools.Diagnostic.Kind;
/** /**
* A {@link PropertyDescriptor} for a constructor parameter. * A {@link PropertyDescriptor} for a constructor parameter.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
*/ */
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> { class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor {
ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, private final ExecutableElement setter;
VariableElement source, String name, TypeMirror type, VariableElement field, ExecutableElement getter,
ExecutableElement setter) { private final VariableElement field;
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
ConstructorParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter,
TypeElement declaringElement, ExecutableElement getter, ExecutableElement setter, VariableElement field) {
super(name, type, parameter, declaringElement, getter);
this.setter = setter;
this.field = field;
} }
@Override @Override
protected boolean isProperty(MetadataGenerationEnvironment env) { protected List<Element> getDeprecatableElements() {
// If it's a constructor parameter, it doesn't matter as we must be able to bind return Arrays.asList(getGetter(), this.setter, this.field);
// it to build the object.
return !isNested(env);
} }
@Override @Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource()); return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
if (defaultValue != null) {
return defaultValue;
}
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
} }
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) { @Override
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element); protected String resolveDescription(MetadataGenerationEnvironment environment) {
List<String> defaultValue = getDefaultValue(environment, annotation); return environment.getTypeUtils().getJavaDoc(this.field);
if (defaultValue != null) {
try {
TypeMirror specificType = determineSpecificType(environment);
if (defaultValue.size() == 1) {
return coerceValue(specificType, defaultValue.get(0));
}
return defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList();
}
catch (IllegalArgumentException ex) {
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation);
}
}
return null;
}
@SuppressWarnings("unchecked")
private List<String> getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) {
if (annotation == null) {
return null;
}
Map<String, Object> values = environment.getAnnotationElementValues(annotation);
return (List<String>) values.get("value");
}
private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) {
TypeMirror candidate = getSource().asType();
TypeMirror elementCandidate = environment.getTypeUtils().extractElementType(candidate);
if (elementCandidate != null) {
candidate = elementCandidate;
}
PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(candidate);
return (primitiveType != null) ? primitiveType : candidate;
}
private Object coerceValue(TypeMirror type, String value) {
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value);
return (coercedValue != null) ? coercedValue : value;
}
private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8<Object, String> {
private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();
private <T extends Number> T parseNumber(String value, Function<String, T> parser,
PrimitiveType primitiveType) {
try {
return parser.apply(value);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
String.format("Invalid %s representation '%s'", primitiveType, value));
}
}
@Override
public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) {
return Boolean.parseBoolean(value);
}
@Override
public Object visitPrimitiveAsByte(PrimitiveType t, String value) {
return parseNumber(value, Byte::parseByte, t);
}
@Override
public Object visitPrimitiveAsShort(PrimitiveType t, String value) {
return parseNumber(value, Short::parseShort, t);
}
@Override
public Object visitPrimitiveAsInt(PrimitiveType t, String value) {
return parseNumber(value, Integer::parseInt, t);
}
@Override
public Object visitPrimitiveAsLong(PrimitiveType t, String value) {
return parseNumber(value, Long::parseLong, t);
}
@Override
public Object visitPrimitiveAsChar(PrimitiveType t, String value) {
if (value.length() > 1) {
throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value));
}
return value;
}
@Override
public Object visitPrimitiveAsFloat(PrimitiveType t, String value) {
return parseNumber(value, Float::parseFloat, t);
}
@Override
public Object visitPrimitiveAsDouble(PrimitiveType t, String value) {
return parseNumber(value, Double::parseDouble, t);
}
}
private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8<Object, Void> {
private static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor();
@Override
public Object visitPrimitiveAsBoolean(PrimitiveType t, Void ignore) {
return false;
}
@Override
public Object visitPrimitiveAsByte(PrimitiveType t, Void ignore) {
return (byte) 0;
}
@Override
public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) {
return (short) 0;
}
@Override
public Object visitPrimitiveAsInt(PrimitiveType t, Void ignore) {
return 0;
}
@Override
public Object visitPrimitiveAsLong(PrimitiveType t, Void ignore) {
return 0L;
}
@Override
public Object visitPrimitiveAsChar(PrimitiveType t, Void ignore) {
return null;
}
@Override
public Object visitPrimitiveAsFloat(PrimitiveType t, Void ignore) {
return 0F;
}
@Override
public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) {
return 0D;
}
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.util.Arrays;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
@@ -25,23 +29,54 @@ import javax.lang.model.type.TypeMirror;
* A {@link PropertyDescriptor} for a standard JavaBean property. * A {@link PropertyDescriptor} for a standard JavaBean property.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
*/ */
class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> { class JavaBeanPropertyDescriptor extends PropertyDescriptor {
JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, ExecutableElement getter, private final ExecutableElement setter;
String name, TypeMirror type, VariableElement field, ExecutableElement setter) {
super(ownerElement, factoryMethod, getter, name, type, field, getter, setter); private final VariableElement field;
private final ExecutableElement factoryMethod;
JavaBeanPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter,
ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) {
super(name, type, declaringElement, getter);
this.setter = setter;
this.field = field;
this.factoryMethod = factoryMethod;
}
ExecutableElement getSetter() {
return this.setter;
} }
@Override @Override
protected boolean isProperty(MetadataGenerationEnvironment env) { protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType()); return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
return !env.isExcluded(getType()) && getGetter() != null && (getSetter() != null || isCollection); }
@Override
protected String resolveDescription(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getJavaDoc(this.field);
} }
@Override @Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
return environment.getFieldDefaultValue(getOwnerElement(), getName()); return environment.getFieldDefaultValue(getDeclaringElement(), getName());
}
@Override
protected List<Element> getDeprecatableElements() {
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
}
@Override
public boolean isProperty(MetadataGenerationEnvironment env) {
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
boolean hasGetter = getGetter() != null;
boolean hasSetter = getSetter() != null;
return !env.isExcluded(getType()) && hasGetter && (hasSetter || isCollection);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,23 +16,25 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
/** /**
* A {@link PropertyDescriptor} for a Lombok field. * A {@link PropertyDescriptor} for a Lombok field.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
*/ */
class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> { class LombokPropertyDescriptor extends PropertyDescriptor {
private static final String LOMBOK_DATA_ANNOTATION = "lombok.Data"; private static final String LOMBOK_DATA_ANNOTATION = "lombok.Data";
@@ -44,13 +46,46 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
private static final String LOMBOK_ACCESS_LEVEL_PUBLIC = "PUBLIC"; private static final String LOMBOK_ACCESS_LEVEL_PUBLIC = "PUBLIC";
LombokPropertyDescriptor(TypeElement typeElement, ExecutableElement factoryMethod, VariableElement field, private final ExecutableElement setter;
String name, TypeMirror type, ExecutableElement getter, ExecutableElement setter) {
super(typeElement, factoryMethod, field, name, type, field, getter, setter); private final VariableElement field;
private final ExecutableElement factoryMethod;
LombokPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter,
ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) {
super(name, type, declaringElement, getter);
this.factoryMethod = factoryMethod;
this.field = field;
this.setter = setter;
}
VariableElement getField() {
return this.field;
} }
@Override @Override
protected boolean isProperty(MetadataGenerationEnvironment env) { protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
return environment.getNestedConfigurationPropertyAnnotation(getField()) != null;
}
@Override
protected String resolveDescription(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getJavaDoc(this.field);
}
@Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
return environment.getFieldDefaultValue(getDeclaringElement(), getName());
}
@Override
protected List<Element> getDeprecatableElements() {
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
}
@Override
public boolean isProperty(MetadataGenerationEnvironment env) {
if (!hasLombokPublicAccessor(env, true)) { if (!hasLombokPublicAccessor(env, true)) {
return false; return false;
} }
@@ -59,29 +94,14 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
} }
@Override @Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) { public boolean isNested(MetadataGenerationEnvironment environment) {
return environment.getFieldDefaultValue(getOwnerElement(), getName()); return hasLombokPublicAccessor(environment, true) && super.isNested(environment);
}
@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(getGetter())
|| environment.isDeprecated(getFactoryMethod());
return deprecated ? environment.resolveItemDeprecation(getGetter()) : null;
} }
private boolean hasSetter(MetadataGenerationEnvironment env) { private boolean hasSetter(MetadataGenerationEnvironment env) {
boolean nonFinalPublicField = !getField().getModifiers().contains(Modifier.FINAL) boolean nonFinalPublicField = !getField().getModifiers().contains(Modifier.FINAL)
&& hasLombokPublicAccessor(env, false); && hasLombokPublicAccessor(env, false);
return getSetter() != null || nonFinalPublicField; return this.setter != null || nonFinalPublicField;
} }
/** /**
@@ -98,12 +118,12 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
if (lombokMethodAnnotationOnField != null) { if (lombokMethodAnnotationOnField != null) {
return isAccessLevelPublic(env, lombokMethodAnnotationOnField); return isAccessLevelPublic(env, lombokMethodAnnotationOnField);
} }
AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getOwnerElement(), annotation); AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getDeclaringElement(), annotation);
if (lombokMethodAnnotationOnElement != null) { if (lombokMethodAnnotationOnElement != null) {
return isAccessLevelPublic(env, lombokMethodAnnotationOnElement); return isAccessLevelPublic(env, lombokMethodAnnotationOnElement);
} }
return (env.hasAnnotation(getOwnerElement(), LOMBOK_DATA_ANNOTATION) return (env.hasAnnotation(getDeclaringElement(), LOMBOK_DATA_ANNOTATION)
|| env.hasAnnotation(getOwnerElement(), LOMBOK_VALUE_ANNOTATION)); || env.hasAnnotation(getDeclaringElement(), LOMBOK_VALUE_ANNOTATION));
} }
private boolean isAccessLevelPublic(MetadataGenerationEnvironment env, AnnotationMirror lombokAnnotation) { private boolean isAccessLevelPublic(MetadataGenerationEnvironment env, AnnotationMirror lombokAnnotation) {

View File

@@ -0,0 +1,216 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor;
import java.util.List;
import java.util.Map;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.TypeKindVisitor8;
import javax.tools.Diagnostic.Kind;
/**
* {@link PropertyDescriptor} created from a constructor or record parameter.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
abstract class ParameterPropertyDescriptor extends PropertyDescriptor {
private final VariableElement parameter;
ParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter, TypeElement declaringElement,
ExecutableElement getter) {
super(name, type, declaringElement, getter);
this.parameter = parameter;
}
final VariableElement getParameter() {
return this.parameter;
}
@Override
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
Object defaultValue = getDefaultValueFromAnnotation(environment, getParameter());
return (defaultValue != null) ? defaultValue
: getParameter().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
}
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) {
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element);
List<String> defaultValue = getDefaultValue(environment, annotation);
if (defaultValue != null) {
TypeMirror specificType = determineSpecificType(environment);
try {
List<Object> coerced = defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList();
return (coerced.size() != 1) ? coerced : coerced.get(0);
}
catch (IllegalArgumentException ex) {
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation);
}
}
return null;
}
@SuppressWarnings("unchecked")
private List<String> getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) {
if (annotation == null) {
return null;
}
Map<String, Object> values = environment.getAnnotationElementValues(annotation);
return (List<String>) values.get("value");
}
private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) {
TypeMirror parameterType = getParameter().asType();
TypeMirror elementType = environment.getTypeUtils().extractElementType(parameterType);
parameterType = (elementType != null) ? elementType : parameterType;
PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(parameterType);
return (primitiveType != null) ? primitiveType : parameterType;
}
private Object coerceValue(TypeMirror type, String value) {
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value);
return (coercedValue != null) ? coercedValue : value;
}
@Override
public boolean isProperty(MetadataGenerationEnvironment env) {
return !isNested(env); // We must be able to bind it to build the object.
}
/**
* Visitor that gets the default value for primitives.
*/
private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8<Object, Void> {
static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor();
@Override
public Object visitPrimitiveAsBoolean(PrimitiveType type, Void parameter) {
return false;
}
@Override
public Object visitPrimitiveAsByte(PrimitiveType type, Void parameter) {
return (byte) 0;
}
@Override
public Object visitPrimitiveAsShort(PrimitiveType type, Void parameter) {
return (short) 0;
}
@Override
public Object visitPrimitiveAsInt(PrimitiveType type, Void parameter) {
return 0;
}
@Override
public Object visitPrimitiveAsLong(PrimitiveType type, Void parameter) {
return 0L;
}
@Override
public Object visitPrimitiveAsChar(PrimitiveType type, Void parameter) {
return null;
}
@Override
public Object visitPrimitiveAsFloat(PrimitiveType type, Void parameter) {
return 0F;
}
@Override
public Object visitPrimitiveAsDouble(PrimitiveType type, Void parameter) {
return 0D;
}
}
/**
* Visitor that gets the default using coercion.
*/
private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8<Object, String> {
static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();
private <T extends Number> T parseNumber(String value, Function<String, T> parser,
PrimitiveType primitiveType) {
try {
return parser.apply(value);
}
catch (NumberFormatException ex) {
throw new IllegalArgumentException(
String.format("Invalid %s representation '%s'", primitiveType, value));
}
}
@Override
public Object visitPrimitiveAsBoolean(PrimitiveType type, String value) {
return Boolean.parseBoolean(value);
}
@Override
public Object visitPrimitiveAsByte(PrimitiveType type, String value) {
return parseNumber(value, Byte::parseByte, type);
}
@Override
public Object visitPrimitiveAsShort(PrimitiveType type, String value) {
return parseNumber(value, Short::parseShort, type);
}
@Override
public Object visitPrimitiveAsInt(PrimitiveType type, String value) {
return parseNumber(value, Integer::parseInt, type);
}
@Override
public Object visitPrimitiveAsLong(PrimitiveType type, String value) {
return parseNumber(value, Long::parseLong, type);
}
@Override
public Object visitPrimitiveAsChar(PrimitiveType type, String value) {
if (value.length() > 1) {
throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value));
}
return value;
}
@Override
public Object visitPrimitiveAsFloat(PrimitiveType type, String value) {
return parseNumber(value, Float::parseFloat, type);
}
@Override
public Object visitPrimitiveAsDouble(PrimitiveType type, String value) {
return parseNumber(value, Double::parseDouble, type);
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,11 +16,12 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.util.List;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind; import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
@@ -30,134 +31,107 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
/** /**
* Description of a property that can be candidate for metadata generation. * 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 * @author Stephane Nicoll
* @author Phillip Webb
*/ */
abstract class PropertyDescriptor<S extends Element> { abstract class PropertyDescriptor {
private final TypeElement ownerElement;
private final ExecutableElement factoryMethod;
private final S source;
private final String name; private final String name;
private final TypeMirror type; private final TypeMirror type;
private final VariableElement field; private final TypeElement declaringElement;
private final ExecutableElement getter; private final ExecutableElement getter;
private final ExecutableElement setter; /**
* Create a new {@link PropertyDescriptor} instance.
protected PropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, S source, String name, * @param name the property name
TypeMirror type, VariableElement field, ExecutableElement getter, ExecutableElement setter) { * @param type the property type
this.ownerElement = ownerElement; * @param declaringElement the element that declared the item
this.factoryMethod = factoryMethod; * @param getter the getter for the property or {@code null}
this.source = source; */
PropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter) {
this.declaringElement = declaringElement;
this.name = name; this.name = name;
this.type = type; this.type = type;
this.field = field;
this.getter = getter; this.getter = getter;
this.setter = setter;
}
TypeElement getOwnerElement() {
return this.ownerElement;
}
ExecutableElement getFactoryMethod() {
return this.factoryMethod;
}
S getSource() {
return this.source;
} }
/**
* Return the name of the property.
* @return the property name
*/
String getName() { String getName() {
return this.name; return this.name;
} }
/**
* Return the type of the property.
* @return the property type
*/
TypeMirror getType() { TypeMirror getType() {
return this.type; return this.type;
} }
VariableElement getField() { /**
return this.field; * Return the element that declared the property.
* @return the declaring element
*/
protected final TypeElement getDeclaringElement() {
return this.declaringElement;
} }
ExecutableElement getGetter() { /**
* Return the getter for the property.
* @return the getter or {@code null}
*/
protected final ExecutableElement getGetter() {
return this.getter; return this.getter;
} }
ExecutableElement getSetter() { /**
return this.setter; * Resolve the {@link ItemMetadata} for this property.
} * @param prefix the property prefix
* @param environment the metadata generation environment
protected abstract boolean isProperty(MetadataGenerationEnvironment environment); * @return the item metadata or {@code null}
*/
protected abstract Object resolveDefaultValue(MetadataGenerationEnvironment environment); final ItemMetadata resolveItemMetadata(String prefix, MetadataGenerationEnvironment environment) {
protected ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
boolean deprecated = environment.isDeprecated(getGetter()) || environment.isDeprecated(getSetter())
|| environment.isDeprecated(getField()) || environment.isDeprecated(getFactoryMethod());
return deprecated ? environment.resolveItemDeprecation(getGetter()) : null;
}
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(environment, typeElement, getOwnerElement());
}
ItemMetadata resolveItemMetadata(String prefix, MetadataGenerationEnvironment environment) {
if (isNested(environment)) { if (isNested(environment)) {
return resolveItemMetadataGroup(prefix, environment); return resolveItemMetadataGroup(prefix, environment);
} }
else if (isProperty(environment)) { if (isProperty(environment)) {
return resolveItemMetadataProperty(prefix, environment); return resolveItemMetadataProperty(prefix, environment);
} }
return null; return null;
} }
private ItemMetadata resolveItemMetadataProperty(String prefix, MetadataGenerationEnvironment environment) { /**
String dataType = resolveType(environment); * Return if this is a nested property.
String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); * @param environment the metadata generation environment
String description = resolveDescription(environment); * @return if the property is nested
Object defaultValue = resolveDefaultValue(environment); * @see #isMarkedAsNested(MetadataGenerationEnvironment)
ItemDeprecation deprecation = resolveItemDeprecation(environment); */
return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, description, defaultValue, boolean isNested(MetadataGenerationEnvironment environment) {
deprecation); Element typeElement = environment.getTypeUtils().asElement(getType());
if (!(typeElement instanceof TypeElement) || typeElement.getKind() == ElementKind.ENUM
|| environment.getConfigurationPropertiesAnnotation(getGetter()) != null) {
return false;
}
if (isMarkedAsNested(environment)) {
return true;
}
return !isCyclePresent(typeElement, getDeclaringElement())
&& isParentTheSame(environment, typeElement, getDeclaringElement());
} }
private ItemMetadata resolveItemMetadataGroup(String prefix, MetadataGenerationEnvironment environment) { /**
Element propertyElement = environment.getTypeUtils().asElement(getType()); * Return if this property has been explicitly marked as nested (for example using an
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, getName()); * annotation}.
String dataType = environment.getTypeUtils().getQualifiedName(propertyElement); * @param environment the metadata generation environment
String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement()); * @return if the property has been marked as nested
String sourceMethod = (getGetter() != null) ? getGetter().toString() : null; */
return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod); protected abstract boolean isMarkedAsNested(MetadataGenerationEnvironment environment);
}
private String resolveType(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getType(getOwnerElement(), getType());
}
private String resolveDescription(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getJavaDoc(getField());
}
private boolean isCyclePresent(Element returnType, Element element) { private boolean isCyclePresent(Element returnType, Element element) {
if (!(element.getEnclosingElement() instanceof TypeElement)) { if (!(element.getEnclosingElement() instanceof TypeElement)) {
@@ -192,4 +166,60 @@ abstract class PropertyDescriptor<S extends Element> {
return getTopLevelType(element.getEnclosingElement()); return getTopLevelType(element.getEnclosingElement());
} }
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(getDeclaringElement());
String sourceMethod = (getGetter() != null) ? getGetter().toString() : null;
return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod);
}
private ItemMetadata resolveItemMetadataProperty(String prefix, MetadataGenerationEnvironment environment) {
String dataType = resolveType(environment);
String ownerType = environment.getTypeUtils().getQualifiedName(getDeclaringElement());
String description = resolveDescription(environment);
Object defaultValue = resolveDefaultValue(environment);
ItemDeprecation deprecation = resolveItemDeprecation(environment);
return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, description, defaultValue,
deprecation);
}
private String resolveType(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getType(getDeclaringElement(), getType());
}
private ItemDeprecation resolveItemDeprecation(MetadataGenerationEnvironment environment) {
boolean deprecated = getDeprecatableElements().stream().anyMatch(environment::isDeprecated);
return deprecated ? environment.resolveItemDeprecation(getGetter()) : null;
}
/**
* Resolve the property description.
* @param environment the metadata generation environment
* @return the property description
*/
protected abstract String resolveDescription(MetadataGenerationEnvironment environment);
/**
* Resolve the default value for this property.
* @param environment the metadata generation environment
* @return the default value or {@code null}
*/
protected abstract Object resolveDefaultValue(MetadataGenerationEnvironment environment);
/**
* Return all the elements that should be considered when checking for deprecation
* annotations.
* @return the deprecatable elements
*/
protected abstract List<Element> getDeprecatableElements();
/**
* Return true if this descriptor is for a property.
* @param environment the metadata generation environment
* @return if this is a property
*/
abstract boolean isProperty(MetadataGenerationEnvironment environment);
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind; import javax.lang.model.element.NestingKind;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
@@ -36,6 +37,7 @@ import javax.lang.model.util.ElementFilter;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Pavel Anisimov
*/ */
class PropertyDescriptorResolver { class PropertyDescriptorResolver {
@@ -54,40 +56,48 @@ class PropertyDescriptorResolver {
* or {@code null} * or {@code null}
* @return the candidate properties for metadata generation * @return the candidate properties for metadata generation
*/ */
Stream<PropertyDescriptor<?>> resolve(TypeElement type, ExecutableElement factoryMethod) { Stream<PropertyDescriptor> resolve(TypeElement type, ExecutableElement factoryMethod) {
TypeElementMembers members = new TypeElementMembers(this.environment, type); TypeElementMembers members = new TypeElementMembers(this.environment, type);
if (factoryMethod != null) { if (factoryMethod != null) {
return resolveJavaBeanProperties(type, factoryMethod, members); return resolveJavaBeanProperties(type, members, factoryMethod);
} }
return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), members); return resolve(Bindable.of(type, this.environment), members);
} }
private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type, TypeElementMembers members) { private Stream<PropertyDescriptor> resolve(Bindable bindable, TypeElementMembers members) {
if (type.isConstructorBindingEnabled()) { if (bindable.isConstructorBindingEnabled()) {
ExecutableElement constructor = type.getBindConstructor(); ExecutableElement bindConstructor = bindable.getBindConstructor();
if (constructor != null) { return (bindConstructor != null)
return resolveConstructorProperties(type.getType(), members, constructor); ? resolveConstructorBoundProperties(bindable.getType(), members, bindConstructor) : Stream.empty();
}
return Stream.empty();
} }
return resolveJavaBeanProperties(type.getType(), null, members); return resolveJavaBeanProperties(bindable.getType(), members, null);
} }
Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type, TypeElementMembers members, private Stream<PropertyDescriptor> resolveConstructorBoundProperties(TypeElement declaringElement,
ExecutableElement constructor) { TypeElementMembers members, ExecutableElement bindConstructor) {
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>(); Map<String, PropertyDescriptor> candidates = new LinkedHashMap<>();
constructor.getParameters().forEach((parameter) -> { bindConstructor.getParameters().forEach((parameter) -> {
String name = getParameterName(parameter); PropertyDescriptor descriptor = extracted(declaringElement, members, parameter);
TypeMirror propertyType = parameter.asType(); register(candidates, descriptor);
ExecutableElement getter = members.getPublicGetter(name, propertyType);
ExecutableElement setter = members.getPublicSetter(name, propertyType);
VariableElement field = members.getFields().get(name);
register(candidates, new ConstructorParameterPropertyDescriptor(type, null, parameter, name, propertyType,
field, getter, setter));
}); });
return candidates.values().stream(); return candidates.values().stream();
} }
private PropertyDescriptor extracted(TypeElement declaringElement, TypeElementMembers members,
VariableElement parameter) {
String name = getParameterName(parameter);
TypeMirror type = parameter.asType();
ExecutableElement getter = members.getPublicGetter(name, type);
ExecutableElement setter = members.getPublicSetter(name, type);
VariableElement field = members.getFields().get(name);
RecordComponentElement recordComponent = members.getRecordComponents().get(name);
return (recordComponent != null)
? new RecordParameterPropertyDescriptor(name, type, parameter, declaringElement, getter,
recordComponent)
: new ConstructorParameterPropertyDescriptor(name, type, parameter, declaringElement, getter, setter,
field);
}
private String getParameterName(VariableElement parameter) { private String getParameterName(VariableElement parameter) {
AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter); AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter);
if (nameAnnotation != null) { if (nameAnnotation != null) {
@@ -96,24 +106,24 @@ class PropertyDescriptorResolver {
return parameter.getSimpleName().toString(); return parameter.getSimpleName().toString();
} }
Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type, ExecutableElement factoryMethod, private Stream<PropertyDescriptor> resolveJavaBeanProperties(TypeElement declaringElement,
TypeElementMembers members) { TypeElementMembers members, ExecutableElement factoryMethod) {
// First check if we have regular java bean properties there // First check if we have regular java bean properties there
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>(); Map<String, PropertyDescriptor> candidates = new LinkedHashMap<>();
members.getPublicGetters().forEach((name, getters) -> { members.getPublicGetters().forEach((name, getters) -> {
VariableElement field = members.getFields().get(name); VariableElement field = members.getFields().get(name);
ExecutableElement getter = findMatchingGetter(members, getters, field); ExecutableElement getter = findMatchingGetter(members, getters, field);
TypeMirror propertyType = getter.getReturnType(); TypeMirror propertyType = getter.getReturnType();
register(candidates, new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name, propertyType, field, register(candidates, new JavaBeanPropertyDescriptor(name, propertyType, declaringElement, getter,
members.getPublicSetter(name, propertyType))); members.getPublicSetter(name, propertyType), field, factoryMethod));
}); });
// Then check for Lombok ones // Then check for Lombok ones
members.getFields().forEach((name, field) -> { members.getFields().forEach((name, field) -> {
TypeMirror propertyType = field.asType(); TypeMirror propertyType = field.asType();
ExecutableElement getter = members.getPublicGetter(name, propertyType); ExecutableElement getter = members.getPublicGetter(name, propertyType);
ExecutableElement setter = members.getPublicSetter(name, propertyType); ExecutableElement setter = members.getPublicSetter(name, propertyType);
register(candidates, register(candidates, new LombokPropertyDescriptor(name, propertyType, declaringElement, getter, setter,
new LombokPropertyDescriptor(type, factoryMethod, field, name, propertyType, getter, setter)); field, factoryMethod));
}); });
return candidates.values().stream(); return candidates.values().stream();
} }
@@ -126,20 +136,20 @@ class PropertyDescriptorResolver {
return candidates.get(0); return candidates.get(0);
} }
private void register(Map<String, PropertyDescriptor<?>> candidates, PropertyDescriptor<?> descriptor) { private void register(Map<String, PropertyDescriptor> candidates, PropertyDescriptor descriptor) {
if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) { if (!candidates.containsKey(descriptor.getName()) && isCandidate(descriptor)) {
candidates.put(descriptor.getName(), descriptor); candidates.put(descriptor.getName(), descriptor);
} }
} }
private boolean isCandidate(PropertyDescriptor<?> descriptor) { private boolean isCandidate(PropertyDescriptor descriptor) {
return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment); return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment);
} }
/** /**
* Wrapper around a {@link TypeElement} that could be bound. * Wrapper around a {@link TypeElement} that could be bound.
*/ */
private static class ConfigurationPropertiesTypeElement { private static class Bindable {
private final TypeElement type; private final TypeElement type;
@@ -147,8 +157,7 @@ class PropertyDescriptorResolver {
private final List<ExecutableElement> boundConstructors; private final List<ExecutableElement> boundConstructors;
ConfigurationPropertiesTypeElement(TypeElement type, List<ExecutableElement> constructors, Bindable(TypeElement type, List<ExecutableElement> constructors, List<ExecutableElement> boundConstructors) {
List<ExecutableElement> boundConstructors) {
this.type = type; this.type = type;
this.constructors = constructors; this.constructors = constructors;
this.boundConstructors = boundConstructors; this.boundConstructors = boundConstructors;
@@ -185,10 +194,10 @@ class PropertyDescriptorResolver {
return boundConstructor; return boundConstructor;
} }
static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) { static Bindable of(TypeElement type, MetadataGenerationEnvironment env) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
List<ExecutableElement> boundConstructors = getBoundConstructors(type, env, constructors); List<ExecutableElement> boundConstructors = getBoundConstructors(type, env, constructors);
return new ConfigurationPropertiesTypeElement(type, constructors, boundConstructors); return new Bindable(type, constructors, boundConstructors);
} }
private static List<ExecutableElement> getBoundConstructors(TypeElement type, MetadataGenerationEnvironment env, private static List<ExecutableElement> getBoundConstructors(TypeElement type, MetadataGenerationEnvironment env,

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationprocessor;
import java.util.Arrays;
import java.util.List;
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.TypeMirror;
/**
* A {@link PropertyDescriptor} for a record parameter.
*
* @author Stephane Nicoll
* @author Pavel Anisimov
* @author Phillip Webb
*/
class RecordParameterPropertyDescriptor extends ParameterPropertyDescriptor {
private final RecordComponentElement recordComponent;
RecordParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter,
TypeElement declaringElement, ExecutableElement getter, RecordComponentElement recordComponent) {
super(name, type, parameter, declaringElement, getter);
this.recordComponent = recordComponent;
}
@Override
protected List<Element> getDeprecatableElements() {
return Arrays.asList(getGetter());
}
@Override
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
return false;
}
@Override
protected String resolveDescription(MetadataGenerationEnvironment environment) {
return environment.getTypeUtils().getJavaDoc(this.recordComponent);
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import java.util.function.Function;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeKind;
@@ -39,12 +40,13 @@ import javax.lang.model.util.ElementFilter;
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Pavel Anisimov
*/ */
class TypeElementMembers { class TypeElementMembers {
private static final String OBJECT_CLASS_NAME = Object.class.getName(); private static final String OBJECT_CLASS_NAME = Object.class.getName();
private static final String RECORD_CLASS_NAME = "java.lang.Record"; private static final String RECORD_CLASS_NAME = Record.class.getName();
private final MetadataGenerationEnvironment env; private final MetadataGenerationEnvironment env;
@@ -54,6 +56,8 @@ class TypeElementMembers {
private final Map<String, VariableElement> fields = new LinkedHashMap<>(); 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>> publicGetters = new LinkedHashMap<>();
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>(); private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
@@ -69,6 +73,9 @@ class TypeElementMembers {
for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) { for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
processField(field); processField(field);
} }
for (RecordComponentElement recordComponent : ElementFilter.recordComponentsIn(element.getEnclosedElements())) {
processRecordComponent(recordComponent);
}
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) { for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
processMethod(method); processMethod(method);
} }
@@ -189,10 +196,19 @@ class TypeElementMembers {
this.fields.putIfAbsent(name, field); this.fields.putIfAbsent(name, field);
} }
private void processRecordComponent(RecordComponentElement recordComponent) {
String name = recordComponent.getSimpleName().toString();
this.recordComponents.putIfAbsent(name, recordComponent);
}
Map<String, VariableElement> getFields() { Map<String, VariableElement> getFields() {
return Collections.unmodifiableMap(this.fields); return Collections.unmodifiableMap(this.fields);
} }
Map<String, RecordComponentElement> getRecordComponents() {
return Collections.unmodifiableMap(this.recordComponents);
}
Map<String, List<ExecutableElement>> getPublicGetters() { Map<String, List<ExecutableElement>> getPublicGetters() {
return Collections.unmodifiableMap(this.publicGetters); return Collections.unmodifiableMap(this.publicGetters);
} }

View File

@@ -24,11 +24,13 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType; import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@@ -44,6 +46,7 @@ import javax.lang.model.util.Types;
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Pavel Anisimov
*/ */
class TypeUtils { class TypeUtils {
@@ -176,6 +179,9 @@ class TypeUtils {
} }
String getJavaDoc(Element element) { String getJavaDoc(Element element) {
if (element instanceof RecordComponentElement) {
return getJavaDoc((RecordComponentElement) element);
}
String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null; String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null;
if (javadoc != null) { if (javadoc != null) {
javadoc = NEW_LINE_PATTERN.matcher(javadoc).replaceAll("").trim(); 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 * A visitor that extracts the fully qualified name of a type, including generic
* information. * information.

View File

@@ -22,6 +22,7 @@ import org.springframework.boot.configurationprocessor.metadata.ConfigurationMet
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.metadata.Metadata; import org.springframework.boot.configurationprocessor.metadata.Metadata;
import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration; import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration;
import org.springframework.boot.configurationsample.record.ExampleRecord;
import org.springframework.boot.configurationsample.record.RecordWithGetter; import org.springframework.boot.configurationsample.record.RecordWithGetter;
import org.springframework.boot.configurationsample.recursive.RecursiveProperties; import org.springframework.boot.configurationsample.recursive.RecursiveProperties;
import org.springframework.boot.configurationsample.simple.ClassWithNestedProperties; import org.springframework.boot.configurationsample.simple.ClassWithNestedProperties;
@@ -516,4 +517,19 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
assertThat(metadata).has(Metadata.withProperty("spring.datasource.dbcp2.password").withNoDeprecation()); assertThat(metadata).has(Metadata.withProperty("spring.datasource.dbcp2.password").withNoDeprecation());
} }
@Test
void recordPropertiesWithDescriptions() {
ConfigurationMetadata metadata = compile(ExampleRecord.class);
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"));
assertThat(metadata).has(Metadata.withProperty("record.descriptions.some-byte", Byte.class)
.withDescription("last description in Javadoc"));
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class);
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "theName"); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "theName");
assertThat(property.getName()).isEqualTo("theName"); assertThat(property.getName()).isEqualTo("theName");
assertThat(property.getSource()).hasToString("theName"); assertThat(property.getParameter()).hasToString("theName");
assertThat(property.getGetter().getSimpleName()).hasToString("getTheName"); assertThat(property.getGetter().getSimpleName()).hasToString("getTheName");
assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isProperty(metadataEnv)).isTrue();
assertThat(property.isNested(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isFalse();
@@ -61,7 +61,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class); TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class);
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first"); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first");
assertThat(property.getName()).isEqualTo("first"); assertThat(property.getName()).isEqualTo("first");
assertThat(property.getSource()).hasToString("first"); assertThat(property.getParameter()).hasToString("first");
assertThat(property.getGetter().getSimpleName()).hasToString("getFirst"); assertThat(property.getGetter().getSimpleName()).hasToString("getFirst");
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
assertThat(property.isNested(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isTrue();
@@ -74,7 +74,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class); TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class);
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third"); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third");
assertThat(property.getName()).isEqualTo("third"); assertThat(property.getName()).isEqualTo("third");
assertThat(property.getSource()).hasToString("third"); assertThat(property.getParameter()).hasToString("third");
assertThat(property.getGetter().getSimpleName()).hasToString("getThird"); assertThat(property.getGetter().getSimpleName()).hasToString("getThird");
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
assertThat(property.isNested(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isTrue();
@@ -87,7 +87,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class);
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "counter"); ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "counter");
assertThat(property.getName()).isEqualTo("counter"); assertThat(property.getName()).isEqualTo("counter");
assertThat(property.getSource()).hasToString("counter"); assertThat(property.getParameter()).hasToString("counter");
assertThat(property.getGetter()).isNull(); assertThat(property.getGetter()).isNull();
assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isProperty(metadataEnv)).isTrue();
assertThat(property.isNested(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isFalse();
@@ -130,8 +130,8 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
ExecutableElement getter = getMethod(ownerElement, "isFlag"); ExecutableElement getter = getMethod(ownerElement, "isFlag");
VariableElement field = getField(ownerElement, "flag"); VariableElement field = getField(ownerElement, "flag");
VariableElement constructorParameter = getConstructorParameter(ownerElement, "flag"); VariableElement constructorParameter = getConstructorParameter(ownerElement, "flag");
ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor(ownerElement, ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor("flag",
null, constructorParameter, "flag", field.asType(), field, getter, null); field.asType(), constructorParameter, ownerElement, getter, null, field);
assertItemMetadata(metadataEnv, property).isProperty().isDeprecatedWithNoInformation(); assertItemMetadata(metadataEnv, property).isProperty().isDeprecatedWithNoInformation();
}); });
} }
@@ -222,8 +222,8 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
VariableElement field = getField(ownerElement, name); VariableElement field = getField(ownerElement, name);
ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name)); ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name));
ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name)); ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name));
return new ConstructorParameterPropertyDescriptor(ownerElement, null, constructorParameter, name, return new ConstructorParameterPropertyDescriptor(name, field.asType(), constructorParameter, ownerElement,
field.asType(), field, getter, setter); getter, setter, field);
} }
private VariableElement getConstructorParameter(TypeElement ownerElement, String name) { private VariableElement getConstructorParameter(TypeElement ownerElement, String name) {

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -43,7 +43,6 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(SimpleTypeProperties.class); TypeElement ownerElement = roundEnv.getRootElement(SimpleTypeProperties.class);
JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, "myString"); JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, "myString");
assertThat(property.getName()).isEqualTo("myString"); assertThat(property.getName()).isEqualTo("myString");
assertThat(property.getSource()).isSameAs(property.getGetter());
assertThat(property.getGetter().getSimpleName()).hasToString("getMyString"); assertThat(property.getGetter().getSimpleName()).hasToString("getMyString");
assertThat(property.getSetter().getSimpleName()).hasToString("setMyString"); assertThat(property.getSetter().getSimpleName()).hasToString("setMyString");
assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isProperty(metadataEnv)).isTrue();
@@ -96,10 +95,9 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
ExecutableElement getter = getMethod(ownerElement, "getSize"); ExecutableElement getter = getMethod(ownerElement, "getSize");
VariableElement field = getField(ownerElement, "size"); VariableElement field = getField(ownerElement, "size");
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, getter, getter, "size", JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("size", field.asType(), ownerElement,
field.asType(), field, null); getter, null, field, getter);
assertThat(property.getName()).isEqualTo("size"); assertThat(property.getName()).isEqualTo("size");
assertThat(property.getSource()).isSameAs(property.getGetter());
assertThat(property.getGetter().getSimpleName()).hasToString("getSize"); assertThat(property.getGetter().getSimpleName()).hasToString("getSize");
assertThat(property.getSetter()).isNull(); assertThat(property.getSetter()).isNull();
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
@@ -112,10 +110,9 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
process(SimpleProperties.class, (roundEnv, metadataEnv) -> { process(SimpleProperties.class, (roundEnv, metadataEnv) -> {
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
VariableElement field = getField(ownerElement, "counter"); VariableElement field = getField(ownerElement, "counter");
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter", JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(),
field.asType(), field, getMethod(ownerElement, "setCounter")); ownerElement, null, getMethod(ownerElement, "setCounter"), field, null);
assertThat(property.getName()).isEqualTo("counter"); assertThat(property.getName()).isEqualTo("counter");
assertThat(property.getSource()).isSameAs(property.getGetter());
assertThat(property.getGetter()).isNull(); assertThat(property.getGetter()).isNull();
assertThat(property.getSetter().getSimpleName()).hasToString("setCounter"); assertThat(property.getSetter().getSimpleName()).hasToString("setCounter");
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
@@ -171,8 +168,8 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
process(SimpleProperties.class, (roundEnv, metadataEnv) -> { process(SimpleProperties.class, (roundEnv, metadataEnv) -> {
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
VariableElement field = getField(ownerElement, "counter"); VariableElement field = getField(ownerElement, "counter");
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter", JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(),
field.asType(), field, getMethod(ownerElement, "setCounter")); ownerElement, null, getMethod(ownerElement, "setCounter"), field, null);
assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull(); assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull();
}); });
} }
@@ -247,7 +244,7 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
ExecutableElement getter = getMethod(ownerElement, getterName); ExecutableElement getter = getMethod(ownerElement, getterName);
ExecutableElement setter = getMethod(ownerElement, setterName); ExecutableElement setter = getMethod(ownerElement, setterName);
VariableElement field = getField(ownerElement, name); VariableElement field = getField(ownerElement, name);
return new JavaBeanPropertyDescriptor(ownerElement, null, getter, name, getter.getReturnType(), field, setter); return new JavaBeanPropertyDescriptor(name, getter.getReturnType(), ownerElement, getter, setter, field, null);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -47,7 +47,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class);
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "name"); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "name");
assertThat(property.getName()).isEqualTo("name"); assertThat(property.getName()).isEqualTo("name");
assertThat(property.getSource()).isSameAs(property.getField());
assertThat(property.getField().getSimpleName()).hasToString("name"); assertThat(property.getField().getSimpleName()).hasToString("name");
assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isProperty(metadataEnv)).isTrue();
assertThat(property.isNested(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isFalse();
@@ -60,7 +59,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class); TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class);
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "items"); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "items");
assertThat(property.getName()).isEqualTo("items"); assertThat(property.getName()).isEqualTo("items");
assertThat(property.getSource()).isSameAs(property.getField());
assertThat(property.getField().getSimpleName()).hasToString("items"); assertThat(property.getField().getSimpleName()).hasToString("items");
assertThat(property.isProperty(metadataEnv)).isTrue(); assertThat(property.isProperty(metadataEnv)).isTrue();
assertThat(property.isNested(metadataEnv)).isFalse(); assertThat(property.isNested(metadataEnv)).isFalse();
@@ -73,7 +71,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first"); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first");
assertThat(property.getName()).isEqualTo("first"); assertThat(property.getName()).isEqualTo("first");
assertThat(property.getSource()).isSameAs(property.getField());
assertThat(property.getField().getSimpleName()).hasToString("first"); assertThat(property.getField().getSimpleName()).hasToString("first");
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
assertThat(property.isNested(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isTrue();
@@ -86,7 +83,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third"); LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third");
assertThat(property.getName()).isEqualTo("third"); assertThat(property.getName()).isEqualTo("third");
assertThat(property.getSource()).isSameAs(property.getField());
assertThat(property.getField().getSimpleName()).hasToString("third"); assertThat(property.getField().getSimpleName()).hasToString("third");
assertThat(property.isProperty(metadataEnv)).isFalse(); assertThat(property.isProperty(metadataEnv)).isFalse();
assertThat(property.isNested(metadataEnv)).isTrue(); assertThat(property.isNested(metadataEnv)).isTrue();
@@ -177,8 +173,8 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class); TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
VariableElement field = getField(ownerElement, "third"); VariableElement field = getField(ownerElement, "third");
ExecutableElement getter = getMethod(ownerElement, "getThird"); ExecutableElement getter = getMethod(ownerElement, "getThird");
LombokPropertyDescriptor property = new LombokPropertyDescriptor(ownerElement, null, field, "third", LombokPropertyDescriptor property = new LombokPropertyDescriptor("third", field.asType(), ownerElement,
field.asType(), getter, null); getter, null, field, null);
assertItemMetadata(metadataEnv, property).isGroup() assertItemMetadata(metadataEnv, property).isGroup()
.hasName("test.third") .hasName("test.third")
.hasType("org.springframework.boot.configurationsample.lombok.SimpleLombokPojo") .hasType("org.springframework.boot.configurationsample.lombok.SimpleLombokPojo")
@@ -276,7 +272,7 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
VariableElement field = getField(ownerElement, name); VariableElement field = getField(ownerElement, name);
ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name)); ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", name));
ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name)); ExecutableElement setter = getMethod(ownerElement, createAccessorMethodName("set", name));
return new LombokPropertyDescriptor(ownerElement, null, field, name, field.asType(), getter, setter); return new LombokPropertyDescriptor(name, field.asType(), ownerElement, getter, setter, field, null);
} }
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -161,7 +161,7 @@ class PropertyDescriptorResolverTests {
} }
private BiConsumer<TypeElement, MetadataGenerationEnvironment> properties( private BiConsumer<TypeElement, MetadataGenerationEnvironment> properties(
Consumer<Stream<PropertyDescriptor<?>>> stream) { Consumer<Stream<PropertyDescriptor>> stream) {
return (element, metadataEnv) -> { return (element, metadataEnv) -> {
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv); PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
stream.accept(resolver.resolve(element, null)); stream.accept(resolver.resolve(element, null));

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ public abstract class PropertyDescriptorTests {
} }
protected ItemMetadataAssert assertItemMetadata(MetadataGenerationEnvironment metadataEnv, protected ItemMetadataAssert assertItemMetadata(MetadataGenerationEnvironment metadataEnv,
PropertyDescriptor<?> property) { PropertyDescriptor property) {
return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv)); return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv));
} }

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.configurationsample.record;
/**
* Example Record Javadoc sample
*
* @param someString very long description that doesn't fit single line
* @param someInteger description with @param and @ pitfalls
* @param someBoolean description with extra spaces
* @param someLong description without space after asterisk
* @param someByte last description in Javadoc
* @since 1.0.0
* @author Pavel Anisimov
*/
@org.springframework.boot.configurationsample.ConfigurationProperties("record.descriptions")
public record ExampleRecord(String someString, Integer someInteger, Boolean someBoolean, Long someLong, Byte someByte) {
}