aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/changelog.markdown3
-rw-r--r--src/core/lombok/ConfigurationKeys.java9
-rw-r--r--src/core/lombok/core/handlers/HandlerUtil.java14
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java35
-rwxr-xr-xsrc/core/lombok/eclipse/handlers/HandleBuilder.java56
-rw-r--r--src/core/lombok/eclipse/handlers/HandleJacksonized.java191
-rw-r--r--src/core/lombok/extern/jackson/Jacksonized.java64
-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.java201
-rw-r--r--src/core/lombok/javac/handlers/HandleSuperBuilder.java2
-rw-r--r--src/core/lombok/javac/handlers/HandleSuperBuilderRemove.java44
-rw-r--r--test/stubs/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java12
-rw-r--r--test/stubs/com/fasterxml/jackson/databind/annotation/JsonDeserialize.java12
-rw-r--r--test/stubs/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java10
-rw-r--r--test/transform/resource/after-delombok/BuilderDefaultsWarnings.java3
-rw-r--r--test/transform/resource/after-delombok/BuilderSingularNoAuto.java1
-rw-r--r--test/transform/resource/after-delombok/BuilderSingularNoAutoWithSetterPrefix.java1
-rw-r--r--test/transform/resource/after-delombok/JacksonizedBuilderComplex.java55
-rw-r--r--test/transform/resource/after-delombok/JacksonizedBuilderSimple.java50
-rw-r--r--test/transform/resource/after-delombok/JacksonizedSuperBuilderSimple.java55
-rw-r--r--test/transform/resource/after-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java48
-rw-r--r--test/transform/resource/after-ecj/JacksonizedBuilderComplex.java44
-rw-r--r--test/transform/resource/after-ecj/JacksonizedBuilderSimple.java38
-rw-r--r--test/transform/resource/after-ecj/JacksonizedSuperBuilderSimple.java44
-rw-r--r--test/transform/resource/after-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java36
-rw-r--r--test/transform/resource/before/JacksonizedBuilderComplex.java10
-rw-r--r--test/transform/resource/before/JacksonizedBuilderSimple.java12
-rw-r--r--test/transform/resource/before/JacksonizedSuperBuilderSimple.java12
-rw-r--r--test/transform/resource/before/JacksonizedSuperBuilderWithJsonDeserialize.java6
-rw-r--r--test/transform/resource/messages-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java.messages1
-rw-r--r--test/transform/resource/messages-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java.messages1
-rw-r--r--website/templates/features/experimental/Jacksonized.html53
-rw-r--r--website/templates/features/experimental/index.html4
34 files changed, 1173 insertions, 71 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index 04a81cd1..9f0e2242 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -1,13 +1,14 @@
Lombok Changelog
----------------
-### v.18.13 "Edgy Guinea Pig"
+### v1.18.13 "Edgy Guinea Pig"
* BREAKING CHANGE: mapstruct users should not add a dependency to lombok-mapstruct-binding. This solves compiling modules with lombok (and mapstruct).
* FEATURE: Similar to `@Builder`, you can now configure a `@SuperBuilder`'s 'setter' prefixes via `@SuperBuilder(setterPrefix = "set")` for example. We still discourage doing this. [Pull Request #2357](https://github.com/rzwitserloot/lombok/pull/2357).
* FEATURE: If using `@Synchronized("lockVar")`, if `lockVar` is referring to a static field, the code lombok generates no longer causes a warning about accessing a static entity incorrectly. [Issue #678](https://github.com/rzwitserloot/lombok/issues/678)
* BUGFIX: Using `@SuperBuilder` on a class that has some fairly convoluted generics usage would fail with 'Wrong number of type arguments'. [Issue #2359](https://github.com/rzwitserloot/lombok/issues/2359) [Pull Request #2362](https://github.com/rzwitserloot/lombok/pull/2362)
* BUGFIX: Various lombok annotations on classes nested inside enums or interfaces would cause errors in eclipse. [Issue #2369](https://github.com/rzwitserloot/lombok/issues/2369)
* BUGFIX: Trying to add `@ExtensionMethod`s with exactly 2 arguments would fail in eclipse. [Issue #1441](https://github.com/rzwitserloot/lombok/issues/1441) [Pull Request #2376](https://github.com/rzwitserloot/lombok/pull/2376) thanks to __@Rawi01__.
+* FEATURE: `@Jacksonized` on a `@Builder` or `@SuperBuilder` will configure [Jackson](https://github.com/FasterXML/jackson) to use this builder when deserializing. [Pull Request #2387](https://github.com/rzwitserloot/lombok/pull/2387). [@Jacksonized documentation](https://projectlombok.org/features/experimental/Jacksonized).
### v1.18.12 (February 1st, 2020)
* PLATFORM: Support for JDK13 (including `yield` in switch expressions, as well as delombok having a nicer style for arrow-style switch blocks, and text blocks).
diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java
index 8a028858..46cf7412 100644
--- a/src/core/lombok/ConfigurationKeys.java
+++ b/src/core/lombok/ConfigurationKeys.java
@@ -651,6 +651,15 @@ public class ConfigurationKeys {
* If set, <em>any</em> usage of {@code @WithBy} results in a warning / error.
*/
public static final ConfigurationKey<FlagUsageType> WITHBY_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.withBy.flagUsage", "Emit a warning or error if @WithBy is used.") {};
+
+ // ----- Jacksonized -----
+
+ /**
+ * lombok configuration: {@code lombok.jacksonized.flagUsage} = {@code WARNING} | {@code ERROR}.
+ *
+ * If set, <em>any</em> usage of {@code @Jacksonized} results in a warning / error.
+ */
+ public static final ConfigurationKey<FlagUsageType> JACKSONIZED_FLAG_USAGE = new ConfigurationKey<FlagUsageType>("lombok.jacksonized.flagUsage", "Emit a warning or error if @Jacksonized is used.") {};
// ----- Configuration System -----
diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java
index 4945adb9..94fd21d9 100644
--- a/src/core/lombok/core/handlers/HandlerUtil.java
+++ b/src/core/lombok/core/handlers/HandlerUtil.java
@@ -76,7 +76,7 @@ public class HandlerUtil {
return 43;
}
- public static final List<String> NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS;
+ public static final List<String> NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS;
static {
NONNULL_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"androidx.annotation.NonNull",
@@ -315,6 +315,18 @@ public class HandlerUtil {
"com.fasterxml.jackson.annotation.JsonProperty",
"com.fasterxml.jackson.annotation.JsonSetter",
}));
+ JACKSON_COPY_TO_BUILDER_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
+ "com.fasterxml.jackson.annotation.JsonFormat",
+ "com.fasterxml.jackson.annotation.JsonIgnoreProperties",
+ "com.fasterxml.jackson.annotation.JsonIgnoreType",
+ "com.fasterxml.jackson.annotation.JsonPropertyOrder",
+ "com.fasterxml.jackson.annotation.JsonRootName",
+ "com.fasterxml.jackson.annotation.JsonSubTypes",
+ "com.fasterxml.jackson.annotation.JsonTypeInfo",
+ "com.fasterxml.jackson.annotation.JsonTypeName",
+ "com.fasterxml.jackson.annotation.JsonView",
+ "com.fasterxml.jackson.databind.annotation.JsonNaming",
+ }));
}
/** Checks if the given name is a valid identifier.
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index ae899d00..4df7a90b 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -1957,12 +1957,16 @@ public class EclipseHandlerUtil {
result = addAnnotation(source, result, JAVAX_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0));
}
if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) {
- result = addAnnotation(source, result, LOMBOK_GENERATED, null);
+ result = addAnnotation(source, result, LOMBOK_GENERATED);
}
return result;
}
- static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode arg) {
+ static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) {
+ return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null);
+ }
+
+ static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode... args) {
char[] simpleName = annotationTypeFqn[annotationTypeFqn.length - 1];
if (originalAnnotationArray != null) for (Annotation ann : originalAnnotationArray) {
@@ -1984,20 +1988,23 @@ public class EclipseHandlerUtil {
QualifiedTypeReference qualifiedType = new QualifiedTypeReference(annotationTypeFqn, poss);
setGeneratedBy(qualifiedType, source);
Annotation ann;
- if (arg instanceof Expression) {
+ if (args != null && args.length == 1 && args[0] instanceof Expression) {
SingleMemberAnnotation sma = new SingleMemberAnnotation(qualifiedType, pS);
sma.declarationSourceEnd = pE;
- arg.sourceStart = pS;
- arg.sourceEnd = pE;
- sma.memberValue = (Expression) arg;
+ args[0].sourceStart = pS;
+ args[0].sourceEnd = pE;
+ sma.memberValue = (Expression) args[0];
setGeneratedBy(sma.memberValue, source);
ann = sma;
- } else if (arg instanceof MemberValuePair) {
+ } else if (args != null && args.length >= 1 && arrayHasOnlyElementsOfType(args, MemberValuePair.class)) {
NormalAnnotation na = new NormalAnnotation(qualifiedType, pS);
na.declarationSourceEnd = pE;
- arg.sourceStart = pS;
- arg.sourceEnd = pE;
- na.memberValuePairs = new MemberValuePair[] {(MemberValuePair) arg};
+ na.memberValuePairs = new MemberValuePair[args.length];
+ for (int i = 0; i < args.length; i++) {
+ args[i].sourceStart = pS;
+ args[i].sourceEnd = pE;
+ na.memberValuePairs[i] = (MemberValuePair) args[i];
+ }
setGeneratedBy(na.memberValuePairs[0], source);
setGeneratedBy(na.memberValuePairs[0].value, source);
na.memberValuePairs[0].value.sourceStart = pS;
@@ -2016,6 +2023,14 @@ public class EclipseHandlerUtil {
return newAnnotationArray;
}
+ private static boolean arrayHasOnlyElementsOfType(Object[] array, Class<?> clazz) {
+ for (Object element : array) {
+ if (!clazz.isInstance(element))
+ return false;
+ }
+ return true;
+ }
+
/**
* Generates a new statement that checks if the given local variable is null, and if so, throws a specified exception with the
* variable name as message.
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index 20bec50e..b8e88522 100755
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -376,32 +376,9 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
thrownExceptions = md.thrownExceptions;
nameOfStaticBuilderMethod = md.selector;
if (replaceNameInBuilderClassName) {
- char[] token;
- if (md.returnType instanceof QualifiedTypeReference) {
- char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
- token = tokens[tokens.length - 1];
- } else if (md.returnType instanceof SingleTypeReference) {
- token = ((SingleTypeReference) md.returnType).token;
- if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
- for (TypeParameter tp : typeParams) {
- if (Arrays.equals(tp.name, token)) {
- annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
- return;
- }
- }
- }
- } else {
- annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
+ char[] token = returnTypeToBuilderClassName(annotationNode, md, typeParams);
+ if (token == null)
return;
- }
-
- if (Character.isLowerCase(token[0])) {
- char[] newToken = new char[token.length];
- System.arraycopy(token, 1, newToken, 1, token.length - 1);
- newToken[0] = Character.toTitleCase(token[0]);
- token = newToken;
- }
-
builderClassName = builderClassName.replace("*", new String(token));
}
} else {
@@ -550,6 +527,35 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
}
}
}
+
+ static char[] returnTypeToBuilderClassName(EclipseNode annotationNode, MethodDeclaration md, TypeParameter[] typeParams) {
+ char[] token;
+ if (md.returnType instanceof QualifiedTypeReference) {
+ char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
+ token = tokens[tokens.length - 1];
+ } else if (md.returnType instanceof SingleTypeReference) {
+ token = ((SingleTypeReference) md.returnType).token;
+ if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
+ for (TypeParameter tp : typeParams) {
+ if (Arrays.equals(tp.name, token)) {
+ annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
+ return null;
+ }
+ }
+ }
+ } else {
+ annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
+ return null;
+ }
+
+ if (Character.isLowerCase(token[0])) {
+ char[] newToken = new char[token.length];
+ System.arraycopy(token, 1, newToken, 1, token.length - 1);
+ newToken[0] = Character.toTitleCase(token[0]);
+ token = newToken;
+ }
+ return token;
+ }
private static final char[] BUILDER_TEMP_VAR = {'b', 'u', 'i', 'l', 'd', 'e', 'r'};
private MethodDeclaration generateToBuilderMethod(CheckerFrameworkVersion cfv, boolean isStatic, String methodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, List<BuilderFieldData> builderFields, boolean fluent, ASTNode source, AccessLevel access, String prefix) {
diff --git a/src/core/lombok/eclipse/handlers/HandleJacksonized.java b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
new file mode 100644
index 00000000..90ee7582
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2013-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.eclipse.handlers;
+
+import static lombok.core.handlers.HandlerUtil.handleExperimentalFlagUsage;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+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.MemberValuePair;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.StringLiteral;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.mangosdk.spi.ProviderFor;
+
+import lombok.Builder;
+import lombok.ConfigurationKeys;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.AST.Kind;
+import lombok.core.handlers.HandlerUtil;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+import lombok.experimental.SuperBuilder;
+import lombok.extern.jackson.Jacksonized;
+
+/**
+ * This (ecj) handler deals with {@code @Jacksonized} modifying the (already
+ * generated) {@code @Builder} or {@code @SuperBuilder} to conform to Jackson's
+ * needs for builders.
+ */
+@ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated).
+public class HandleJacksonized extends EclipseAnnotationHandler<Jacksonized> {
+
+ 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");
+
+ @Override public void handle(AnnotationValues<Jacksonized> annotation, Annotation ast, EclipseNode annotationNode) {
+ handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized");
+
+ EclipseNode annotatedNode = annotationNode.up();
+
+ EclipseNode 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.
+ TypeDeclaration td = (TypeDeclaration) tdNode.get();
+
+ 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.");
+ return;
+ }
+
+ if (builderAnnotationNode != null && superBuilderAnnotationNode != null) {
+ annotationNode.addError("@Jacksonized cannot process both @Builder and @SuperBuilder on the same class.");
+ return;
+ }
+
+ boolean isAbstract = (td.modifiers & ClassFileConstants.AccAbstract) != 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();
+
+ // Now lets find the generated builder class.
+ EclipseNode builderClassNode = null;
+ TypeDeclaration builderClass = null;
+ String builderClassName = getBuilderClassName(ast, annotationNode, annotatedNode, td, builderAnnotation);
+ for (EclipseNode member : tdNode.down()) {
+ ASTNode astNode = member.get();
+ if (astNode instanceof TypeDeclaration && Arrays.equals(((TypeDeclaration)astNode).name, builderClassName.toCharArray())) {
+ builderClassNode = member;
+ builderClass = (TypeDeclaration) astNode;
+ 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;
+ }
+ long p = (long) ast.sourceStart << 32 | ast.sourceEnd;
+ TypeReference builderClassExpression = namePlusTypeParamsToTypeReference(builderClassNode, null, p);
+ ClassLiteralAccess builderClassLiteralAccess = new ClassLiteralAccess(td.sourceEnd, builderClassExpression);
+ MemberValuePair builderMvp = new MemberValuePair("builder".toCharArray(), td.sourceStart, td.sourceEnd, builderClassLiteralAccess);
+ td.annotations = addAnnotation(td, td.annotations, JSON_DESERIALIZE_ANNOTATION, builderMvp);
+
+ // Copy annotations from the class to the builder class.
+ Annotation[] copyableAnnotations = findJacksonAnnotationsOnClass(td, tdNode);
+ builderClass.annotations = copyAnnotations(builderClass, builderClass.annotations, copyableAnnotations);
+
+ // Insert @JsonPOJOBuilder on the builder class.
+ StringLiteral withPrefixLiteral = new StringLiteral(setPrefix.toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, 0);
+ MemberValuePair withPrefixMvp = new MemberValuePair("withPrefix".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, withPrefixLiteral);
+ StringLiteral buildMethodNameLiteral = new StringLiteral(buildMethodName.toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, 0);
+ MemberValuePair buildMethodNameMvp = new MemberValuePair("buildMethodName".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, buildMethodNameLiteral);
+ builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JSON_POJO_BUILDER_ANNOTATION, withPrefixMvp, buildMethodNameMvp);
+
+ // @SuperBuilder? Make it package-private!
+ if (superBuilderAnnotationNode != null)
+ builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate;
+ }
+
+ private String getBuilderClassName(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, TypeDeclaration td, AnnotationValues<Builder> builderAnnotation) {
+ 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";
+
+ MethodDeclaration fillParametersFrom = annotatedNode.get() instanceof MethodDeclaration ? (MethodDeclaration) annotatedNode.get() : null;
+ char[] replacement;
+ if (fillParametersFrom != null) {
+ // @Builder on a method: Use name of return type for builder class name.
+ replacement = HandleBuilder.returnTypeToBuilderClassName(annotationNode, fillParametersFrom, fillParametersFrom.typeParameters);
+ } else {
+ // @Builder on class or constructor: Use the class name.
+ replacement = td.name;
+ }
+ builderClassName = builderClassName.replace("*", new String(replacement));
+ }
+
+ if (builderAnnotation == null)
+ builderClassName += "Impl"; // For @SuperBuilder, all Jackson annotations must be put on the BuilderImpl class.
+
+ return builderClassName;
+ }
+
+ private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0];
+
+ private static Annotation[] findJacksonAnnotationsOnClass(TypeDeclaration td, EclipseNode node) {
+ if (td.annotations == null) return EMPTY_ANNOTATIONS_ARRAY;
+
+ List<Annotation> result = new ArrayList<Annotation>();
+ for (Annotation annotation : td.annotations) {
+ TypeReference typeRef = annotation.type;
+ if (typeRef != null && typeRef.getTypeName() != null) {
+ for (String bn : HandlerUtil.JACKSON_COPY_TO_BUILDER_ANNOTATIONS) {
+ if (typeMatches(bn, node, typeRef)) {
+ result.add(annotation);
+ break;
+ }
+ }
+ }
+ }
+ return result.toArray(EMPTY_ANNOTATIONS_ARRAY);
+ }
+}
diff --git a/src/core/lombok/extern/jackson/Jacksonized.java b/src/core/lombok/extern/jackson/Jacksonized.java
new file mode 100644
index 00000000..cf6678da
--- /dev/null
+++ b/src/core/lombok/extern/jackson/Jacksonized.java
@@ -0,0 +1,64 @@
+/*
+ * 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.extern.jackson;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import lombok.Builder;
+import lombok.experimental.SuperBuilder;
+
+/**
+ * The {@code @Jacksonized} annotation is an add-on annotation for
+ * {@code @}{@link Builder} and {@code @}{@link SuperBuilder}. It automatically
+ * configures the generated builder class to be used by Jackson's
+ * deserialization. It only has an effect if present at a context where there is
+ * also a {@code @Builder} or a {@code @SuperBuilder}; a warning is emitted
+ * otherwise.
+ * <p>
+ * In particular, the annotation does the following:
+ * <ul>
+ * <li>Configure Jackson to use the builder for deserialization using
+ * {@code @JsonDeserialize(builder=Foobar.FoobarBuilder[Impl].class)}
+ * on the class (where <em>Foobar</em> is the name of the annotated class).</li>
+ * <li>Copy Jackson-related configuration annotations (like
+ * {@code @JsonIgnoreProperties}) from the class to the builder class. This is
+ * necessary so that Jackson recognizes them when using the builder.</li>
+ * <li>Insert {@code @JsonPOJOBuilder(withPrefix="")} on the generated builder
+ * class to override Jackson's default prefix "with". If you configured a
+ * different prefix in lombok using {@code setterPrefix}, this value is used. If
+ * you changed the name of the {@code build()} method using using
+ * {@code buildMethodName}, this is also made known to Jackson.</li>
+ * <li>For {@code @SuperBuilder}, make the builder implementation class
+ * package-private.</li>
+ * </ul>
+ * This annotation does <em>not</em> change the behavior of the generated builder.
+ * A {@code @Jacksonized} {@code @SuperBuilder} remains fully compatible to
+ * regular {@code @SuperBuilder}s.
+ */
+@Target({TYPE, METHOD, CONSTRUCTOR})
+@Retention(SOURCE)
+public @interface Jacksonized {
+}
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..aff0bf63
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleJacksonized.java
@@ -0,0 +1,201 @@
+/*
+ * 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.core.handlers.HandlerUtil.handleExperimentalFlagUsage;
+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) {
+ handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized");
+
+ 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);
+ }
+}
diff --git a/test/stubs/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java b/test/stubs/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java
new file mode 100644
index 00000000..c90ef914
--- /dev/null
+++ b/test/stubs/com/fasterxml/jackson/annotation/JsonIgnoreProperties.java
@@ -0,0 +1,12 @@
+package com.fasterxml.jackson.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonIgnoreProperties {
+ public boolean ignoreUnknown() default false;
+}
diff --git a/test/stubs/com/fasterxml/jackson/databind/annotation/JsonDeserialize.java b/test/stubs/com/fasterxml/jackson/databind/annotation/JsonDeserialize.java
new file mode 100644
index 00000000..0964f82c
--- /dev/null
+++ b/test/stubs/com/fasterxml/jackson/databind/annotation/JsonDeserialize.java
@@ -0,0 +1,12 @@
+package com.fasterxml.jackson.databind.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonDeserialize {
+ public Class<?> builder() default Void.class;
+}
diff --git a/test/stubs/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java b/test/stubs/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java
new file mode 100644
index 00000000..4906dc58
--- /dev/null
+++ b/test/stubs/com/fasterxml/jackson/databind/annotation/JsonPOJOBuilder.java
@@ -0,0 +1,10 @@
+package com.fasterxml.jackson.databind.annotation;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonPOJOBuilder {
+ public String buildMethodName() default "build";
+ public String withPrefix() default "with";
+}
diff --git a/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java b/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java
index 3c84c71b..ae865570 100644
--- a/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java
+++ b/test/transform/resource/after-delombok/BuilderDefaultsWarnings.java
@@ -1,3 +1,5 @@
+import lombok.Builder;
+@Builder
public class BuilderDefaultsWarnings {
long x = System.currentTimeMillis();
final int y = 5;
@@ -78,6 +80,7 @@ public class BuilderDefaultsWarnings {
}
class NoBuilderButHasDefaults {
private final long z = 5;
+ @Builder
public NoBuilderButHasDefaults() {
}
@java.lang.SuppressWarnings("all")
diff --git a/test/transform/resource/after-delombok/BuilderSingularNoAuto.java b/test/transform/resource/after-delombok/BuilderSingularNoAuto.java
index ba6ddc52..4c9de174 100644
--- a/test/transform/resource/after-delombok/BuilderSingularNoAuto.java
+++ b/test/transform/resource/after-delombok/BuilderSingularNoAuto.java
@@ -1,4 +1,5 @@
import java.util.List;
+@lombok.Builder
class BuilderSingularNoAuto {
private List<String> things;
private List<String> widgets;
diff --git a/test/transform/resource/after-delombok/BuilderSingularNoAutoWithSetterPrefix.java b/test/transform/resource/after-delombok/BuilderSingularNoAutoWithSetterPrefix.java
index 857348d3..48b396eb 100644
--- a/test/transform/resource/after-delombok/BuilderSingularNoAutoWithSetterPrefix.java
+++ b/test/transform/resource/after-delombok/BuilderSingularNoAutoWithSetterPrefix.java
@@ -1,4 +1,5 @@
import java.util.List;
+@lombok.Builder(setterPrefix = "with")
class BuilderSingularNoAutoWithSetterPrefix {
private List<String> things;
private List<String> widgets;
diff --git a/test/transform/resource/after-delombok/JacksonizedBuilderComplex.java b/test/transform/resource/after-delombok/JacksonizedBuilderComplex.java
new file mode 100644
index 00000000..30f6b9af
--- /dev/null
+++ b/test/transform/resource/after-delombok/JacksonizedBuilderComplex.java
@@ -0,0 +1,55 @@
+//CONF: lombok.builder.className = Test*Name
+import java.util.List;
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderComplex.TestVoidName.class)
+class JacksonizedBuilderComplex {
+ private static <T extends Number> void testVoidWithGenerics(T number, int arg2, String arg3, JacksonizedBuilderComplex selfRef) {
+ }
+ @java.lang.SuppressWarnings("all")
+ @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "with", buildMethodName = "execute")
+ public static class TestVoidName<T extends Number> {
+ @java.lang.SuppressWarnings("all")
+ private T number;
+ @java.lang.SuppressWarnings("all")
+ private int arg2;
+ @java.lang.SuppressWarnings("all")
+ private String arg3;
+ @java.lang.SuppressWarnings("all")
+ private JacksonizedBuilderComplex selfRef;
+ @java.lang.SuppressWarnings("all")
+ TestVoidName() {
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderComplex.TestVoidName<T> withNumber(final T number) {
+ this.number = number;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderComplex.TestVoidName<T> withArg2(final int arg2) {
+ this.arg2 = arg2;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderComplex.TestVoidName<T> withArg3(final String arg3) {
+ this.arg3 = arg3;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderComplex.TestVoidName<T> withSelfRef(final JacksonizedBuilderComplex selfRef) {
+ this.selfRef = selfRef;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public void execute() {
+ JacksonizedBuilderComplex.<T>testVoidWithGenerics(this.number, this.arg2, this.arg3, this.selfRef);
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public java.lang.String toString() {
+ return "JacksonizedBuilderComplex.TestVoidName(number=" + this.number + ", arg2=" + this.arg2 + ", arg3=" + this.arg3 + ", selfRef=" + this.selfRef + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ public static <T extends Number> JacksonizedBuilderComplex.TestVoidName<T> builder() {
+ return new JacksonizedBuilderComplex.TestVoidName<T>();
+ }
+} \ No newline at end of file
diff --git a/test/transform/resource/after-delombok/JacksonizedBuilderSimple.java b/test/transform/resource/after-delombok/JacksonizedBuilderSimple.java
new file mode 100644
index 00000000..bd79df90
--- /dev/null
+++ b/test/transform/resource/after-delombok/JacksonizedBuilderSimple.java
@@ -0,0 +1,50 @@
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+@JsonIgnoreProperties(ignoreUnknown = true)
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder.class)
+class JacksonizedBuilderSimple<T> {
+ private final int noshow = 0;
+ private final int yes;
+ private List<T> also;
+ private int $butNotMe;
+ @java.lang.SuppressWarnings("all")
+ JacksonizedBuilderSimple(final int yes, final List<T> also) {
+ this.yes = yes;
+ this.also = also;
+ }
+ @java.lang.SuppressWarnings("all")
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "", buildMethodName = "build")
+ protected static class JacksonizedBuilderSimpleBuilder<T> {
+ @java.lang.SuppressWarnings("all")
+ private int yes;
+ @java.lang.SuppressWarnings("all")
+ private List<T> also;
+ @java.lang.SuppressWarnings("all")
+ JacksonizedBuilderSimpleBuilder() {
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> yes(final int yes) {
+ this.yes = yes;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> also(final List<T> also) {
+ this.also = also;
+ return this;
+ }
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedBuilderSimple<T> build() {
+ return new JacksonizedBuilderSimple<T>(this.yes, this.also);
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public java.lang.String toString() {
+ return "JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder(yes=" + this.yes + ", also=" + this.also + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ protected static <T> JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> builder() {
+ return new JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T>();
+ }
+}
diff --git a/test/transform/resource/after-delombok/JacksonizedSuperBuilderSimple.java b/test/transform/resource/after-delombok/JacksonizedSuperBuilderSimple.java
new file mode 100644
index 00000000..7fd3f8dc
--- /dev/null
+++ b/test/transform/resource/after-delombok/JacksonizedSuperBuilderSimple.java
@@ -0,0 +1,55 @@
+public class JacksonizedSuperBuilderSimple {
+ @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
+ @com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl.class)
+ public static class Parent {
+ int field1;
+ @java.lang.SuppressWarnings("all")
+ public static abstract class ParentBuilder<C extends JacksonizedSuperBuilderSimple.Parent, B extends JacksonizedSuperBuilderSimple.Parent.ParentBuilder<C, B>> {
+ @java.lang.SuppressWarnings("all")
+ private int field1;
+ @java.lang.SuppressWarnings("all")
+ protected abstract B self();
+ @java.lang.SuppressWarnings("all")
+ public abstract C build();
+ @java.lang.SuppressWarnings("all")
+ public B field1(final int field1) {
+ this.field1 = field1;
+ return self();
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public java.lang.String toString() {
+ return "JacksonizedSuperBuilderSimple.Parent.ParentBuilder(field1=" + this.field1 + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
+ @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "", buildMethodName = "build")
+ static final class ParentBuilderImpl extends JacksonizedSuperBuilderSimple.Parent.ParentBuilder<JacksonizedSuperBuilderSimple.Parent, JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl> {
+ @java.lang.SuppressWarnings("all")
+ private ParentBuilderImpl() {
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ protected JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl self() {
+ return this;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedSuperBuilderSimple.Parent build() {
+ return new JacksonizedSuperBuilderSimple.Parent(this);
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ protected Parent(final JacksonizedSuperBuilderSimple.Parent.ParentBuilder<?, ?> b) {
+ this.field1 = b.field1;
+ }
+ @java.lang.SuppressWarnings("all")
+ public static JacksonizedSuperBuilderSimple.Parent.ParentBuilder<?, ?> builder() {
+ return new JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl();
+ }
+ }
+ public static void test() {
+ Parent x = Parent.builder().field1(5).build();
+ }
+}
diff --git a/test/transform/resource/after-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java b/test/transform/resource/after-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java
new file mode 100644
index 00000000..df52db33
--- /dev/null
+++ b/test/transform/resource/after-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java
@@ -0,0 +1,48 @@
+@lombok.experimental.SuperBuilder
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize
+public class JacksonizedSuperBuilderWithJsonDeserialize {
+ int field1;
+ @java.lang.SuppressWarnings("all")
+ public static abstract class JacksonizedSuperBuilderWithJsonDeserializeBuilder<C extends JacksonizedSuperBuilderWithJsonDeserialize, B extends JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<C, B>> {
+ @java.lang.SuppressWarnings("all")
+ private int field1;
+ @java.lang.SuppressWarnings("all")
+ protected abstract B self();
+ @java.lang.SuppressWarnings("all")
+ public abstract C build();
+ @java.lang.SuppressWarnings("all")
+ public B field1(final int field1) {
+ this.field1 = field1;
+ return self();
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public java.lang.String toString() {
+ return "JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder(field1=" + this.field1 + ")";
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ private static final class JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl extends JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<JacksonizedSuperBuilderWithJsonDeserialize, JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl> {
+ @java.lang.SuppressWarnings("all")
+ private JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl() {
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ protected JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl self() {
+ return this;
+ }
+ @java.lang.Override
+ @java.lang.SuppressWarnings("all")
+ public JacksonizedSuperBuilderWithJsonDeserialize build() {
+ return new JacksonizedSuperBuilderWithJsonDeserialize(this);
+ }
+ }
+ @java.lang.SuppressWarnings("all")
+ protected JacksonizedSuperBuilderWithJsonDeserialize(final JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<?, ?> b) {
+ this.field1 = b.field1;
+ }
+ @java.lang.SuppressWarnings("all")
+ public static JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<?, ?> builder() {
+ return new JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl();
+ }
+} \ No newline at end of file
diff --git a/test/transform/resource/after-ecj/JacksonizedBuilderComplex.java b/test/transform/resource/after-ecj/JacksonizedBuilderComplex.java
new file mode 100644
index 00000000..8a06b34b
--- /dev/null
+++ b/test/transform/resource/after-ecj/JacksonizedBuilderComplex.java
@@ -0,0 +1,44 @@
+import java.util.List;
+import lombok.Builder;
+import lombok.extern.jackson.Jacksonized;
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderComplex.TestVoidName.class) class JacksonizedBuilderComplex {
+ public static @java.lang.SuppressWarnings("all") @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "with",buildMethodName = "execute") class TestVoidName<T extends Number> {
+ private @java.lang.SuppressWarnings("all") T number;
+ private @java.lang.SuppressWarnings("all") int arg2;
+ private @java.lang.SuppressWarnings("all") String arg3;
+ private @java.lang.SuppressWarnings("all") JacksonizedBuilderComplex selfRef;
+ @java.lang.SuppressWarnings("all") TestVoidName() {
+ super();
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderComplex.TestVoidName<T> withNumber(final T number) {
+ this.number = number;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderComplex.TestVoidName<T> withArg2(final int arg2) {
+ this.arg2 = arg2;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderComplex.TestVoidName<T> withArg3(final String arg3) {
+ this.arg3 = arg3;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderComplex.TestVoidName<T> withSelfRef(final JacksonizedBuilderComplex selfRef) {
+ this.selfRef = selfRef;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") void execute() {
+ JacksonizedBuilderComplex.<T>testVoidWithGenerics(this.number, this.arg2, this.arg3, this.selfRef);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
+ return (((((((("JacksonizedBuilderComplex.TestVoidName(number=" + this.number) + ", arg2=") + this.arg2) + ", arg3=") + this.arg3) + ", selfRef=") + this.selfRef) + ")");
+ }
+ }
+ JacksonizedBuilderComplex() {
+ super();
+ }
+ private static @Jacksonized @Builder(buildMethodName = "execute",setterPrefix = "with") <T extends Number>void testVoidWithGenerics(T number, int arg2, String arg3, JacksonizedBuilderComplex selfRef) {
+ }
+ public static @java.lang.SuppressWarnings("all") <T extends Number>JacksonizedBuilderComplex.TestVoidName<T> builder() {
+ return new JacksonizedBuilderComplex.TestVoidName<T>();
+ }
+}
diff --git a/test/transform/resource/after-ecj/JacksonizedBuilderSimple.java b/test/transform/resource/after-ecj/JacksonizedBuilderSimple.java
new file mode 100644
index 00000000..2e0a4e0c
--- /dev/null
+++ b/test/transform/resource/after-ecj/JacksonizedBuilderSimple.java
@@ -0,0 +1,38 @@
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+@lombok.extern.jackson.Jacksonized @JsonIgnoreProperties(ignoreUnknown = true) @lombok.Builder(access = lombok.AccessLevel.PROTECTED) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder.class) class JacksonizedBuilderSimple<T> {
+ protected static @java.lang.SuppressWarnings("all") @JsonIgnoreProperties(ignoreUnknown = true) @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "",buildMethodName = "build") class JacksonizedBuilderSimpleBuilder<T> {
+ private @java.lang.SuppressWarnings("all") int yes;
+ private @java.lang.SuppressWarnings("all") List<T> also;
+ @java.lang.SuppressWarnings("all") JacksonizedBuilderSimpleBuilder() {
+ super();
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> yes(final int yes) {
+ this.yes = yes;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> also(final List<T> also) {
+ this.also = also;
+ return this;
+ }
+ public @java.lang.SuppressWarnings("all") JacksonizedBuilderSimple<T> build() {
+ return new JacksonizedBuilderSimple<T>(this.yes, this.also);
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
+ return (((("JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder(yes=" + this.yes) + ", also=") + this.also) + ")");
+ }
+ }
+ private final int noshow = 0;
+ private final int yes;
+ private List<T> also;
+ private int $butNotMe;
+ @java.lang.SuppressWarnings("all") JacksonizedBuilderSimple(final int yes, final List<T> also) {
+ super();
+ this.yes = yes;
+ this.also = also;
+ }
+ protected static @java.lang.SuppressWarnings("all") <T>JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T> builder() {
+ return new JacksonizedBuilderSimple.JacksonizedBuilderSimpleBuilder<T>();
+ }
+}
diff --git a/test/transform/resource/after-ecj/JacksonizedSuperBuilderSimple.java b/test/transform/resource/after-ecj/JacksonizedSuperBuilderSimple.java
new file mode 100644
index 00000000..8227162a
--- /dev/null
+++ b/test/transform/resource/after-ecj/JacksonizedSuperBuilderSimple.java
@@ -0,0 +1,44 @@
+public class JacksonizedSuperBuilderSimple {
+ public static @lombok.extern.jackson.Jacksonized @lombok.experimental.SuperBuilder @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) @com.fasterxml.jackson.databind.annotation.JsonDeserialize(builder = JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl.class) class Parent {
+ public static abstract @java.lang.SuppressWarnings("all") class ParentBuilder<C extends JacksonizedSuperBuilderSimple.Parent, B extends JacksonizedSuperBuilderSimple.Parent.ParentBuilder<C, B>> {
+ private @java.lang.SuppressWarnings("all") int field1;
+ public ParentBuilder() {
+ super();
+ }
+ protected abstract @java.lang.SuppressWarnings("all") B self();
+ public abstract @java.lang.SuppressWarnings("all") C build();
+ public @java.lang.SuppressWarnings("all") B field1(final int field1) {
+ this.field1 = field1;
+ return self();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
+ return (("JacksonizedSuperBuilderSimple.Parent.ParentBuilder(field1=" + this.field1) + ")");
+ }
+ }
+ static final @java.lang.SuppressWarnings("all") @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true) @com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder(withPrefix = "",buildMethodName = "build") class ParentBuilderImpl extends JacksonizedSuperBuilderSimple.Parent.ParentBuilder<JacksonizedSuperBuilderSimple.Parent, JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl> {
+ private ParentBuilderImpl() {
+ super();
+ }
+ protected @java.lang.Override @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl self() {
+ return this;
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderSimple.Parent build() {
+ return new JacksonizedSuperBuilderSimple.Parent(this);
+ }
+ }
+ int field1;
+ protected @java.lang.SuppressWarnings("all") Parent(final JacksonizedSuperBuilderSimple.Parent.ParentBuilder<?, ?> b) {
+ super();
+ this.field1 = b.field1;
+ }
+ public static @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderSimple.Parent.ParentBuilder<?, ?> builder() {
+ return new JacksonizedSuperBuilderSimple.Parent.ParentBuilderImpl();
+ }
+ }
+ public JacksonizedSuperBuilderSimple() {
+ super();
+ }
+ public static void test() {
+ Parent x = Parent.builder().field1(5).build();
+ }
+} \ No newline at end of file
diff --git a/test/transform/resource/after-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java b/test/transform/resource/after-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java
new file mode 100644
index 00000000..a5677275
--- /dev/null
+++ b/test/transform/resource/after-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java
@@ -0,0 +1,36 @@
+public @lombok.extern.jackson.Jacksonized @lombok.experimental.SuperBuilder @com.fasterxml.jackson.databind.annotation.JsonDeserialize class JacksonizedSuperBuilderWithJsonDeserialize {
+ public static abstract @java.lang.SuppressWarnings("all") class JacksonizedSuperBuilderWithJsonDeserializeBuilder<C extends JacksonizedSuperBuilderWithJsonDeserialize, B extends JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<C, B>> {
+ private @java.lang.SuppressWarnings("all") int field1;
+ public JacksonizedSuperBuilderWithJsonDeserializeBuilder() {
+ super();
+ }
+ protected abstract @java.lang.SuppressWarnings("all") B self();
+ public abstract @java.lang.SuppressWarnings("all") C build();
+ public @java.lang.SuppressWarnings("all") B field1(final int field1) {
+ this.field1 = field1;
+ return self();
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") java.lang.String toString() {
+ return (("JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder(field1=" + this.field1) + ")");
+ }
+ }
+ private static final @java.lang.SuppressWarnings("all") class JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl extends JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<JacksonizedSuperBuilderWithJsonDeserialize, JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl> {
+ private JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl() {
+ super();
+ }
+ protected @java.lang.Override @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl self() {
+ return this;
+ }
+ public @java.lang.Override @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderWithJsonDeserialize build() {
+ return new JacksonizedSuperBuilderWithJsonDeserialize(this);
+ }
+ }
+ int field1;
+ protected @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderWithJsonDeserialize(final JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<?, ?> b) {
+ super();
+ this.field1 = b.field1;
+ }
+ public static @java.lang.SuppressWarnings("all") JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilder<?, ?> builder() {
+ return new JacksonizedSuperBuilderWithJsonDeserialize.JacksonizedSuperBuilderWithJsonDeserializeBuilderImpl();
+ }
+} \ No newline at end of file
diff --git a/test/transform/resource/before/JacksonizedBuilderComplex.java b/test/transform/resource/before/JacksonizedBuilderComplex.java
new file mode 100644
index 00000000..dec1cd3b
--- /dev/null
+++ b/test/transform/resource/before/JacksonizedBuilderComplex.java
@@ -0,0 +1,10 @@
+//CONF: lombok.builder.className = Test*Name
+import java.util.List;
+import lombok.Builder;
+import lombok.extern.jackson.Jacksonized;
+
+class JacksonizedBuilderComplex {
+ @Jacksonized
+ @Builder(buildMethodName = "execute", setterPrefix = "with")
+ private static <T extends Number> void testVoidWithGenerics(T number, int arg2, String arg3, JacksonizedBuilderComplex selfRef) {}
+}
diff --git a/test/transform/resource/before/JacksonizedBuilderSimple.java b/test/transform/resource/before/JacksonizedBuilderSimple.java
new file mode 100644
index 00000000..6da015ec
--- /dev/null
+++ b/test/transform/resource/before/JacksonizedBuilderSimple.java
@@ -0,0 +1,12 @@
+import java.util.List;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@lombok.extern.jackson.Jacksonized
+@JsonIgnoreProperties(ignoreUnknown = true)
+@lombok.Builder(access = lombok.AccessLevel.PROTECTED)
+class JacksonizedBuilderSimple<T> {
+ private final int noshow = 0;
+ private final int yes;
+ private List<T> also;
+ private int $butNotMe;
+}
diff --git a/test/transform/resource/before/JacksonizedSuperBuilderSimple.java b/test/transform/resource/before/JacksonizedSuperBuilderSimple.java
new file mode 100644
index 00000000..1e3bd0fc
--- /dev/null
+++ b/test/transform/resource/before/JacksonizedSuperBuilderSimple.java
@@ -0,0 +1,12 @@
+public class JacksonizedSuperBuilderSimple {
+ @lombok.extern.jackson.Jacksonized
+ @lombok.experimental.SuperBuilder
+ @com.fasterxml.jackson.annotation.JsonIgnoreProperties(ignoreUnknown = true)
+ public static class Parent {
+ int field1;
+ }
+
+ public static void test() {
+ Parent x = Parent.builder().field1(5).build();
+ }
+}
diff --git a/test/transform/resource/before/JacksonizedSuperBuilderWithJsonDeserialize.java b/test/transform/resource/before/JacksonizedSuperBuilderWithJsonDeserialize.java
new file mode 100644
index 00000000..0c80d1a6
--- /dev/null
+++ b/test/transform/resource/before/JacksonizedSuperBuilderWithJsonDeserialize.java
@@ -0,0 +1,6 @@
+@lombok.extern.jackson.Jacksonized
+@lombok.experimental.SuperBuilder
+@com.fasterxml.jackson.databind.annotation.JsonDeserialize
+public class JacksonizedSuperBuilderWithJsonDeserialize {
+ int field1;
+}
diff --git a/test/transform/resource/messages-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java.messages b/test/transform/resource/messages-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java.messages
new file mode 100644
index 00000000..5c69304f
--- /dev/null
+++ b/test/transform/resource/messages-delombok/JacksonizedSuperBuilderWithJsonDeserialize.java.messages
@@ -0,0 +1 @@
+1 @JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson. \ No newline at end of file
diff --git a/test/transform/resource/messages-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java.messages b/test/transform/resource/messages-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java.messages
new file mode 100644
index 00000000..5c69304f
--- /dev/null
+++ b/test/transform/resource/messages-ecj/JacksonizedSuperBuilderWithJsonDeserialize.java.messages
@@ -0,0 +1 @@
+1 @JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson. \ No newline at end of file
diff --git a/website/templates/features/experimental/Jacksonized.html b/website/templates/features/experimental/Jacksonized.html
new file mode 100644
index 00000000..fd2bfe68
--- /dev/null
+++ b/website/templates/features/experimental/Jacksonized.html
@@ -0,0 +1,53 @@
+<#import "../_features.html" as f>
+
+<@f.scaffold title="@Jacksonized" logline="Make Jackson use your builders.">
+ <@f.history>
+ <p>
+ <code>@Jacksonized</code> was introduced as experimental feature in lombok v1.18.14.
+ </p>
+ </@f.history>
+
+ <@f.overview>
+ <p>
+ The <code>@Jacksonized</code> annotation is an add-on annotation for <a href="/features/Builder"><code>@Builder</code></a> and <a href="/features/experimental/SuperBuilder"><code>@SuperBuilder</code></a>.
+ It automatically configures the generated builder class to be used by <a href="https://github.com/FasterXML/jackson">Jackson</a>'s deserialization.
+ It only has an effect if present at a context where there is also a <code>@Builder</code> or a <code>@SuperBuilder</code>; a warning is emitted otherwise.
+ </p><p>
+ Without <code>@Jacksonized</code>, you would have to customize your builder class(es).
+ With <code>@Jacksonized</code>, you can simply write something like this to let Jackson use the generated builder:<div class="snippet"><div class="java" align="left"><pre>
+@Jacksonized @Builder
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class JacksonExample {
+ private List&lt;Foo&gt; foos;
+}
+</pre></div></div>
+ </p><p>
+ This annotation is especially useful when deserializing into immutable (sub-)classes that only use <code>@SuperBuilder</code> to create instances.
+ With <code>@Jacksonized</code>, you do not have to put the complex <code>@SuperBuilder</code> class header into your code just to configure it for Jackson.
+ </p><p>
+ This annotation does <i>not</i> change the behavior of the generated builder.
+ A <code>@Jacksonized</code> <code>@SuperBuilder</code> remains fully compatible to regular <code>@SuperBuilder</code>s.
+ </p>
+ </@f.overview>
+
+ <@f.smallPrint>
+ <p>
+ In particular, the annotation does the following:
+ <ul>
+ <li>
+ Configure Jackson to use the builder for deserialization using <code>@JsonDeserialize(builder=<em>Foobar</em>.<em>Foobar</em>Builder[Impl].class))</code> on the class (where <em>Foobar</em> is the name of the annotated class).
+ (An error is emitted if such an annotation already exists.)
+ </li><li>
+ Copy Jackson-related configuration annotations (like <code>@JsonIgnoreProperties</code>) from the class to the builder class.
+ This is necessary so that Jackson recognizes them when using the builder.
+ </li><li>
+ Insert <code>@JsonPOJOBuilder(withPrefix="")</code> on the generated builder class to override Jackson's default prefix "with".
+ If you configured a different prefix in lombok using <code>setterPrefix</code>, this value is used.
+ If you changed the name of the <code>build()</code> method using using <code>buildMethodName</code>, this is also made known to Jackson.
+ </li><li>
+ For <code>@SuperBuilder</code>, make the builder implementation class package-private.
+ </li>
+ </ul>
+ </p>
+ </@f.smallPrint>
+</@f.scaffold>
diff --git a/website/templates/features/experimental/index.html b/website/templates/features/experimental/index.html
index b158d381..dc7870cf 100644
--- a/website/templates/features/experimental/index.html
+++ b/website/templates/features/experimental/index.html
@@ -71,6 +71,10 @@
<@main.feature title="@Tolerate" href="Tolerate">
Skip, jump, and forget! Make lombok disregard an existing method or constructor.
</@main.feature>
+
+ <@main.feature title="@Jacksonized" href="Jacksonized">
+ Make Jackson use your builders.
+ </@main.feature>
</div>
<@f.confKeys>