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;
@ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-512)
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();
else
tdNode = annotatedNode;
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();
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;
}
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);
Annotation[] copyableAnnotations = findJacksonAnnotationsOnClass(td, tdNode);
builderClass.annotations = copyAnnotations(builderClass, builderClass.annotations, copyableAnnotations);
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);
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) {
replacement = HandleBuilder.returnTypeToBuilderClassName(annotationNode, fillParametersFrom, fillParametersFrom.typeParameters);
} else {
replacement = td.name;
}
builderClassName = builderClassName.replace("*", new String(replacement));
}
if (builderAnnotation == null)
builderClassName += "Impl";
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);
}
}