aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/javac/handlers
diff options
context:
space:
mode:
authorJan Rieke <it@janrieke.de>2020-02-16 15:17:20 +0100
committerJan Rieke <it@janrieke.de>2020-03-03 13:01:48 +0100
commit8be4b0ffe6b5e5f89e8bb3d94e87d0aa98dc5aeb (patch)
tree1fc16ba2d1b271f1c2e06eeb2120a71d74ff75ef /src/core/lombok/javac/handlers
parent59aa6761115cb912d5223870be348b70e0e9fd9c (diff)
downloadlombok-8be4b0ffe6b5e5f89e8bb3d94e87d0aa98dc5aeb.tar.gz
lombok-8be4b0ffe6b5e5f89e8bb3d94e87d0aa98dc5aeb.tar.bz2
lombok-8be4b0ffe6b5e5f89e8bb3d94e87d0aa98dc5aeb.zip
@Jacksonized: modify builders that they can be used by Jackson
Diffstat (limited to 'src/core/lombok/javac/handlers')
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilder.java73
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilderRemove.java44
-rw-r--r--src/core/lombok/javac/handlers/HandleJacksonized.java198
-rw-r--r--src/core/lombok/javac/handlers/HandleSuperBuilder.java2
-rw-r--r--src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java44
5 files changed, 327 insertions, 34 deletions
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
index 2f000546..91a74d62 100644
--- a/src/core/lombok/javac/handlers/HandleBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -149,7 +149,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
if (!checkName("builderClassName", builderClassName, annotationNode)) return;
}
- deleteAnnotationIfNeccessary(annotationNode, Builder.class, "lombok.experimental.Builder");
+ // Do not delete the Builder annotation here, we need it for @Jacksonized.
JavacNode parent = annotationNode.up();
@@ -257,38 +257,9 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
returnType = cloneType(tdParent.getTreeMaker(), returnType, ast, annotationNode.getContext());
}
if (replaceNameInBuilderClassName) {
- String replStr = null;
- if (returnType instanceof JCFieldAccess) {
- replStr = ((JCFieldAccess) returnType).name.toString();
- } else if (returnType instanceof JCIdent) {
- Name n = ((JCIdent) returnType).name;
-
- for (JCTypeParameter tp : typeParams) {
- if (tp.name.equals(n)) {
- annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
- return;
- }
- }
- replStr = n.toString();
- } else if (returnType instanceof JCPrimitiveTypeTree) {
- replStr = returnType.toString();
- if (Character.isLowerCase(replStr.charAt(0))) {
- replStr = Character.toTitleCase(replStr.charAt(0)) + replStr.substring(1);
- }
- } else if (returnType instanceof JCTypeApply) {
- JCExpression clazz = ((JCTypeApply) returnType).clazz;
- if (clazz instanceof JCFieldAccess) {
- replStr = ((JCFieldAccess) clazz).name.toString();
- } else if (clazz instanceof JCIdent) {
- replStr = ((JCIdent) clazz).name.toString();
- }
- }
-
- if (replStr == null || replStr.isEmpty()) {
- // This shouldn't happen.
- System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass());
- replStr = td.name.toString();
- }
+ String replStr = returnTypeToBuilderClassName(annotationNode, td, returnType, typeParams);
+ if (replStr == null)
+ return;
builderClassName = builderClassName.replace("*", replStr);
replaceNameInBuilderClassName = false;
}
@@ -507,6 +478,42 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> {
}
}
}
+
+ static String returnTypeToBuilderClassName(JavacNode annotationNode, JCClassDecl td, JCExpression returnType, List<JCTypeParameter> typeParams) {
+ String replStr = null;
+ if (returnType instanceof JCFieldAccess) {
+ replStr = ((JCFieldAccess) returnType).name.toString();
+ } else if (returnType instanceof JCIdent) {
+ Name n = ((JCIdent) returnType).name;
+
+ for (JCTypeParameter tp : typeParams) {
+ if (tp.name.equals(n)) {
+ annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
+ return null;
+ }
+ }
+ replStr = n.toString();
+ } else if (returnType instanceof JCPrimitiveTypeTree) {
+ replStr = returnType.toString();
+ if (Character.isLowerCase(replStr.charAt(0))) {
+ replStr = Character.toTitleCase(replStr.charAt(0)) + replStr.substring(1);
+ }
+ } else if (returnType instanceof JCTypeApply) {
+ JCExpression clazz = ((JCTypeApply) returnType).clazz;
+ if (clazz instanceof JCFieldAccess) {
+ replStr = ((JCFieldAccess) clazz).name.toString();
+ } else if (clazz instanceof JCIdent) {
+ replStr = ((JCIdent) clazz).name.toString();
+ }
+ }
+
+ if (replStr == null || replStr.isEmpty()) {
+ // This shouldn't happen.
+ System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass());
+ replStr = td.name.toString();
+ }
+ return replStr;
+ }
private static String unpack(JCExpression expr) {
StringBuilder sb = new StringBuilder();
diff --git a/src/core/lombok/javac/handlers/HandleBuilderRemove.java b/src/core/lombok/javac/handlers/HandleBuilderRemove.java
new file mode 100644
index 00000000..6e59b40f
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleBuilderRemove.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+
+import lombok.Builder;
+import lombok.core.AlreadyHandledAnnotations;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+@ProviderFor(JavacAnnotationHandler.class)
+@HandlerPriority(65536)
+@AlreadyHandledAnnotations
+public class HandleBuilderRemove extends JavacAnnotationHandler<Builder> {
+ @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ deleteAnnotationIfNeccessary(annotationNode, Builder.class, "lombok.experimental.Builder");
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleJacksonized.java b/src/core/lombok/javac/handlers/HandleJacksonized.java
new file mode 100644
index 00000000..889dd27d
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleJacksonized.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2020 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+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.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.ListBuffer;
+
+import lombok.Builder;
+import lombok.ConfigurationKeys;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.handlers.HandlerUtil;
+import lombok.experimental.SuperBuilder;
+import lombok.extern.jackson.Jacksonized;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+import lombok.javac.JavacTreeMaker;
+
+/**
+ * This (javac) handler deals with {@code @Jacksonized} modifying the (already
+ * generated) {@code @Builder} or {@code @SuperBuilder} to conform to Jackson's
+ * needs for builders.
+ */
+@ProviderFor(JavacAnnotationHandler.class)
+@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated).
+public class HandleJacksonized extends JavacAnnotationHandler<Jacksonized> {
+
+ @Override public void handle(AnnotationValues<Jacksonized> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ JavacNode annotatedNode = annotationNode.up();
+ deleteAnnotationIfNeccessary(annotationNode, Jacksonized.class);
+
+ JavacNode tdNode;
+ if (annotatedNode.getKind() != Kind.TYPE)
+ tdNode = annotatedNode.up(); // @Jacksonized on a constructor or a static factory method.
+ else
+ tdNode = annotatedNode; // @Jacksonized on the class.
+ JCClassDecl td = (JCClassDecl) tdNode.get();
+
+ JavacNode builderAnnotationNode = findAnnotation(Builder.class, annotatedNode);
+ JavacNode superBuilderAnnotationNode = findAnnotation(SuperBuilder.class, annotatedNode);
+ if (builderAnnotationNode == null && superBuilderAnnotationNode == null) {
+ annotationNode.addWarning("@Jacksonized requires @Builder or @SuperBuilder for it to mean anything.");
+ return;
+ }
+
+ if (builderAnnotationNode != null && superBuilderAnnotationNode != null) {
+ annotationNode.addError("@Jacksonized cannot process both @Builder and @SuperBuilder on the same class.");
+ return;
+ }
+
+ boolean isAbstract = (td.mods.flags & Flags.ABSTRACT) != 0;
+ if (isAbstract) {
+ annotationNode.addError("Builders on abstract classes cannot be @Jacksonized (the builder would never be used).");
+ return;
+ }
+
+ AnnotationValues<Builder> builderAnnotation = builderAnnotationNode != null ?
+ createAnnotation(Builder.class, builderAnnotationNode) :
+ null;
+ AnnotationValues<SuperBuilder> superBuilderAnnotation = superBuilderAnnotationNode != null ?
+ createAnnotation(SuperBuilder.class, superBuilderAnnotationNode) :
+ null;
+
+ String setPrefix = builderAnnotation != null ?
+ builderAnnotation.getInstance().setterPrefix() :
+ superBuilderAnnotation.getInstance().setterPrefix();
+ String buildMethodName = builderAnnotation != null ?
+ builderAnnotation.getInstance().buildMethodName() :
+ superBuilderAnnotation.getInstance().buildMethodName();
+
+ JavacTreeMaker maker = annotatedNode.getTreeMaker();
+
+ // Now lets find the generated builder class.
+ String builderClassName = getBuilderClassName(ast, annotationNode, annotatedNode, td, builderAnnotation, maker);
+
+ JCClassDecl builderClass = null;
+ for (JCTree member : td.getMembers()) {
+ if (member instanceof JCClassDecl && ((JCClassDecl) member).getSimpleName().contentEquals(builderClassName)) {
+ builderClass = (JCClassDecl) member;
+ break;
+ }
+ }
+
+ if (builderClass == null) {
+ annotationNode.addError("Could not find @(Super)Builder's generated builder class for @Jacksonized processing. If there are other compiler errors, fix them first.");
+ return;
+ }
+
+ // Insert @JsonDeserialize on annotated class.
+ if (hasAnnotation("com.fasterxml.jackson.databind.annotation.JsonDeserialize", tdNode)) {
+ annotationNode.addError("@JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson.");
+ return;
+ }
+ JCExpression jsonDeserializeType = chainDots(annotatedNode, "com", "fasterxml", "jackson", "databind", "annotation", "JsonDeserialize");
+ JCExpression builderClassExpression = namePlusTypeParamsToTypeReference(maker, tdNode, annotationNode.toName(builderClassName), false, List.<JCTypeParameter>nil());
+ JCFieldAccess builderClassReference = maker.Select(builderClassExpression, annotatedNode.toName("class"));
+ JCExpression assign = maker.Assign(maker.Ident(annotationNode.toName("builder")), builderClassReference);
+ JCAnnotation annotationJsonDeserialize = maker.Annotation(jsonDeserializeType, List.of(assign));
+ td.mods.annotations = td.mods.annotations.append(annotationJsonDeserialize);
+
+ // Copy annotations from the class to the builder class.
+ List<JCAnnotation> copyableAnnotations = findJacksonAnnotationsOnClass(tdNode);
+ List<JCAnnotation> copiedAnnotations = copyAnnotations(copyableAnnotations);
+ builderClass.mods.annotations = builderClass.mods.annotations.appendList(copiedAnnotations);
+
+ // Insert @JsonPOJOBuilder on the builder class.
+ JCExpression jsonPOJOBuilderType = chainDots(annotatedNode, "com", "fasterxml", "jackson", "databind", "annotation", "JsonPOJOBuilder");
+ JCExpression withPrefixExpr = maker.Assign(maker.Ident(annotationNode.toName("withPrefix")), maker.Literal(setPrefix));
+ JCExpression buildMethodNameExpr = maker.Assign(maker.Ident(annotationNode.toName("buildMethodName")), maker.Literal(buildMethodName));
+ JCAnnotation annotationJsonPOJOBuilder = maker.Annotation(jsonPOJOBuilderType, List.of(withPrefixExpr, buildMethodNameExpr));
+ builderClass.mods.annotations = builderClass.mods.annotations.append(annotationJsonPOJOBuilder);
+
+ // @SuperBuilder? Make it package-private!
+ if (superBuilderAnnotationNode != null)
+ builderClass.mods.flags = builderClass.mods.flags & ~Flags.PRIVATE;
+
+ }
+
+ private String getBuilderClassName(JCAnnotation ast, JavacNode annotationNode, JavacNode annotatedNode, JCClassDecl td, AnnotationValues<Builder> builderAnnotation, JavacTreeMaker maker) {
+ String builderClassName = builderAnnotation != null ?
+ builderAnnotation.getInstance().builderClassName() : null;
+ if (builderClassName == null || builderClassName.isEmpty()) {
+ builderClassName = annotationNode.getAst().readConfiguration(ConfigurationKeys.BUILDER_CLASS_NAME);
+ if (builderClassName == null || builderClassName.isEmpty())
+ builderClassName = "*Builder";
+
+ JCMethodDecl fillParametersFrom = annotatedNode.get() instanceof JCMethodDecl ? (JCMethodDecl)annotatedNode.get() : null;
+ String replacement;
+ if (fillParametersFrom != null && !fillParametersFrom.getName().toString().equals("<init>")) {
+ // @Builder on a method: Use name of return type for builder class name.
+ JCExpression returnType = fillParametersFrom.restype;
+ List<JCTypeParameter> typeParams = fillParametersFrom.typarams;
+ if (returnType instanceof JCTypeApply) {
+ returnType = cloneType(maker, returnType, ast, annotationNode.getContext());
+ }
+ replacement = HandleBuilder.returnTypeToBuilderClassName(annotationNode, td, returnType, typeParams);
+ } else {
+ // @Builder on class or constructor: Use the class name.
+ replacement = td.name.toString();
+ }
+ builderClassName = builderClassName.replace("*", replacement);
+ }
+
+ if (builderAnnotation == null)
+ builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
+
+ return builderClassName;
+ }
+
+ private static List<JCAnnotation> findJacksonAnnotationsOnClass(JavacNode node) {
+ ListBuffer<JCAnnotation> result = new ListBuffer<JCAnnotation>();
+ for (JavacNode child : node.down()) {
+ if (child.getKind() == Kind.ANNOTATION) {
+ JCAnnotation annotation = (JCAnnotation) child.get();
+ for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
+ if (typeMatches(bn, node, annotation.annotationType)) {
+ result.append(annotation);
+ break;
+ }
+ }
+ }
+ }
+ return result.toList();
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
index 5f4f3c1d..cc70b333 100644
--- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java
+++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java
@@ -99,7 +99,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder");
CheckerFrameworkVersion cfv = getCheckerFrameworkVersion(annotationNode);
SuperBuilder superbuilderAnnotation = annotation.getInstance();
- deleteAnnotationIfNeccessary(annotationNode, SuperBuilder.class);
+ // Do not delete the SuperBuilder annotation here, we need it for @Jacksonized.
String builderMethodName = superbuilderAnnotation.builderMethodName();
String buildMethodName = superbuilderAnnotation.buildMethodName();
diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java b/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java
new file mode 100644
index 00000000..cca69729
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.javac.handlers;
+
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+
+import lombok.core.AlreadyHandledAnnotations;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.experimental.SuperBuilder;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+@ProviderFor(JavacAnnotationHandler.class)
+@HandlerPriority(65536)
+@AlreadyHandledAnnotations
+public class HandleSuperBuilderRemove extends JavacAnnotationHandler<SuperBuilder> {
+ @Override public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ deleteAnnotationIfNeccessary(annotationNode, SuperBuilder.class);
+ }
+}