From 7ea95700c3dfe10edd630079423035453701e1c9 Mon Sep 17 00:00:00 2001 From: Jan Rieke Date: Wed, 27 Mar 2024 11:24:14 +0100 Subject: [PATCH] [WIP] --- .../lombok/core/handlers/HandlerUtil.java | 19 +++++++- .../lombok/extern/jackson/Jacksonized.java | 3 +- .../javac/handlers/HandleJacksonized.java | 47 ++++++++++++++++++- .../after-delombok/JacksonizedAccessors.java | 18 +++++++ .../resource/before/JacksonizedAccessors.java | 10 ++++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 test/transform/resource/after-delombok/JacksonizedAccessors.java create mode 100644 test/transform/resource/before/JacksonizedAccessors.java diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 07704aa8a6..e8b89aad42 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -79,7 +79,7 @@ public static int primeForNull() { return 43; } - public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS; + public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_GETTER_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS; static { // This is a list of annotations with a __highly specific meaning__: All annotations in this list indicate that passing null for the relevant item is __never__ acceptable, regardless of settings or circumstance. // In other words, things like 'this models a database table, and the db table column has a nonnull constraint', or 'this represents a web form, and if this is null, the form is invalid' __do not count__ and should not be in this list; @@ -423,6 +423,23 @@ public static int primeForNull() { "org.checkerframework.common.value.qual.UnknownVal", "org.checkerframework.framework.qual.PurityUnqualified", })); + COPY_TO_GETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { + "com.fasterxml.jackson.annotation.JacksonInject", + "com.fasterxml.jackson.annotation.JsonAlias", + "com.fasterxml.jackson.annotation.JsonFormat", + "com.fasterxml.jackson.annotation.JsonIgnore", + "com.fasterxml.jackson.annotation.JsonIgnoreProperties", + "com.fasterxml.jackson.annotation.JsonProperty", + "com.fasterxml.jackson.annotation.JsonSetter", + "com.fasterxml.jackson.annotation.JsonSubTypes", + "com.fasterxml.jackson.annotation.JsonTypeInfo", + "com.fasterxml.jackson.annotation.JsonUnwrapped", + "com.fasterxml.jackson.annotation.JsonView", + "com.fasterxml.jackson.databind.annotation.JsonDeserialize", + "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper", + "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty", + "com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText", + })); COPY_TO_SETTER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { "com.fasterxml.jackson.annotation.JacksonInject", "com.fasterxml.jackson.annotation.JsonAlias", diff --git a/src/core/lombok/extern/jackson/Jacksonized.java b/src/core/lombok/extern/jackson/Jacksonized.java index bba654c302..4b7d7e54ee 100644 --- a/src/core/lombok/extern/jackson/Jacksonized.java +++ b/src/core/lombok/extern/jackson/Jacksonized.java @@ -38,9 +38,10 @@ * {@code @}{@link Builder}, {@code @}{@link SuperBuilder}, and * {@code @}{@link Accessors}. *

- * For {@code @}{@link Accessors}{@code (fluent = true)}, it automatically + * For {@code @}{@link Accessors}{@code (fluent = true)} on a type, it automatically * configures Jackson to use the generated setters and getters for * (de-)serialization by inserting {@code @}{@link JsonProperty} annotations. + * (Note that {@code @Jacksonized} {@code @Accessors} on fields are not supported.) *

* For {@code @}{@link Builder} and {@code @}{@link SuperBuilder}, it * automatically configures the generated builder class to be used by Jackson's diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java index 7b1cb63f81..b7e570129f 100644 --- a/src/core/lombok/javac/handlers/HandleJacksonized.java +++ b/src/core/lombok/javac/handlers/HandleJacksonized.java @@ -28,6 +28,7 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; @@ -56,7 +57,7 @@ * needs for builders. */ @Provides -@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated). +@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated), but before all handlers generating getters/setters. public class HandleJacksonized extends JavacAnnotationHandler { @Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) { @@ -79,7 +80,49 @@ public class HandleJacksonized extends JavacAnnotationHandler { annotationNode.addWarning("@Jacksonized requires @Builder, @SuperBuilder, or @Accessors for it to mean anything."); return; } + + if (builderAnnotationNode != null || superBuilderAnnotationNode != null) { + handleJacksonizedBuilder(annotationNode, annotatedNode, tdNode, td, builderAnnotationNode, superBuilderAnnotationNode); + } + + if (accessorsAnnotationNode != null) { + handleJacksonizedAccessors(annotationNode, annotatedNode, tdNode, td, accessorsAnnotationNode); + } + } + private void handleJacksonizedAccessors(JavacNode annotationNode, JavacNode annotatedNode, JavacNode tdNode, JCClassDecl td, JavacNode 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 (JavacNode javacNode : tdNode.down()) { + if (javacNode.getKind() == Kind.FIELD) { + createJsonPropertyForField(javacNode, annotationNode); + } + } + } + + private void createJsonPropertyForField(JavacNode fieldNode, JavacNode annotationNode) { + if (hasAnnotation("com.fasterxml.jackson.annotation.JsonProperty", fieldNode)) { + return; + } + JavacTreeMaker maker = fieldNode.getTreeMaker(); + + JCExpression jsonPropertyType = chainDots(fieldNode, "com", "fasterxml", "jackson", "annotation", "JsonProperty"); + 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); + } + + private void handleJacksonizedBuilder(JavacNode annotationNode, JavacNode annotatedNode, JavacNode tdNode, JCClassDecl td, JavacNode builderAnnotationNode, JavacNode superBuilderAnnotationNode) { if (builderAnnotationNode != null && superBuilderAnnotationNode != null) { annotationNode.addError("@Jacksonized cannot process both @Builder and @SuperBuilder on the same class."); return; @@ -155,7 +198,7 @@ public class HandleJacksonized extends JavacAnnotationHandler { // @SuperBuilder? Make it package-private! if (superBuilderAnnotationNode != null) builderClass.mods.flags = builderClass.mods.flags & ~Flags.PRIVATE; - } + } private String getBuilderClassName(JavacNode annotationNode, JavacNode annotatedNode, JCClassDecl td, AnnotationValues builderAnnotation, JavacTreeMaker maker) { String builderClassName = builderAnnotation != null ? diff --git a/test/transform/resource/after-delombok/JacksonizedAccessors.java b/test/transform/resource/after-delombok/JacksonizedAccessors.java new file mode 100644 index 0000000000..7ffa312a6e --- /dev/null +++ b/test/transform/resource/after-delombok/JacksonizedAccessors.java @@ -0,0 +1,18 @@ +public class JacksonizedAccessors { + @com.fasterxml.jackson.annotation.JsonProperty("intValue") + private int intValue; + @java.lang.SuppressWarnings("all") + @com.fasterxml.jackson.annotation.JsonProperty("intValue") + public int intValue() { + return this.intValue; + } + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + @com.fasterxml.jackson.annotation.JsonProperty("intValue") + public 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 new file mode 100644 index 0000000000..f6df4b016a --- /dev/null +++ b/test/transform/resource/before/JacksonizedAccessors.java @@ -0,0 +1,10 @@ +import lombok.Getter; +import lombok.experimental.Accessors; +import lombok.extern.jackson.Jacksonized; + +@Jacksonized +@Accessors(fluent = true) +@Getter +public class JacksonizedAccessors { + private int intValue; +}