annotation processing
Post on 12-Jan-2017
94 Views
Preview:
TRANSCRIPT
Annotations Processing
Annotations Processing
Florent Champigny @florent_champ
@florent37
Annotation Processing ?
Quésaco ?
Inspection d’annotations
Génération de code
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
Qui utilise ça ?
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/
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/
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/
@Annotation
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
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
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
Back to basics : @Annotation
@Retention(SOURCE)@Target({TYPE, FIELD, METHOD})public @interface Author {
String name(); int revision() default 1;
}
Back to basics : @Annotation
@Retention(SOURCE)@Target({TYPE, FIELD, METHOD})public @interface Author {
String name(); int revision() default 1;
}
Back to basics : @Annotation
Retention : Source / Class / Runtime
Target : Type / Annotation_Type / PackageMethod / ConstructorField / Local_Variable / Parameter
APT ?
APT ?
Annotation Processing Tool
Java 6
JSR 269
APT ?
*.java Analyse APT
@
Génère des fichiers .java
*.class
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()
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());}
}
Annotation Processing API : getSupportedAnnotationsType@Retention(CLASS) @Target(FIELD)public @interface MyAnnotation {}
@SupportedAnnotationTypes(“com.github.florent37.processor.MyAnnotation”)public class MyProcessor extends AbstractProcessor {
}
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
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
RoundsEnvironment
getRootElements()Liste des classes parcourues par le processor
getElementsAnnotatedWith(Class<? extends Annotation>)Cherche dans ces éléments les occurrences d’une
annotation
RoundsEnvironment
class MyModel {@MyAnnotation int number;
}
env.getRootElements() = [ R, BuildConfig, MainActivity, appcompat.R, MyModel ]
env.getElementsAnnotatedWith(MyAnnotation.class) =Element{ name=”number”, type=”int” }
ProcessingEnvironment
Disponible depuis le AbstractProcessor :
this.processingEnv
Utilitaires : .getMessager().getFiler().getElementUtils().getTypeUtils()
Element ? Type ?
Element
Représente un “élément” du programme
.getKind() -> ElementKind
.asType() -> ElementType
javax.lang.model.element.Element
Element
Représente un “élément” du programmePackageClassMethodVariable
.getKind() -> ElementKind
.asType() -> Type
javax.lang.model.element.Element
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
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
Elementclass MyModel {
@MyAnnotation private int number;}
Element element = env.getElementsAnnotatedWith(MyAnnotation.class).get(0);
{ element.getKind() = ElementKind.FIELD ; element.asType() = JCPrimitiveType }
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();
Comment le faire connaître au compilateur ?
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
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{ }
Génération de code
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
JavaPoetpackage com.example.helloworld;
public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); }}
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();
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();
Et comment on débug ?
Et comment on débug ?
gradle.properties :
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5007
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
Exemple
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");
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"); }}
HolyFragment https://github.com/florent37/HolyFragment
Modules :
annotations compiler
java library java library
app
android module
HolyFragment https://github.com/florent37/HolyFragment
/annotations/.../Holy.java :
@Retention(CLASS) @Target(FIELD)public @interface Holy {}
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")}
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;}
}
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;}
}
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)); }}
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)); }}
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));
}}
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));
}}
HolyFragment https://github.com/florent37/HolyFragment
public class HolyFragmentHolder { ClassName classNameComplete; List<Variable> args; String className;
...}
public class Variable { TypeName typeName; String elementName;
...}
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); }
...
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);}
…
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 "";}
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();
…
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());
Questions ?
top related