diff options
author | Jan Rieke <it@janrieke.de> | 2020-02-16 15:17:20 +0100 |
---|---|---|
committer | Jan Rieke <it@janrieke.de> | 2020-03-03 13:01:48 +0100 |
commit | 8be4b0ffe6b5e5f89e8bb3d94e87d0aa98dc5aeb (patch) | |
tree | 1fc16ba2d1b271f1c2e06eeb2120a71d74ff75ef /src/core/lombok/javac/handlers | |
parent | 59aa6761115cb912d5223870be348b70e0e9fd9c (diff) | |
download | lombok-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')
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); + } +} |