Slide 1

Slide 1 text

@Hello("Annotation Processing!") December 10, 2016 Tatsuya Maki

Slide 2

Slide 2 text

2. Annotation Processorをつくる 1. Annotation Processingをしる 3. Annotation Processorをつかう

Slide 3

Slide 3 text

2. Annotation Processorをつくる 1. Annotation Processingをしる 3. Annotation Processorをつかう

Slide 4

Slide 4 text

AutoValue https://github.com/google/auto

Slide 5

Slide 5 text

@AutoValue
 public abstract class User {
 public abstract String name();
 
 public abstract int age();
 }

Slide 6

Slide 6 text

final class AutoValue_User extends User {
 private final String name;
 private final int age;
 
 AutoValue_User(String name, int age) {
 if (name == null) {
 throw new NullPointerException("Null name");
 }
 this.name = name;
 this.age = age;
 }
 
 @Override
 public String name() {
 return name;
 }
 
 @Override
 public int age() {
 return age;
 }
 
 @Override
 public String toString() {
 return "User{" + "name=" + name + ", " + "age=" + age + "}";
 }
 
 @Override
 public boolean equals(Object o) {
 if (o == this) {
 return true;
 }


Slide 7

Slide 7 text

Butter Knife https://github.com/JakeWharton/butterknife/

Slide 8

Slide 8 text

public class MainActivity extends AppCompatActivity {
 @BindView(R.id.fab)
 FloatingActionButton fab;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 ButterKnife.bind(this);
 }
 
 @OnClick(R.id.fab)
 void showSnackBar(View view) {
 Snackbar.make(view, "FloatingActionButton is clicked!", Snackbar.LENGTH_LONG)
 .setAction("Action", null).show();
 }
 }

Slide 9

Slide 9 text

public class MainActivity_ViewBinding implements Unbinder {
 protected T target;
 private View view2131492974;
 
 @UiThread
 public MainActivity_ViewBinding(final T target, View source) {
 this.target = target;
 View view;
 view = Utils.findRequiredView(source, R.id.fab, "field 'fab' and method 'showSnackBar'");
 target.fab = Utils.castView(view, R.id.fab, "field 'fab'", FloatingActionButton.class);
 view2131492974 = view;
 view.setOnClickListener(new DebouncingOnClickListener() {
 @Override
 public void doClick(View p0) {
 target.showSnackBar(p0);
 }
 });
 }
 
 @Override
 @CallSuper
 public void unbind() {
 T target = this.target;
 if (target == null) throw new IllegalStateException("Bindings already cleared.");
 target.fab = null;
 view2131492974.setOnClickListener(null);
 view2131492974 = null;
 this.target = null;
 }
 }

Slide 10

Slide 10 text

Dagger 2 https://github.com/google/dagger

Slide 11

Slide 11 text

@ActivityScope
 @Component(modules = {ActivityModule.class})
 public interface ActivityComponent {
 void inject(@NonNull MainActivity activity);
 }

Slide 12

Slide 12 text

public final class DaggerActivityComponent implements ActivityComponent {
 private DaggerActivityComponent(Builder builder) {
 assert builder != null;
 }
 
 public static Builder builder() {
 return new Builder();
 }
 
 public static ActivityComponent create() {
 return builder().build();
 }
 
 @Override
 public void inject(MainActivity activity) {
 MembersInjectors.noOp().injectMembers(activity);
 }
 
 public static final class Builder {
 private Builder() {
 }
 
 public ActivityComponent build() {
 return new DaggerActivityComponent(this);
 }
 
 public Builder activityModule(ActivityModule activityModule) {
 Preconditions.checkNotNull(activityModule);
 return this;
 }
 }
 }

Slide 13

Slide 13 text

Annotation Processingは、 JSR 269で定義されている仕様。 コンパイル時にアノテーションを処理する。 Pluggable Annotation Processor APIのこと。

Slide 14

Slide 14 text

*.java *.class

Slide 15

Slide 15 text

*.java Parse & Enter 構文解析し、シンボルテーブルへ入力 *.class

Slide 16

Slide 16 text

*.java Parse & Enter Annotation Processing アノテーションを処理 *.class

Slide 17

Slide 17 text

*.java Parse & Enter Annotation Processing 新しいファイルが生成されると再処理 *.class

Slide 18

Slide 18 text

*.java *.class Parse & Enter Annotation Processing Analyze & Generate 構文木を分析し、classファイルに変換

Slide 19

Slide 19 text

ソースコードを生成するので安心。 実行時に余計な処理をしないので高速。 コンパイル時に型が検証できるので安全。 Reflectionと比較して、

Slide 20

Slide 20 text

2. Annotation Processorをつくる 1. Annotation Processingをしる 3. Annotation Processorをつかう

Slide 21

Slide 21 text

Annotationの実装 2 1 3 AnnotationProcessorの実装 AnnotationProcessorを登録

Slide 22

Slide 22 text

Annotationの実装 2 1 3 AnnotationProcessorの実装 AnnotationProcessorを登録

Slide 23

Slide 23 text

@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface MyAnnotation {
 String message();
 }

Slide 24

Slide 24 text

@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface MyAnnotation {
 String message();
 }

Slide 25

Slide 25 text

@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface MyAnnotation {
 String message();
 }

Slide 26

Slide 26 text

@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface MyAnnotation {
 String message();
 }

Slide 27

Slide 27 text

@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
 public @interface MyAnnotation {
 String message();
 }

Slide 28

Slide 28 text

Annotationの実装 2 1 3 AnnotationProcessorの実装 AnnotationProcessorを登録

Slide 29

Slide 29 text

public interface Processor {
 Set getSupportedOptions();
 
