Search code examples
javaannotationsabstract-syntax-tree

Java AST form annotation from String


I'm trying to create annotations from inner string which contains other annotations.

This is SimpleAnnotation that should be processed:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface SimpleAnnotation {
    String[] value() default {};
}

This is annotated class

@SimpleAnnotation({
                   "@com.demo.annotations.Entity(name = \"simple_name\")", 
                   "@com.demo.annotations.CustomAnnotation"
                 })
public class Simple {
}

The compilation result of annotated class should be

@com.demo.annotations.Entity(name = "simple_name")
@com.demo.annotations.CustomAnnotation                     
public class Simple {
}

I've tried to use custom annotation processor that processes class declaration. It gets class modifiers with annotations and analyzes derived annotation as tree

    public class SimpleAnnotationProcessor extends AbstractProcessor {

        private Messager messager;

        private Trees trees;
        private ChangeTranslator visitor;

        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(SimpleAnnotation.class.getCanonicalName());
        }

        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.RELEASE_8;
        }


        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
        ............
        }

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(SimpleAnnotation.class);

            for (Element element : elementsAnnotatedWith) {
                Name simpleName = element.getSimpleName();
                System.out.println(simpleName);
                messager.printMessage(Diagnostic.Kind.NOTE, "found with annotation " + simpleName);

                JCTree tree = (JCTree) trees.getTree(element);

                visitor.setElement(element);

                tree.accept(visitor);
            }

            return true;
        }


       public class ChangeTranslator extends TreeTranslator {

        private JavacProcessingEnvironment javacProcessingEnvironment;
        private TreeMaker treeMaker;
        private Messager messager;

        public ChangeTranslator(JavacProcessingEnvironment javacProcessingEnvironment, TreeMaker treeMaker, Messager messager) {
            this.javacProcessingEnvironment = javacProcessingEnvironment;
            this.treeMaker = treeMaker;
            this.messager = messager;
        }    

        @Override
        public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
            super.visitClassDef(jcClassDecl);

            if (isNeedProcessing(jcClassDecl)) {

                JCTree.JCModifiers modifiers = jcClassDecl.getModifiers();

                List<JCTree.JCAnnotation> annotations = modifiers.getAnnotations();

                List<JCTree.JCAnnotation> jcAnnotations = List.nil();

                for (JCTree.JCAnnotation a : annotations) {
                    if (a.getAnnotationType().toString().contains(SimpleAnnotation.class.getSimpleName())) {                           
                        List<JCTree.JCExpression> arguments = a.getArguments();

                        for (JCTree.JCExpression arg : arguments) {

                            JCTree.JCNewArray expressions = (JCTree.JCNewArray) ((JCTree.JCAssign) arg).getExpression();
                            List<JCTree.JCExpression> elems = expressions.elems;
                            for (JCTree.JCExpression expression : elems) {                                    


                               // parse annotation from string
                               String value = (String) ((JCTree.JCLiteral) expression).getValue();

                          // e.g com.demo.annotations.Entity
                          String substringName = value.trim().substring(1, 28);
                                Class<? extends Class> aClass = null;

                                try {
                                    aClass = Class.forName(substringName);                                      

                                } catch (ClassNotFoundException e) {
                                    e.printStackTrace();
                                }



                                // 1 - attribute to create annotation from 
                                Attribute attribute = new Attribute.Compound(aClass, null);

                                // 2 - place where annotation should be created
                                treeMaker.Annotation(attribute);
                            }
                        }
                    }
                }

                modifiers.annotations = jcAnnotations;

                System.out.println(result);
            }

        }

        private boolean isNeedProcessing(JCTree.JCClassDecl jcClassDecl) {
            return jcClassDecl.getModifiers().toString().contains("@SimpleAnnotation");
        }

    }
}

The issue is to get information from Class type to create com.sun.tools.javac.code.Type.ClassType which is used to create JCAnnotation.

Any help is appreciated.


Solution

  • public class SimpleAnnotationProcessor extends AbstractProcessor {
    
        ...
        @Override
        public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
            ...
            ListBuffer<JCTree.JCExpression> params = new ListBuffer<JCTree.JCExpression>();
            params.append(treeMaker.Assign(treeMaker.Ident(names.fromString("name")), treeMaker.Literal("simple_name")));
            JCTree.JCAnnotation entity = treeMaker.Annotation(select("com.demo.annotations.Entity"), params.toList());
            JCTree.JCAnnotation customAnnotation = treeMaker.Annotation(select("com.demo.annotations.CustomAnnotation"), List.nil());
            // then append annotation to modifiers of you want
            // NOTE: List<A>.append() method will return a new List in javac
            ...
        }
    
        JCTree.JCExpression select(String path) {
            JCTree.JCExpression expression = null;
            int i = 0;
            for (String split : path.split("\\.")) {
                if (i == 0)
                    expression = treeMaker.Ident(names.fromString(split));
                else {
                    expression = treeMaker.Select(expression, names.fromString(split));
                }
                i++;
            }
    
            return expression;
        }
    }
    

    Hope it helps those who have the same problem