diff options
Diffstat (limited to 'src/core/lombok/eclipse')
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); + } +} |