From 70dd3f09404eaa3addcf029b9cbd273e928ebe25 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Thu, 27 Mar 2025 14:06:27 +0100 Subject: [PATCH] ecj: Jacksonized for accessors [WIP] --- src/core/lombok/ConfigurationKeys.java | 2 +- .../eclipse/handlers/EclipseHandlerUtil.java | 8 ++- .../eclipse/handlers/HandleBuilder.java | 2 +- .../eclipse/handlers/HandleJacksonized.java | 52 ++++++++++++++++++- .../lombok/eclipse/handlers/HandleSetter.java | 2 +- .../eclipse/handlers/HandleSuperBuilder.java | 2 +- .../singulars/EclipseGuavaSingularizer.java | 2 +- .../EclipseJavaUtilListSetSingularizer.java | 2 +- .../EclipseJavaUtilMapSingularizer.java | 2 +- .../lombok/extern/jackson/Jacksonized.java | 2 - .../javac/handlers/HandleJacksonized.java | 2 +- .../javac/handlers/HandleSuperBuilder.java | 4 +- .../after-ecj/JacksonizedAccessors.java | 16 ++++++ .../resource/before/JacksonizedAccessors.java | 15 ++---- 14 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 test/transform/resource/after-ecj/JacksonizedAccessors.java diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index b54e4d30..18d7cb9a 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -747,7 +747,7 @@ public class ConfigurationKeys { /** * lombok configuration: {@code lombok.copyJacksonAnnotationsToSetters} = {@code true} | {@code false}. * - * If true, copy certain Jackson annotations from a field to its corresponding getter. This was the behavior from lombok 1.18.16 to 1.18.34. + * If true, copy certain Jackson annotations from a field to its corresponding getter. This was the behavior from lombok 1.18.16 to 1.18.36. * However, it turned out to be harmful in certain situations. Thus, the default is now false. */ public static final ConfigurationKey COPY_JACKSON_ANNOTATIONS_TO_SETTERS = new ConfigurationKey("lombok.copyJacksonAnnotationsToSetters", "Copy these annotations to getters, setters, with methods, builder-setters, etc.") {}; diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index f357a6c4..3a18d049 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -828,8 +828,14 @@ public class EclipseHandlerUtil { /** * Searches the given field node for annotations that are specifically intentioned to be copied to the setter. + * + * @param forceCopy If true, always copy the annotations regardless of the lombok configuration. */ - public static Annotation[] findCopyableToSetterAnnotations(EclipseNode node) { + public static Annotation[] findCopyableToSetterAnnotations(EclipseNode node, boolean forceCopy) { + if (!forceCopy) { + Boolean copyAnnotations = node.getAst().readConfiguration(ConfigurationKeys.COPY_JACKSON_ANNOTATIONS_TO_SETTERS); + if (copyAnnotations == null || !copyAnnotations) return EMPTY_ANNOTATIONS_ARRAY; + } return findAnnotationsInList(node, JACKSON_COPY_TO_SETTER_ANNOTATIONS); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index a63d3b36..a22eb49b 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1047,7 +1047,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { } List methodAnnsList = Collections.emptyList(); - Annotation[] methodAnns = EclipseHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode); + Annotation[] methodAnns = EclipseHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode, true); if (methodAnns != null && methodAnns.length > 0) methodAnnsList = Arrays.asList(methodAnns); ASTNode source = job.sourceNode.get(); MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, bfd.name, bfd.nameOfSetFlag, job.oldChain, toEclipseModifier(job.accessInners), diff --git a/src/core/lombok/eclipse/handlers/HandleJacksonized.java b/src/core/lombok/eclipse/handlers/HandleJacksonized.java index 466cc338..abb50fe0 100644 --- a/src/core/lombok/eclipse/handlers/HandleJacksonized.java +++ b/src/core/lombok/eclipse/handlers/HandleJacksonized.java @@ -31,6 +31,7 @@ import java.util.List; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; @@ -47,6 +48,7 @@ import lombok.core.handlers.HandlerUtil; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Accessors; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; import lombok.spi.Provides; @@ -62,6 +64,7 @@ public class HandleJacksonized extends EclipseAnnotationHandler { private static final char[][] JSON_POJO_BUILDER_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder"); private static final char[][] JSON_DESERIALIZE_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonDeserialize"); + private static final char[][] JSON_PROPERTY_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.annotation.JsonProperty"); @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized"); @@ -77,8 +80,10 @@ public class HandleJacksonized extends EclipseAnnotationHandler { EclipseNode builderAnnotationNode = findAnnotation(Builder.class, annotatedNode); EclipseNode superBuilderAnnotationNode = findAnnotation(SuperBuilder.class, annotatedNode); - if (builderAnnotationNode == null && superBuilderAnnotationNode == null) { - annotationNode.addWarning("@Jacksonized requires @Builder or @SuperBuilder for it to mean anything."); + EclipseNode accessorsAnnotationNode = (annotatedNode.getKind() == Kind.TYPE) ? findAnnotation(Accessors.class, annotatedNode) : null; + + if (builderAnnotationNode == null && superBuilderAnnotationNode == null && accessorsAnnotationNode == null) { + annotationNode.addWarning("@Jacksonized requires @Builder, @SuperBuilder, or @Accessors for it to mean anything."); return; } @@ -92,7 +97,18 @@ public class HandleJacksonized extends EclipseAnnotationHandler { annotationNode.addError("Builders on abstract classes cannot be @Jacksonized (the builder would never be used)."); return; } + + if (builderAnnotationNode != null || superBuilderAnnotationNode != null) { + handleJacksonizedBuilder(ast, annotationNode, annotatedNode, tdNode, td, builderAnnotationNode, superBuilderAnnotationNode); + } + + if (accessorsAnnotationNode != null) { + handleJacksonizedAccessors(ast, annotationNode, annotatedNode, tdNode, td, accessorsAnnotationNode); + } + } + + private void handleJacksonizedBuilder(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, EclipseNode tdNode, TypeDeclaration td, EclipseNode builderAnnotationNode, EclipseNode superBuilderAnnotationNode) { AnnotationValues builderAnnotation = builderAnnotationNode != null ? createAnnotation(Builder.class, builderAnnotationNode) : null; AnnotationValues superBuilderAnnotation = superBuilderAnnotationNode != null ? createAnnotation(SuperBuilder.class, superBuilderAnnotationNode) : null; @@ -144,6 +160,38 @@ public class HandleJacksonized extends EclipseAnnotationHandler { builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate; } + private void handleJacksonizedAccessors(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, EclipseNode tdNode, TypeDeclaration td, EclipseNode accessorsAnnotationNode) { + AnnotationValues accessorsAnnotation = accessorsAnnotationNode != null ? + createAnnotation(Accessors.class, accessorsAnnotationNode) : + null; + boolean fluent = accessorsAnnotation != null && accessorsAnnotation.getInstance().fluent(); + + if (!fluent) { + // No changes required for chained-only accessors. + return; + } + + // Add @JsonProperty to all fields. It will be automatically copied to the getter/setters later. + for (EclipseNode eclipseNode : tdNode.down()) { + if (eclipseNode.getKind() == Kind.FIELD) { + createJsonPropertyForField(eclipseNode, annotationNode); + } + } + } + + private void createJsonPropertyForField(EclipseNode fieldNode, EclipseNode annotationNode) { + if (hasAnnotation("com.fasterxml.jackson.annotation.JsonProperty", fieldNode)) { + return; + } + ASTNode astNode = fieldNode.get(); + if (astNode instanceof FieldDeclaration) { + FieldDeclaration fd = (FieldDeclaration)astNode; + StringLiteral fieldName = new StringLiteral(fd.name, 0, 0, 0); + ((FieldDeclaration)astNode).annotations = addAnnotation(fieldNode.get(), fd.annotations, JSON_PROPERTY_ANNOTATION, fieldName); + } + } + + private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, TypeDeclaration td, AnnotationValues builderAnnotation) { String builderClassName = builderAnnotation != null ? builderAnnotation.getInstance().builderClassName() : null; diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 99552efc..5c372019 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -228,7 +228,7 @@ public class HandleSetter extends EclipseAnnotationHandler { if (isFieldDeprecated(fieldNode) || deprecate) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode)); + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated, findCopyableToSetterAnnotations(fieldNode, false)); Argument param = new Argument(paramName, p, copyType(field.type, source), Modifier.FINAL); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 82e561de..c37da9d7 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -1007,7 +1007,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { if (Arrays.equals(setterName.toCharArray(), existingName) && !isTolerate(fieldNode, existing[i])) return; } - List methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode)); + List methodAnnsList = Arrays.asList(EclipseHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode, true)); addCheckerFrameworkReturnsReceiver(returnType, job.source, job.checkerFramework); MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, paramName, nameOfSetFlag, returnType, returnStatement, ClassFileConstants.AccPublic, job.sourceNode, methodAnnsList, annosOnParam != null ? Arrays.asList(copyAnnotations(job.source, annosOnParam)) : Collections.emptyList()); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java index a43e0331..eff24cd4 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -219,7 +219,7 @@ abstract class EclipseGuavaSingularizer extends EclipseSingularizer { char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray(); md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName(builderType, "addAll", new String(data.getPluralName())).toCharArray(); Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource()); - Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up())); + Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), false)); md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class); if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java index 810a7878..c94666e0 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -195,7 +195,7 @@ abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingula char[] prefixedSelector = data.getSetterPrefix().length == 0 ? data.getPluralName() : HandlerUtil.buildAccessorName(builderType, new String(data.getSetterPrefix()), new String(data.getPluralName())).toCharArray(); md.selector = fluent ? prefixedSelector : HandlerUtil.buildAccessorName(builderType, "addAll", new String(data.getPluralName())).toCharArray(); Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource()); - Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up())); + Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), false)); md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class); if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md); diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java index 16ed2d44..0e3ab0dd 100755 --- a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -332,7 +332,7 @@ public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer md.selector = setterName.toCharArray(); Annotation[] selfReturnAnnotations = generateSelfReturnAnnotations(deprecate, data.getSource()); - Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up())); + Annotation[] copyToSetterAnnotations = copyAnnotations(md, findCopyableToSetterAnnotations(data.getAnnotation().up(), false)); md.annotations = concat(selfReturnAnnotations, copyToSetterAnnotations, Annotation.class); if (returnStatement != null) createRelevantNonNullAnnotation(builderType, md); diff --git a/src/core/lombok/extern/jackson/Jacksonized.java b/src/core/lombok/extern/jackson/Jacksonized.java index 4b7d7e54..fd482d00 100644 --- a/src/core/lombok/extern/jackson/Jacksonized.java +++ b/src/core/lombok/extern/jackson/Jacksonized.java @@ -27,8 +27,6 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.Retention; import java.lang.annotation.Target; -import com.fasterxml.jackson.annotation.JsonProperty; - import lombok.Builder; import lombok.experimental.Accessors; import lombok.experimental.SuperBuilder; diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java index f956d782..7b41ec13 100644 --- a/src/core/lombok/javac/handlers/HandleJacksonized.java +++ b/src/core/lombok/javac/handlers/HandleJacksonized.java @@ -116,7 +116,7 @@ public class HandleJacksonized extends JavacAnnotationHandler { JavacTreeMaker maker = fieldNode.getTreeMaker(); JCExpression jsonPropertyType = chainDots(fieldNode, "com", "fasterxml", "jackson", "annotation", "JsonProperty"); - JCAnnotation annotationJsonProperty = maker.Annotation(jsonPropertyType, List.of(maker.Literal(fieldNode.getName()))); + JCAnnotation annotationJsonProperty = maker.Annotation(jsonPropertyType, List.of(maker.Literal(fieldNode.getName()))); recursiveSetGeneratedBy(annotationJsonProperty, annotationNode); JCVariableDecl fieldDecl = ((JCVariableDecl)fieldNode.get()); fieldDecl.mods.annotations = fieldDecl.mods.annotations.append(annotationJsonProperty); diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index 3669bf2e..2390c7aa 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -21,9 +21,9 @@ */ package lombok.javac.handlers; -import static lombok.javac.handlers.HandleBuilder.*; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.Javac.*; +import static lombok.javac.handlers.HandleBuilder.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.ArrayList; @@ -968,7 +968,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode, true); returnType = addCheckerFrameworkReturnsReceiver(returnType, maker, job.builderType, job.checkerFramework); - JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, paramName, nameOfSetFlag, returnType, returnStatement, job.sourceNode, methodAnns, annosOnParam, false); + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, paramName, nameOfSetFlag, returnType, returnStatement, job.sourceNode, methodAnns, annosOnParam, true); if (job.sourceNode.up().getKind() == Kind.METHOD) { copyJavadocFromParam(originalFieldNode.up(), newMethod, paramName.toString()); } else { diff --git a/test/transform/resource/after-ecj/JacksonizedAccessors.java b/test/transform/resource/after-ecj/JacksonizedAccessors.java new file mode 100644 index 00000000..83fe581c --- /dev/null +++ b/test/transform/resource/after-ecj/JacksonizedAccessors.java @@ -0,0 +1,16 @@ +public @lombok.extern.jackson.Jacksonized @lombok.experimental.Accessors(fluent = true) @lombok.Getter @lombok.Setter class JacksonizedAccessors { + private @com.fasterxml.jackson.annotation.JsonProperty("intValue") int intValue; + public JacksonizedAccessors() { + super(); + } + public @java.lang.SuppressWarnings("all") @lombok.Generated @com.fasterxml.jackson.annotation.JsonProperty("intValue") int intValue() { + return this.intValue; + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") @lombok.Generated @com.fasterxml.jackson.annotation.JsonProperty("intValue") JacksonizedAccessors intValue(final int intValue) { + this.intValue = intValue; + return this; + } +} \ No newline at end of file diff --git a/test/transform/resource/before/JacksonizedAccessors.java b/test/transform/resource/before/JacksonizedAccessors.java index 3242890e..0fe01684 100644 --- a/test/transform/resource/before/JacksonizedAccessors.java +++ b/test/transform/resource/before/JacksonizedAccessors.java @@ -1,12 +1,7 @@ -import lombok.Getter; -import lombok.Setter; -import lombok.experimental.Accessors; -import lombok.extern.jackson.Jacksonized; - -@Jacksonized -@Accessors(fluent = true) -@Getter -@Setter +@lombok.extern.jackson.Jacksonized +@lombok.experimental.Accessors(fluent = true) +@lombok.Getter +@lombok.Setter public class JacksonizedAccessors { private int intValue; -} +} \ No newline at end of file