Daggerate
your code
ANNOTATION PROCESSING
Bartosz Kosarzycki
@bkosarzycki
● Annotations in Java
● Libraries useful in annotation processing
● Basic annotation processor
● Round environment
● Idea
● How to write your annotation processor tutorial
● Debug annotation processing
Agenda
Annotations in Java:
- since Java 1.5
- store meta-information
#pragma in c / c# attributes
- JSR-269 - since 1.6
Pluggable Annotation
Processing API for apt tool,
built-into javac
- custom annotations
Annotations in Java:
- Annotation @Retention
how long annotations
are kept
SOURCE,
CLASS,
RUNTIME
Annotations in Java:
SOURCE
discarded by the compiler
CLASS
kept int the class file, not loaded
by the JVM at runtime
RUNTIME
loaded by the JVM at runtime
Libraries
● Google AutoService link
register implementations of well-known types using META-INF
metadata
● Square JavaPoet link
generate Java source code
● Google Compile-Testing link
test javac compilation process
Basic annotation processor
@AutoService(Processor.class)
public class YourProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnvironment) {
return false;
}
}
@AutoService(Processor.class)
public class YourProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton( Injection.class.getCanonicalName() );
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
}
}
Implement these methods as well
Basic annotation processor
Basic annotation processor
@AutoService(Processor.class)
public class YourProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnvironment) {
return false;
}
}
Processing rounds
javac
apt
Processing rounds
javac
apt
rounds
until there is no change in sources
source
source
set
the annotation types requested to be processed
roundEnvironment
environment information about the current and prior round
return:
whether or not the set of annotations are claimed by this processor
@Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnvironment) {
return false;
}
*** always return false
IDEA
What was the
RoboJuice + Dagger 2
RoboJuice
DYNAMIC
final RoboInjector injector = RoboGuice.getInjector(context);
annotation injector.injectMembers(this);
or
foo = injector.getInstance(Foo.class);
Dagger 2
Compile time
No runtime overhead of scanning the classpath
Idea
Modules can be imported dynamically
Modules expose Dagger 2 injectable objects
Module 1
Module 2
App
Developer imports modules
Injector constains MembersInjector &
Providers maps
Injector.get() & Injector.inject() methods
work dynamically
REUSABLE ANDROID SDK
ENVIRONMENT
Android
developer
dependencies {
compile 'com.you.app:module.one:6.6.6'
}
@Injection(modules = { ModuleOne.class })
public class MainApplication extends Application {
}
Foo foo = Injector.getInstance(Foo.class);
How to write
an annotation
processor?
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton( Injection.class.getCanonicalName() );
}
How-to
Set annotations supported by your processor:
Set supported java-source version:
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest(); // SourceVersion.RELEASE_8;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
filer = processingEnv.getFiler();
}
How-to
Get “Filer” to write source code:
Files are written to:
build/generated/source/apt/debug
build/generated/source/apt/release
Hint
Class
Object
TypeMirror
- Use description of types instead of types
- Classes are NOT available yet - they are STILL
COMPILED
Class<?> clazz = Class.forName("your.app.package.YourType");
ClassNotFoundException
@Example
public class Foo {
private int a;
private Other other;
public Foo () {
}
public void setA
( int newA
) { }
}
Type descriptions
TypeElement
VariableElement
ExecutableElement
ExecutableElement
TypeElement
Analyse source
Gather information
Write new source files
Modify current source files
Use gathered information
AnnotationProcessor
for (Element typeElement :
roundEnvironment.getElementsAnnotatedWith(Injection.class)) {
String name = typeElement.getSimpleName().toString();
}
Get all elements with a specific annotation:
typeElement.getEnclosedElements();
typeElement.getEnclosingElement();
Moving up & down the type hiararchy:
AnnotationProcessor
for (AnnotationMirror annotationMirror : ((DeclaredType)module).asElement().getAnnotationMirrors()) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
annotationMirror.getElementValues().entrySet()) {
// use the entry with AnnotationValueVisitor
}
}
Get annotations from TypeMirror:
final String[] annotationParam = new String[1];
final AnnotationValueVisitor valueVisitor = new AnnotationValueVisitor();
valueVisitor.setFunction(new Function<TypeMirror, Void>() {
@Nullable @Override public Void apply(@Nullable TypeMirror param) {
TypeMirror moduleClass = param; //assembles modules list
return null;
}
});
annotationParam[0] = entry.getKey().getSimpleName().toString();
entry.getValue().accept(valueVisitor, null);
@Annotation(param = { Sample.class })
Class<?>[] param()
Sample.class
Square’s
JavaPoet
Thank you
@JakeWharton ;)
Square’s JavaPoet
Thank you
@JakeWharton ;)
● works with TypeMirrors
out of the box
● adds imports automatically
● removes duplicate imports
● supports static imports
● code & control flow
for / if etc.
● built-in support for Filer
JavaPoet
MethodSpec.Builder constructorMethod =
MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
.addCode("this(" +
"$1T" +
".builder().build()); n",
typeMirror);
Create methods using JavaPoet:
TypeSpec typeSpec = TypeSpec.classBuilder(outputClassName)
.addModifiers(Modifier.PUBLIC)
.superclass(...)
.addJavadoc("annotation processor ...")
.addField(componentElem, "innerField", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(constructorMethod.build())
.build();
Create types using JavaPoet:
* imports are added automatically here
JavaPoet
ClassName.get("your.package.name”, "DaggerInjector")
Create custom unreachable types (ClassName) :
* imports are added automatically as well
Filer
JavaFile.builder("your.package.name", typeSpec).build().writeTo(filer);
Write source code using JavaPoet & Filer:
filer.createSourceFile("public class A() {}", null);
Write source code Filer:
Debugging
Debugging
● Annotation processing cannot
be debugged directly
● Compile project with gradle
in debug mode
● Attach to the compile process
from IDE
● [OPTIONAL] configure scripts
to launch debug from IDE
Detailed tutorial here
Links
- Angelika Langer, Annotation Processing link
- Hannes Dorfmann, Annotation Processing 101 link link
- JavaDocs: Element, TypeElement, ExecutableElement
Thank you!
QUESTIONS
Bartosz Kosarzycki
@bkosarzycki

Daggerate your code - Write your own annotation processor

  • 1.
  • 2.
    ● Annotations inJava ● Libraries useful in annotation processing ● Basic annotation processor ● Round environment ● Idea ● How to write your annotation processor tutorial ● Debug annotation processing Agenda
  • 3.
    Annotations in Java: -since Java 1.5 - store meta-information #pragma in c / c# attributes - JSR-269 - since 1.6 Pluggable Annotation Processing API for apt tool, built-into javac - custom annotations
  • 4.
    Annotations in Java: -Annotation @Retention how long annotations are kept SOURCE, CLASS, RUNTIME
  • 5.
    Annotations in Java: SOURCE discardedby the compiler CLASS kept int the class file, not loaded by the JVM at runtime RUNTIME loaded by the JVM at runtime
  • 6.
    Libraries ● Google AutoServicelink register implementations of well-known types using META-INF metadata ● Square JavaPoet link generate Java source code ● Google Compile-Testing link test javac compilation process
  • 7.
    Basic annotation processor @AutoService(Processor.class) publicclass YourProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
  • 8.
    @AutoService(Processor.class) public class YourProcessorextends AbstractProcessor { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton( Injection.class.getCanonicalName() ); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } @Override public synchronized void init(ProcessingEnvironment processingEnv) { } } Implement these methods as well Basic annotation processor
  • 9.
    Basic annotation processor @AutoService(Processor.class) publicclass YourProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
  • 10.
  • 11.
    Processing rounds javac apt rounds until thereis no change in sources source source
  • 12.
    set the annotation typesrequested to be processed roundEnvironment environment information about the current and prior round return: whether or not the set of annotations are claimed by this processor @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } *** always return false
  • 13.
  • 14.
    RoboJuice + Dagger2 RoboJuice DYNAMIC final RoboInjector injector = RoboGuice.getInjector(context); annotation injector.injectMembers(this); or foo = injector.getInstance(Foo.class); Dagger 2 Compile time No runtime overhead of scanning the classpath
  • 15.
    Idea Modules can beimported dynamically Modules expose Dagger 2 injectable objects Module 1 Module 2 App Developer imports modules Injector constains MembersInjector & Providers maps Injector.get() & Injector.inject() methods work dynamically REUSABLE ANDROID SDK ENVIRONMENT Android developer
  • 16.
    dependencies { compile 'com.you.app:module.one:6.6.6' } @Injection(modules= { ModuleOne.class }) public class MainApplication extends Application { } Foo foo = Injector.getInstance(Foo.class);
  • 17.
    How to write anannotation processor?
  • 18.
    @Override public Set<String> getSupportedAnnotationTypes(){ return Collections.singleton( Injection.class.getCanonicalName() ); } How-to Set annotations supported by your processor: Set supported java-source version: @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); // SourceVersion.RELEASE_8; }
  • 19.
    @Override public synchronized voidinit(ProcessingEnvironment processingEnv) { filer = processingEnv.getFiler(); } How-to Get “Filer” to write source code: Files are written to: build/generated/source/apt/debug build/generated/source/apt/release
  • 20.
    Hint Class Object TypeMirror - Use descriptionof types instead of types - Classes are NOT available yet - they are STILL COMPILED Class<?> clazz = Class.forName("your.app.package.YourType"); ClassNotFoundException
  • 21.
    @Example public class Foo{ private int a; private Other other; public Foo () { } public void setA ( int newA ) { } } Type descriptions TypeElement VariableElement ExecutableElement ExecutableElement TypeElement
  • 22.
    Analyse source Gather information Writenew source files Modify current source files Use gathered information
  • 23.
    AnnotationProcessor for (Element typeElement: roundEnvironment.getElementsAnnotatedWith(Injection.class)) { String name = typeElement.getSimpleName().toString(); } Get all elements with a specific annotation: typeElement.getEnclosedElements(); typeElement.getEnclosingElement(); Moving up & down the type hiararchy:
  • 24.
    AnnotationProcessor for (AnnotationMirror annotationMirror: ((DeclaredType)module).asElement().getAnnotationMirrors()) { for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror.getElementValues().entrySet()) { // use the entry with AnnotationValueVisitor } } Get annotations from TypeMirror: final String[] annotationParam = new String[1]; final AnnotationValueVisitor valueVisitor = new AnnotationValueVisitor(); valueVisitor.setFunction(new Function<TypeMirror, Void>() { @Nullable @Override public Void apply(@Nullable TypeMirror param) { TypeMirror moduleClass = param; //assembles modules list return null; } }); annotationParam[0] = entry.getKey().getSimpleName().toString(); entry.getValue().accept(valueVisitor, null); @Annotation(param = { Sample.class }) Class<?>[] param() Sample.class
  • 25.
  • 26.
    Square’s JavaPoet Thank you @JakeWharton;) ● works with TypeMirrors out of the box ● adds imports automatically ● removes duplicate imports ● supports static imports ● code & control flow for / if etc. ● built-in support for Filer
  • 27.
    JavaPoet MethodSpec.Builder constructorMethod = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) .addCode("this("+ "$1T" + ".builder().build()); n", typeMirror); Create methods using JavaPoet: TypeSpec typeSpec = TypeSpec.classBuilder(outputClassName) .addModifiers(Modifier.PUBLIC) .superclass(...) .addJavadoc("annotation processor ...") .addField(componentElem, "innerField", Modifier.PRIVATE, Modifier.FINAL) .addMethod(constructorMethod.build()) .build(); Create types using JavaPoet: * imports are added automatically here
  • 28.
    JavaPoet ClassName.get("your.package.name”, "DaggerInjector") Create customunreachable types (ClassName) : * imports are added automatically as well
  • 29.
    Filer JavaFile.builder("your.package.name", typeSpec).build().writeTo(filer); Write sourcecode using JavaPoet & Filer: filer.createSourceFile("public class A() {}", null); Write source code Filer:
  • 30.
  • 31.
    Debugging ● Annotation processingcannot be debugged directly ● Compile project with gradle in debug mode ● Attach to the compile process from IDE ● [OPTIONAL] configure scripts to launch debug from IDE Detailed tutorial here
  • 32.
    Links - Angelika Langer,Annotation Processing link - Hannes Dorfmann, Annotation Processing 101 link link - JavaDocs: Element, TypeElement, ExecutableElement
  • 33.