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.
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