• Tidak ada hasil yang ditemukan

Writing Your Own Annotation Processor

Dalam dokumen Advanced java Preparing you for java mastery (Halaman 115-118)

We are going to develop several kinds of annotation processors, starting from the simplest one, immutability checker. Let us define a simple annotationImmutablewhich we are going to use in order to annotate the class to ensure it does not allow to modify its state.

@Target( ElementType.TYPE )

@Retention( RetentionPolicy.CLASS ) public @interface Immutable { }

Following the retention policy, the annotation is going to be retained by Java compiler in the class file during the compilation phase however it will not be (and should not be) available at runtime.

As we already know frompart 3of the tutorial,How to design Classes and Interfaces, immutability is really hard in Java. To keep things simple, our annotation processor is going to verify that all fields of the class are declared as final. Luckily, the Java standard library provides an abstract annotation processor, javax.annotation.processing.AbstractProcessor, which is designed to be a convenient superclass for most concrete annotation processors. Let us take a look on SimpleAnnotationProcessor annotation processor implementation.

@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;

} }

TheSupportedAnnotationTypesannotation is probably the most important detail which defines what kind of annotations this annotation processor is interested in. It is possible to use*here to handle all available annotations.

Because of the provided scaffolding, ourSimpleAnnotationProcessorhas to implement only a single method, proc ess. The implementation itself is pretty straightforward and basically just verifies if class being processed has any field declared withoutfinalmodifier. Let us take a look on an example of the class which violates this naïve immutability contract.

@Immutable

public class MutableClass { private String name;

public MutableClass( final String name ) { this.name = name;

}

public String getName() { return name;

} }

Running theSimpleAnnotationProcessoragainst this class is going to output the following error on the console:

Class ’MutableClass’ is annotated as @Immutable, but field ’name’ is not declared as final Thus confirming that the annotation processor successfully detected the misuse ofImmutableannotation on a mutable class.

By and large, performing some introspection (and code generation) is the area where annotation processors are being used most of the time. Let us complicate the task a little bit and apply some knowledge of Java Compiler API from thepart 13of the tutorial, Java Compiler API. The annotation processor we are going to write this time is going to mutate (or modify) the generated bytecode by adding thefinal modifier directly to the class field declaration to make sure this field will not be reassigned anywhere else.

@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )

@SupportedSourceVersion( SourceVersion.RELEASE_7 )

public class MutatingAnnotationProcessor extends AbstractProcessor { private Trees trees;

@Override

public void init (ProcessingEnvironment processingEnv) { super.init( processingEnv );

trees = Trees.instance( processingEnv );

}

@Override

public boolean process( final Set< ? extends TypeElement > annotations, final RoundEnvironment roundEnv) {

final TreePathScanner< Object, CompilationUnitTree > scanner = new TreePathScanner< Object, CompilationUnitTree >() {

@Override

public Trees visitClass(final ClassTree classTree, final CompilationUnitTree unitTree) {

if (unitTree instanceof JCCompilationUnit) {

final JCCompilationUnit compilationUnit = ( JCCompilationUnit )unitTree;

// Only process on files which have been compiled from source

if (compilationUnit.sourcefile.getKind() == JavaFileObject.Kind.SOURCE) { compilationUnit.accept(new TreeTranslator() {

public void visitVarDef( final JCVariableDecl tree ) { super.visitVarDef( tree );

if ( ( tree.mods.flags &amp; Flags.FINAL ) == 0 ) { tree.mods.flags |= Flags.FINAL;

} } });

} }

return trees;

} };

for( final Element element: roundEnv.getElementsAnnotatedWith( Immutable.class ) ) { final TreePath path = trees.getPath( element );

scanner.scan( path, path.getCompilationUnit() );

}

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

} }

The implementation became more complex, however many classes (likeTreePathScanner,TreePath) should be already familiar. Running the annotation processor against the sameMutableClassclass will generate following byte code (which could be verified by executingjavap -p MutableClass.classcommand):

public class com.javacodegeeks.advanced.processor.examples.MutableClass { private final java.lang.String name;

public com.javacodegeeks.advanced.processor.examples.MutableClass(java.lang.String);

public java.lang.String getName();

}

Indeed, thenamefield hasfinalmodifier present nonetheless it was omitted in the original Java source file. Our last example is going to show off the code generation capabilities of annotation processors (and conclude the discussion). Continuing in the same vein, let us implement an annotation processor which will generate new source file (and new class respectively) by appendingImmutablesuffix to class name annotated withImmutableannotation.

@SupportedAnnotationTypes( "com.javacodegeeks.advanced.processor.Immutable" )

@SupportedSourceVersion( SourceVersion.RELEASE_7 )

public class GeneratingAnnotationProcessor 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;

final PackageElement packageElement =

( PackageElement )typeElement.getEnclosingElement();

try {

final String className = typeElement.getSimpleName() + "Immutable";

final JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(

packageElement.getQualifiedName() + "." + className);

try( Writer writter = fileObject.openWriter() ) {

writter.append( "package " + packageElement.getQualifiedName() + ";" );

writter.append( "\\n\\n");

writter.append( "public class " + className + " {" );

writter.append( "\\n");

writter.append( "}");

}

} catch( final IOException ex ) {

processingEnv.getMessager().printMessage(Kind.ERROR, ex.getMessage());

} } }

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

} }

As the result of injecting this annotation processor into compilation process of theMutableClassclass, the following file will be generated:

package com.javacodegeeks.advanced.processor.examples;

public class MutableClassImmutable { }

Nevertheless the source file and its class have been generated using primitive string concatenations (and it fact, this class is really very useless) the goal was to demonstrate how the code generation performed by annotation processors works so more sophisticated generation techniques may be applied.

Dalam dokumen Advanced java Preparing you for java mastery (Halaman 115-118)

Dokumen terkait