blogger templates blogger widgets
This is part of a list of blog posts.
To browse the contents go to

Short Dive into Annotations


Thanks to this excellent blog.
Most of what follows are notes from there.

Annotations syntax

Annotations always appears before the annotated piece of code and by convention, usually in its own line, indented at the same level.

Annotations may apply to
- packages,
- types (classes, interfaces, enums and annotation types),
- variables (class, instance and local variables – including that defined in a for or while loop),
- constructors,
- methods and parameters.

The simplest form of an annotation is without any element included, for example:
@Override()
public void theMethod() {…}


Creating a new annotation type

An annotation type is defined using @interface instead of interface. Here are some examples,


public @interface Author {
 String name();
 String created();
 int revision() default 1;
 String[] reviewers() default {};
}
public @interface Complexity {
 ComplexityLevel value() default ComplexityLevel.MEDIUM;
}
public enum ComplexityLevel {
 VERY_SIMPLE, SIMPLE, MEDIUM, COMPLEX, VERY_COMPLEX;
}

Annotation Types have some differences compared to regular interfaces:

- Only primitives, strings, enums, class literals and arrays of them are allowed.
- Note that as Objects in general are not allowed, arrays of arrays are not allowed in Annotation Types (every array is an object).
- The annotation elements are defined with a syntax very similar to that of methods, but keep in mind that modifiers and parameters are not allowed.
- Default values are defined using the default keyword followed by the value that will be a literal, an array initializer or an enum value.


The JDK comes with some annotations that are used to modify the behavior of the Annotation Types that we are defining:
  • @Documented
    Indicates that the marked Annotation Type should be documented by Javadoc each time it is found in an annotated element.
  • @Inherited
    Indicates that the marked Annotation Type is inherited by subclasses. This way, if the marked annotation is not present in a subclass it inherits the annotation in the superclass, if present. Only applies to class inheritance and not to interface implementations.
  • @Retention
    Indicates how long the marked Annotation Type will be retained.
    Possible values are those of
    public enum RetentionPolicy {
        /**
         * Annotations are to be discarded by the compiler.
         */
        SOURCE,
    
        /**
         * Annotations are to be recorded in the class file by the compiler
         * but need not be retained by the VM at run time.  This is the default
         * behavior.
         */
        CLASS,
    
        /**
         * Annotations are to be recorded in the class file by the compiler and
         * retained by the VM at run time, so they may be read reflectively.
         *
         * @see java.lang.reflect.AnnotatedElement
         */
        RUNTIME
    }
  • @Target
    Indicates the element types to which the marked Annotation Type is applicable.
    Possible values are those of
    public enum ElementType {
        /** Class, interface (including annotation type), or enum declaration */
        TYPE,
    
        /** Field declaration (includes enum constants) */
        FIELD,
    
        /** Method declaration */
        METHOD,
    
        /** Parameter declaration */
        PARAMETER,
    
        /** Constructor declaration */
        CONSTRUCTOR,
    
        /** Local variable declaration */
        LOCAL_VARIABLE,
    
        /** Annotation type declaration */
        ANNOTATION_TYPE,
    
        /** Package declaration */
        PACKAGE
    }
    

Annotations at compile time (Retention type: CLASS)

- At compile time, Annotation Processors, a specialized type of classes, will handle the different annotations found in code being compiled.
- A standalone tool named apt, the Annotation Processor Tool, was needed to process annotations, and the Mirror API, used by apt to write custom processors, was distributed in com.sun.mirror packages.
- Starting with Java 6, Annotation Processors were standardized through JSR 269 and the tool apt seamlessly integrated with the javac tool.

The custom processor may use three annotations to configure itself:

  • javax.annotation.processing.SupportedAnnotationTypes
    This annotation is used to register the annotations that the processor supports.
  • javax.annotation.processing.SupportedSourceVersion
    This annotation is used to register the source version that the processor supports.
  • javax.annotation.processing.SupportedOptions
    This annotation is used to register allowed custom options that may be passed through the command line.


Finally, we provide our implementation of the process() method.

To interact with the annotated class, the process() method receives two parameters:

