Annotation processing

64
Annotations Processing

Transcript of Annotation processing

Page 1: Annotation processing

Annotations Processing

Page 2: Annotation processing

Annotations Processing

Florent Champigny @florent_champ

@florent37

Page 3: Annotation processing

Annotation Processing ?

Page 4: Annotation processing

Quésaco ?

Inspection d’annotations

Génération de code

Page 5: Annotation processing

Et pourquoi ?

Éviter de répéter la même tâche encore et encore

Réduire le boilerplate

Assurer une bonne qualité du code

Suivre facilement des standards / patterns

Remonter des erreurs à la compilation

Page 6: Annotation processing

Qui utilise ça ?

Page 7: Annotation processing

Qui utilise ça ?public class ExampleActivity extends Activity {

@Bind(R.id.title) TextView title; @Bind(R.id.avatar) ImageView image;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); }}

http://jakewharton.github.io/butterknife/

Page 8: Annotation processing

Qui utilise ça ?public class ExampleActivity extends Activity {

@Inject UserManager userManager;

@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); MyApplication.get().component().inject(this); }}

http://google.github.io/dagger/

Page 9: Annotation processing

Qui utilise ça ?@Data @Builderpublic class DataExample { private final String name; @Setter(AccessLevel.PACKAGE) private int age; private double score; private String[] tags; }

https://projectlombok.org/

Page 10: Annotation processing

@Annotation

Page 11: Annotation processing

Back to basics : @Annotation

Java 5

Précède la partie de code gérée

Formats@Override, @NonNull, @Inject@Bind(R.id.text), @Author(name = “Florent”)

Package, Types, Variables, Constructors

Page 12: Annotation processing

Back to basics : @Annotation

@OverrideAssure à la compilation que la méthode est bien une

surcharge

@NonNull Object myObject;Vérifie qu’aucune valeur nulle ne sera insérée dans myObject

@Bind(R.id.text) TextView myLabel;Exécutera un findViewById(R.id.text) pour le placer dans

myLabelVérifie que la vue trouvée est bien une TextView

Page 13: Annotation processing

Back to basics : @Annotation

JDK / SDK :java.lang.Overrideandroid.support.annotation.NonNull

Interfaces JavaX : (JSR 330 Dependency Injection for Java)

javax.Injectjavax.Singleton

Tierce : butterknife.Bind

Page 14: Annotation processing

Back to basics : @Annotation

@Retention(SOURCE)@Target({TYPE, FIELD, METHOD})public @interface Author {

String name(); int revision() default 1;

}

Page 15: Annotation processing

Back to basics : @Annotation

@Retention(SOURCE)@Target({TYPE, FIELD, METHOD})public @interface Author {

String name(); int revision() default 1;

}

Page 16: Annotation processing

Back to basics : @Annotation

Retention : Source / Class / Runtime

Target : Type / Annotation_Type / PackageMethod / ConstructorField / Local_Variable / Parameter

Page 17: Annotation processing

APT ?

Page 18: Annotation processing

APT ?

Annotation Processing Tool

Java 6

JSR 269

Page 19: Annotation processing

APT ?

*.java Analyse APT

@

Génère des fichiers .java

*.class

Page 20: Annotation processing

Annotation Processing API

Annotation Processorimplémenter javax.annotation.processing.Processorétendre

javax.annotation.processing.AbstractProcessor

Définir les annotations acceptées : getSupportedAnnotationsType()

Traiter les annotations : process()

Page 21: Annotation processing

Annotation Processing API : getSupportedAnnotationsType@Retention(CLASS) @Target(FIELD)public @interface MyAnnotation {}

public class MyProcessor implements Processor{

@Override Set<String> getSupportedAnnotationType(){return Sets.newHashSet(

MyAnnotation.class.getCanonicalName());}

}

Page 22: Annotation processing

Annotation Processing API : getSupportedAnnotationsType@Retention(CLASS) @Target(FIELD)public @interface MyAnnotation {}

