Skip to main content

My experience while writing an annotation processor - part I

Posted by ss141213 on November 27, 2007 at 9:40 AM PST

Recently I was writing an annotation processor that would generate a persistence.xml file when I compile my JPA entity classes. If you are a Java Persistence API user, then you may actually be interested in that annotation processor. OK, enough of shameless self promotion. Coming to the issue I want to discuss in this blog... While writing the annotation processor, certain things did not happen as per my expectation. I will share them with you so that you won't waste time as I did. All those issues are generic in nature.

1. Thread.getContextClassLoader is not the class loader you want to use
In my annotation processor's process() method, I wanted to load a resource (say) foo/bar.xml which is part of the same jar which houses the annotation processor itself. So, I wrote:
Thread.currentThread().getContextClassLoader().getResourceAsStream("foo/bar.xml").
This returns null. So, I had to change my code to:
this.getClass().getClassLoader().getResourceAsStream("foo/bar.xml").
May be it is by design, in that case I don't understand the rationale. Why the class loader of the thread executing my annotation processor is not able to load a resource which is part of a jar that is passed as -classpath to javac. More over, it's the same jar from where the annotation processor has been loaded!

2. Filer.getResource(SOURCE_PATH...) throws NPE if -sourcepath not specified
In my annotation processor, I wanted to locate a file in source path, so I wrote the following code:
FileObject fo = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", "META-INF/persistence.xml");
It throws NullPointerException if I do not specify -sourcepath option while invoking javac. I have two questions:
1) Is "." not the default value for source search path?
2) Instead of throwing an IOException as per the javadocs of getResource, why is it throwing an NPE?
The stack trace is available in the unanswered forum posting of mine.

3. Filer.createResource ignores directory part of relative URI
My annotation processor tries to create an output file called META-INF/persistence.xml in the classes output directory (i.e. -d option to javac). So, I wrote following code:
final FileObject fo = processingEnv.getFiler().createResource(
StandardLocation.CLASS_OUTPUT, // -d option to javac
"",
"META-INF/persistence.xml",
null);

This works as long as I pass -d option to javac. e.g., if I invoke:
javac -d . Foo.java
it creates ./META-INF/persistence.xml. But, if I invoke
javac Foo.java
it creates ./persistence.xml! Where is my META-INF directory gone?

4. javac does not print enough information about an exception
I am very surprised to find that javac does not print the stack trace of exceptions that are thrown by annotation processors, not even in -verbose mode. Nor does it tell me which annotation processor threw the exception. Given below is an example of javac output in -verbose mode:
Round 1:
input files: {sahoo.EmploymentRecord}
annotations: [javax.persistence.Embeddable]
last round: false
error: Exception thrown while constructing Processor object: java.lang.NullPointerException

My question in the JDK forum has fallen into deaf ears.
Of course, the work around is a simple one - just catch all exceptions in your code and log them before re-throwing. But, I think I should not have to do this.

5. Not able to debug annotation processor
It's my lack of knowledge. Until now, I never had to debug javac. Now that I am writing some code that gets called as part of javac, I felt the need to debug javac. I have still not found a way to do so. I tried passing the standard JVM debug options using "-J" option, but in vain. If you know how to attach a debugger to javac, please let me know. I use NetBeans IDE.

6. Maven not printing information printed using Messager
This is more of an issue about how maven-javac-plugin interacts with javac. I am using Maven 2.0.7. In my code, I use Messager object to report progress of my annotation processor. Those messages appear in my console when I run javac directly, but they appear nowhere when maven invokes the compiler using maven-compiler-plugin. Finally, I switched to using System.out, not a bright idea as it defeats the purpose of a Messager in the first place, but I just could not afford the extra time required to get to the root cause.

As usual, your comments are most welcome. Next time, I shall share the issues that I faced while using Maven.

Comments

Throwaway account, but I found a possible solution to ...

Throwaway account, but I found a possible solution to debugging processors: http://www.pingtimeout.fr/2012/10/debugging-annotation-processor-in-ever...

tl;dr is: use both "-J-Xdebug" and "-J-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" to debug the JVM that runs the annotation processor, and connect to it remotely with however your tooling allows.

In Eclipse you have to make it a plugin (I'm doing that right now - it works, and hot code swapping is *wonderful*. It's nearly worth the pain of setting up the plugin!) since I can't find any way to remotely debug with it. Maybe there is. It's probably a plugin.

-processorpath pain

Thanks for the post. I had a similar problem and your post helped me track it down. In my processor, Class.forName was failing. I know the classpath arg was good because javac found the classes while compiling other files. It was only the annotation processor ClassLoader that was unable to load the classes. I finally worked around the problem by removing the -processorpath argument from the javac command line, and simply adding the annotation processor to the normal classpath. I don't know if its a bug or feature of the annotation processor, but it is certainly non-intuitive, and undocumented as far as I can tell.