Personnaliser le compilateur Java pour traiter votre annotation (Annotation Processing Tool)

View more Tutorials:

1- Introduction

Le document est basé sur:
  • Eclipse 4.6 (NEON)

  • Java 7

2- Qu'est ce qu'Annotation Processing Tool (APT)?

La situation est comme suivante:

Vous créez vos propres Annotations et les utilisez dans votre application Java. Vous définissez les règles pour ces annotations. Vous souhaitez que le compilateur Java (Java compiler) notifie l'erreur d'appliquer les règles à tort, le cas échéant, au moment de la compilation. Si vous utilisez Eclipse pour écrire des codes, vous souhaitez que Eclipse notifie l'erreur d'utilisation sur IDE.

Cette demande est possible pour APT ( Annotation Processing Tool).
 
Définition de l' APT:
APT (Java annotation processing tool) est un outil aui vous permet le traitement des annotations avant compilation des sources Java. Tout ce dont vous avez besoin consiste à implémenter un processeur d'annotation.
  • Par exemple:
@PublicFinal est une des vos annotations. Votre règle est que cela peut annoter sur la méthode ou le champ dont le modificateur est public et définitif. Si vous avons tort, un avis sera affiché au moment de la compilation et un avis sur IDE à la fois.

3- Le modèle d'un exemple

C'est le modèle de l'exemple que je vais vous introduire dans ce document:
Votre Annotation:
  • @PublicFinal n'est utilisé pour le méthode ou le champ dont le modicateur est public et final.
  • @Controller n'est appliqué pour la classe et le nom de la classe doit comprendre le suffixe Controller.
  • @Action n'est utilisé pour le méthode et le retour de la chaine.
Les processeurs  PublicFinalProcessor, ControllerProcessor, ActionProcesser sont responsable de notifier au cas où il y a des erreurs au moment de la compilation, y compris l'affichage des erreurs de notification sur  IDE Eclipse.

4- Project APTProcessor

D'abord, créez un projet pour démarrer.
  • APTProcessor
Action.java
package org.o7planning.ann;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Action {

}
Controller.java
package org.o7planning.ann;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Controller {

}
PublicFinal.java
package org.o7planning.ann;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface PublicFinal {

}
AcctionProccessor.java
package org.o7planning.aptprocessor;

import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;

// Work with @Action
@SupportedAnnotationTypes({ "org.o7planning.ann.Action" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ActionProcessor extends AbstractProcessor {

  private Filer filer;
  private Messager messager;

  @Override
  public void init(ProcessingEnvironment env) {
      filer = env.getFiler();
      messager = env.getMessager();
  }


  @Override
  public boolean process(Set<? extends TypeElement> annotations,
          RoundEnvironment env) {
      DevLog.log("\n\n");
      DevLog.log(" ======================================================== ");
      DevLog.log("#process(...) in " + this.getClass().getSimpleName());
      DevLog.log(" ======================================================== ");

      for (TypeElement ann : annotations) {
          DevLog.log(" ==> TypeElement ann = " + ann);

          List<? extends Element> es = ann.getEnclosedElements();
          DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
          for (Element e : es) {
              DevLog.log(" ========> EnclosedElement: " + e);
          }
          Element enclosingElement = ann.getEnclosingElement();

          DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);

          ElementKind kind = ann.getKind();
          DevLog.log(" ====> ann.getKind() = " + kind);
          Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);

          DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = "
                  + e2s.size());
          for (Element e2 : e2s) {
              DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
              DevLog.log("           - Kind : " + e2.getKind());


              // @Action use for method only
              // notify if misuse
              if (e2.getKind() != ElementKind.METHOD) {
                  DevLog.log("           - Error!!!");
                  messager.printMessage(Kind.ERROR, "@Action using for method only ",
                          e2);
              } else {

                  // The name of the method is annotated by @Action
                  String methodName = e2.getSimpleName().toString();

                  // (ExecutableElement described for method, constructor,...)
                  ExecutableElement method = (ExecutableElement) e2;

                  DevLog.log("           - method : " + method);
                  TypeMirror retType = method.getReturnType();
                  DevLog.log("           -- method.getReturnType() : "
                          + retType);


                  // @Action Only used for method returns the String
                  // Notify if misuse
                  if (!String.class.getName().equals(retType.toString())) {
                      DevLog.log("           - Error!!!");
                      messager.printMessage(Kind.ERROR,
                              "Method using @Action must return String", e2);
                  }
              }
          }
      }
      return true;
  }

}
ControllProcessor.java
package org.o7planning.aptprocessor;

import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;


