Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Discover the class of a methodinvocation in the Annotation Processor for java

I am writing some tools for our build system to enforce some strict calling conventions on methods belonging to classes containing certain annotations.

I'm using the Compiler Tree API...

What i'm wondering is when traversing the 'tree', how can you tell the type of class/interface for a MethodInvocation.

I'm subclassing TreePathScanner with :

@Override
public Object visitMethodInvocation(MethodInvocationTree node, Trees trees) {

}

I'm hoping theres a way to tell the type of the class(or interface) that you're trying to invoke the method on. Am I going about this the wrong way? Thanks for any ideas...

like image 338
Vincent Avatar asked Jun 30 '09 23:06

Vincent


People also ask

What is @interface annotation in Java?

Annotation is defined like a ordinary Java interface, but with an '@' preceding the interface keyword (i.e., @interface ). You can declare methods inside an annotation definition (just like declaring abstract method inside an interface). These methods are called elements instead.

What is annotation process in Java?

"Annotation Processing" is a hook into the compile process of the java compiler, to analyse the source code for user defined annotations and handle then (by producing compiler errors, compiler warning, emitting source code, byte code ...). API reference: javax. annotation.

Which type of statements are annotations in the Java code?

Java annotations are metadata (data about data) for our program source code. They provide additional information about the program to the compiler but are not part of the program itself. These annotations do not affect the execution of the compiled program. Let's take an example of @Override annotation.


1 Answers

There are a couple of issues here. You can either be interested in knowing the Java type of the method invocation receiver or just knowing the class on the method is invoked. Java information is more informative as it gives you generic types as well, e.g. List<String> while Elements would only provide you with the class, e.g. List<E>.

Getting the Element

To get the Element of the class the method is invoked on, you can do the following:


  MethodInvocationTree node = ...;
  Element method =
        TreeInfo.symbol((JCTree)node.getMethodSelect());
  TypeElement invokedClass = (TypeElement)method.getEnclosingElement();

Corner cases:

1. invokedClass might be a superclass of the receiver type. So running the snippet on new ArrayList<String>.equals(null) would return AbstractList rather than ArrayList, since equals() is implemented in AbstractList not ArrayList.

2. When handling array invocations, e.g. new int[].clone(), you would get TypeElement of class Array.

Getting the actual type

To get the type, there is no direct way for determining it what the receiver type is. There is some complexity in handling method invocations within inner classes where the receiver is not given explicitly (e.g. unlike OuterClass.this.toString()). Here is a sample implementation:


  MethodInvocationTree node = ...;
  TypeMirror receiver;
  if (methodSel.getKind() == Tree.Kind.MEMBER_SELECT) {
    ExpressionTree receiver = ((MemberSelectTree)methodSel).getExpression();
    receiverType = ((JCTree)receiver).type;
  } else if (methodSel.getKind() == Tree.Kind.IDENTIFIER) {
    // need to resolve implicit this, which is described in
    //  JLS3 15.12.1 and 15.9.2

    // A bit too much work that I don't want to work on now
    // Look at source code of
    //   Attr.visitApply(JCMethodInvocation)
    //   resolveImplicitThis(DiagnosticPosition, Env, Type)
  } else
    throw new AssertionError("Unexpected type: " + methodSel.getKind());

Note:

The receiver type needs to be TypeMirror not DeclaredType unfortunately. When calling new int[5].clone(), receiver would be an ArrayType of int[], which is more informative than the previous method.

Getting it to run

Both of the previous methods require the compiler to resolve the type information for the classes. In usual circumstances, the compiler only resolve the types for method declarations but not the bodies. Hence, the methods described earlier would return null instead.

To have the compiler resolve the type information, you can do one of the following ways:

1. Use AbstractTypeProcessor class that just got added to the compiler repository for JDK 7. Check out the work on JSR 308 and their compiler. While the work is mainly on annotated types, it might be useful for. The compiler allows you to use the provided class in a backward compatible manner with Java 5.

This approach allows you to write processors that get invoked just like your current processors.

2. Use JavacTask instead and call JavacTask.analyze(). Look at the main method of this javac test to see how to invoke your visitor on the classes.

This approach makes your processor look more like an analysis tool rather than a plug-in to the compiler, as you would need to invoke it directly rather than have it be a regular process.

like image 101
notnoop Avatar answered Sep 18 '22 05:09

notnoop