Skip to content

Commit bce468f

Browse files
committed
Polish 'Generate configuration metadata for records'
Restructure `PropertyDescriptor` type hierarchy and polish code. See gh-29403
1 parent af976ca commit bce468f

13 files changed

+579
-382
lines changed

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConstructorParameterPropertyDescriptor.java

Lines changed: 16 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -16,209 +16,47 @@
1616

1717
package org.springframework.boot.configurationprocessor;
1818

19+
import java.util.Arrays;
1920
import java.util.List;
20-
import java.util.Map;
21-
import java.util.function.Function;
2221

23-
import javax.lang.model.element.AnnotationMirror;
2422
import javax.lang.model.element.Element;
2523
import javax.lang.model.element.ExecutableElement;
26-
import javax.lang.model.element.RecordComponentElement;
2724
import javax.lang.model.element.TypeElement;
2825
import javax.lang.model.element.VariableElement;
29-
import javax.lang.model.type.PrimitiveType;
3026
import javax.lang.model.type.TypeMirror;
31-
import javax.lang.model.util.TypeKindVisitor8;
32-
import javax.tools.Diagnostic.Kind;
3327

3428
/**
3529
* A {@link PropertyDescriptor} for a constructor parameter.
3630
*
3731
* @author Stephane Nicoll
38-
* @author Pavel Anisimov
32+
* @author Phillip Webb
3933
*/
40-
class ConstructorParameterPropertyDescriptor extends PropertyDescriptor<VariableElement> {
34+
class ConstructorParameterPropertyDescriptor extends ParameterPropertyDescriptor {
4135

42-
private final RecordComponentElement recordComponent;
36+
private final ExecutableElement setter;
4337

44-
ConstructorParameterPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod,
45-
VariableElement source, String name, TypeMirror type, VariableElement field,
46-
RecordComponentElement recordComponent, ExecutableElement getter, ExecutableElement setter) {
47-
super(ownerElement, factoryMethod, source, name, type, field, getter, setter);
48-
this.recordComponent = recordComponent;
38+
private final VariableElement field;
39+
40+
ConstructorParameterPropertyDescriptor(String name, TypeMirror type, VariableElement parameter,
41+
TypeElement declaringElement, ExecutableElement getter, ExecutableElement setter, VariableElement field) {
42+
super(name, type, parameter, declaringElement, getter);
43+
this.setter = setter;
44+
this.field = field;
4945
}
5046

5147
@Override
52-
protected boolean isProperty(MetadataGenerationEnvironment env) {
53-
// If it's a constructor parameter, it doesn't matter as we must be able to bind
54-
// it to build the object.
55-
return !isNested(env);
48+
protected List<Element> getDeprecatableElements() {
49+
return Arrays.asList(getGetter(), this.setter, this.field);
5650
}
5751

5852
@Override
59-
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
60-
Object defaultValue = getDefaultValueFromAnnotation(environment, getSource());
61-
if (defaultValue != null) {
62-
return defaultValue;
63-
}
64-
return getSource().asType().accept(DefaultPrimitiveTypeVisitor.INSTANCE, null);
53+
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
54+
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
6555
}
6656

6757
@Override
6858
protected String resolveDescription(MetadataGenerationEnvironment environment) {
69-
// record components descriptions are written using @param tag
70-
if (this.recordComponent != null) {
71-
return environment.getTypeUtils().getJavaDoc(this.recordComponent);
72-
}
73-
return super.resolveDescription(environment);
74-
}
75-
76-
private Object getDefaultValueFromAnnotation(MetadataGenerationEnvironment environment, Element element) {
77-
AnnotationMirror annotation = environment.getDefaultValueAnnotation(element);
78-
List<String> defaultValue = getDefaultValue(environment, annotation);
79-
if (defaultValue != null) {
80-
try {
81-
TypeMirror specificType = determineSpecificType(environment);
82-
if (defaultValue.size() == 1) {
83-
return coerceValue(specificType, defaultValue.get(0));
84-
}
85-
return defaultValue.stream().map((value) -> coerceValue(specificType, value)).toList();
86-
}
87-
catch (IllegalArgumentException ex) {
88-
environment.getMessager().printMessage(Kind.ERROR, ex.getMessage(), element, annotation);
89-
}
90-
}
91-
return null;
92-
}
93-
94-
@SuppressWarnings("unchecked")
95-
private List<String> getDefaultValue(MetadataGenerationEnvironment environment, AnnotationMirror annotation) {
96-
if (annotation == null) {
97-
return null;
98-
}
99-
Map<String, Object> values = environment.getAnnotationElementValues(annotation);
100-
return (List<String>) values.get("value");
101-
}
102-
103-
private TypeMirror determineSpecificType(MetadataGenerationEnvironment environment) {
104-
TypeMirror candidate = getSource().asType();
105-
TypeMirror elementCandidate = environment.getTypeUtils().extractElementType(candidate);
106-
if (elementCandidate != null) {
107-
candidate = elementCandidate;
108-
}
109-
PrimitiveType primitiveType = environment.getTypeUtils().getPrimitiveType(candidate);
110-
return (primitiveType != null) ? primitiveType : candidate;
111-
}
112-
113-
private Object coerceValue(TypeMirror type, String value) {
114-
Object coercedValue = type.accept(DefaultValueCoercionTypeVisitor.INSTANCE, value);
115-
return (coercedValue != null) ? coercedValue : value;
116-
}
117-
118-
private static final class DefaultValueCoercionTypeVisitor extends TypeKindVisitor8<Object, String> {
119-
120-
private static final DefaultValueCoercionTypeVisitor INSTANCE = new DefaultValueCoercionTypeVisitor();
121-
122-
private <T extends Number> T parseNumber(String value, Function<String, T> parser,
123-
PrimitiveType primitiveType) {
124-
try {
125-
return parser.apply(value);
126-
}
127-
catch (NumberFormatException ex) {
128-
throw new IllegalArgumentException(
129-
String.format("Invalid %s representation '%s'", primitiveType, value));
130-
}
131-
}
132-
133-
@Override
134-
public Object visitPrimitiveAsBoolean(PrimitiveType t, String value) {
135-
return Boolean.parseBoolean(value);
136-
}
137-
138-
@Override
139-
public Object visitPrimitiveAsByte(PrimitiveType t, String value) {
140-
return parseNumber(value, Byte::parseByte, t);
141-
}
142-
143-
@Override
144-
public Object visitPrimitiveAsShort(PrimitiveType t, String value) {
145-
return parseNumber(value, Short::parseShort, t);
146-
}
147-
148-
@Override
149-
public Object visitPrimitiveAsInt(PrimitiveType t, String value) {
150-
return parseNumber(value, Integer::parseInt, t);
151-
}
152-
153-
@Override
154-
public Object visitPrimitiveAsLong(PrimitiveType t, String value) {
155-
return parseNumber(value, Long::parseLong, t);
156-
}
157-
158-
@Override
159-
public Object visitPrimitiveAsChar(PrimitiveType t, String value) {
160-
if (value.length() > 1) {
161-
throw new IllegalArgumentException(String.format("Invalid character representation '%s'", value));
162-
}
163-
return value;
164-
}
165-
166-
@Override
167-
public Object visitPrimitiveAsFloat(PrimitiveType t, String value) {
168-
return parseNumber(value, Float::parseFloat, t);
169-
}
170-
171-
@Override
172-
public Object visitPrimitiveAsDouble(PrimitiveType t, String value) {
173-
return parseNumber(value, Double::parseDouble, t);
174-
}
175-
176-
}
177-
178-
private static final class DefaultPrimitiveTypeVisitor extends TypeKindVisitor8<Object, Void> {
179-
180-
private static final DefaultPrimitiveTypeVisitor INSTANCE = new DefaultPrimitiveTypeVisitor();
181-
182-
@Override
183-
public Object visitPrimitiveAsBoolean(PrimitiveType t, Void ignore) {
184-
return false;
185-
}
186-
187-
@Override
188-
public Object visitPrimitiveAsByte(PrimitiveType t, Void ignore) {
189-
return (byte) 0;
190-
}
191-
192-
@Override
193-
public Object visitPrimitiveAsShort(PrimitiveType t, Void ignore) {
194-
return (short) 0;
195-
}
196-
197-
@Override
198-
public Object visitPrimitiveAsInt(PrimitiveType t, Void ignore) {
199-
return 0;
200-
}
201-
202-
@Override
203-
public Object visitPrimitiveAsLong(PrimitiveType t, Void ignore) {
204-
return 0L;
205-
}
206-
207-
@Override
208-
public Object visitPrimitiveAsChar(PrimitiveType t, Void ignore) {
209-
return null;
210-
}
211-
212-
@Override
213-
public Object visitPrimitiveAsFloat(PrimitiveType t, Void ignore) {
214-
return 0F;
215-
}
216-
217-
@Override
218-
public Object visitPrimitiveAsDouble(PrimitiveType t, Void ignore) {
219-
return 0D;
220-
}
221-
59+
return environment.getTypeUtils().getJavaDoc(this.field);
22260
}
22361

22462
}

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot.configurationprocessor;
1818

19+
import java.util.Arrays;
20+
import java.util.List;
21+
22+
import javax.lang.model.element.Element;
1923
import javax.lang.model.element.ExecutableElement;
2024
import javax.lang.model.element.TypeElement;
2125
import javax.lang.model.element.VariableElement;
@@ -25,23 +29,54 @@
2529
* A {@link PropertyDescriptor} for a standard JavaBean property.
2630
*
2731
* @author Stephane Nicoll
32+
* @author Phillip Webb
2833
*/
29-
class JavaBeanPropertyDescriptor extends PropertyDescriptor<ExecutableElement> {
34+
class JavaBeanPropertyDescriptor extends PropertyDescriptor {
35+
36+
private final ExecutableElement setter;
37+
38+
private final VariableElement field;
3039

31-
JavaBeanPropertyDescriptor(TypeElement ownerElement, ExecutableElement factoryMethod, ExecutableElement getter,
32-
String name, TypeMirror type, VariableElement field, ExecutableElement setter) {
33-
super(ownerElement, factoryMethod, getter, name, type, field, getter, setter);
40+
private final ExecutableElement factoryMethod;
41+
42+
JavaBeanPropertyDescriptor(String name, TypeMirror type, TypeElement declaringElement, ExecutableElement getter,
43+
ExecutableElement setter, VariableElement field, ExecutableElement factoryMethod) {
44+
super(name, type, declaringElement, getter);
45+
this.setter = setter;
46+
this.field = field;
47+
this.factoryMethod = factoryMethod;
48+
}
49+
50+
ExecutableElement getSetter() {
51+
return this.setter;
3452
}
3553

3654
@Override
37-
protected boolean isProperty(MetadataGenerationEnvironment env) {
38-
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
39-
return !env.isExcluded(getType()) && getGetter() != null && (getSetter() != null || isCollection);
55+
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
56+
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
57+
}
58+
59+
@Override
60+
protected String resolveDescription(MetadataGenerationEnvironment environment) {
61+
return environment.getTypeUtils().getJavaDoc(this.field);
4062
}
4163

4264
@Override
4365
protected Object resolveDefaultValue(MetadataGenerationEnvironment environment) {
44-
return environment.getFieldDefaultValue(getOwnerElement(), getName());
66+
return environment.getFieldDefaultValue(getDeclaringElement(), getName());
67+
}
68+
69+
@Override
70+
protected List<Element> getDeprecatableElements() {
71+
return Arrays.asList(getGetter(), this.setter, this.field, this.factoryMethod);
72+
}
73+
74+
@Override
75+
public boolean isProperty(MetadataGenerationEnvironment env) {
76+
boolean isCollection = env.getTypeUtils().isCollectionOrMap(getType());
77+
boolean hasGetter = getGetter() != null;
78+
boolean hasSetter = getSetter() != null;
79+
return !env.isExcluded(getType()) && hasGetter && (hasSetter || isCollection);
4580
}
4681

4782
}

0 commit comments

Comments
 (0)