 Set getSupportedAnnotationTypes();
 
 SourceVersion getSupportedSourceVersion();
 
 void init(ProcessingEnvironment processingEnv);
 
 boolean process(Set annotations, RoundEnvironment roundEnv);
 
 Iterable getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText);
 }

Slide 30

Slide 30 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 31

Slide 31 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 32

Slide 32 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 33

Slide 33 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 34

Slide 34 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 35

Slide 35 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public Set getSupportedAnnotationTypes() {
 return Collections.singleton(MyAnnotation.class.getCanonicalName());
 } @Override
 public SourceVersion getSupportedSourceVersion() {
 return SourceVersion.latestSupported();
 } @Override
 public synchronized void init(ProcessingEnvironment environment) {
 super.init(environment);
 messager = environment.getMessager();
 elements = environment.getElementUtils();
 types = environment.getTypeUtils();
 filer = environment.getFiler();
 } @Override
 public boolean process(Set annotations, RoundEnvironment environment) { ... } }

Slide 36

Slide 36 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String message = annotation.message();
 writer.append(" return \"").append(message).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 37

Slide 37 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 38

Slide 38 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 39

Slide 39 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 40

Slide 40 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 41

Slide 41 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 42

Slide 42 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String message = annotation.message();
 writer.append(" return \"").append(message).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 43

Slide 43 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String message = annotation.message();
 writer.append(" return \"").append(message).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 44

Slide 44 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String message = annotation.message();
 writer.append(" return \"").append(message).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 45

Slide 45 text

public class MyAnnotationProcessor extends AbstractProcessor { @Override
 public boolean process(Set annotations, RoundEnvironment environment) {
 final Set elements = environment.getElementsAnnotatedWith(MyAnnotation.class);
 elements.forEach(element -> {
 final Name className = element.getSimpleName();
 final Name packageName = this.elements.getPackageOf(element).getQualifiedName();
 try {
 final JavaFileObject file = filer.createSourceFile(packageName + ".MyAnnotation_" + className);
 try (final Writer writer = file.openWriter()) {
 writer.append("package ").append(packageName).append(";\n\n")
 .append("public class MyAnnotation_").append(className).append(" {\n\n")
 .append(" @Override\n")
 .append(" public String toString() {\n");
 
 final MyAnnotation annotation = element.getAnnotation(MyAnnotation.class);
 final String value = annotation.value();
 writer.append(" return \"").append(value).append("\";\n")
 .append(" }\n")
 .append("}\n");
 writer.flush();
 }
 } catch (IOException e) {
 messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate a file:" + packageName);
 }
 });
 return false;
 }
 }

Slide 46

Slide 46 text

Annotationの実装 2 1 3 AnnotationProcessorの実装 AnnotationProcessorを登録

Slide 47

Slide 47 text

. |--src | |-- main | | |-- java | | | `-- io.t28.example.MyAnnotationProcessor.java | | `-- resources | | `-- META-INF.services | | `-- javax.annotation.processing.Processor | `-- test | `-- java | `-- io.t28.example.MyAnnotationProcessorTest.java `-- build.gradle

Slide 48

Slide 48 text

. |--src | |-- main | | |-- java | | | `-- io.t28.example.MyAnnotationProcessor.java | | `-- resources | | `-- META-INF.services | | `-- javax.annotation.processing.Processor | `-- test | `-- java | `-- io.t28.example.MyAnnotationProcessorTest.java `-- build.gradle

Slide 49

Slide 49 text

2. Annotation Processorをつくる 1. Annotation Processingをしる 3. Annotation Processorをつかう

Slide 50

Slide 50 text

dependencies {
 compile project(':library')
 annotationProcessor project(':processor') }

Slide 51

Slide 51 text

@MyAnnotation(message = "Hello, world.")
 public class MyClass {
 }

Slide 52

Slide 52 text

public class MyAnnotation_MyClass {
 
 @Override
 public String toString() {
 return "Hello, world.";
 }
 }


Slide 53

Slide 53 text

まとめ

Slide 54

Slide 54 text

2. コンパイル時にコードの検証や生成が可能 1. Pluggable Annotation Processing 3. ボイラープレートコードの生成に効果的

Slide 55

Slide 55 text

おまけ

Slide 56

Slide 56 text

AutoService https://github.com/google/auto/tree/master/service Compile Testing https://github.com/google/compile-testing JavaPoet https://github.com/square/javapoet

Slide 57

Slide 57 text

@Thanks