aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/eclipse
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/lombok/eclipse')
-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
3 files changed, 247 insertions, 35 deletions
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);
+ }
+}