Annotation Processing
& Code Gen
@kojilin
2015/05/30@TWJUG
Outline
•My requirements
•Annotation Processing
•Code gen
My Requirements
Kaif Android Client
•Using REST client to fetch article and
debates
•Need stable connection
Kaif Android Client
•Using REST client to fetch article and
debates
•Need stable connection
•If not, stale data is better than no data
•Local cache Database
•Http cache response
Rest Http Client
•Retrofit from Square
•A type-safe REST client for Android
and Java
•http://square.github.io/retrofit/
public interface GitHubService {
@GET("/users/{user}/repos")
List<Repo> listRepos(@Path("user") String user);
}
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.build();
GitHubService service =

restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");
Header
@Headers("Cache-Control: max-stale=86400")
@GET("/users/{user}/repos")
List<Repo> listRepos$$RetryStale(@Path("user") 

String user);
How to retry ?
try {
// normal access
} catch (Exception e) {
// force local cache if network error
}
How to retry ?
try {
service.listRepos("koji");
} catch (Exception e) {
if (e instanceof RetrofitError
&& isNetworkError(e)) {
service.listRepos$$RetryStale("koji");
}
}
If we do it manually
•Every service access need previous try
& catch code.
try {
service.listRepos("koji");
} catch (Exception e) {
if (e instanceof RetrofitError
&& isNetworkError(e)) {
service.listRepos$$RetryStale("koji");
}

}
If we do it manually
•Every service access need previous try
& catch code.
•Using Proxy pattern
service.listRepos("koji");
service = 

Proxy.newProxyInstance(serviceClass.getClassLoader(),

new Class[] { serviceClass },

new RetryStaleHandler(restAdapter.create(

Class.forName(serviceClass.getName()

+ "$$RetryStale"))));
@Override 

public Object invoke(Object proxy, Method 

method, Object[] args) throws Throwable {

...

if(method isGet and canRetry) {

try & catch block
}

...

}
RetryStaleHandler.java
If we do it manually
•Every service access need previous try
& catch code.
•Using Proxy pattern
•Almost every GET method need 

$$RetryStale version.
public interface GitHubService {
@GET("/users/{user}/repos")

List<Repo> listRepos(@Path("user") String user);



@GET("/users/{user}/repos")

@Headers("Cache-Control: max-stale=86400")

List<Repo> listRepos$$RetryStale(@Path("user") String user);



@GET("/repos/{owner}/{repo}/contributors")

List<Contributor> contributors(@Path("owner") String owner, 

@Path("repo") String repo);



@GET("/repos/{owner}/{repo}/contributors")

@Headers("Cache-Control: max-stale=86400")

List<Contributor> contributors$$RetryStale(@Path("owner") String 

owner, @Path("repo") String repo);
}
If we do it manually
•Every service access need previous try
& catch code.
•Using Proxy pattern.
•Almost every GET method need 

$$RetryStale.
•Generating boilerplate code during
build.
How ?
•Scan all interface has @GET method.
•Generate new interface has both non-
cache and cache method.
When scan?
•Compile time.
•Annotation Processing.
•Run time.
•Reflection.
•Slow and feedback at runtime
•Do we know the concrete class we want
to generate when we are writing ?
Generate code
•bytecode
•.java
Generate bytecode
•Can do more than what java source can.
•Bytecode is difficult to read and write.
•ASM
•JarJar
•Dexmaker for android
Generate .java
•Readable and maintainable.
•JavaPoet from Square
•Successor of JavaWriter
•https://github.com/square/javapoet
public interface GitHubService {
@GET("/users/{user}/repos")

List<Repo> listRepos(@Path("user") String user);



@GET("/repos/{owner}/{repo}/contributors")

List<Contributor> contributors(@Path("owner") String owner, 

@Path("repo") String repo);

}
public interface GitHubService$$RetryStale {
@GET("/users/{user}/repos")

List<Repo> listRepos(@Path("user") String user);



@GET("/users/{user}/repos")

@Headers("Cache-Control: max-stale=86400")

List<Repo> listRepos$$RetryStale(@Path("user") String user);



@GET("/repos/{owner}/{repo}/contributors")

List<Contributor> contributors(@Path("owner") String owner, 

@Path("repo") String repo);



@GET("/repos/{owner}/{repo}/contributors")

@Headers("Cache-Control: max-stale=86400")

List<Contributor> contributors$$RetryStale(@Path("owner") String 

owner, @Path("repo") String repo);
}
AutoValue
@AutoValue

