Houria Marref
Houria Marref

Reputation: 31

Create custom annotation for Lombok 1.16.8

I'm writing a custom Lombok annotation, named @PrintedValue that generates method printing Hiii. For that, I created the annotation class as below:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface PrintValue {
    }

Then, I've created two handlers (javac and eclipse handlers). Here is my code:

@ProviderFor(EclipseAnnotationHandler.class) public class HandlePrintValue extends EclipseAnnotationHandler<PrintValue> {
    
    @Override public void handle(AnnotationValues<PrintValue> annotation, Annotation ast, EclipseNode annotationNode) {
        handleFlagUsage(annotationNode, ConfigurationKeys.PRINT_VALUE_FLAG_USAGE, "@PrintValue");
        EclipseHandlerUtil.unboxAndRemoveAnnotationParameter(ast, "onType", "@PrintValue(onType=", annotationNode);
        EclipseNode typeNode = annotationNode.up();
        MethodDeclaration printValMethod = createPrintVal(typeNode, annotationNode, annotationNode.get(), ast);
        injectMethod(typeNode, printValMethod);
        return;
    }
    
    private MethodDeclaration createPrintVal(EclipseNode typeNode, EclipseNode errorNode, ASTNode astNode, Annotation source) {
        TypeDeclaration typeDecl = (TypeDeclaration) typeNode.get();
        
        MethodDeclaration method = new MethodDeclaration(typeDecl.compilationResult);
        setGeneratedBy(method, astNode);
        method.annotations = null;
        method.modifiers = toEclipseModifier(AccessLevel.PUBLIC);
        method.typeParameters = null;
        method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0);
        method.selector = "printVal".toCharArray();
        method.arguments = null;
        method.binding = null;
        method.thrownExceptions = null;
        method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
        
        NameReference systemOutReference = createNameReference("System.out", source);
        Expression[] printlnArguments = new Expression[] {new StringLiteral("Hiiii".toCharArray(), astNode.sourceStart, astNode.sourceEnd, 0)};
        
        MessageSend printlnInvocation = new MessageSend();
        printlnInvocation.arguments = printlnArguments;
        printlnInvocation.receiver = systemOutReference;
        printlnInvocation.selector = "println".toCharArray();
        setGeneratedBy(printlnInvocation, source);
        
        method.bodyStart = method.declarationSourceStart = method.sourceStart = astNode.sourceStart;
        method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = astNode.sourceEnd;
        method.statements = new Statement[] {printlnInvocation};
        return method;
    }
    
}

@ProviderFor(JavacAnnotationHandler.class)
public class HandlePrintValue extends JavacAnnotationHandler<PrintValue> {
    
    @Override
    public void handle(AnnotationValues<PrintValue> annotation, JCAnnotation ast, JavacNode annotationNode) {
        // JavacHandlerUtil.markAnnotationAsProcessed(annotationNode,
        // HelloWorld.class);
        handleFlagUsage(annotationNode, ConfigurationKeys.PRINT_VALUE_FLAG_USAGE, "@PrintValue");
        Context context = annotationNode.getContext();
        Javac8BasedLombokOptions options = Javac8BasedLombokOptions.replaceWithDelombokOptions(context);
        options.deleteLombokAnnotations();
        JavacHandlerUtil.deleteAnnotationIfNeccessary(annotationNode, PrintValue.class);
        JavacHandlerUtil.deleteImportFromCompilationUnit(annotationNode, "lombok.AccessLevel");
        JavacNode typeNode = annotationNode.up();
        JCMethodDecl printValMethod = createPrintVal(typeNode);
        
        JavacHandlerUtil.injectMethod(typeNode, printValMethod);
        return;
    }
    
    private JCMethodDecl createPrintVal(JavacNode type) {
        JavacTreeMaker treeMaker = type.getTreeMaker();
        
        JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);
        List<JCTypeParameter> methodGenericTypes = List.<JCTypeParameter>nil();
        JCExpression methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID));
        Name methodName = type.toName("printVal");
        List<JCVariableDecl> methodParameters = List.<JCVariableDecl>nil();
        List<JCExpression> methodThrows = List.<JCExpression>nil();
        
        JCExpression printlnMethod = JavacHandlerUtil.chainDots(type, "System", "out", "println");
        List<JCExpression> printlnArgs = List.<JCExpression>of(treeMaker.Literal("val"));
        JCMethodInvocation printlnInvocation = treeMaker.Apply(List.<JCExpression>nil(), printlnMethod, printlnArgs);
        JCBlock methodBody = treeMaker.Block(0, List.<JCStatement>of(treeMaker.Exec(printlnInvocation)));
        
        JCExpression defaultValue = null;
        
        return treeMaker.MethodDef(modifiers, methodName, methodType, methodGenericTypes, methodParameters, methodThrows, methodBody, defaultValue);
    }
}

After building my project and adding the generated lombok jar to my project, I can call @PrintValue annotation but the printVal method is undefined for Student class. import lombok.PrintValue;

public @PrintValue class Student {
    private int age;
    private String name;
}

I tried enabling the annotation processing, but still having the same problem. if anyone can help me, I'll be thankfull.

jdk:8 eclipse 2018-09 lombok 1.16.8

Upvotes: 1

Views: 1414

Answers (1)

rzwitserloot
rzwitserloot

Reputation: 102903

The only way to add new lombok annotations is to fork lombok, and for your annotation typename (@PrintValue) and the handlers to be in a package that starts with 'lombok.'. The reasons for this are hard to explain in a succint answer: It's a combination of:

  • How lombok needs to integrate into the various relevant modules in the Equinox engine (the runtime modularization platform that eclipse runs on top of).
  • Lombok does not want to expose any methods/types except those that it wants, i.e., the lombok annotations and nothing more. In particular, if the ASM library (which lombok uses internally) is 'exposed', that would make it somewhat more difficult to use lombok with any project that also uses ASM. Lombok solved this problem, but it is part of the reason for why you have to fork to create annotations.

When I say 'fork lombok', I mean:

  • You end up with a single jar. This jar contains everything lombok contains, and also contains your PrintValue annotation and your handlers.
  • It is built by the lombok buildsystem, such that e.g. your annotation type is a class file in the jar file, but the handler class file ends up as a file entry in the jar with extension .SCL.lombok and not .class.

The easiest way to do this, by far, is to just get the lombok sources (clone the git repo), and create your annotation type in the same place lombok has them (/src/core/lombok/PrintValue.java), and create your handlers in the same place lombok has them (/src/core/lombok/eclipse/handlers/HandlePrintValue.java for example).

Then just ant dist and then java -jar dist/lombok.jar install /path/to/your/eclipse.

If you want to create your own sub-package of lombok for these, do a search for lombok.extern and lombok/extern - particularly in the build files (in the buildScripts subdir); if you want your annotation's full name to be e.g. lombok.houria.PrintValue, you need to tell lombok's build process that lombok.houria must not get the .SCL.lombok treatment, just like lombok.extern, lombok, and lombok.experimental don't.

Upvotes: 3

Related Questions