Blog der Heimetli Software AG

Ein einfacher Annotation Processor

Nach einem langen Kampf habe ich es geschafft, einen Annotation Processor zum laufen zu bringen! Eigentlich ist das gar nicht so schwierig, aber die elenden Details haben mich viel Zeit gekostet...

Die Annotation

Die Annotation ist so einfach wie möglich gehalten, damit auch der Prozessor nicht viel machen muss:

/******************************************************************************/
/*                                                                            */
/*                                                             FILE: Tip.java */
/*                                                                            */
/*       An extremely simple annotation for the annotation processor          */
/*       ===========================================================          */
/*                                                                            */
/*       V1.00    27-JAN-2015  Te                                             */
/*                                                                            */
/******************************************************************************/

package pack ;

import java.lang.annotation.* ;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)

/**
 * An extremely simple annotation for the annotation processor
 */
public @interface Tip
{
  String value() ;
}

Das Attribut heisst nicht zufällig value. Das ist das Standard-Attribut und kann ohne Namen angegeben werden.

Der Code für den Prozessor

Der Prozessor ist abgeleitet von AbstractProcessor, damit nur gerade eine Methode nötig ist:

/******************************************************************************/
/*                                                                            */
/*                                                    FILE: TipProcessor.java */
/*                                                                            */
/*       Prints the value of the Tip annotation                               */
/*       ======================================                               */
/*                                                                            */
/*       V1.00    27-JAN-2015  Te                                             */
/*                                                                            */
/******************************************************************************/

package ch.heimetli.tip ;

import java.lang.annotation.* ;
import java.util.* ;
import javax.annotation.processing.* ;
import javax.lang.model.* ;
import javax.lang.model.element.* ;

import pack.Tip ;

/**
 * Prints the value of the Tip annotation.
 *
 * This class is instatiated by javac. The compiler
 * will call process whenever something happens that
 * might be of interest for the annotation processor.
 *
 * Based on AbstractProcessor to keep the code as
 * simple as possible.
 */
@SupportedAnnotationTypes("pack.Tip")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TipProcessor extends AbstractProcessor
{
   public TipProcessor()
   {
      super() ;
   }

   /**
    * Called by javac when it processes something related
    * to annotations
    */
   @Override
   public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment env )
   {
      // Print the value of the Tip annotations
      for( Element element : env.getElementsAnnotatedWith(Tip.class) )
      {
         System.out.println( element.getSimpleName() + " -> " + element.getAnnotation(Tip.class).value() ) ;
      }

      return true ;
   }
}

Die process-Methode wird mehrfach aufgerufen. Die for-Schleife gibt aber nur die passenden passenden Elemente im Environment aus.

Eines der Details das viel Zeit gekostet hat: die Angabe der Annotation muss vollständig qualifiziert sein.

Beispielcode mit Annotation

Der Code macht gar nichts, er dient nur zur Demonstration.

/******************************************************************************/
/*                                                                            */
/*                                                          FILE: Tipped.java */
/*                                                                            */
/*       Some java code with Tip annotations                                  */
/*       ===================================                                  */
/*                                                                            */
/*       V1.00    27-JAN-2015  Te                                             */
/*                                                                            */
/******************************************************************************/

import pack.Tip ;

/**
 * A class with Tip annotations to demonstrate
 * the annotation processor.
 */
public class Tipped
{
  @Tip("Schaut mal her")
  public void tip()
  {
  }

  public void top()
  {
  }

  public static void main( String[] args )
  {
  }
}

Die Annotationen bearbeiten

Der Prozessor wird zusammen mit Meta-Informationen in ein JAR gepackt und im classpath des Compilers angegeben.

Der Compiler erkennt an den Meta-Informationen, dass es sich um einen Annotation-Processor handelt und instanziert die Klasse. Wenn er etwas findet das für den Prozessor interessant sein könnte, ruft er die entsprechenden Methoden der Klasse auf.

javac -cp .;processor.jar Tipped.java

Bei meinem simplen Prozessor werden die Ausgaben direkt unter diese Zeile geschrieben.

Das JAR erzeugen

Wie oben zu sehen, soll der Prozessor in ein JAR gepackt werden. Damit der Compiler den Prozessor erkennt, braucht er noch Informationen die er im Directory META-INF findet.

Was mich viel Zeit gekostet hat: der Compiler liest anscheinend auch den Inhalt von META-INF im aktuellen Directory...

Bis ich das gemerkt habe, konnte ich den TipProcessor einfach nicht kompilieren. Was genau passiert, habe ich nicht herausgefunden. Der Compiler hat das File ohne Fehlermeldung bearbeitet, aber kein class-File erzeugt!

Es macht den Eindruck als ob er den Prozessor irgendwie mit sich selber prozessiert, und das Ergebnis ist NICHTS.

Mehr oder minder zufällig bin ich dann drauf gekommen, dem Directory META-INF während dem Compilieren einen anderen Namen zu geben, und seither kann ich den Code nach Lust und Laune neu builden.

Den Code nachbauen

Der Code liegt in mehreren Packages und deshalb habe ich ihn hier als ZIP zum Download bereitgestellt.

Im ZIP finden Sie die Sourcen, META-INF und ein Batch-File um den Code zu builden. Der Beispielcode und ein Batch zum Aufruf des Prozessors sind ebenfalls enthalten.

Der Code und die Batch-Files wurden auf Windows geschrieben, aber es sollte keine grosse Sache sein, das auch auf Linux zum laufen zu bringen.