java.lang.model.TypeElement
A set of TypeElement objects are passed in. Annotation processing is done in one or several rounds. In each round the processors are called and they receive in this set the types of the annotations being processed in the current round.
javax.annotation.processing.RoundEnvironment
This object gives access to the annotated source elements being processed in the current and previous round.

Create a Java/Utility project. An existing java project could be changed to utility project by modifying it's project facet.

package com.anno;

public @interface Complexity {
    ComplexityLevel value() default ComplexityLevel.MEDIUM;
}


package com.anno;

public enum ComplexityLevel {
    VERY_SIMPLE, SIMPLE, MEDIUM, COMPLEX, VERY_COMPLEX;
}

package com.anno.processors;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
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.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

import com.anno.Complexity;

@SupportedAnnotationTypes("com.anno.Complexity")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ComplexityProcessor extends AbstractProcessor {

 public ComplexityProcessor() {
  super();
 }

 @Override
 public boolean process(Set annotations,
   RoundEnvironment roundEnv) {
  //a simple logging to stream
  for (Element elem : roundEnv.getElementsAnnotatedWith(Complexity.class)) {
   Complexity complexity = elem.getAnnotation(Complexity.class);
   String message = "annotation found in " + elem.getSimpleName()
     + " with complexity " + complexity.value();
   processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
     message);
  }
  //a simple code generation 
  for (Element elem : roundEnv.getElementsAnnotatedWith(Complexity.class)) {
   if (elem.getKind() == ElementKind.CLASS) {
    TypeElement classElement = (TypeElement) elem;
    PackageElement packageElement = (PackageElement) classElement
      .getEnclosingElement();

    JavaFileObject jfo;
    try {
     jfo = processingEnv.getFiler().createSourceFile(
       classElement.getQualifiedName() + "BeanInfo");

     BufferedWriter bw = new BufferedWriter(jfo.openWriter());
     if(packageElement.getQualifiedName()!=null && packageElement.getQualifiedName().length()>0) {
      bw.append("package ");
      bw.append(packageElement.getQualifiedName());
      bw.append(";");
     }
     bw.newLine();
     bw.append("class "+ classElement.getQualifiedName() + "BeanInfo");
     bw.append("{}");
     bw.flush();
     bw.close();
    } catch (IOException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
  }
  return true; // no further processing of this annotation type
 }
}

The easiest way to register the processor is to leverage the standard Java services mechanism:
  • Package your Annotation Processor in a Jar file. In eclipse enable the project as utitlity.
  • Include in the Jar file a directory META-INF/services.
  • Include in the directory a file named javax.annotation.processing.Processor.
  • Write in the file the fully qualified names of the processors contained in the Jar file, one per line.
    com.anno.processors.ComplexityProcessor


Export the project as jar.

Write a test class.

import com.anno.Complexity;
import com.anno.ComplexityLevel;

@Complexity(ComplexityLevel.VERY_SIMPLE)
public class Test {

 public static void main(String[] args) {
  Test t = new Test();

 }
}

C:\Users\eIPe\Desktop>javac -cp .;Anno.jar Test.java
Note: annotation found in Test with complexity VERY_SIMPLE

C:\Users\eIPe\Desktop>dir | find "TestBean"
11-08-2014 14:44 198 TestBeanInfo.class
11-08-2014 14:44 22 TestBeanInfo.java

Doing the same through eclipse didn't work for me (based on steps outlined in http://deors.wordpress.com/2011/10/08/annotation-processors/).


We will look at annotations at runtime (Retention type: RUNTIME)

Consider this sample Author annotation.

package com.anno;

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

@Target(value = {ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Author {
 String name() default "unknown";
}


class TestProg {
 public TestProg(){
  
 }
 @Author(name="John Eipe")
 public void m1(){
  System.out.println("m1 called");
 }
}


public class Test {

 public static void main(String[] args) {
  TestProg obj = new TestProg();
   Method[] methods = obj.getClass().getMethods();

         for (Method method : methods) {
          Author annos = method.getAnnotation(Author.class);
             if (annos != null) {
                 try {
                  System.out.println("Method, "+method.getName()+" written by "+annos.name()+" about to be called.");
                     method.invoke(obj);
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
             }
         }

 }
 
}

Method, m1 written by John Eipe about to be called.
m1 called

More about compiler APIs here

No comments:

Post a Comment