Add support for record accessors in spring-boot-configuration-processor

Closes gh-29526
This commit is contained in:
Moritz Halbritter
2023-01-18 12:02:34 +01:00
parent 6c44055fd4
commit 26d658802f
2 changed files with 62 additions and 10 deletions

View File

@@ -38,15 +38,20 @@ import javax.lang.model.util.ElementFilter;
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Moritz Halbritter
*/
class TypeElementMembers {
private static final String OBJECT_CLASS_NAME = Object.class.getName();
private static final String RECORD_CLASS_NAME = "java.lang.Record";
private final MetadataGenerationEnvironment env;
private final TypeElement targetType;
private final boolean isRecord;
private final Map<String, VariableElement> fields = new LinkedHashMap<>();
private final Map<String, List<ExecutableElement>> publicGetters = new LinkedHashMap<>();
@@ -56,18 +61,20 @@ class TypeElementMembers {
TypeElementMembers(MetadataGenerationEnvironment env, TypeElement targetType) {
this.env = env;
this.targetType = targetType;
this.isRecord = RECORD_CLASS_NAME.equals(targetType.getSuperclass().toString());
process(targetType);
}
private void process(TypeElement element) {
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
processMethod(method);
}
for (VariableElement field : ElementFilter.fieldsIn(element.getEnclosedElements())) {
processField(field);
}
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
processMethod(method);
}
Element superType = this.env.getTypeUtils().asElement(element.getSuperclass());
if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())) {
if (superType instanceof TypeElement && !OBJECT_CLASS_NAME.equals(superType.toString())
&& !RECORD_CLASS_NAME.equals(superType.toString())) {
process((TypeElement) superType);
}
}
@@ -122,12 +129,22 @@ class TypeElementMembers {
}
private boolean isGetter(ExecutableElement method) {
boolean hasParameters = !method.getParameters().isEmpty();
boolean returnsVoid = TypeKind.VOID == method.getReturnType().getKind();
if (hasParameters || returnsVoid) {
return false;
}
String name = method.getSimpleName().toString();
return ((name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2))
&& method.getParameters().isEmpty() && (TypeKind.VOID != method.getReturnType().getKind());
if (this.isRecord && this.fields.containsKey(name)) {
return true;
}
return (name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2);
}
private boolean isSetter(ExecutableElement method) {
if (this.isRecord) {
return false;
}
final String name = method.getSimpleName().toString();
return (name.startsWith("set") && name.length() > 3 && method.getParameters().size() == 1
&& isSetterReturnType(method));
@@ -151,16 +168,29 @@ class TypeElementMembers {
}
private String getAccessorName(String methodName) {
String name = methodName.startsWith("is") ? methodName.substring(2) : methodName.substring(3);
if (this.isRecord) {
return methodName;
}
String name;
if (methodName.startsWith("is")) {
name = methodName.substring(2);
}
else if (methodName.startsWith("get")) {
name = methodName.substring(3);
}
else if (methodName.startsWith("set")) {
name = methodName.substring(3);
}
else {
throw new AssertionError("methodName must start with 'is', 'get' or 'set', was '" + methodName + "'");
}
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
return name;
}
private void processField(VariableElement field) {
String name = field.getSimpleName().toString();
if (!this.fields.containsKey(name)) {
this.fields.put(name, field);
}
this.fields.putIfAbsent(name, field);
}
Map<String, VariableElement> getFields() {

View File

@@ -223,6 +223,28 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
.withNoDeprecation().fromSource(type));
}
@Test
@EnabledForJreRange(min = JRE.JAVA_16)
void deprecatedPropertyOnRecord(@TempDir File temp) throws IOException {
File exampleRecord = new File(temp, "DeprecatedRecord.java");
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) {
writer.println("@org.springframework.boot.configurationsample.ConstructorBinding");
writer.println(
"@org.springframework.boot.configurationsample.ConfigurationProperties(\"deprecated-record\")");
writer.println("public record DeprecatedRecord(String alpha, String bravo) {");
writer.println("@java.lang.Deprecated");
writer.println(
"@org.springframework.boot.configurationsample.DeprecatedConfigurationProperty(reason = \"some-reason\")");
writer.println("public String alpha() { return this.alpha; }");
writer.println("}");
}
ConfigurationMetadata metadata = compile(exampleRecord);
assertThat(metadata).has(Metadata.withGroup("deprecated-record"));
assertThat(metadata).has(
Metadata.withProperty("deprecated-record.alpha", String.class).withDeprecation("some-reason", null));
assertThat(metadata).has(Metadata.withProperty("deprecated-record.bravo", String.class));
}
@Test
void typBoxing() {
Class<?> type = BoxingPojo.class;