I'm writing a Java compiler plugin using the jdk.compiler module and Java 15 with preview features enabled. The jdk.compiler module, introduced in Java 9, according to Oracle, contains all the APIs we used to have in java 8's tools.jar except for the com.sun.tools.javac.tree package. What I want to do is find all the classes that are annotated with my annotation and get the value() attribute, in this case of type Class<?>.
Plugin's code:
public class ExtensionMethodPlugin implements Plugin{
@Override
public String getName() {
return "ExtensionMethodPlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new TaskListener() {
@Override
public void started(TaskEvent event) {
}
@Override
public void finished(TaskEvent event) {
if (event.getKind() != TaskEvent.Kind.PARSE) {
return;
}
event.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitClass(ClassTree node, Void aVoid) {
var trees = Trees.instance(task);
var targetClass = InstrumentationUtils.findExtensionTargetForClass(node, trees, event.getCompilationUnit());
if (targetClass.isEmpty()) {
return super.visitClass(node, aVoid);
}
var methods = node
.getMembers()
.stream()
.filter(e -> e instanceof MethodTree)
.map(e -> (MethodTree) e)
.filter(tree -> tree.getReturnType() != null)
.filter(e -> InstrumentationUtils.shouldInstrumentMethod(e, targetClass.get()))
.collect(Collectors.toList());
throw new RuntimeException("Not implemented");
}
}, null);
}
});
}
}
InstrumentationUtils' code:
@UtilityClass
public class InstrumentationUtils {
public Optional<Class<?>> findExtensionTargetForClass(ClassTree node, Trees trees, CompilationUnitTree tree) {
return node
.getModifiers()
.getAnnotations()
.stream()
.filter(a -> Extension.class.getSimpleName().equals(a.getAnnotationType().toString()))
.findFirst()
.map(e -> InstrumentationUtils.findConstantTargetClassInAnnotationTree(e, trees, tree));
}
private Class<?> findConstantTargetClassInAnnotationTree(AnnotationTree tree, Trees trees, CompilationUnitTree compilationUnitTree) {
return tree
.getArguments()
.stream()
.filter(entry -> entry.getKind() == Tree.Kind.ASSIGNMENT)
.findFirst()
.map(e -> (AssignmentTree) e)
.map(e -> classForName(e, trees, compilationUnitTree))
.orElseThrow(() -> new RuntimeException("Cannot compile: Missing constant class target in annotation!"));
}
private Class<?> classForName(AssignmentTree tree, Trees trees, CompilationUnitTree compilationUnitTree){
//Don't know how to move from here
}
}
Annnotation's code:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Extension {
Class<?> value();
}
The only solutions I've found involve using the JCTree classes, but these, as I said previously, are no longer available. I do know I could force the export of that package in the jdk.compiler module quite easily, but I would prefer to find a better way. I've tried to use a TreeScanner, but this leads nowhere:
tree.accept(new TreeScanner<Class<?>, Void>() {
// b.class
@Override
public Class<?> visitAssignment(AssignmentTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
//.class
@Override
public Class<?> visitMemberSelect(MemberSelectTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
// class ???
@Override
public Class<?> visitIdentifier(IdentifierTree node, Void unused) {
trees.printMessage(Diagnostic.Kind.WARNING, node.getName(), tree, compilationUnitTree);
return super.visitIdentifier(node, unused);
}
}, null);
}
}, null);
}
}, null);
Thanks in advance!
When all you need to do, is to “find all the classes that are annotated with my annotation and get the value() attribute”, then you don’t need the tree API from the
jdk.compiler
module at all, the annotation processor API contained in thejava.compiler
module is sufficient and much simpler to handle.Here is a self-contained example:
This prints
Of course, this functionality can be combined with features from the
jdk.compiler
extension when needed.