Introduction
APT-Jelly is an engine for generating artifacts (e.g. source code, config files) from Java source code. APT-Jelly provides a template-oriented
approach to artifact generation (as opposed to raw string-writing to a stream) by providing an interface for Sun's Annotation Processing Tool (APT)
to your favorite templating engine. Currently, APT-Jelly has direct support for both Jakarta
Commons Jelly and Freemarker, and indirect support for Velocity
(through the merging capabilities of Jelly). APT-Jelly allows developers to take full advantage of the latest Java syntax and features including
metadata (annotations), generics, and typesafe enums.
Background
So you've migrated to Java 5 and have started annotating your declarations. You're thrilled with the concept of defining your own attribute types and
that annotation syntax errors are caught at compile-time. Wow, this sure beats having to use JavaDoc-based attributes that your IDE doesn't recognize
and where errors can't be caught until much later. Sun has even provided a tool for processing your beautifully-annotated source code that you can use
to generate config files or even more beautiful source code! Can life get any better?
Okay, so let's take a look at this remarkable tool that Sun has provided. What's it called? Ah. The
Annotation Processing Tool (APT). Of course. How creative. Just for starters,
let's figure out how to use it to generate a class that prints to System.out a list of all classes and methods in our source
base. So let's start with the getting started guide.
The getting started guide says that we're going to need to implement an AnnotationProcessorFactory and an
AnnotationProcessor. When the processor is invoked, we have to create our source file in code and use an associated
PrintWriter on which we invoke a bunch of printlns.
You've got to be kidding:
package net.sf.jelly.apt.examples;
import java.util.Collection;
import java.util.Set;
import java.util.Collections;
import java.io.IOException;
import java.io.PrintWriter;
import com.sun.mirror.apt.AnnotationProcessorFactory;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
public class ClassAndMethodPrinterAnnotationProcessorFactory implements AnnotationProcessorFactory {
public Collection<String> supportedOptions() {
return Collections.EMPTY_LIST;
}
public Collection<String> supportedAnnotationTypes() {
return Collections.EMPTY_LIST;
}
public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> atds, AnnotationProcessorEnvironment env) {
return new ClassAndMethodPrinterAnnotationProcessor(env);
}
private class ClassAndMethodPrinterAnnotationProcessor implements AnnotationProcessor {
AnnotationProcessorEnvironment env;
public ClassAndMethodPrinterAnnotationProcessor(AnnotationProcessorEnvironment env) {
this.env = env;
}
public void process() {
try {
PrintWriter writer = env.getFiler().createSourceFile("net.sf.jelly.apt.examples.ClassAndMethodPrinter");
writer.println("package net.sf.jelly.apt.examples;");
writer.println();
writer.println("public class ClassAndMethodPrinter {");
writer.println();
writer.println(" public static void main(String[] args) {");
for (TypeDeclaration typeDeclaration : env.getTypeDeclarations()) {
writer.println(String.format(" System.out.println(\"Class: %s\");",
typeDeclaration.getQualifiedName()));
for (MethodDeclaration methodDeclaration : typeDeclaration.getMethods()) {
writer.println(String.format(" System.out.println(\"Method: %s.%s\");",
typeDeclaration.getQualifiedName(),
methodDeclaration.getSimpleName()));
}
}
writer.println(" }");
writer.println();
writer.println("}");
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
This is like having to write your favorite J2EE webapp while being limited to just servlets and their associated writers! Can you imagine not
having the convenience of something like JSP to help separate the HTML from the Java code?
APT-Jelly presents an alternative:
<@javaSource name="net.sf.jelly.apt.examples.ClassAndMethodPrinter">
package net.sf.jelly.apt.examples;
public class ClassAndMethodPrinter {
public static void main(String[] args) {
<@forAllTypes var="type">
System.out.println("${type.qualifiedName}");
<@forAllMethods var="method">
System.out.println("${type.qualifiedName}.${method.simpleName}");
</@forAllMethods>
</@forAllTypes>
}
}
</@javaSource>
Example Freemarker template for the ClassAndMethodPrinter. Click here to see
the corresponding Jelly template.
Now isn't that much nicer? You don't need to implement any interfaces, you don't need to fully swallow Sun's Mirror API, and the format of your code
is cleaner and reflects better the output. Not to mention that you can fully leverage the capabilities and features of the templating engine....
Documentation
If you'd like to give APT-Jelly a try, you'll want to refer to the following documentation:
Project Scope
APT-Jelly is intended to be used by Java developers who need to generate artifacts (e.g. configuration files, Java source code) from existing Java source
code at pre-compile time. Use of APT-Jelly requires Sun's
Annotation Processing Tool (APT) that ships with the
JDK 5.0. While APT was designed to leverage J2SE
metadata (annotations) framework (see
JSR 175), it should be noted that APT can be used to parse and process any Java source code (including
source code based on previous versions of the JDK). So you don't have to be using JDK 5.0 annotations, but if you need to leverage other
annotation frameworks (like JavaDoc-based attribute notations), APT-Jelly might not be the tool for you. (You may want to consider
commons-attributes or XDoclet.)
If you are developing on JDK 5.0 and would like to leverage its annotation framework, you still may not need to generate artifacts at pre-compile time.
Consider using the reflection API to lookup metadata at
runtime.
Having said that, there could be a variety of reasons you may need to generate artifacts from source code. Perhaps you'd like to generate a configuration
file based off the annotations of your declarations to provide backwards-compatability with a third-party tool or library. Or perhaps you'd like to
generate some source code based off existing source code. Whatever be the reason, if you want to generate artifacts at pre-compile time (and especially if
you want to leverage the JDK 5.0 metadata framework), you'll probably find this project interesting.
About APT-Jelly
APT-Jelly is an open-source project, licenced under the Apache License, version 2.0. It was
designed and built by Ryan Heaton, working for familysearch.org. Comments,
enhancements or bug fixes to the project are welcome.
|