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:
@@ -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.
|
||||
|
||||
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".
|
||||
|
||||
@@ -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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -16,195 +16,47 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a constructor parameter.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor {
|
||||
|
||||
ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod,
|
||||
VariableElement source, String name, TypeMirror type, VariableElement field, ExecutableElement getter,
|
||||
ExecutableElement setter) {
|
||||
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
|
||||
private final ExecutableElement setter;
|
||||
|
||||
private final VariableElement field;
|
||||
|
||||
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
|
||||
protected boolean isProperty(MetadataGenerationEnvironment env) {
|
||||
// If it's a constructor parameter, it doesn't matter as we must be able to bind
|
||||
// it to build the object.
|
||||
return !isNested(env);
|
||||
protected List<Element> getDeprecatableElements() {
|
||||
return Arrays.asList(getGetter(), this.setter, this.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource());
|
||||
if (defaultValue != null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
|
||||
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
|
||||
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
|
||||
}
|
||||
|
||||
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) {
|
||||
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element);
|
||||
List<String> defaultValue = getDefaultValue(environment, annotation);
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getJavaDoc(this.field);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
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.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
@@ -25,23 +29,54 @@ import javax.lang.model.type.TypeMirror;
|
||||
* A {@link PropertyDescriptor} for a standard JavaBean property.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> {
|
||||
class JavaBeanPropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, ExecutableElement getter,
|
||||
String name, TypeMirror type, VariableElement field, ExecutableElement setter) {
|
||||
super(ownerElement, factoryMethod, getter, name, type, field, getter, setter);
|
||||
private final ExecutableElement 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
|
||||
protected boolean isProperty(MetadataGenerationEnvironment env) {
|
||||
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
|
||||
return !env.isExcluded(getType()) && getGetter() != null && (getSetter() != null || isCollection);
|
||||
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
|
||||
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getJavaDoc(this.field);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,23 +16,25 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||
|
||||
/**
|
||||
* A {@link PropertyDescriptor} for a Lombok field.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
class LombokPropertyDescriptor extends PropertyDescriptor {
|
||||
|
||||
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";
|
||||
|
||||
LombokPropertyDescriptor(TypeElement typeElement, ExecutableElement factoryMethod, VariableElement field,
|
||||
String name, TypeMirror type, ExecutableElement getter, ExecutableElement setter) {
|
||||
super(typeElement, factoryMethod, field, name, type, field, getter, setter);
|
||||
private final ExecutableElement 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
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
@@ -59,29 +94,14 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
|
||||
return environment.getFieldDefaultValue(getOwnerElement(), getName());
|
||||
}
|
||||
|
||||
@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;
|
||||
public boolean isNested(MetadataGenerationEnvironment environment) {
|
||||
return hasLombokPublicAccessor(environment, true) && super.isNested(environment);
|
||||
}
|
||||
|
||||
private boolean hasSetter(MetadataGenerationEnvironment env) {
|
||||
boolean nonFinalPublicField = !getField().getModifiers().contains(Modifier.FINAL)
|
||||
&& hasLombokPublicAccessor(env, false);
|
||||
return getSetter() != null || nonFinalPublicField;
|
||||
return this.setter != null || nonFinalPublicField;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,12 +118,12 @@ class LombokPropertyDescriptor extends PropertyDescriptor<VariableElement> {
|
||||
if (lombokMethodAnnotationOnField != null) {
|
||||
return isAccessLevelPublic(env, lombokMethodAnnotationOnField);
|
||||
}
|
||||
AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getOwnerElement(), annotation);
|
||||
AnnotationMirror lombokMethodAnnotationOnElement = env.getAnnotation(getDeclaringElement(), annotation);
|
||||
if (lombokMethodAnnotationOnElement != null) {
|
||||
return isAccessLevelPublic(env, lombokMethodAnnotationOnElement);
|
||||
}
|
||||
return (env.hasAnnotation(getOwnerElement(), LOMBOK_DATA_ANNOTATION)
|
||||
|| env.hasAnnotation(getOwnerElement(), LOMBOK_VALUE_ANNOTATION));
|
||||
return (env.hasAnnotation(getDeclaringElement(), LOMBOK_DATA_ANNOTATION)
|
||||
|| env.hasAnnotation(getDeclaringElement(), LOMBOK_VALUE_ANNOTATION));
|
||||
}
|
||||
|
||||
private boolean isAccessLevelPublic(MetadataGenerationEnvironment env, AnnotationMirror lombokAnnotation) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
package org.springframework.boot.configurationprocessor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
|
||||
@@ -30,134 +31,107 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
/**
|
||||
* Description of a property that can be candidate for metadata generation.
|
||||
*
|
||||
* @param <S> the type of the source element that determines the property
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
abstract class PropertyDescriptor<S extends Element> {
|
||||
|
||||
private final TypeElement ownerElement;
|
||||
|
||||
private final ExecutableElement factoryMethod;
|
||||
|
||||
private final S source;
|
||||
abstract class PropertyDescriptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final TypeMirror type;
|
||||
|
||||
private final VariableElement field;
|
||||
private final TypeElement declaringElement;
|
||||
|
||||
private final ExecutableElement getter;
|
||||
|
||||
private final ExecutableElement setter;
|
||||
|
||||
protected PropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, S source, String name,
|
||||
TypeMirror type, VariableElement field, ExecutableElement getter, ExecutableElement setter) {
|
||||
this.ownerElement = ownerElement;
|
||||
this.factoryMethod = factoryMethod;
|
||||
this.source = source;
|
||||
/**
|
||||
* Create a new {@link PropertyDescriptor} instance.
|
||||
* @param name the property name
|
||||
* @param type the property type
|
||||
* @param declaringElement the element that declared the item
|
||||
* @param getter the getter for the property or {@code null}
|
||||
*/
|
||||
PropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter) {
|
||||
this.declaringElement = declaringElement;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.field = field;
|
||||
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() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the property.
|
||||
* @return the property type
|
||||
*/
|
||||
TypeMirror getType() {
|
||||
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;
|
||||
}
|
||||
|
||||
ExecutableElement getSetter() {
|
||||
return this.setter;
|
||||
}
|
||||
|
||||
protected abstract boolean isProperty(MetadataGenerationEnvironment environment);
|
||||
|
||||
protected abstract Object resolveDefaultValue(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) {
|
||||
/**
|
||||
* Resolve the {@link ItemMetadata} for this property.
|
||||
* @param prefix the property prefix
|
||||
* @param environment the metadata generation environment
|
||||
* @return the item metadata or {@code null}
|
||||
*/
|
||||
final ItemMetadata resolveItemMetadata(String prefix, MetadataGenerationEnvironment environment) {
|
||||
if (isNested(environment)) {
|
||||
return resolveItemMetadataGroup(prefix, environment);
|
||||
}
|
||||
else if (isProperty(environment)) {
|
||||
if (isProperty(environment)) {
|
||||
return resolveItemMetadataProperty(prefix, environment);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ItemMetadata resolveItemMetadataProperty(String prefix, MetadataGenerationEnvironment environment) {
|
||||
String dataType = resolveType(environment);
|
||||
String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement());
|
||||
String description = resolveDescription(environment);
|
||||
Object defaultValue = resolveDefaultValue(environment);
|
||||
ItemDeprecation deprecation = resolveItemDeprecation(environment);
|
||||
return ItemMetadata.newProperty(prefix, getName(), dataType, ownerType, null, description, defaultValue,
|
||||
deprecation);
|
||||
/**
|
||||
* Return if this is a nested property.
|
||||
* @param environment the metadata generation environment
|
||||
* @return if the property is nested
|
||||
* @see #isMarkedAsNested(MetadataGenerationEnvironment)
|
||||
*/
|
||||
boolean isNested(MetadataGenerationEnvironment environment) {
|
||||
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());
|
||||
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, getName());
|
||||
String dataType = environment.getTypeUtils().getQualifiedName(propertyElement);
|
||||
String ownerType = environment.getTypeUtils().getQualifiedName(getOwnerElement());
|
||||
String sourceMethod = (getGetter() != null) ? getGetter().toString() : null;
|
||||
return ItemMetadata.newGroup(nestedPrefix, dataType, ownerType, sourceMethod);
|
||||
}
|
||||
|
||||
private String resolveType(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getType(getOwnerElement(), getType());
|
||||
}
|
||||
|
||||
private String resolveDescription(MetadataGenerationEnvironment environment) {
|
||||
return environment.getTypeUtils().getJavaDoc(getField());
|
||||
}
|
||||
/**
|
||||
* Return if this property has been explicitly marked as nested (for example using an
|
||||
* annotation}.
|
||||
* @param environment the metadata generation environment
|
||||
* @return if the property has been marked as nested
|
||||
*/
|
||||
protected abstract boolean isMarkedAsNested(MetadataGenerationEnvironment environment);
|
||||
|
||||
private boolean isCyclePresent(Element returnType, Element element) {
|
||||
if (!(element.getEnclosingElement() instanceof TypeElement)) {
|
||||
@@ -192,4 +166,60 @@ abstract class PropertyDescriptor<S extends Element> {
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* 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.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
@@ -36,6 +37,7 @@ import javax.lang.model.util.ElementFilter;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class PropertyDescriptorResolver {
|
||||
|
||||
@@ -54,40 +56,48 @@ class PropertyDescriptorResolver {
|
||||
* or {@code null}
|
||||
* @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);
|
||||
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) {
|
||||
if (type.isConstructorBindingEnabled()) {
|
||||
ExecutableElement constructor = type.getBindConstructor();
|
||||
if (constructor != null) {
|
||||
return resolveConstructorProperties(type.getType(), members, constructor);
|
||||
}
|
||||
return Stream.empty();
|
||||
private Stream<PropertyDescriptor> resolve(Bindable bindable, TypeElementMembers members) {
|
||||
if (bindable.isConstructorBindingEnabled()) {
|
||||
ExecutableElement bindConstructor = bindable.getBindConstructor();
|
||||
return (bindConstructor != null)
|
||||
? resolveConstructorBoundProperties(bindable.getType(), members, bindConstructor) : Stream.empty();
|
||||
}
|
||||
return resolveJavaBeanProperties(type.getType(), null, members);
|
||||
return resolveJavaBeanProperties(bindable.getType(), members, null);
|
||||
}
|
||||
|
||||
Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type, TypeElementMembers members,
|
||||
ExecutableElement constructor) {
|
||||
Map<String, PropertyDescriptor<?>> candidates = new LinkedHashMap<>();
|
||||
constructor.getParameters().forEach((parameter) -> {
|
||||
String name = getParameterName(parameter);
|
||||
TypeMirror propertyType = parameter.asType();
|
||||
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));
|
||||
private Stream<PropertyDescriptor> resolveConstructorBoundProperties(TypeElement declaringElement,
|
||||
TypeElementMembers members, ExecutableElement bindConstructor) {
|
||||
Map<String, PropertyDescriptor> candidates = new LinkedHashMap<>();
|
||||
bindConstructor.getParameters().forEach((parameter) -> {
|
||||
PropertyDescriptor descriptor = extracted(declaringElement, members, parameter);
|
||||
register(candidates, descriptor);
|
||||
});
|
||||
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) {
|
||||
AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter);
|
||||
if (nameAnnotation != null) {
|
||||
@@ -96,24 +106,24 @@ class PropertyDescriptorResolver {
|
||||
return parameter.getSimpleName().toString();
|
||||
}
|
||||
|
||||
Stream<PropertyDescriptor<?>> resolveJavaBeanProperties(TypeElement type, ExecutableElement factoryMethod,
|
||||
TypeElementMembers members) {
|
||||
private Stream<PropertyDescriptor> resolveJavaBeanProperties(TypeElement declaringElement,
|
||||
TypeElementMembers members, ExecutableElement factoryMethod) {
|
||||
// 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) -> {
|
||||
VariableElement field = members.getFields().get(name);
|
||||
ExecutableElement getter = findMatchingGetter(members, getters, field);
|
||||
TypeMirror propertyType = getter.getReturnType();
|
||||
register(candidates, new JavaBeanPropertyDescriptor(type, factoryMethod, getter, name, propertyType, field,
|
||||
members.getPublicSetter(name, propertyType)));
|
||||
register(candidates, new JavaBeanPropertyDescriptor(name, propertyType, declaringElement, getter,
|
||||
members.getPublicSetter(name, propertyType), field, factoryMethod));
|
||||
});
|
||||
// Then check for Lombok ones
|
||||
members.getFields().forEach((name, field) -> {
|
||||
TypeMirror propertyType = field.asType();
|
||||
ExecutableElement getter = members.getPublicGetter(name, propertyType);
|
||||
ExecutableElement setter = members.getPublicSetter(name, propertyType);
|
||||
register(candidates,
|
||||
new LombokPropertyDescriptor(type, factoryMethod, field, name, propertyType, getter, setter));
|
||||
register(candidates, new LombokPropertyDescriptor(name, propertyType, declaringElement, getter, setter,
|
||||
field, factoryMethod));
|
||||
});
|
||||
return candidates.values().stream();
|
||||
}
|
||||
@@ -126,20 +136,20 @@ class PropertyDescriptorResolver {
|
||||
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)) {
|
||||
candidates.put(descriptor.getName(), descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCandidate(PropertyDescriptor<?> descriptor) {
|
||||
private boolean isCandidate(PropertyDescriptor descriptor) {
|
||||
return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around a {@link TypeElement} that could be bound.
|
||||
*/
|
||||
private static class ConfigurationPropertiesTypeElement {
|
||||
private static class Bindable {
|
||||
|
||||
private final TypeElement type;
|
||||
|
||||
@@ -147,8 +157,7 @@ class PropertyDescriptorResolver {
|
||||
|
||||
private final List<ExecutableElement> boundConstructors;
|
||||
|
||||
ConfigurationPropertiesTypeElement(TypeElement type, List<ExecutableElement> constructors,
|
||||
List<ExecutableElement> boundConstructors) {
|
||||
Bindable(TypeElement type, List<ExecutableElement> constructors, List<ExecutableElement> boundConstructors) {
|
||||
this.type = type;
|
||||
this.constructors = constructors;
|
||||
this.boundConstructors = boundConstructors;
|
||||
@@ -185,10 +194,10 @@ class PropertyDescriptorResolver {
|
||||
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> 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
* 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.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
@@ -39,12 +40,13 @@ import javax.lang.model.util.ElementFilter;
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Moritz Halbritter
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class TypeElementMembers {
|
||||
|
||||
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;
|
||||
|
||||
@@ -54,6 +56,8 @@ class TypeElementMembers {
|
||||
|
||||
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, RecordComponentElement> recordComponents = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, List<ExecutableElement>> publicGetters = new LinkedHashMap<>();
|
||||
|
||||
private final Map<String, List<ExecutableElement>> publicSetters = new LinkedHashMap<>();
|
||||
@@ -69,6 +73,9 @@ class TypeElementMembers {
|
||||
for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
|
||||
processField(field);
|
||||
}
|
||||
for (RecordComponentElement recordComponent : ElementFilter.recordComponentsIn(element.getEnclosedElements())) {
|
||||
processRecordComponent(recordComponent);
|
||||
}
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
|
||||
processMethod(method);
|
||||
}
|
||||
@@ -189,10 +196,19 @@ class TypeElementMembers {
|
||||
this.fields.putIfAbsent(name, field);
|
||||
}
|
||||
|
||||
private void processRecordComponent(RecordComponentElement recordComponent) {
|
||||
String name = recordComponent.getSimpleName().toString();
|
||||
this.recordComponents.putIfAbsent(name, recordComponent);
|
||||
}
|
||||
|
||||
Map<String, VariableElement> getFields() {
|
||||
return Collections.unmodifiableMap(this.fields);
|
||||
}
|
||||
|
||||
Map<String, RecordComponentElement> getRecordComponents() {
|
||||
return Collections.unmodifiableMap(this.recordComponents);
|
||||
}
|
||||
|
||||
Map<String, List<ExecutableElement>> getPublicGetters() {
|
||||
return Collections.unmodifiableMap(this.publicGetters);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
@@ -44,6 +46,7 @@ import javax.lang.model.util.Types;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Phillip Webb
|
||||
* @author Pavel Anisimov
|
||||
*/
|
||||
class TypeUtils {
|
||||
|
||||
@@ -176,6 +179,9 @@ class TypeUtils {
|
||||
}
|
||||
|
||||
String getJavaDoc(Element element) {
|
||||
if (element instanceof RecordComponentElement) {
|
||||
return getJavaDoc((RecordComponentElement) element);
|
||||
}
|
||||
String javadoc = (element != null) ? this.env.getElementUtils().getDocComment(element) : null;
|
||||
if (javadoc != null) {
|
||||
javadoc = NEW_LINE_PATTERN.matcher(javadoc).replaceAll("").trim();
|
||||
@@ -247,6 +253,24 @@ class TypeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private String getJavaDoc(RecordComponentElement recordComponent) {
|
||||
String recordJavadoc = this.env.getElementUtils().getDocComment(recordComponent.getEnclosingElement());
|
||||
if (recordJavadoc != null) {
|
||||
Pattern paramJavadocPattern = paramJavadocPattern(recordComponent.getSimpleName().toString());
|
||||
Matcher paramJavadocMatcher = paramJavadocPattern.matcher(recordJavadoc);
|
||||
if (paramJavadocMatcher.find()) {
|
||||
String paramJavadoc = NEW_LINE_PATTERN.matcher(paramJavadocMatcher.group()).replaceAll("").trim();
|
||||
return paramJavadoc.isEmpty() ? null : paramJavadoc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Pattern paramJavadocPattern(String paramName) {
|
||||
String pattern = String.format("(?<=@param +%s).*?(?=([\r\n]+ *@)|$)", paramName);
|
||||
return Pattern.compile(pattern, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* A visitor that extracts the fully qualified name of a type, including generic
|
||||
* information.
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.boot.configurationprocessor.metadata.ConfigurationMet
|
||||
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
|
||||
import org.springframework.boot.configurationprocessor.metadata.Metadata;
|
||||
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.recursive.RecursiveProperties;
|
||||
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());
|
||||
}
|
||||
|
||||
@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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* 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);
|
||||
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "theName");
|
||||
assertThat(property.getName()).isEqualTo("theName");
|
||||
assertThat(property.getSource()).hasToString("theName");
|
||||
assertThat(property.getParameter()).hasToString("theName");
|
||||
assertThat(property.getGetter().getSimpleName()).hasToString("getTheName");
|
||||
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||
@@ -61,7 +61,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
|
||||
TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class);
|
||||
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first");
|
||||
assertThat(property.getName()).isEqualTo("first");
|
||||
assertThat(property.getSource()).hasToString("first");
|
||||
assertThat(property.getParameter()).hasToString("first");
|
||||
assertThat(property.getGetter().getSimpleName()).hasToString("getFirst");
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||
@@ -74,7 +74,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
|
||||
TypeElement ownerElement = roundEnv.getRootElement(ImmutableInnerClassProperties.class);
|
||||
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third");
|
||||
assertThat(property.getName()).isEqualTo("third");
|
||||
assertThat(property.getSource()).hasToString("third");
|
||||
assertThat(property.getParameter()).hasToString("third");
|
||||
assertThat(property.getGetter().getSimpleName()).hasToString("getThird");
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||
@@ -87,7 +87,7 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
|
||||
TypeElement ownerElement = roundEnv.getRootElement(ImmutableSimpleProperties.class);
|
||||
ConstructorParameterPropertyDescriptor property = createPropertyDescriptor(ownerElement, "counter");
|
||||
assertThat(property.getName()).isEqualTo("counter");
|
||||
assertThat(property.getSource()).hasToString("counter");
|
||||
assertThat(property.getParameter()).hasToString("counter");
|
||||
assertThat(property.getGetter()).isNull();
|
||||
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||
@@ -130,8 +130,8 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
|
||||
ExecutableElement getter = getMethod(ownerElement, "isFlag");
|
||||
VariableElement field = getField(ownerElement, "flag");
|
||||
VariableElement constructorParameter = getConstructorParameter(ownerElement, "flag");
|
||||
ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor(ownerElement,
|
||||
null, constructorParameter, "flag", field.asType(), field, getter, null);
|
||||
ConstructorParameterPropertyDescriptor property = new ConstructorParameterPropertyDescriptor("flag",
|
||||
field.asType(), constructorParameter, ownerElement, getter, null, field);
|
||||
assertItemMetadata(metadataEnv, property).isProperty().isDeprecatedWithNoInformation();
|
||||
});
|
||||
}
|
||||
@@ -222,8 +222,8 @@ class ConstructorParameterPropertyDescriptorTests extends PropertyDescriptorTest
|
||||
VariableElement field = getField(ownerElement, name);
|
||||
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);
|
||||
return new ConstructorParameterPropertyDescriptor(name, field.asType(), constructorParameter, ownerElement,
|
||||
getter, setter, field);
|
||||
}
|
||||
|
||||
private VariableElement getConstructorParameter(TypeElement ownerElement, String name) {
|
||||
|
||||
@@ -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");
|
||||
* 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);
|
||||
JavaBeanPropertyDescriptor property = createPropertyDescriptor(ownerElement, "myString");
|
||||
assertThat(property.getName()).isEqualTo("myString");
|
||||
assertThat(property.getSource()).isSameAs(property.getGetter());
|
||||
assertThat(property.getGetter().getSimpleName()).hasToString("getMyString");
|
||||
assertThat(property.getSetter().getSimpleName()).hasToString("setMyString");
|
||||
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||
@@ -96,10 +95,9 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
|
||||
ExecutableElement getter = getMethod(ownerElement, "getSize");
|
||||
VariableElement field = getField(ownerElement, "size");
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, getter, getter, "size",
|
||||
field.asType(), field, null);
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("size", field.asType(), ownerElement,
|
||||
getter, null, field, getter);
|
||||
assertThat(property.getName()).isEqualTo("size");
|
||||
assertThat(property.getSource()).isSameAs(property.getGetter());
|
||||
assertThat(property.getGetter().getSimpleName()).hasToString("getSize");
|
||||
assertThat(property.getSetter()).isNull();
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
@@ -112,10 +110,9 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
process(SimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
|
||||
VariableElement field = getField(ownerElement, "counter");
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter",
|
||||
field.asType(), field, getMethod(ownerElement, "setCounter"));
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(),
|
||||
ownerElement, null, getMethod(ownerElement, "setCounter"), field, null);
|
||||
assertThat(property.getName()).isEqualTo("counter");
|
||||
assertThat(property.getSource()).isSameAs(property.getGetter());
|
||||
assertThat(property.getGetter()).isNull();
|
||||
assertThat(property.getSetter().getSimpleName()).hasToString("setCounter");
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
@@ -171,8 +168,8 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
process(SimpleProperties.class, (roundEnv, metadataEnv) -> {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(SimpleProperties.class);
|
||||
VariableElement field = getField(ownerElement, "counter");
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor(ownerElement, null, null, "counter",
|
||||
field.asType(), field, getMethod(ownerElement, "setCounter"));
|
||||
JavaBeanPropertyDescriptor property = new JavaBeanPropertyDescriptor("counter", field.asType(),
|
||||
ownerElement, null, getMethod(ownerElement, "setCounter"), field, null);
|
||||
assertThat(property.resolveItemMetadata("test", metadataEnv)).isNull();
|
||||
});
|
||||
}
|
||||
@@ -247,7 +244,7 @@ class JavaBeanPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
ExecutableElement getter = getMethod(ownerElement, getterName);
|
||||
ExecutableElement setter = getMethod(ownerElement, setterName);
|
||||
VariableElement field = getField(ownerElement, name);
|
||||
return new JavaBeanPropertyDescriptor(ownerElement, null, getter, name, getter.getReturnType(), field, setter);
|
||||
return new JavaBeanPropertyDescriptor(name, getter.getReturnType(), ownerElement, getter, setter, field, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* 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);
|
||||
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "name");
|
||||
assertThat(property.getName()).isEqualTo("name");
|
||||
assertThat(property.getSource()).isSameAs(property.getField());
|
||||
assertThat(property.getField().getSimpleName()).hasToString("name");
|
||||
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||
@@ -60,7 +59,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(LombokSimpleProperties.class);
|
||||
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "items");
|
||||
assertThat(property.getName()).isEqualTo("items");
|
||||
assertThat(property.getSource()).isSameAs(property.getField());
|
||||
assertThat(property.getField().getSimpleName()).hasToString("items");
|
||||
assertThat(property.isProperty(metadataEnv)).isTrue();
|
||||
assertThat(property.isNested(metadataEnv)).isFalse();
|
||||
@@ -73,7 +71,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
|
||||
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "first");
|
||||
assertThat(property.getName()).isEqualTo("first");
|
||||
assertThat(property.getSource()).isSameAs(property.getField());
|
||||
assertThat(property.getField().getSimpleName()).hasToString("first");
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||
@@ -86,7 +83,6 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
|
||||
LombokPropertyDescriptor property = createPropertyDescriptor(ownerElement, "third");
|
||||
assertThat(property.getName()).isEqualTo("third");
|
||||
assertThat(property.getSource()).isSameAs(property.getField());
|
||||
assertThat(property.getField().getSimpleName()).hasToString("third");
|
||||
assertThat(property.isProperty(metadataEnv)).isFalse();
|
||||
assertThat(property.isNested(metadataEnv)).isTrue();
|
||||
@@ -177,8 +173,8 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
TypeElement ownerElement = roundEnv.getRootElement(LombokInnerClassProperties.class);
|
||||
VariableElement field = getField(ownerElement, "third");
|
||||
ExecutableElement getter = getMethod(ownerElement, "getThird");
|
||||
LombokPropertyDescriptor property = new LombokPropertyDescriptor(ownerElement, null, field, "third",
|
||||
field.asType(), getter, null);
|
||||
LombokPropertyDescriptor property = new LombokPropertyDescriptor("third", field.asType(), ownerElement,
|
||||
getter, null, field, null);
|
||||
assertItemMetadata(metadataEnv, property).isGroup()
|
||||
.hasName("test.third")
|
||||
.hasType("org.springframework.boot.configurationsample.lombok.SimpleLombokPojo")
|
||||
@@ -276,7 +272,7 @@ class LombokPropertyDescriptorTests extends PropertyDescriptorTests {
|
||||
VariableElement field = getField(ownerElement, name);
|
||||
ExecutableElement getter = getMethod(ownerElement, createAccessorMethodName("get", 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -161,7 +161,7 @@ class PropertyDescriptorResolverTests {
|
||||
}
|
||||
|
||||
private BiConsumer<TypeElement, MetadataGenerationEnvironment> properties(
|
||||
Consumer<Stream<PropertyDescriptor<?>>> stream) {
|
||||
Consumer<Stream<PropertyDescriptor>> stream) {
|
||||
return (element, metadataEnv) -> {
|
||||
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
|
||||
stream.accept(resolver.resolve(element, null));
|
||||
|
||||
@@ -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");
|
||||
* 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,
|
||||
PropertyDescriptor<?> property) {
|
||||
PropertyDescriptor property) {
|
||||
return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv));
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
Reference in New Issue
Block a user