javaabstract-syntax-treeannotation-processinganonymous-class

How to find an anonymous class in the annotation processing environment?


I'm writing an annotation processor with Java 8.

Let's say I have a class like this somewhere in my project:

public class SampleClass {
    public void foo(int i) {
        new Runnable() {
            @Override
            public void run() {
                System.out.println(i);
            }
        };
    }
}

Somewhere else in my code, I have my annotation, containing the fully qualified name of SampleClass and the unique integer identifying the anonymous class once it's compiled (as in SampleClass$id).

To perform my task, I need to find the javax.lang.model.element.Element object representing that specific anonymous class. Or, barring that, at least getting a list of the Element objects representing the anonymous classes within a certain class would be a start. I'm pretty sure it's possible, since anonymous classes are referenced multiple times in the documentation of the javax.lang.model package, but I'm at a loss on how specifically you'd get them in my case.

I did try the most obvious things. Let's say I want to write a method that, given the fully-qualified name (fqn) of a class and a ProcessingEnvironment object (env), builds a list of all anonymous classes contained within that element (and, recursively, each of its child elements, and their child elements, and so on). This was my best guess on how to do it:

public List<Element> findAnonymousClasses(String fqn, ProcessingEnvironment env) {
    Element sampleClassElement = processingEnvironment.getElementUtils().getTypeElement(fqn);
    List<Element> anons = new ArrayList<>();
    sampleClassElement.getEnclosedElements()
        .forEach(e -> {
            if(e.getSimpleName().length() == 0)
                anons.add(e);
            anons.addAll(getAnonymousClasses(e, env));
    });
}

My reasoning being that getSimpleName(), according to its documentation:

If it represents an anonymous class or instance initializer, an empty name is returned.

and

An empty {@code Name} has a length of zero.

The code is obviously flawed, since it would returns the anonymous classes of within the methods of classes as well, but it should at least return something! Yet, apparently, that List is empty. Replacing e.getSimpleName().length() == 0 with e instanceof TypeElement && ((TypeElement) e).getNestingKind() == NestingKind.ANONYMOUS yields the same result.

Further inspection revealed that the problem is NOT with how I filter it, however. Rather, it would seem that Element.getEnclosedElements() never contains anonymous classes. How can I find them then? Surely, if there is handling for them coded in (i.e. NestingKind.ANONYMOUS), they must be obtainable somehow? Hoping anyone here can give me a nudge in the right direction.


Solution

  • To perform my task, I need to find the javax.lang.model.element.Element object representing that specific anonymous class.

    It doesn't exist. There's your problem.

    I have my annotation, containing the fully qualified name of SampleClass and the unique integer identifying the anonymous class once it's compiled (as in SampleClass$id).

    That name you are talking about (SampleClass$1) is the binary name and doesn't really apply to source code. The compilation process goes through stages. The stage where annotation processing occurs, predates the stage where names are treated in binary form. There is no way to refer to an anonymous inner class at the stage annotation processors execute.

    This goes rather deep. Javac creates a 'lazy' model, i.e., it only creates those nodes it needs to. This notably excludes method local types (and anonymous inner classes are a kind of method-local). It's not just 'not possible to access the TypeElement given the info you have - The TypeElement instance doesn't even exist at the time you want to access it.

    Javac is a complicated project and those nodes are used for various things. Thus, the conclusion 'hey, there is a Kind.ANONYMOUS, surely that means all anonymous classes are accessible if only I know how' doesn't work, even if, indeed, it's a plausible conclusion to draw.

    With hackery you can dig deeper and open the innards of javac, which may or may not allow you to do what you want. This is rocket science that takes months of work to write. I'd know. (I co-maintain Project Lombok which does just that). I doubt you want to do that.

    Perhaps take a step back: Why do you need this? Surely you have some actual problem that you wanted to solve, and you thought 'I will use an AP to access anonymous inner classes' was a good solution to whatever problem you wanted to solve.

    Unfortunately, it probably isn't. However, if you ask a new question (don't edit this one - fundamentally changing the nature of a question isn't what edit is for, I think), stating the problem you're trying to solve, perhaps there is an alternate solution you aren't aware of that would be a better fit.