Upgrade to Pro — share decks privately, control downloads, hide ads and more …

@Eliminate("Boilerplate")

Ryan Harter
September 29, 2016

 @Eliminate("Boilerplate")

DroidconNYC talk about using Annotation Processors to eliminate boilerplate in Java.

Ryan Harter

September 29, 2016
Tweet

More Decks by Ryan Harter

Other Decks in Technology

Transcript

  1. @Eliminate(“Boilerplate”)
    Ryan Harter
    @rharter

    View Slide

  2. JAVA HAS BOILERPLATE

    View Slide

  3. JAVA IS BOILERPLATE

    View Slide

  4. public class User {
    String username;
    String firstname;
    String lastname;
    int age;
    public String getUsername() {
    return username;
    }
    public String getFirstname() {
    return firstname;
    }
    public String getLastname() {
    return lastname;
    }
    public int getAge() {
    return age;
    }
    }

    View Slide

  5. public final class UserBuilder {
    private String username;
    private String firstName;
    private String lastName;
    private int age;
    public UserBuilder() {
    }\
    public UserBuilder username(String username) {
    this.username = username;
    return this;
    }\
    public UserBuilder firstName(String firstName) {
    this.firstName = firstName;
    return this;
    }\

    View Slide

  6. return this;
    }\
    public UserBuilder lastName(String lastName) {
    this.lastName = lastName;
    return this;
    }\
    public UserBuilder age(int age) {
    this.age = age;
    return this;
    }\
    public User build() {
    User user = new User();
    user.username = this.username;
    user.firstName = this.firstName;
    user.lastName = this.lastName;
    user.age = this.age;
    return user;
    }\
    }\

    View Slide

  7. Annotation Processing
    +
    Code Generation

    View Slide

  8. Annotation Processing

    View Slide

  9. Annotation Processing
    • Part of javac
    • Read @Annotated source
    • Generate .java source files

    View Slide

  10. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  11. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  12. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  13. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  14. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  15. APT (.java)
    Package (.apk)
    Dex (.dex)
    Package (.jar)
    Compile (.class)
    Write (.java)
    Annotation Processing

    View Slide

  16. Benefits
    • Write your code generator once
    • Trust the generated code

    View Slide

  17. Components
    • Annotations
    • Processor
    • APT (Annotation Processing Tool)
    • Annotated Source

    View Slide

  18. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }

    View Slide

  19. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }

    View Slide

  20. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }

    View Slide

  21. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }

    View Slide

  22. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }

    View Slide

  23. Annotations
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Builder {
    }
    @Builder public class Builder

    View Slide

  24. Annotation Processor
    public class FooProcessor extends AbstractProcessor {
    }\\

    View Slide

  25. Annotation Processor
    public class FooProcessor extends AbstractProcessor {
    public FooProcessor() {
    super();
    }\
    }\\

    View Slide

  26. Annotation Processor
    public class FooProcessor extends AbstractProcessor {
    private Messager messager;
    private Filer filer;

    @Override public synchronized void init(
    ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    this.messager = processingEnv.getMessager();
    this.filer = processingEnv.getFiler();
    }\
    }\\

    View Slide

  27. Annotation Processor
    public class FooProcessor extends AbstractProcessor {

    @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }\
    }\\

    View Slide

  28. Annotation Processor
    public class FooProcessor extends AbstractProcessor {

    @Override public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }
    @Override public Set getSupportedAnnotationTypes() {
    return ImmutableSet.of(Builder.class.getCanonicalName());
    }
    }\\

    View Slide

  29. Annotation Processor
    public class FooProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    ...
    }
    }\\

    View Slide

  30. Annotation Processor
    public class FooProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    ...
    }
    }

    View Slide

  31. Processing Rounds
    Java Sources APT Completed

    View Slide

  32. Processing Rounds
    Java Sources APT Completed
    Round 1

    View Slide

  33. Processing Rounds
    Java Sources APT Completed
    Round 1

    View Slide

  34. Processing Rounds
    Java Sources APT Completed
    Round 1

    View Slide

  35. Processing Rounds
    Java Sources APT Completed
    Round 2

    View Slide

  36. Processing Rounds
    Java Sources APT Completed
    Complete

    View Slide

  37. Annotation Processor
    • ServiceLoader Discovery File
    com.ryanharter.example.annotations.BuilderProcessor
    META-INF/services/javax.annotation.processing.Processor

    View Slide

  38. Add to APT Classpath
    dependencies {
    compile project(':annotation')
    apt project(':processor')
    }
    app/build.gradle
    https://github.com/tbroyer/gradle-apt-plugin
    https://bitbucket.org/hvisser/android-apt

    View Slide

  39. Code Generation

    View Slide

  40. Code Generation
    • Pair well with Annotation Processors
    • Used to generate new Java source files
    • JavaPoet represents Java source as model
    https://github.com/square/javapoet

    View Slide

  41. JavaPoet
    • Uses Fluent API with builders
    • Based on Specs
    • TypeSpec
    • MethodSpec
    • ParameterSpec
    • FieldSpec
    https://github.com/square/javapoet

    View Slide

  42. JavaPoet
    public final class UserBuilder {
    // fields
    private String username;

    // methods
    public UserBuilder username(String username) {
    this.username = username;
    return this;
    }

    }\

    View Slide

  43. JavaPoet
    public final class UserBuilder {
    // fields
    private String username;

    // methods
    public UserBuilder username(String username) {
    this.username = username;
    return this;
    }

    }\

    View Slide

  44. JavaPoet
    public final class UserBuilder
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);

    View Slide

  45. JavaPoet
    public final class UserBuilder {
    // fields
    private String username;

    // methods
    public UserBuilder username(String username) {
    this.username = username;
    return this;
    }

    }\

    View Slide

  46. private String username;
    JavaPoet

    View Slide

  47. JavaPoet
    Modifiers
    private String username;

    View Slide

  48. JavaPoet
    Modifiers Type
    private String username;

    View Slide

  49. JavaPoet
    Modifiers Type
    private String username;
    Name

    View Slide

  50. JavaPoet
    FieldSpec username = FieldSpec
    .builder(String.class, "username", Modifier.PRIVATE)
    .build();
    private String username;
    Modifiers Type
    private String username;
    Name

    View Slide

  51. private String username;
    JavaPoet
    Modifiers Type Name
    FieldSpec username = FieldSpec
    .builder(String.class, "username", Modifier.PRIVATE)
    .build();
    private String username;

    View Slide

  52. JavaPoet
    FieldSpec username = FieldSpec
    .builder(String.class, "username", Modifier.PRIVATE)
    .build();
    private String username;

    View Slide

  53. JavaPoet
    public final class UserBuilder {\
    // fields
    private String username;

    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    }\

    View Slide

  54. JavaPoet
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  55. JavaPoet
    Modifiers
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  56. JavaPoet
    Modifiers Return Type
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  57. JavaPoet
    Modifiers Return Type Name
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  58. JavaPoet
    Modifiers Return Type Name Parameters
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  59. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  60. JavaPoet
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  61. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    View Slide

  62. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();

    View Slide

  63. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();

    View Slide

  64. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();

    View Slide

  65. JavaPoet
    Modifiers Return Type Name Parameters
    Statements
    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();

    View Slide

  66. // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\
    JavaPoet
    MethodSpec usernameSetter = MethodSpec.methodBuilder("username")
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(String.class, "username")
    .addStatement("this.$N = username", username)
    .addStatement("return this")
    .build();

    View Slide

  67. JavaPoet
    public final class UserBuilder {\
    // fields
    private String username;

    // methods
    public UserBuilder username(String username) {\
    this.username = username;
    return this;
    }\

    }\

    View Slide

  68. JavaPoet
    public final class UserBuilder {\
    }\

    View Slide

  69. JavaPoet
    Modifiers
    public final class UserBuilder {\
    }\

    View Slide

  70. JavaPoet
    Modifiers Name
    public final class UserBuilder {\
    }\

    View Slide

  71. JavaPoet
    Modifiers Name
    Content
    public final class UserBuilder {\
    }\

    View Slide

  72. JavaPoet
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addField(username)
    .addMethod(usernameSetter)
    .build();
    Modifiers Name
    Content
    public final class UserBuilder {\
    }\

    View Slide

  73. JavaPoet
    Modifiers Name
    Content
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addField(username)
    .addMethod(usernameSetter)
    .build();
    public final class UserBuilder {\
    }\

    View Slide

  74. JavaPoet
    Modifiers Name
    Content
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addField(username)
    .addMethod(usernameSetter)
    .build();
    public final class UserBuilder {\
    }\

    View Slide

  75. JavaPoet
    Modifiers Name
    Content
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addField(username)
    .addMethod(usernameSetter)
    .build();
    public final class UserBuilder {\
    }\

    View Slide

  76. JavaPoet
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addField(username)
    .addMethod(usernameSetter)
    .build();
    public final class UserBuilder {\
    }\

    View Slide

  77. Annotation Processor
    @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    }

    View Slide

  78. @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    for (Element el : roundEnv.getElementsAnnotatedWith(Builder.class)) {
    }
    }
    @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    for (Element el : roundEnv.getElementsAnnotatedWith(Builder.class)) {
    }
    }
    Annotation Processor

    View Slide

  79. @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    for (Element el : roundEnv.getElementsAnnotatedWith(Builder.class)) {
    // get element metadata
    // create private fields and public setters
    // create the build method
    // create the builder type
    // write the java source file
    }
    }
    Annotation Processor

    View Slide

  80. // get element metadata
    String packageName = getPackageName(type);
    String targetName = lowerCamelCase(type.getSimpleName().toString());
    Set vars = getNonPrivateVariables(type);
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);
    Annotation Processor

    View Slide

  81. // get element metadata
    String packageName = getPackageName(type);
    String targetName = lowerCamelCase(type.getSimpleName().toString());
    Set vars = getNonPrivateVariables(type);
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);
    Annotation Processor

    View Slide

  82. // get element metadata
    String packageName = getPackageName(type);
    String targetName = lowerCamelCase(type.getSimpleName().toString());
    Set vars = getNonPrivateVariables(type);
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);
    Annotation Processor

    View Slide

  83. // get element metadata
    String packageName = getPackageName(type);
    String targetName = lowerCamelCase(type.getSimpleName().toString());
    Set vars = getNonPrivateVariables(type);
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);
    Annotation Processor

    View Slide

  84. // get element metadata
    String packageName = getPackageName(type);
    String targetName = lowerCamelCase(type.getSimpleName().toString());
    Set vars = getNonPrivateVariables(type);
    String builderName = String.format("%sBuilder", type.getSimpleName());
    ClassName builderType = ClassName.get(packageName, builderName);
    Annotation Processor

    View Slide

  85. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  86. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  87. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  88. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  89. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  90. // create private fields and public setters
    List fields = new ArrayList(vars.size());
    List setters = new ArrayList(vars.size());
    for (VariableElement var : vars) {
    TypeName typeName = TypeName.get(var.asType());
    String name = var.getSimpleName().toString();
    // create the field
    fields.add(FieldSpec.builder(typeName, name, PRIVATE).build());
    // create the setter
    setters.add(MethodSpec.methodBuilder(name)
    .addModifiers(PUBLIC)
    .returns(builderType)
    .addParameter(typeName, name)
    .addStatement("this.$N = $N", name, name)
    .addStatement("return this")
    .build());
    }
    Annotation Processor

    View Slide

  91. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  92. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  93. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  94. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  95. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  96. // create the build method
    TypeName targetType = TypeName.get(type.asType());
    MethodSpec.Builder buildMethodBuilder =
    MethodSpec.methodBuilder("build")
    .addModifiers(PUBLIC)
    .returns(targetType)
    .addStatement("$1T $2N = new $1T()", targetType, targetName);
    for (FieldSpec field : fields) {
    buildMethodBuilder
    .addStatement("$1N.$2N = this.$2N", targetName, field);
    }
    buildMethodBuilder.addStatement("return $N", targetName);
    MethodSpec buildMethod = buildMethodBuilder.build();
    Annotation Processor

    View Slide

  97. Annotation Processor
    // create the builder
    TypeSpec builder = TypeSpec.classBuilder(builderType)
    .addModifiers(PUBLIC, FINAL)
    .addFields(fields)
    .addMethods(setters)
    .addMethod(buildMethod)
    .build();

    View Slide

  98. // write java source file
    JavaFile file = JavaFile
    .builder(builderType.packageName(), builder.build())
    .build();
    try {
    file.writeTo(filer);
    } catch (IOException e) {
    messager.printMessage(Diagnostic.Kind.ERROR,
    "Failed to write file for element", el);
    }
    Annotation Processor

    View Slide

  99. @Override
    public boolean process(Set extends TypeElement> annotations,
    RoundEnvironment roundEnv) {
    for (Element el : roundEnv.getElementsAnnotatedWith(Builder.class)) {
    // get element metadata
    // create private fields and public setters
    // create the build method
    // create the builder type
    // write the java source file
    }
    }
    Annotation Processor

    View Slide

  100. Using our Annotation
    public class User {/
    String username;/
    String firstName;/
    String lastName;/
    int age;/
    }/

    View Slide

  101. Using our Annotation
    @Builder public class User {/
    String username;/
    String firstName;/
    String lastName;/
    int age;/
    }/

    View Slide

  102. Using our Annotation
    @Builder public class User {
    String username;
    String firstName;
    String lastName;
    int age;
    }
    User user = new UserBuilder()
    .username("rharter")
    .firstName("Ryan")
    .lastName("Harter")
    .age(30)
    .build();

    View Slide

  103. References
    • Android APT Plugin - https://bitbucket.org/hvisser/android-apt
    • Gradle APT Plugin - https://github.com/tbroyer/gradle-apt-plugin
    • JavaPoet - https://github.com/square/javapoet

    View Slide

  104. Find Me
    • @rharter
    • http://ryanharter.com

    View Slide