// Apply for @Controller
@SupportedAnnotationTypes({ "org.o7planning.ann.Controller" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ControllerProcessor extends AbstractProcessor {

  private Filer filer;
  private Messager messager;

  @Override
  public void init(ProcessingEnvironment env) {
      filer = env.getFiler();
      messager = env.getMessager();
  }


  @Override
  public boolean process(Set<? extends TypeElement> annotations,
          RoundEnvironment env) {
      DevLog.log("\n\n");
      DevLog.log(" ======================================================== ");
      DevLog.log("#process(...) in " + this.getClass().getSimpleName());
      DevLog.log(" ======================================================== ");

      for (TypeElement ann : annotations) {
          DevLog.log(" ==> TypeElement ann = " + ann);
          //
          List<? extends Element> es = ann.getEnclosedElements();
          DevLog.log(" ====> ann.getEnclosedElements() count = " + es.size());
          for (Element e : es) {
              DevLog.log(" ========> EnclosedElement: " + e);
          }
          Element enclosingElement = ann.getEnclosingElement();

          DevLog.log(" ====> ann.getEnclosingElement() = " + enclosingElement);

          ElementKind kind = ann.getKind();
          DevLog.log(" ====> ann.getKind() = " + kind);
          Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);

          DevLog.log(" ====> env.getElementsAnnotatedWith(ann) count = "
                  + e2s.size());
          for (Element e2 : e2s) {
              DevLog.log(" ========> ElementsAnnotatedWith: " + e2);
              DevLog.log("           - Kind : " + e2.getKind());


              // @Controller only use for Class
              // Notify if misuse
              if (e2.getKind() != ElementKind.CLASS) {
                  DevLog.log("           - Error!!!");
                  messager.printMessage(Kind.ERROR,
                          "@Controller using for class only ", e2);
              } else {

                  // The name of the class is annotated by @Controller
                  String className = e2.getSimpleName().toString();

                  // @Controller using for class with suffix Controller
                  // Notify if misuse
                  if (!className.endsWith("Controller")) {
                      DevLog.log("           - Error!!!");
                      messager.printMessage(
                              Kind.ERROR,
                              "Class using @Controller must have suffix Controller",
                              e2);
                  }
              }
          }

      }


      return true;
  }

}
PublicFinalProcessor.java
package org.o7planning.aptprocessor;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import org.o7planning.log.DevLog;


// Apply for @PublicFinal
@SupportedAnnotationTypes(value = { "org.o7planning.ann.PublicFinal" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class PublicFinalProcessor extends AbstractProcessor {

  private Filer filer;
  private Messager messager;

  @Override
  public void init(ProcessingEnvironment env) {
      filer = env.getFiler();
      messager = env.getMessager();
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations,
          RoundEnvironment env) {
      DevLog.log("\n\n");
      DevLog.log(" ======================================================== ");
      DevLog.log("#process(...) in " + this.getClass().getSimpleName());
      DevLog.log(" ======================================================== ");

      DevLog.log(" annotations count = " + annotations.size());

      for (TypeElement ann : annotations) {

          Set<? extends Element> e2s = env.getElementsAnnotatedWith(ann);
          for (Element e2 : e2s) {
              DevLog.log("- e2 = " + e2);

              Set<Modifier> modifiers = e2.getModifiers();

              // @PublicFinal only using for public & final
              // Notify if misuse
              if (!(modifiers.contains(Modifier.FINAL) && modifiers
                      .contains(Modifier.PUBLIC))) {
                  DevLog.log("- Error!!!");
                  messager.printMessage(Kind.ERROR,
                          "Method/field wasn't public and final", e2);

              }
          }
      }

      // All PublicFinal annotations are handled by this Processor.
      return true;
  }
}
DevLog.java
package org.o7planning.log;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class DevLog {

 public static final String LOG_FILE = "C:/APT/log.txt";

 public static void log(Object message) {
     if (message == null) {
         return;
     }
   
     // Make sure the path exists.
     new File(LOG_FILE).getParentFile().mkdirs();
     //
     FileWriter writer = null;
     try {
         writer = new FileWriter(LOG_FILE, true);
         writer.append(message.toString());
         writer.append("\n");
         writer.close();
     } catch (IOException e) {
         e.printStackTrace();
         try {
             writer.close();
         } catch (IOException e1) {
         }
     }
 }
}

Déclarer Service

Créez le fichier  javax.annotation.processing.Processor qui se trouve dans le dossier  META-INF/services comme l'illustration suivante:
javax.annotation.processing.Processor
org.o7planning.aptprocessor.PublicFinalProcessor
org.o7planning.aptprocessor.ActionProcessor
org.o7planning.aptprocessor.ControllerProcessor
Emballez le projet  APTProcessor sous le fichier de jar:
Cliquez le bouton droit du Projet et sélectionnez Export:
Exportez avec succès:

5- Project APTTutorial

Créez le Projet APTTutorial:
Cliquez le bouton droit de   APTTutorial et sélectionnez propriétés.
Déclarez de l'utilisation la bibliothèque  APTProccessor que vous venez de créer.
Déclarez de l'utiliser votre  Annotation Processor avec  Compiler.
Déclarez la location de la bibliothèque  Processor:
Vous pouvez cliquer à Advanced.. afin de voir des Processeurs qui sont enregistrés avec Compiler
Cliquez OK pour terminer:
Créez quelques test de classe en utilisant votre Annotation et Processor:
PublicFinalTest.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.PublicFinal;

public class PublicFinalTest {

  @PublicFinal
  public final static int ABC = 100;

  @PublicFinal
  private static String MODULE_NAME = "APT";  
 
 
}
Les notifications d'erreur sont présentées sur IDE:
TestActionController_01.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;

@Controller
public class TestActionController_01 {

  @Action
  public String exit() {
      return null;
  }

  @Action
  public void print() {

  }

  @Action
  public int error() {
      return 0;
  }
}
TestActionController_02.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Controller;

@Controller
public interface TestActionController_02 {

  public String close();

}
TestActionController.java
package org.o7planning.tutorial.apttest;

import org.o7planning.ann.Action;
import org.o7planning.ann.Controller;

@Controller
public class TestActionController {

  @Action
  public String login() {
      return null;
  }
}

6- Voir Log

Vous pouvez voir log que Processor est résolu à:
  • C:/APT/log.txt
log.txt do class DevLog est créé pour le seul but de programmer.

View more Tutorials: