NetBeans 6 delivers great updates to the Matisse GUI builder. Spend a few minutes with Roman Strobl and get an expert briefing on what's new and what has changed. (sponsored)
In this, the third and final installation of Andres' Introduction to Groovy series, you learn about how Groovy handles variable numbers of arguments, named parameters, currying, and more about Groovy operators. Including, some new operators.
Swing Fuse (actually just Fuse), is a framework designed to make it easier to create your own custom desktop components. In this article, Daniel Spiewak shows you how to get started and provides sample source code you can download.
Willam Louth shows how he uses JXInsight Probes to investigate probable performance issues with code bases that he is not familiar with. He also highlights possible pitfalls in creating a benchmark, as well as in the analysis of results.
Today I want to cover some of the tools that really make APT powerful. So far what I have shown you is what APT can do given individual source files that it is processing. What I haven't shown is how this all fits in to the grand-scheme of the APT lifecycle. In the end, APT is there so a Java developer can interact with source-file annotations during the compilation process to produce results different from the base compiler. Let's look at the APT lifecycle in a little more detail.
APT Rounds
As I have said previously, APT is like a wrapper around the Sun Java compiler. With that wrapping, APT brings with it a circular lifecycle. APT has the ability to produce new source files (as I will show momentarily) - because of that, each round of APT processing that produces new source files, will effectively queue up another round of APT processing. Therefore, it is perfectly valid for source files produced
by APT
to have annotations in it to be processed by a subsequent round of APT processing - I will show how to produce source files from APT shortly. The APT processing cycle can be monitored by using the listener API built-in to APT - using the
com.sun.mirror.apt.RoundCompleteListener
API, and the accompanying
com.sun.mirror.apt.RoundCompleteEvent
and
com.sun.mirror.apt.RoundState
result objects. The theory presented by the documentation is that by intercepting these events you can be given the opportunity to close off any files you have opened or write any trailing characters/text before APT completes. That's as good of an example as I can come up with today, so rather than worry about showing the details, I'll just show you how you add a listener of your own:
AnnotationProcessorEnvironment environment = // ... (get from factory)
environment.addListener([listener here]);
Piece of cake. Ok - remember the round-complete listener API because you will surely need it - but for now, on to more important things.
The Messager
The
com.sun.mirror.apt.Messager
is the first (and more simple) of the two helper classes that allow you to interact with the compilation process. The messager simply allows you to act like your own compile-time checker. Remember the
@Override
annotation? That is a perfect example of something you as a developer might want to check for. Ok, not
@Override
specifically (the compiler already does that), but something similar. The
Messager
is very easy to use if you find such a case, and want to report a notification, warning, or error. Be aware, if you emit an error, it WILL stop compilation! Here is our note processor from yesterday using the Messager notification system:
package com.javalobby.tnt.apt;
import java.util.*;
import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.*;
import com.sun.mirror.type.AnnotationType;
import com.sun.mirror.type.TypeMirror;
import com.sun.mirror.util.*;
publicclass NoteAnnotationProcessor implements AnnotationProcessor {
private AnnotationProcessorEnvironment environment;
private TypeDeclaration noteDeclaration;
private DeclarationVisitor declarationVisitor;
public NoteAnnotationProcessor(AnnotationProcessorEnvironment env) {
environment = env;
// get the annotation type declaration for our 'Note' annotation.
noteDeclaration = environment
.getTypeDeclaration("com.javalobby.tnt.annotation.Note");
declarationVisitor = new AllDeclarationsVisitor();
}
publicvoid process() {
Collection<TypeDeclaration> declarations = environment.getTypeDeclarations();
DeclarationVisitor scanner = DeclarationVisitors
.getSourceOrderDeclarationScanner(declarationVisitor,
DeclarationVisitors.NO_OP);
for (TypeDeclaration declaration : declarations) {
declaration.accept(scanner); // invokes the processing on the scanner.
}
}
privateclass AllDeclarationsVisitor extends SimpleDeclarationVisitor {
@Override
publicvoid visitDeclaration(Declaration arg0) {
Collection<AnnotationMirror> annotations = arg0.getAnnotationMirrors();
for (AnnotationMirror mirror : annotations) {
// if the mirror in this iteration is for our note declaration...
if (mirror.getAnnotationType().getDeclaration().equals(noteDeclaration)) {
// print out the goodies.
SourcePosition position = mirror.getPosition();
Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror
.getElementValues();
StringBuffer notice = new StringBuffer();
notice.append(noteDeclaration);
notice.append("Values: ");
for (Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : values
.entrySet()) {
AnnotationTypeElementDeclaration elemDecl = entry.getKey();
AnnotationValue value = entry.getValue();
notice.append("\n" + elemDecl + "=" + value);
}
environment.getMessager().printNotice(position, notice.toString());
}
}
}
}
}
... and here is the new (more informative) output:
MyClass.java:4: Note: com.javalobby.tnt.annotation.NoteValues:
value()="This class isn\'t finished"
priority()=com.javalobby.tnt.annotation.Priority.HIGH
public class MyClass {
^
MyClass.java:7: Note: com.javalobby.tnt.annotation.NoteValues:
value()="This field isn\'t finished"
private String fieldA;
^
MyClass.java:10: Note: com.javalobby.tnt.annotation.NoteValues:
value()="Constructor isn\'t finished"
public MyClass() {
^
MyClass.java:15: Note: com.javalobby.tnt.annotation.NoteValues:
value()="methodA isn\'t finished"
public void methodA() {
^
1 warning
Neat!
The Filer
Now the real fun begins. The
com.sun.mirror.apt.Filter
is the main hook into the round-lifecycle of APT, and essentially gives you four methods to help you write to the output pass:
PrintWriter createTextFile(Filer.Location, String pkg, File relPath, String charSet) throws IOException
- This method allows you to create a text file to become part of the result given various location-based parameters. Write to the print writer and close it when done. This would be a good tool to achieve something similar to what
XDoclet + Hibernate
can do regarding hibernate mapping files. Incidentally, Hibernate already supplies support for Java 5 annotations and mapping:
Hibernate and Java 5 Annotations
.
OutputStream createBinaryFile(Filer.Location, String pkg, File relPath, String charSet) throws IOException
- This method is just like the text counterpart above, but allows you to write in binary.
PrintWriter createSourceFile(String name) throws IOException
- This method allows you to write out source code that will be processed (and compiled) by APT in a subsequent pass. The name passed in should be, as the documentation puts it,
"[the] canonical (fully qualified) name of the principal type being declared in this file"
- so in other-words, "my.package.MyNewClass".
OutputStream createClassFile(String name) throws IOException
- This is another counterpart method, and allows you to write an already finished class file (perhaps you are using byte code engineering libraries?).
The
Filer.Location
argument in the first two helps determine whether you want the text/binary file to be placed with the intermediate source files produced by APT, or with the completed class files. The
pkg
parameter allows you to make them relative to a particular package, and the
File
argument allows you to apply a file-path after the relative package path. Finally, the
charSet
argument allows you to change the encoding if you wish.
Here is a code snippet that (very crudely) manufactures some source-code with the filer - keep in mind, you would typically modify the source in some way using the annotations you run into - I've omitted that part for simplicities sake here:
I hope this little foray into APT has sparked your interest in annotation processing, and will give you insight on where to go from here. As usual, I'm expecting a lively discussion from the community! Have a great weekend!
Re: Apt: Even More Compile-Time Annotation Processing with Java
What if the source file you want to write to already exists? i.e. is it possible to modify source files from apt, perhaps even the one it is operating on?
Apt: Even More Compile-Time Annotation Processing with Java
At 6:02 PM on Mar 25, 2005, R.J. Lorimer wrote:
Fresh Jobs for Developers Post a job opportunity
Part 3
Hello again, part three of my apt tip series is underway. If you haven't already, please check out parts one and two:
APT: Compile-Time Annotation Processing with Java
APT: More Compile-Time Annotation Processing with Java
Today I want to cover some of the tools that really make APT powerful. So far what I have shown you is what APT can do given individual source files that it is processing. What I haven't shown is how this all fits in to the grand-scheme of the APT lifecycle. In the end, APT is there so a Java developer can interact with source-file annotations during the compilation process to produce results different from the base compiler. Let's look at the APT lifecycle in a little more detail.
APT Rounds
As I have said previously, APT is like a wrapper around the Sun Java compiler. With that wrapping, APT brings with it a circular lifecycle. APT has the ability to produce new source files (as I will show momentarily) - because of that, each round of APT processing that produces new source files, will effectively queue up another round of APT processing. Therefore, it is perfectly valid for source files produced by APT to have annotations in it to be processed by a subsequent round of APT processing - I will show how to produce source files from APT shortly. The APT processing cycle can be monitored by using the listener API built-in to APT - using the
com.sun.mirror.apt.RoundCompleteListenerAPI, and the accompanyingcom.sun.mirror.apt.RoundCompleteEventandcom.sun.mirror.apt.RoundStateresult objects. The theory presented by the documentation is that by intercepting these events you can be given the opportunity to close off any files you have opened or write any trailing characters/text before APT completes. That's as good of an example as I can come up with today, so rather than worry about showing the details, I'll just show you how you add a listener of your own:Piece of cake. Ok - remember the round-complete listener API because you will surely need it - but for now, on to more important things.
The Messager
The
com.sun.mirror.apt.Messageris the first (and more simple) of the two helper classes that allow you to interact with the compilation process. The messager simply allows you to act like your own compile-time checker. Remember the@Overrideannotation? That is a perfect example of something you as a developer might want to check for. Ok, not@Overridespecifically (the compiler already does that), but something similar. TheMessageris very easy to use if you find such a case, and want to report a notification, warning, or error. Be aware, if you emit an error, it WILL stop compilation! Here is our note processor from yesterday using the Messager notification system:package com.javalobby.tnt.apt; import java.util.*; import com.sun.mirror.apt.*; import com.sun.mirror.declaration.*; import com.sun.mirror.type.AnnotationType; import com.sun.mirror.type.TypeMirror; import com.sun.mirror.util.*; public class NoteAnnotationProcessor implements AnnotationProcessor { private AnnotationProcessorEnvironment environment; private TypeDeclaration noteDeclaration; private DeclarationVisitor declarationVisitor; public NoteAnnotationProcessor(AnnotationProcessorEnvironment env) { environment = env; // get the annotation type declaration for our 'Note' annotation. noteDeclaration = environment .getTypeDeclaration("com.javalobby.tnt.annotation.Note"); declarationVisitor = new AllDeclarationsVisitor(); } public void process() { Collection<TypeDeclaration> declarations = environment.getTypeDeclarations(); DeclarationVisitor scanner = DeclarationVisitors .getSourceOrderDeclarationScanner(declarationVisitor, DeclarationVisitors.NO_OP); for (TypeDeclaration declaration : declarations) { declaration.accept(scanner); // invokes the processing on the scanner. } } private class AllDeclarationsVisitor extends SimpleDeclarationVisitor { @Override public void visitDeclaration(Declaration arg0) { Collection<AnnotationMirror> annotations = arg0.getAnnotationMirrors(); for (AnnotationMirror mirror : annotations) { // if the mirror in this iteration is for our note declaration... if (mirror.getAnnotationType().getDeclaration().equals(noteDeclaration)) { // print out the goodies. SourcePosition position = mirror.getPosition(); Map<AnnotationTypeElementDeclaration, AnnotationValue> values = mirror .getElementValues(); StringBuffer notice = new StringBuffer(); notice.append(noteDeclaration); notice.append("Values: "); for (Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : values .entrySet()) { AnnotationTypeElementDeclaration elemDecl = entry.getKey(); AnnotationValue value = entry.getValue(); notice.append("\n" + elemDecl + "=" + value); } environment.getMessager().printNotice(position, notice.toString()); } } } } }... and here is the new (more informative) output:
MyClass.java:4: Note: com.javalobby.tnt.annotation.NoteValues: value()="This class isn\'t finished" priority()=com.javalobby.tnt.annotation.Priority.HIGH public class MyClass { ^ MyClass.java:7: Note: com.javalobby.tnt.annotation.NoteValues: value()="This field isn\'t finished" private String fieldA; ^ MyClass.java:10: Note: com.javalobby.tnt.annotation.NoteValues: value()="Constructor isn\'t finished" public MyClass() { ^ MyClass.java:15: Note: com.javalobby.tnt.annotation.NoteValues: value()="methodA isn\'t finished" public void methodA() { ^ 1 warningNeat!
The Filer
Now the real fun begins. The
com.sun.mirror.apt.Filteris the main hook into the round-lifecycle of APT, and essentially gives you four methods to help you write to the output pass:PrintWriter createTextFile(Filer.Location, String pkg, File relPath, String charSet) throws IOException- This method allows you to create a text file to become part of the result given various location-based parameters. Write to the print writer and close it when done. This would be a good tool to achieve something similar to what XDoclet + Hibernate can do regarding hibernate mapping files. Incidentally, Hibernate already supplies support for Java 5 annotations and mapping: Hibernate and Java 5 Annotations .OutputStream createBinaryFile(Filer.Location, String pkg, File relPath, String charSet) throws IOException- This method is just like the text counterpart above, but allows you to write in binary.PrintWriter createSourceFile(String name) throws IOException- This method allows you to write out source code that will be processed (and compiled) by APT in a subsequent pass. The name passed in should be, as the documentation puts it, "[the] canonical (fully qualified) name of the principal type being declared in this file" - so in other-words, "my.package.MyNewClass".OutputStream createClassFile(String name) throws IOException- This is another counterpart method, and allows you to write an already finished class file (perhaps you are using byte code engineering libraries?).The
Filer.Locationargument in the first two helps determine whether you want the text/binary file to be placed with the intermediate source files produced by APT, or with the completed class files. Thepkgparameter allows you to make them relative to a particular package, and theFileargument allows you to apply a file-path after the relative package path. Finally, thecharSetargument allows you to change the encoding if you wish.Here is a code snippet that (very crudely) manufactures some source-code with the filer - keep in mind, you would typically modify the source in some way using the annotations you run into - I've omitted that part for simplicities sake here:
String className = "com.javalobby.tnt.annotation.MyClass"; StringBuffer sourceString = new StringBuffer(); sourceString.append("package com.javalobby.tnt.annotation;"); sourceString.append("public class MyClass { }"); try { PrintWriter writer = environment.getFiler().createSourceFile(className); writer.append(sourceString); } catch(IOException e) { environment.getMessager().printError("Unable to create: " + className); }I hope this little foray into APT has sparked your interest in annotation processing, and will give you insight on where to go from here. As usual, I'm expecting a lively discussion from the community! Have a great weekend!
Until next time,
R.J. Lorimer
rj -at- javalobby.org
http://www.coffee-bytes.com
1 replies so far (
Post your own)
Re: Apt: Even More Compile-Time Annotation Processing with Java
What if the source file you want to write to already exists? i.e. is it possible to modify source files from apt, perhaps even the one it is operating on?