Modify the new Handleable.java file to make the following changes. Save the file.
package ann;
public @interface Handleable {
}
This is how annotations are declared, and it is quite similar to an interface declaration.
The difference is that the interface keyword must be preceded with an at sign (@).
This annotation is called Handleable.
Additional Information. In annotation declarations, you can also specify
additional parameters, for example, what types of elements can be annotated, e.g. classes or methods.
You do this by adding @Target(value = {ElementType.TYPE}) for classes
and @Target(value = {ElementType.METHOD}).
So, the annotation declaration becomes annotated itself with meta-annotations.
You now need to add code for the annotation processor to process the Handleable annotation.
Modify the HandleableProcessor.java class to add the following code. Save your changes.
package proc;
import ann.Handleable;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
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.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
@SupportedAnnotationTypes("ann.Handleable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class HandleableProcessor extends AbstractProcessor {
/** public for ServiceLoader */
public HandleableProcessor() {
}
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element e : roundEnv.getElementsAnnotatedWith(Handleable.class)) {
if (e.getKind() != ElementKind.FIELD) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Not a field", e);
continue;
}
String name = capitalize(e.getSimpleName().toString());
TypeElement clazz = (TypeElement) e.getEnclosingElement();
try {
JavaFileObject f = processingEnv.getFiler().
createSourceFile(clazz.getQualifiedName() + "Extras");
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Creating " + f.toUri());
Writer w = f.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package "
+ clazz.getEnclosingElement().getSimpleName() + ";");
pw.println("public abstract class "
+ clazz.getSimpleName() + "Extras {");
pw.println(" protected " + clazz.getSimpleName()
+ "Extras() {}");
TypeMirror type = e.asType();
pw.println(" /** Handle something. */");
pw.println(" protected final void handle" + name
+ "(" + type + " value) {");
pw.println(" System.out.println(value);");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
return true;
}
private static String capitalize(String name) {
char[] c = name.toCharArray();
c[0] = Character.toUpperCase(c[0]);
return new String(c);
}
}
Let's take a closer look at the main parts that constitute the code for the annotation processor
(note that for convenience, only parts of the code are provided).
At first, you specify the annotation types that the annotation processor supports
(by using @SupportedAnnotationTypes) and the version of the source files that are supported
(by using @SupportedSourceVersion), in this case the version is JDK 6:
@SupportedAnnotationTypes("ann.Handleable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
Then, you declare a public class for the processor that extends the AbstractProcessor
class from the javax.annotation.processing package.
AbstractProcessor is a standard superclass for concrete annotation processors that
contains necessary methods for processing annotations.
public class HandleableProcessor extends AbstractProcessor {
...
}
You now need to provide a public constructor for the class.
public class HandleableProcessor extends AbstractProcessor {
public HandleableProcessor() {
}
...
}
Then, you call the process() method of the parent AbstractProcessor class.
Through this method the annotations available for processing are provided.
In addition, this method contains information about the round of processing.
public class HandleableProcessor extends AbstractProcessor {
...
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv) {
...
}
}
The annotation processor's logic is contained within the process() method of the AbstractProcessor class.
Note that through AbstractProcessor, you also access the ProcessingEnvironment interface,
which allows annotation processors to use several useful facilities, such as Filer
(a filer handler that enables annotation processors to create new files) and
Messager (a way for annotation processors to report errors).
public class HandleableProcessor extends AbstractProcessor {
...
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv) {
//For each element annotated with the Handleable annotation
for (Element e : roundEnv.getElementsAnnotatedWith(Handleable.class)) {
//Check if the type of the annotated element is not a field. If yes, return a warning.
if (e.getKind() != ElementKind.FIELD) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Not a field", e);
continue;
}
//Define the following variables: name and clazz.
String name = capitalize(e.getSimpleName().toString());
TypeElement clazz = (TypeElement) e.getEnclosingElement();
//Generate a source file with a specified class name.
try {
JavaFileObject f = processingEnv.getFiler().
createSourceFile(clazz.getQualifiedName() + "Extras");
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Creating " + f.toUri());
Writer w = f.openWriter();
//Add the content to the newly generated file.
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package "
+ clazz.getEnclosingElement().getSimpleName() + ";");
pw.println("public abstract class "
+ clazz.getSimpleName() + "Extras {");
pw.println(" protected " + clazz.getSimpleName()
+ "Extras() {}");
TypeMirror type = e.asType();
pw.println(" /** Handle something. */");
pw.println(" protected final void handle" + name
+ "(" + type + " value) {");
pw.println(" System.out.println(value);");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
return true;
}
...
}
The last block in this code declares the capitalize method that is used to capitalize
the name of the annotated element.
public class HandleableProcessor extends AbstractProcessor {
...
private static String capitalize(String name) {
char[] c = name.toCharArray();
c[0] = Character.toUpperCase(c[0]);
return new String(c);
}
}