A few hours ago I noticed some comments on Joe Darcy's blog post about annotation processors.
I originally lost interest in annotations very quickly when I realised that annotation processors didn't have access to the full AST. I fiddled around with ANTLR a little, with CheckStyle, PMD and Findbugs' APIs to see what I could do. Not a lot, as it turned out. ANTLR looked promising, but as I recall, the language file for Java 5 was a bit flaky at the time. The others didn't provide me with complete types, so I couldn't detect things like code that tried to find a StringBuffer in a List of Strings. I gave up for a while, went off and learned to be productive in Haskell, and Lisp, and stuck my head into closures, which I'm very optimistic about.
I followed some links (about 6 deep) from Joe's blog, and came across the compiler API and compiler tree APIs. I have used it before, when I got some novice students to write their own compiling Java editor (that was fun), but I hadn't realised that you could get access to the actual compiler types - the standard API is rather thin. All you need to do is cast the task returned by the Java compiler, into a com.sun.source.util.JavacTask, and you're away.
I'm already mildly familiar with the compiler, having fiddled with its source a month or two back. I was trying to add a language feature that Stephen Colebourne, Stefan Schulz and I had been discussing, but we dropped the idea before I finished (well, before I really got started). Today I decided to attack a problem that used to catch me out when reading novices' code - an if statement with a semi-colon on the end, which just means 'if this, do nothing' and it executes the next statement regardless of the condition.
Javac uses the visitor pattern incredibly heavily, to a scale I've never seen anywhere else. I like it, but it caught me out temporarily. I don't really write subclasses anymore, I always use an alternative where practical. So I subclassed TreeScanner, which has nice 'return null;' implementations of every method, just ready for me to override. Which I didn't. Though I thought I was. The @Override annotation saved me when I realised..
Because I have my IDE set to Java version 5, and I wanted to run this code against version 6, I was just editing in vim and running from bash in Cygwin. No warnings about unused methods there then..
The short-term solution is to make sure I always remember @Override. The long-term, unlikely solution, is to persuade the javac project lead, sometime after Java 7, to accept my patch that makes these visitors more 'functional', so that instead of overriding, you give them a closure to invoke. The other advantage there is that you would be able to provide real booleans, rather than Booleans, etc.
There was only one more gotcha. Not only do I not subclass, I also don't use null very often, so I'd forgotten how to deal with boolean operators and null (I had to use Boolean, not boolean, thanks to generics). See if you can reduce my bestOf method into a chain of ternary operator calls. I couldn't do that without getting a NPE. I might investigate later.
There were no more gotchas. It was actually really easy, 30 minutes end to end. I included an 'empty' if statement in the source itself for testing. I think my next task should be to find places where @Override should be..
After all that fanfare, the code wouldn't fit in my blog's layout particularly well, so I'm afraid you'll have to click here to see it.