public abstract class Foo {
public abstract String name();
public abstract int age();
}


final class AutoValue_Foo extends Foo {

private final String name;

private final int age;



AutoValue_Foo(

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 "Foo{"

+ "name=" + name

+ ", age=" + age

+ "}";

}



@Override

public boolean equals(Object o) {

if (o == this) {

return true;

}

if (o instanceof Foo) {

Foo that = (Foo) o;

return 

(this.name.equals(that.name()))

&& (this.age == that.age());

}

return false;

}



@Override

public int hashCode() {

int h = 1;

h *= 1000003;

h ^= name.hashCode();

h *= 1000003;

h ^= age;

return h;

}

}
Butter Knife


@InjectView(R.id.content)

public TextView content;

@InjectView(R.id.last_update_time)

public TextView lastUpdateTime;

@InjectView(R.id.vote_score)

public TextView voteScore;

@InjectView(R.id.debater_name)

public TextView debaterName;

@InjectView(R.id.debate_actions)

public DebateActions debateActions;



public DebateViewHolder(View itemView) {

super(itemView);

ButterKnife.inject(this, itemView);

content.setOnTouchListener(new 

ClickableSpanTouchListener());

}


@Override public void inject(final Finder finder, final T target, Object
source) {

View view;

view = finder.findRequiredView(source, 2131296343, "field 'content'");

target.content = finder.castView(view, 2131296343, "field 'content'");

view = 

finder.findRequiredView(source, 2131296375, "field 'lastUpdateTime'");

target.lastUpdateTime = 

finder.castView(view, 2131296375, "field 'lastUpdateTime'");

view = finder.findRequiredView(source, 2131296374, "field 'voteScore'");

target.voteScore = finder.castView(view, 2131296374, "field
'voteScore'");

view = finder.findRequiredView(source, 2131296373, "field
'debaterName'");

target.debaterName =
finder.castView(view, 2131296373, "field 'debaterName'");

view = finder.findRequiredView(source, 2131296370, "field
'debateActions'");

target.debateActions = 

finder.castView(view, 2131296370, "field 'debateActions'");

}



@Override public void reset(T target) {

target.content = null;

target.lastUpdateTime = null;

target.voteScore = null;

target.debaterName = null;

target.debateActions = null;

}
JPA metamodel
@Entity

public class Pet {
@Id

Long id;
String name;
}
@StaticMetaModel(Pet.class)

public class Pet_ {
public static volatile SingularAttribute<Person, 

Long> id;
public static volatile SingularAttribute<Person, 

String> name;
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Pet.class);
cq.where(cb.equal(itemNode.get(Pet_.id), 5))

.distinct(true);
Annotation Processing
•JSR 269
•Pluggable Annotation Processing API
•Run automatically when javac runs
•Using ServiceLoader to find
•META-INF/services/
javax.annotation.processing.Processor
•Running inside JVM
@SupportedSourceVersion(...)

@SupportedAnnotationTypes( 

"retrofit.http.GET")

public class MyProcessor extends 

AbstractProcessor {
@Override public synchronized void 

init(ProcessingEnvironment env){ }
@Override public boolean process(

Set<? extends TypeElement> annotations, 

RoundEnvironment env) { }

}
@SupportedSourceVersion(...)

@SupportedAnnotationTypes( 

"retrofit.http.GET")

public class MyProcessor extends 

AbstractProcessor {
@Override public synchronized void 

init(ProcessingEnvironment env){ }
@Override public boolean process(

Set<? extends TypeElement> annotations, 

RoundEnvironment env) { }

}
@SupportedSourceVersion(...)

@SupportedAnnotationTypes(...)

public class MyProcessor extends 

AbstractProcessor {
@Override public synchronized void 

init(ProcessingEnvironment env){ }
@Override public boolean process(

Set<? extends TypeElement> annotations, 

RoundEnvironment env) { }

}
ProcessingEnvironment
•Types
•ElementUtils
•Filer
•Messager
Elements
package foo.bar;



public class Foo { // Type Element
String bar; // Variable Element
public void hoge() { // Executable Element

System.out.println("Hello!");

}

}
Types
package foo.bar;



public class Foo {
String bar;
public void hoge() { 

System.out.println("Hello!");

}

}
@SupportedSourceVersion(...)

@SupportedAnnotationTypes(...)

public class MyProcessor extends 

AbstractProcessor {
@Override public synchronized void 

init(ProcessingEnvironment env){ }
@Override public boolean process(

Set<? extends TypeElement> annotations, 

RoundEnvironment env) { }

}
@Override public boolean process(

Set<? extends TypeElement> annotations, 

RoundEnvironment env) {
Set<? extends Element> elements = 

env.getElementsAnnotatedWith(GET.cl ass);
// generating code for elements
}

JavaPoet
package foo.bar;



public final class Foo {

public static void main(String[] args) {

System.out.println("Hello, JavaPoet!");

}

}
MethodSpec 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("foo.bar", helloWorld).build();



javaFile.writeTo(System.out);
Element to JavaPoet
•AnnotationSpec#get
•AnnotationMirror to AnnotationSpec
•MethodSpect#overriding
•Override MethodElement
public interface Foo$$RetryStale {
@GET("/foo/{user}")

List<Foo> foo(@Path("user") String user);



@GET("/foo/{user}")

@Headers("Cache-Control: max-stale=86400")

List<Foo> foo$$RetryStale(@Path("user") String user);



}
TypeSpec.Builder typeSpecBuilder = TypeSpec.interfaceBuilder(

classElement.getSimpleName() + "$RetryStale")

.addModifiers(Modifier.PUBLIC);
Interface
public interface Foo$$RetryStale
Method


MethodSpec.Builder builder = 

MethodSpec.methodBuilder("foo$RetryStale");

builder.addModifiers(Modifier.ABSTRACT)

.addModifiers(Modifier.PUBLIC);



methodElement.getParameters().stream().map(variableElement -> {



ParameterSpec.Builder parameterBuilder = 

ParameterSpec.builder(TypeName.get(variableElement.asType()),

variableElement.getSimpleName().toString());



variableElement.getAnnotationMirrors().stream()
.map(AnnotationSpecUtil::generate)

.forEach(parameterBuilder::addAnnotation);



return parameterBuilder.build();



}).forEach(builder::addParameter);
List<Foo> foo(@Path("user") String user)
Method
methodElement.getAnnotationMirrors()

.stream()

.map(AnnotationSpecUtil::generate)

.forEach(builder::addAnnotation);

@GET("/foo/{user}")
Convenient Libraries
•Google Auto-Service
•Truth
•Making your tests and their error
messages more readable and
discoverable
•Google Compile testing
AutoService
@AutoService(Processor.class)

public class MyProcessor extends AbstracProcessor {
...
}
•Generating META-INF/services/
javax.annotation.processing.Processor
Truth and Compile testing
ASSERT.about(javaSources())

.that(ImmutableList.of(inputFile))

.processedWith(new MyProcessor())

.compilesWithoutError()

.and()

.generatesSources(outputFile);
References
•ANNOTATION PROCESSING 101

http://hannesdorfmann.com/annotation-processing/
annotationprocessing101/
•Annotation Processing Boilerplate
Destruction

https://speakerdeck.com/jakewharton/annotation-
processing-boilerplate-destruction-square-waterloo-2014

Annotation processing and code gen

  • 1.
    Annotation Processing & CodeGen @kojilin 2015/05/30@TWJUG
  • 2.
  • 3.
  • 4.
    Kaif Android Client •UsingREST client to fetch article and debates •Need stable connection
  • 5.
    Kaif Android Client •UsingREST client to fetch article and debates •Need stable connection •If not, stale data is better than no data •Local cache Database •Http cache response
  • 6.
    Rest Http Client •Retrofitfrom Square •A type-safe REST client for Android and Java •http://square.github.io/retrofit/
  • 7.
    public interface GitHubService{ @GET("/users/{user}/repos") List<Repo> listRepos(@Path("user") String user); }
  • 8.
    RestAdapter restAdapter =new RestAdapter.Builder() .setEndpoint("https://api.github.com") .build(); GitHubService service =
 restAdapter.create(GitHubService.class);
  • 9.
    List<Repo> repos =service.listRepos("octocat");
  • 10.
  • 11.
    How to retry? try { // normal access } catch (Exception e) { // force local cache if network error }
  • 12.
    How to retry? try { service.listRepos("koji"); } catch (Exception e) { if (e instanceof RetrofitError && isNetworkError(e)) { service.listRepos$$RetryStale("koji"); } }
  • 13.
    If we doit manually •Every service access need previous try & catch code. try { service.listRepos("koji"); } catch (Exception e) { if (e instanceof RetrofitError && isNetworkError(e)) { service.listRepos$$RetryStale("koji"); }
 }
  • 14.
    If we doit manually •Every service access need previous try & catch code. •Using Proxy pattern
  • 15.
    service.listRepos("koji"); service = 
 Proxy.newProxyInstance(serviceClass.getClassLoader(),
 newClass[] { serviceClass },
 new RetryStaleHandler(restAdapter.create(
 Class.forName(serviceClass.getName()
 + "$$RetryStale")))); @Override 
 public Object invoke(Object proxy, Method 
 method, Object[] args) throws Throwable {
 ...
 if(method isGet and canRetry) {
 try & catch block }
 ...
 } RetryStaleHandler.java
  • 16.
    If we doit manually •Every service access need previous try & catch code. •Using Proxy pattern •Almost every GET method need 
 $$RetryStale version.
  • 17.
    public interface GitHubService{ @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") String user);
 
 @GET("/users/{user}/repos")
 @Headers("Cache-Control: max-stale=86400")
 List<Repo> listRepos$$RetryStale(@Path("user") String user);
 
 @GET("/repos/{owner}/{repo}/contributors")
 List<Contributor> contributors(@Path("owner") String owner, 
 @Path("repo") String repo);
 
 @GET("/repos/{owner}/{repo}/contributors")
 @Headers("Cache-Control: max-stale=86400")
 List<Contributor> contributors$$RetryStale(@Path("owner") String 
 owner, @Path("repo") String repo); }
  • 18.
    If we doit manually •Every service access need previous try & catch code. •Using Proxy pattern. •Almost every GET method need 
 $$RetryStale. •Generating boilerplate code during build.
  • 19.
    How ? •Scan allinterface has @GET method. •Generate new interface has both non- cache and cache method.
  • 20.
    When scan? •Compile time. •AnnotationProcessing. •Run time. •Reflection. •Slow and feedback at runtime •Do we know the concrete class we want to generate when we are writing ?
  • 21.
  • 22.
    Generate bytecode •Can domore than what java source can. •Bytecode is difficult to read and write. •ASM •JarJar •Dexmaker for android
  • 23.
    Generate .java •Readable andmaintainable. •JavaPoet from Square •Successor of JavaWriter •https://github.com/square/javapoet
  • 24.
    public interface GitHubService{ @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") String user);
 
 @GET("/repos/{owner}/{repo}/contributors")
 List<Contributor> contributors(@Path("owner") String owner, 
 @Path("repo") String repo);
 }
  • 25.
    public interface GitHubService$$RetryStale{ @GET("/users/{user}/repos")
 List<Repo> listRepos(@Path("user") String user);
 
 @GET("/users/{user}/repos")
 @Headers("Cache-Control: max-stale=86400")
 List<Repo> listRepos$$RetryStale(@Path("user") String user);
 
 @GET("/repos/{owner}/{repo}/contributors")
 List<Contributor> contributors(@Path("owner") String owner, 
 @Path("repo") String repo);
 
 @GET("/repos/{owner}/{repo}/contributors")
 @Headers("Cache-Control: max-stale=86400")
 List<Contributor> contributors$$RetryStale(@Path("owner") String 
 owner, @Path("repo") String repo); }
  • 26.
    AutoValue @AutoValue
 public abstract classFoo { public abstract String name(); public abstract int age(); }
  • 27.
    
 final class AutoValue_Fooextends Foo {
 private final String name;
 private final int age;
 
 AutoValue_Foo(
 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 "Foo{"
 + "name=" + name
 + ", age=" + age
 + "}";
 }
 
 @Override
 public boolean equals(Object o) {
 if (o == this) {
 return true;
 }
 if (o instanceof Foo) {
 Foo that = (Foo) o;
 return 
 (this.name.equals(that.name()))
 && (this.age == that.age());
 }
 return false;
 }
 
 @Override
 public int hashCode() {
 int h = 1;
 h *= 1000003;
 h ^= name.hashCode();
 h *= 1000003;
 h ^= age;
 return h;
 }
 }
  • 28.
    Butter Knife 
 @InjectView(R.id.content)
 public TextViewcontent;
 @InjectView(R.id.last_update_time)
 public TextView lastUpdateTime;
 @InjectView(R.id.vote_score)
 public TextView voteScore;
 @InjectView(R.id.debater_name)
 public TextView debaterName;
 @InjectView(R.id.debate_actions)
 public DebateActions debateActions;
 
 public DebateViewHolder(View itemView) {
 super(itemView);
 ButterKnife.inject(this, itemView);
 content.setOnTouchListener(new 
 ClickableSpanTouchListener());
 }
  • 29.
    
 @Override public voidinject(final Finder finder, final T target, Object source) {
 View view;
 view = finder.findRequiredView(source, 2131296343, "field 'content'");
 target.content = finder.castView(view, 2131296343, "field 'content'");
 view = 
 finder.findRequiredView(source, 2131296375, "field 'lastUpdateTime'");
 target.lastUpdateTime = 
 finder.castView(view, 2131296375, "field 'lastUpdateTime'");
 view = finder.findRequiredView(source, 2131296374, "field 'voteScore'");
 target.voteScore = finder.castView(view, 2131296374, "field 'voteScore'");
 view = finder.findRequiredView(source, 2131296373, "field 'debaterName'");
 target.debaterName = finder.castView(view, 2131296373, "field 'debaterName'");
 view = finder.findRequiredView(source, 2131296370, "field 'debateActions'");
 target.debateActions = 
 finder.castView(view, 2131296370, "field 'debateActions'");
 }
 
 @Override public void reset(T target) {
 target.content = null;
 target.lastUpdateTime = null;
 target.voteScore = null;
 target.debaterName = null;
 target.debateActions = null;
 }
  • 30.
    JPA metamodel @Entity
 public classPet { @Id
 Long id; String name; }
  • 31.
    @StaticMetaModel(Pet.class)
 public class Pet_{ public static volatile SingularAttribute<Person, 
 Long> id; public static volatile SingularAttribute<Person, 
 String> name; } CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Order> cq = cb.createQuery(Pet.class); cq.where(cb.equal(itemNode.get(Pet_.id), 5))
 .distinct(true);
  • 32.
    Annotation Processing •JSR 269 •PluggableAnnotation Processing API •Run automatically when javac runs •Using ServiceLoader to find •META-INF/services/ javax.annotation.processing.Processor •Running inside JVM
  • 33.
    @SupportedSourceVersion(...)
 @SupportedAnnotationTypes( 
 "retrofit.http.GET")
 public classMyProcessor extends 
 AbstractProcessor { @Override public synchronized void 
 init(ProcessingEnvironment env){ } @Override public boolean process(
 Set<? extends TypeElement> annotations, 
 RoundEnvironment env) { }
 }
  • 34.
    @SupportedSourceVersion(...)
 @SupportedAnnotationTypes( 
 "retrofit.http.GET")
 public classMyProcessor extends 
 AbstractProcessor { @Override public synchronized void 
 init(ProcessingEnvironment env){ } @Override public boolean process(
 Set<? extends TypeElement> annotations, 
 RoundEnvironment env) { }
 }
  • 35.
    @SupportedSourceVersion(...)
 @SupportedAnnotationTypes(...)
 public class MyProcessorextends 
 AbstractProcessor { @Override public synchronized void 
 init(ProcessingEnvironment env){ } @Override public boolean process(
 Set<? extends TypeElement> annotations, 
 RoundEnvironment env) { }
 }
  • 36.
  • 37.
    Elements package foo.bar;
 
 public classFoo { // Type Element String bar; // Variable Element public void hoge() { // Executable Element
 System.out.println("Hello!");
 }
 }
  • 38.
    Types package foo.bar;
 
 public classFoo { String bar; public void hoge() { 
 System.out.println("Hello!");
 }
 }
  • 39.
    @SupportedSourceVersion(...)
 @SupportedAnnotationTypes(...)
 public class MyProcessorextends 
 AbstractProcessor { @Override public synchronized void 
 init(ProcessingEnvironment env){ } @Override public boolean process(
 Set<? extends TypeElement> annotations, 
 RoundEnvironment env) { }
 }
  • 40.
    @Override public booleanprocess(
 Set<? extends TypeElement> annotations, 
 RoundEnvironment env) { Set<? extends Element> elements = 
 env.getElementsAnnotatedWith(GET.cl ass); // generating code for elements }

  • 41.
    JavaPoet package foo.bar;
 
 public finalclass Foo {
 public static void main(String[] args) {
 System.out.println("Hello, JavaPoet!");
 }
 }
  • 42.
    MethodSpec 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("foo.bar", helloWorld).build();
 
 javaFile.writeTo(System.out);
  • 43.
    Element to JavaPoet •AnnotationSpec#get •AnnotationMirrorto AnnotationSpec •MethodSpect#overriding •Override MethodElement
  • 44.
    public interface Foo$$RetryStale{ @GET("/foo/{user}")
 List<Foo> foo(@Path("user") String user);
 
 @GET("/foo/{user}")
 @Headers("Cache-Control: max-stale=86400")
 List<Foo> foo$$RetryStale(@Path("user") String user);
 
 }
  • 45.
    TypeSpec.Builder typeSpecBuilder =TypeSpec.interfaceBuilder(
 classElement.getSimpleName() + "$RetryStale")
 .addModifiers(Modifier.PUBLIC); Interface public interface Foo$$RetryStale
  • 46.
    Method 
 MethodSpec.Builder builder =
 MethodSpec.methodBuilder("foo$RetryStale");
 builder.addModifiers(Modifier.ABSTRACT)
 .addModifiers(Modifier.PUBLIC);
 
 methodElement.getParameters().stream().map(variableElement -> {
 
 ParameterSpec.Builder parameterBuilder = 
 ParameterSpec.builder(TypeName.get(variableElement.asType()),
 variableElement.getSimpleName().toString());
 
 variableElement.getAnnotationMirrors().stream() .map(AnnotationSpecUtil::generate)
 .forEach(parameterBuilder::addAnnotation);
 
 return parameterBuilder.build();
 
 }).forEach(builder::addParameter); List<Foo> foo(@Path("user") String user)
  • 47.
  • 48.
    Convenient Libraries •Google Auto-Service •Truth •Makingyour tests and their error messages more readable and discoverable •Google Compile testing
  • 49.
    AutoService @AutoService(Processor.class)
 public class MyProcessorextends AbstracProcessor { ... } •Generating META-INF/services/ javax.annotation.processing.Processor
  • 50.
    Truth and Compiletesting ASSERT.about(javaSources())
 .that(ImmutableList.of(inputFile))
 .processedWith(new MyProcessor())
 .compilesWithoutError()
 .and()
 .generatesSources(outputFile);
  • 51.
    References •ANNOTATION PROCESSING 101
 http://hannesdorfmann.com/annotation-processing/ annotationprocessing101/ •AnnotationProcessing Boilerplate Destruction
 https://speakerdeck.com/jakewharton/annotation- processing-boilerplate-destruction-square-waterloo-2014