Skip to content

Commit 26d6588

Browse files
committed
Add support for record accessors in spring-boot-configuration-processor
Closes gh-29526
1 parent 6c44055 commit 26d6588

File tree

2 files changed

+62
-10
lines changed

2 files changed

+62
-10
lines changed

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

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,20 @@
3838
*
3939
* @author Stephane Nicoll
4040
* @author Phillip Webb
41+
* @author Moritz Halbritter
4142
*/
4243
class TypeElementMembers {
4344

4445
private static final String OBJECT_CLASS_NAME = Object.class.getName();
4546

47+
private static final String RECORD_CLASS_NAME = "java.lang.Record";
48+
4649
private final MetadataGenerationEnvironment env;
4750

4851
private final TypeElement targetType;
4952

53+
private final boolean isRecord;
54+
5055
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
5156

5257
private final Map<String, List<ExecutableElement>> publicGetters = new LinkedHashMap<>();
@@ -56,18 +61,20 @@ class TypeElementMembers {
5661
TypeElementMembers(MetadataGenerationEnvironment env, TypeElement targetType) {
5762
this.env = env;
5863
this.targetType = targetType;
64+
this.isRecord = RECORD_CLASS_NAME.equals(targetType.getSuperclass().toString());
5965
process(targetType);
6066
}
6167

6268
private void process(TypeElement element) {
63-
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
64-
processMethod(method);
65-
}
6669
for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
6770
processField(field);
6871
}
72+
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
73+
processMethod(method);
74+
}
6975
Element superType = this.env.getTypeUtils().asElement(element.getSuperclass());
70-
if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())) {
76+
if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())
77+
&& !RECORD_CLASS_NAME.equals(superType.toString())) {
7178
process((TypeElement) superType);
7279
}
7380
}
@@ -122,12 +129,22 @@ private ExecutableElement getMatchingAccessor(List<ExecutableElement> candidates
122129
}
123130

124131
private boolean isGetter(ExecutableElement method) {
132+
boolean hasParameters = !method.getParameters().isEmpty();
133+
boolean returnsVoid = TypeKind.VOID == method.getReturnType().getKind();
134+
if (hasParameters || returnsVoid) {
135+
return false;
136+
}
125137
String name = method.getSimpleName().toString();
126-
return ((name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2))
127-
&& method.getParameters().isEmpty() && (TypeKind.VOID != method.getReturnType().getKind());
138+
if (this.isRecord && this.fields.containsKey(name)) {
139+
return true;
140+
}
141+
return (name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2);
128142
}
129143

130144
private boolean isSetter(ExecutableElement method) {
145+
if (this.isRecord) {
146+
return false;
147+
}
131148
final String name = method.getSimpleName().toString();
132149
return (name.startsWith("set") && name.length() > 3 && method.getParameters().size() == 1
133150
&& isSetterReturnType(method));
@@ -151,16 +168,29 @@ private boolean isSetterReturnType(ExecutableElement method) {
151168
}
152169

153170
private String getAccessorName(String methodName) {
154-
String name = methodName.startsWith("is") ? methodName.substring(2) : methodName.substring(3);
171+
if (this.isRecord) {
172+
return methodName;
173+
}
174+
String name;
175+
if (methodName.startsWith("is")) {
176+
name = methodName.substring(2);
177+
}
178+
else if (methodName.startsWith("get")) {
179+
name = methodName.substring(3);
180+
}
181+
else if (methodName.startsWith("set")) {
182+
name = methodName.substring(3);
183+
}
184+
else {
185+
throw new AssertionError("methodName must start with 'is', 'get' or 'set', was '" + methodName + "'");
186+
}
155187
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
156188
return name;
157189
}
158190

159191
private void processField(VariableElement field) {
160192
String name = field.getSimpleName().toString();
161-
if (!this.fields.containsKey(name)) {
162-
this.fields.put(name, field);
163-
}
193+
this.fields.putIfAbsent(name, field);
164194
}
165195

166196
Map<String, VariableElement> getFields() {

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,28 @@ void deprecatedWithLessPreciseType() {
223223
.withNoDeprecation().fromSource(type));
224224
}
225225

226+
@Test
227+
@EnabledForJreRange(min = JRE.JAVA_16)
228+
void deprecatedPropertyOnRecord(@TempDir File temp) throws IOException {
229+
File exampleRecord = new File(temp, "DeprecatedRecord.java");
230+
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) {
231+
writer.println("@org.springframework.boot.configurationsample.ConstructorBinding");
232+
writer.println(
233+
"@org.springframework.boot.configurationsample.ConfigurationProperties(\"deprecated-record\")");
234+
writer.println("public record DeprecatedRecord(String alpha, String bravo) {");
235+
writer.println("@java.lang.Deprecated");
236+
writer.println(
237+
"@org.springframework.boot.configurationsample.DeprecatedConfigurationProperty(reason = \"some-reason\")");
238+
writer.println("public String alpha() { return this.alpha; }");
239+
writer.println("}");
240+
}
241+
ConfigurationMetadata metadata = compile(exampleRecord);
242+
assertThat(metadata).has(Metadata.withGroup("deprecated-record"));
243+
assertThat(metadata).has(
244+
Metadata.withProperty("deprecated-record.alpha", String.class).withDeprecation("some-reason", null));
245+
assertThat(metadata).has(Metadata.withProperty("deprecated-record.bravo", String.class));
246+
}
247+
226248
@Test
227249
void typBoxing() {
228250
Class<?> type = BoxingPojo.class;

0 commit comments

Comments
 (0)