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

@Eliminate("Boilerplate")

 @Eliminate("Boilerplate")

Using Java annotations to reduce boilerplate code.

Ryan Harter

July 28, 2016
Tweet

More Decks by Ryan Harter

Other Decks in Technology

Transcript

  1. 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; } }
  2. 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; }\
  3. 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; }\ }\
  4. 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(); }\ }\\
  5. Annotation Processor public class FooProcessor extends AbstractProcessor { … @Override

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

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

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

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { ... } }
  9. 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
  10. Code Generation • Pair well with Annotation Processors • Used

    to generate new Java source files • JavaPoet represents Java source as model
  11. 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
  12. JavaPoet • Uses Fluent API with builders • Based on

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

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

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

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

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

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

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

    username; … // methods public UserBuilder username(String username) { this.username = username; return this; } … }\
  20. private String username; JavaPoet Modifiers Type Name FieldSpec username =

    FieldSpec .builder(String.class, "username", Modifier.PRIVATE) .build(); private String username;
  21. JavaPoet public final class UserBuilder {\ // fields private String

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

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

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

    UserBuilder username(String username) {\ this.username = username; return this; }\
  25. 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; }\
  26. 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; }\
  27. 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();
  28. 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();
  29. 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();
  30. 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();
  31. // 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();
  32. JavaPoet public final class UserBuilder {\ // fields private String

    username; … // methods public UserBuilder username(String username) {\ this.username = username; return this; }\ … }\
  33. JavaPoet Modifiers Name Content TypeSpec builder = TypeSpec.classBuilder(builderType) .addModifiers(PUBLIC, FINAL)

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

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

    .addField(username) .addMethod(usernameSetter) .build(); public final class UserBuilder {\ }\
  36. @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
  37. @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
  38. @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
  39. // 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
  40. // 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
  41. // 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
  42. // 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
  43. // 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
  44. // 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
  45. // 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
  46. // 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
  47. // 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
  48. // 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
  49. // 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
  50. // 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
  51. // 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
  52. // 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
  53. // 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
  54. // 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
  55. // 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
  56. Annotation Processor // create the builder TypeSpec builder = TypeSpec.classBuilder(builderType)

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

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

    String firstName;/ String lastName;/ int age;/ }/
  61. 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();
  62. 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