apt-jelly

  soothes irritation and swelling caused by abrasive annotation processing


   

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.


page design by Phlash