Java annotation processing with source code manipulation

user1128134 picture user1128134 · Feb 12, 2013 · Viewed 7.4k times · Source

I have been looking for a solution for below requirement -

  • Source files are written with Custom Annotation on a method
  • Method body needs a little variation based on the annotation.
  • Source file should not be changed, but input to compiler should be modified source file

I have looked at below APIs -

  • javax.annotation.processing - Annotation processing.
  • javax.lang.model.* - Language model used in annotation processing and Compiler Tree API
  • com.sun.source.* - Compiler Tree API.

I thought of designing this by following :

  1. Write an annotation processor
  2. Generate the compiler tree
  3. Edit the compiler tree at runtime without affecting origional source file
  4. Supply the tree to compiler

Compiler Tree API appears to be promissing where it gives access to com.sun.source.tree.MethodTree

However compiler Tree API appears to be Read Only. I can not figure out how to acomplish the steps 3 & 4

Is there any API for this which I can adopt to acomplish the task

NOTE: I am looking for only Source Code manipulation technique. No runtime byte code manipulation / AOP

Environment: Java 6

Answer

bhantol picture bhantol · Sep 26, 2014

You can do this as something below which will let you accomplish 3) and 4).

Example taken from java annotation processor example

@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )
@SupportedSourceVersion( SourceVersion.RELEASE_7 )
public class SimpleAnnotationProcessor extends AbstractProcessor {
  @Override
  public boolean process(final Set< ? extends TypeElement > annotations, 
      final RoundEnvironment roundEnv) {

    for( final Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) {
      if( element instanceof TypeElement ) {
        final TypeElement typeElement = ( TypeElement )element;

        for( final Element eclosedElement: typeElement.getEnclosedElements() ) {
       if( eclosedElement instanceof VariableElement ) {
           final VariableElement variableElement = ( VariableElement )eclosedElement;

           if( !variableElement.getModifiers().contains( Modifier.FINAL ) ) {
             processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR,
               String.format( "Class '%s' is annotated as @Immutable, 
                 but field '%s' is not declared as final", 
                 typeElement.getSimpleName(), variableElement.getSimpleName()            
               ) 
             );                     
           }
         }
       }
    }

    // Claiming that annotations have been processed by this processor 
    return true;
  }
}

Another way using projectlombok with custom handler.

Example built in handler from GitHub Project Lombok. This annotation adds try catch block

public class SneakyThrowsExample implements Runnable {
    @SneakyThrows(UnsupportedEncodingException.class)
    public String utf8ToString(byte[] bytes) {
        return new String(bytes, "UTF-8");
    }

    @SneakyThrows
    public void run() {
        throw new Throwable();
    }
}

This gets processed to

public String utf8ToString(byte[] bytes) {
    try {
        return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw Lombok.sneakyThrow(e);
    }
}

public void run() {
    try {
        throw new Throwable();
    } catch (Throwable t) {
        throw Lombok.sneakyThrow(t);
    }
}

You can find the Handler code on the same Github/lombok site.