@SupportedAnnotationTypes(“com.github.florent37.processor.MyAnnotation”)public class MyProcessor extends AbstractProcessor {

}

Page 23: Annotation processing

Annotation Processing API : process

@SupportedAnnotationTypes(“com.github.florent37.processor.MyAnnotation”)public class MyProcessor extends AbstractProcessor {

@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {

return true;}} Indique au compilateur que le

processeur a géré l’annotation

Page 24: Annotation processing

Rounds

L’annotation processing s'exécute sur plusieurs rounds

Sur chaque round, une partie des sources sont compilées, et les processeurs acceptant les annotations trouvées sont appelés

Ce procédé s’arrête lorsque toutes les sources/annotations ont été examinées

La méthode process() reçoit deux paramètres :Set<? extends TypeElement> - Les types d’annotations qui ont débuté le

traitementRoundEnvironment - Les informations sur le round actuel

Page 25: Annotation processing

RoundsEnvironment

getRootElements()Liste des classes parcourues par le processor

getElementsAnnotatedWith(Class<? extends Annotation>)Cherche dans ces éléments les occurrences d’une

annotation

Page 26: Annotation processing

RoundsEnvironment

class MyModel {@MyAnnotation int number;

}

env.getRootElements() = [ R, BuildConfig, MainActivity, appcompat.R, MyModel ]

env.getElementsAnnotatedWith(MyAnnotation.class) =Element{ name=”number”, type=”int” }

Page 27: Annotation processing

ProcessingEnvironment

Disponible depuis le AbstractProcessor :

this.processingEnv

Utilitaires : .getMessager().getFiler().getElementUtils().getTypeUtils()

Page 28: Annotation processing

Element ? Type ?

Page 29: Annotation processing

Element

Représente un “élément” du programme

.getKind() -> ElementKind

.asType() -> ElementType

javax.lang.model.element.Element

Page 30: Annotation processing

Element

Représente un “élément” du programmePackageClassMethodVariable

.getKind() -> ElementKind

.asType() -> Type

javax.lang.model.element.Element

Page 31: Annotation processing

Element

Représente un “élément” du programme

.getKind() -> ElementKind package : PACKAGE, declared types : ENUM, CLASS, ANNOTATION_TYPEvariables : FIELD, PARAMETER, LOCAL_VARIABLEexecuytables : METHOD, CONSTRUCTOR, STATIC_INIT

.asType() -> Type

javax.lang.model.element.ElementKind

Page 32: Annotation processing

Element

Représente un “élément” du programme

.getKind() -> ElementKind

.asType() -> TypeJCPrimitiveType int, float, booleanJCNoType voidArrayType int[], float[]ClassType java.lang.StringMethodType int count(char[] seq) throws NullPointerException...

JC : JavacCodejavax.lang.model.type.TypeMiror

Page 33: Annotation processing

Elementclass MyModel {

@MyAnnotation private int number;}

Element element = env.getElementsAnnotatedWith(MyAnnotation.class).get(0);

{ element.getKind() = ElementKind.FIELD ; element.asType() = JCPrimitiveType }

Page 34: Annotation processing

Elementclass MyModel {

@MyAnnotation private int number;}

number : String variableName = element.getSimpleName().toString();

int : TypeName variableType = TypeName.get(element.asType());

MyModel : Element parent = element.getEnclosingElement();

private : Set<Modifier> modifiers = element.getModifiers();

Page 35: Annotation processing

Comment le faire connaître au compilateur ?

Page 36: Annotation processing

Comment le faire connaître au compilateur ?

Inclure un fichier

META-INF/services/javax.annotation.processing.Processor

Contenant la liste des processeurs (nom complet)com.github.florent37.processor.MyProcessor

Page 37: Annotation processing

Comment le faire connaître au compilateur ?

Ou utiliser Auto-Service (génère le fichier META-INF/services/...)

compile 'com.google.auto.service:auto-service:1.0-rc2'

Ajouter l’annotation @AutoService(Processor.class) au processor

