Add support for record accessors in spring-boot-configuration-processor
Closes gh-29526
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user