@Eliminate("Boilerplate")

 @Eliminate("Boilerplate")

Using Java annotations to reduce boilerplate code.

E86c7de302b490b7f2a67e54960510d0?s=128

Ryan Harter

July 28, 2016
Tweet

Transcript

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

  2. JAVA HAS BOILERPLATE

  3. JAVA IS BOILERPLATE

  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; } }
  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; }\
  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; }\ }\
  7. Annotation Processing + Code Generation

  8. Annotation Processing

  9. Annotation Processing

  10. Annotation Processing • Part of javac

  11. Annotation Processing • Part of javac • Read @Annotated source

  12. Annotation Processing • Part of javac • Read @Annotated source

    • Generate .java source files
  13. Benefits

  14. Benefits • Write your code generator once

  15. Benefits • Write your code generator once • Trust the

    generated code
  16. Components

  17. Components • Annotations

  18. Components • Annotations • Processor

  19. Components • Annotations • Processor • APT (Annotation Processing Tool)

  20. Components • Annotations • Processor • APT (Annotation Processing Tool)

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

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

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

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

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

  26. Annotation Processor public class FooProcessor extends AbstractProcessor { public FooProcessor()

    { super(); }\ }\\
  27. 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(); }\ }\\
  28. Annotation Processor public class FooProcessor extends AbstractProcessor { … @Override

    public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }\ }\\
  29. Annotation Processor public class FooProcessor extends AbstractProcessor { … @Override

    public Set<String> getSupportedAnnotationTypes() { return ImmutableSet.of(Builder.class.getCanonicalName()); } }\\
  30. Annotation Processor public class FooProcessor extends AbstractProcessor { … @Override

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ... } }\\
  31. Annotation Processor public class FooProcessor extends AbstractProcessor { … @Override

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ... } }
  32. Processing Rounds Java Sources APT Completed

  33. Processing Rounds Java Sources APT Completed Round 1

  34. Processing Rounds Java Sources APT Completed Round 1

  35. Processing Rounds Java Sources APT Completed Round 1

  36. Processing Rounds Java Sources APT Completed Round 1

  37. Processing Rounds Java Sources APT Completed Round 2

  38. Processing Rounds Java Sources APT Completed Round 2

  39. Processing Rounds Java Sources APT Completed Complete

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

  41. 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
  42. Code Generation

  43. Code Generation

  44. Code Generation • Pair well with Annotation Processors

  45. Code Generation • Pair well with Annotation Processors • Used

    to generate new Java source files
  46. Code Generation • Pair well with Annotation Processors • Used

    to generate new Java source files • JavaPoet represents Java source as model
  47. 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
  48. JavaPoet https://github.com/square/javapoet

  49. JavaPoet • Uses Fluent API with builders https://github.com/square/javapoet

  50. JavaPoet • Uses Fluent API with builders • Based on

    Specs https://github.com/square/javapoet
  51. JavaPoet • Uses Fluent API with builders • Based on

    Specs • TypeSpec https://github.com/square/javapoet
  52. JavaPoet • Uses Fluent API with builders • Based on

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

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

    Specs • TypeSpec • MethodSpec • ParameterSpec • FieldSpec https://github.com/square/javapoet
  55. JavaPoet public final class UserBuilder { // fields private String

    username; … // methods public UserBuilder username(String username) { this.username = username; return this; } … }\
  56. JavaPoet public final class UserBuilder { // fields private String

    username; … // methods public UserBuilder username(String username) { this.username = username; return this; } … }\
  57. JavaPoet public final class UserBuilder String builderName = String.format("%sBuilder", type.getSimpleName());

    ClassName builderType = ClassName.get(packageName, builderName);
  58. JavaPoet public final class UserBuilder { // fields private String

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

  60. JavaPoet Modifiers private String username;

  61. JavaPoet Modifiers Type private String username;

  62. JavaPoet Modifiers Type private String username; Name

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

    String username; Modifiers Type private String username; Name
  64. private String username; JavaPoet Modifiers Type Name FieldSpec username =

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

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

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

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

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

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

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

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

    UserBuilder username(String username) {\ this.username = username; return this; }\
  73. 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; }\
  74. 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; }\
  75. 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();
  76. 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();
  77. 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();
  78. 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();
  79. // 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();
  80. JavaPoet public final class UserBuilder {\ // fields private String

    username; … // methods public UserBuilder username(String username) {\ this.username = username; return this; }\ … }\
  81. JavaPoet public final class UserBuilder {\ }\

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

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

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

  85. JavaPoet TypeSpec builder = TypeSpec.classBuilder(builderType) .addModifiers(PUBLIC, FINAL) .addField(username) .addMethod(usernameSetter) .build();

    Modifiers Name Content public final class UserBuilder {\ }\
  86. JavaPoet Modifiers Name Content TypeSpec builder = TypeSpec.classBuilder(builderType) .addModifiers(PUBLIC, FINAL)

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

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

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

    public final class UserBuilder {\ }\
  90. Annotation Processor @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment

    roundEnv) { }
  91. @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
  92. @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
  93. @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
  94. // get element metadata String packageName = getPackageName(type); String targetName

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

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

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

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

    = lowerCamelCase(type.getSimpleName().toString()); Set<VariableElement> vars = getNonPrivateVariables(type); String builderName = String.format("%sBuilder", type.getSimpleName()); ClassName builderType = ClassName.get(packageName, builderName); Annotation Processor
  99. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  100. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  101. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  102. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  103. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  104. // create private fields and public setters List<FieldSpec> fields =

    new ArrayList<FieldSpec>(vars.size()); List<MethodSpec> setters = new ArrayList<MethodSpec>(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
  105. // 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
  106. // 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
  107. // 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
  108. // 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
  109. // 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
  110. // 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
  111. Annotation Processor // create the builder TypeSpec builder = TypeSpec.classBuilder(builderType)

    .addModifiers(PUBLIC, FINAL) .addFields(fields) .addMethods(setters) .addMethod(buildMethod) .build();
  112. // 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
  113. @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
  114. Using our Annotation public class User {/ String username;/ String

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

    String firstName;/ String lastName;/ int age;/ }/
  116. 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();
  117. 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
  118. Find Me • @rharter • http://ryanharter.com