@AutoService(Processor.class) public class MyProcessor extends Processor{ }

Page 38: Annotation processing

Génération de code

Page 39: Annotation processing

Génération de code

JavaPoet

A Java API for generating .java source files

Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.

https://github.com/square/javapoet

Page 40: Annotation processing

JavaPoetpackage com.example.helloworld;

public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); }}

Page 41: Annotation processing

JavaPoetMethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(main) .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld) .build();

Page 42: Annotation processing

JavaPoet

$L for Literals .addStatement("getIntent().get$LExtra(“a”,0) ”, “Int”) getIntent().getIntExtra(“a”,0)

$S for String .addStatement("getIntent().getStringExtra($S) ”, “name”) getIntent().getStringExtra(“name”)

$T for TypesGénère automatiquement les imports

import java.util.Date;

.addStatement("return new $T()", Date.class) return new Date();

Page 43: Annotation processing

Et comment on débug ?

Page 44: Annotation processing

Et comment on débug ?

gradle.properties :

org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5007

Page 45: Annotation processing

Et comment on débug ?

1. Lancer le gradle daemon ./gradlew --daemon

2. Ajouter des points d’arrêt

3. Lancer le remote debug “APT”

4. Titiller gradle./gradlew clean build

Page 46: Annotation processing

Exemple

Page 47: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

public class MyFragment extends Fragment{@Holy int number;@Holy String name;

@Override public void onCreate(){super.onCreate();HolyMyFragment.bless(this);

}}

MyFragment fragment = HolyMyFragment.newInstance(2,"florent");

Page 48: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

public final class HolyMyFragment { public static MyFragment newInstance(int number, String name) { Bundle bundle = new Bundle(); bundle.putInt("number",number); bundle.putString("name",name); MyFragment fragment = new MyFragment(); fragment.setArguments(bundle); return fragment; }

public static void bless(MyFragment fragment) { Bundle args = fragment.getArguments(); fragment.number = args.getInt("number"); fragment.name = args.getString("name"); }}

Page 49: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

Modules :

annotations compiler

java library java library

app

android module

Page 50: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

/annotations/.../Holy.java :

@Retention(CLASS) @Target(FIELD)public @interface Holy {}

Page 51: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

/compiler/build.gradle

apply plugin: 'java'

targetCompatibility = JavaVersion.VERSION_1_7sourceCompatibility = JavaVersion.VERSION_1_7

dependencies { compile 'com.squareup:javapoet:1.3.0' compile 'com.google.auto.service:auto-service:1.0-rc2'

compile project(":annotations")}

Page 52: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

/compiler/.../HolyProcessor.java

@SupportedAnnotationTypes("com.github.florent37.holy.annotations.Holy")@AutoService(Processor.class)public class HolyProcessor extends AbstractProcessor {

@Override public boolean process(Set<? extends TypeElement> annotations,

RoundEnvironment env) {

processHolys(env);

writeHoldersOnJavaFile();

return true;}

}

Page 53: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

/compiler/.../HolyProcessor.java

@SupportedAnnotationTypes("com.github.florent37.holy.annotations.Holy")@AutoService(Processor.class)public class HolyProcessor extends AbstractProcessor {

@Override public boolean process(Set<? extends TypeElement> annotations,

RoundEnvironment env) {

processHolys(env);

writeHoldersOnJavaFile();

return true;}

}

Page 54: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

protected void processHolys(RoundEnvironment env) { for (Element element : env.getElementsAnnotatedWith(Holy.class)) {

String variableName = element.getSimpleName().toString(); //number TypeName variableType = TypeName.get(element.asType()); //int

TypeElement enclosing = (TypeElement) element.getEnclosingElement();

String elementName = enclosing.getSimpleName().toString();

ClassName enclosingClassName = ClassName.get(enclosing);

HolyFragmentHolder holder = findOrCreateHolyFragmentHolder(enclosing, elementName);

holder.addArgument(new Variable(variableType, variableName)); }}

Page 55: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

protected void processHolys(RoundEnvironment env) { for (Element element : env.getElementsAnnotatedWith(Holy.class)) { //ex: @Holy Integer number; String variableName = element.getSimpleName().toString(); //number TypeName variableType = TypeName.get(element.asType()); //int

TypeElement enclosing = (TypeElement) element.getEnclosingElement();

String elementName = enclosing.getSimpleName().toString();

ClassName enclosingClassName = ClassName.get(enclosing);

HolyFragmentHolder holder = findOrCreateHolyFragmentHolder(enclosing, elementName);

holder.addArgument(new Variable(variableType, variableName)); }}

Page 56: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

protected void processHolys(RoundEnvironment env) { for (Element element : env.getElementsAnnotatedWith(Holy.class)) { String variableName = element.getSimpleName().toString(); //number TypeName variableType = TypeName.get(element.asType()); //int

//ex : com.github.florent37.MyFragment TypeElement enclosing = (TypeElement) element.getEnclosingElement();

//ex: MyFragment String elementName = enclosing.getSimpleName().toString();

//ex : com.github.florent37.MyFragment ClassName enclosingClassName = ClassName.get(enclosing);

HolyFragmentHolder holder = findOrCreateHolyFragmentHolder(enclosing, elementName);

holder.addArgument(new Variable(variableType, variableName));

}}

Page 57: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

protected void processHolys(RoundEnvironment env) { for (Element element : env.getElementsAnnotatedWith(Holy.class)) { String variableName = element.getSimpleName().toString(); //number TypeName variableType = TypeName.get(element.asType()); //int

TypeElement enclosing = (TypeElement) element.getEnclosingElement();

String elementName = enclosing.getSimpleName().toString();

//ex : com.github.florent37.MyFragment ClassName enclosingClassName = ClassName.get(enclosing);

HolyFragmentHolder holder = findOrCreateHolyFragmentHolder(enclosing, elementName);

holder.addArgument(new Variable(variableType, variableName));

}}

Page 58: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

public class HolyFragmentHolder { ClassName classNameComplete; List<Variable> args; String className;

...}

public class Variable { TypeName typeName; String elementName;

...}

Page 59: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

public static MyFragment newInstance(int number, String name){

}

public void construct(HolyFragmentHolder holder) { MethodSpec.Builder newInstanceB = MethodSpec.methodBuilder("newInstance") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(holder.classNameComplete);

for (Variable arg : holder.args) { newInstanceB.addParameter(arg.typeName, arg.elementName); }

...

Page 60: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

Bundle bundle = new Bundle();bundle.putInt(“number”,number);bundle.putString(“name”,name);

ClassName bundleClass = ClassName.get("android.os", "Bundle");

newInstanceB.addStatement("$T bundle = new $T()", bundleClass, bundleClass);

for (Variable arg : holder.args) { newInstanceB.addStatement("bundle.put$L($S,$L)",

getType(arg.typeName), arg.elementName, arg.elementName);}

Page 61: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

protected String getType(TypeName typeName) { if (typeName == TypeName.INT || typeName == ClassName.get(Integer.class)) return "Int"; if (typeName.equals(ClassName.get(String.class))) return "String"; return "";}

Page 62: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

MyFragment fragment = new MyFragment();fragment.setArguments(bundle);return fragment;

newInstanceB.addStatement("$T fragment = new $T()",

holder.classNameComplete, holder.classNameComplete)

.addStatement("fragment.setArguments(bundle)") .addStatement("return

fragment");

MethodSpec newInstance = newInstanceB.build();

Page 63: Annotation processing

HolyFragment https://github.com/florent37/HolyFragment

TypeSpec holyFragment = TypeSpec.classBuilder("Holy" + holder.className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(newInstance) .addMethod(bless) .build();

JavaFile javaFile = JavaFile.builder(holder.classNameComplete.packageName(), holyFragment

).build();

javaFile.writeTo(System.out);

javaFile.writeTo(this.processingEnv.getFiler());

Page 64: Annotation processing

Questions ?