git clone http://danamlund/git/ya-builder/.git
Log | Files | Refs | LICENSE

README.org (10472B)


      1 #+TITLE:	Yet another builder-pattern generator
      2 #+AUTHOR:	Dan Amlund Thomsen
      3 #+EMAIL:	dan@danamlund.dk
      4 #+DATE:		2016-03-30
      5 
      6 * What
      7 Yet another builder-pattern generator java annotation processor.
      8 
      9 The gimmick of this one is that it generates builders that give you
     10 compile-time errors when you forget to define a required
     11 parameter. This trick is nicely explained in [[https://michid.wordpress.com/2008/08/13/type-safe-builder-pattern-in-java/][Michids Type-safe Builder
     12 Pattern in Java]].
     13 
     14 Note: This is a joke. I cannot imagine any situation where this
     15 builder generator would be useful.
     16 
     17 * How
     18 #+BEGIN_SRC java
     19 public class Usage {
     20     private final int id;
     21     private final String name;
     22 
     23     @Builder("UsageBuilder")
     24     Usage(@Required int id, @Default("none") String name) {
     25         this.id = id;
     26         this.name = name;
     27     }
     28     
     29     public static void main(String[] args) {
     30         Usage a = UsageBuilder.build(build -> build.id(42));
     31         Usage b = UsageBuilder.build(build -> build.id(42)
     32                                                    .name("not empty"));
     33     }
     34 }
     35 #+END_SRC
     36 
     37 ** Compile-time error when @Required not set
     38 #+BEGIN_EXAMPLE
     39   UsageBuilder.build(build -> build.name("foo"));
     40 #+END_EXAMPLE
     41 
     42 Results in the error:
     43 #+BEGIN_EXAMPLE
     44 $ javac -cp lib/ya-builder-1.0.jar -d build src/Usage.java
     45 src/Usage.java:14: error: incompatible types: bad return type in lambda expression
     46         UsageBuilder.build(build -> build.name("foo"));
     47                                               ^
     48     UsageBuilder<MissingId> cannot be converted to UsageBuilder<Good>
     49 #+END_EXAMPLE
     50 
     51 The important part of this cryptic error is "MissingId". This error
     52 tells you that you forgot to call the =.id(42)= method on the builder.
     53 
     54 The same error for a more complex builder:
     55 #+BEGIN_EXAMPLE
     56 $ javac -cp lib/ya-builder-1.0.jar -d build src/Complex.java
     57 src/Complex.java:32: error: incompatible types: bad return type in lambda expression
     58                                               .uuid("d3b07384d113edec49eaa6238ad5ff00")
     59                                                    ^
     60     ComplexBuilder<Good,Good,MissingBirthDate,Good,Good,MissingOneOfHeight,Good> cannot be converted to ComplexBuilder<Good,Good,Good,Good,Good,Good,Good>
     61 Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
     62 1 error
     63 #+END_EXAMPLE
     64 
     65 ** Generated builders
     66 The generated =UsageBuilder.java= from the Usage example:
     67 #+BEGIN_SRC java
     68 public class UsageBuilder<ID> {
     69   private int id;
     70   private java.lang.String name = "none";
     71 
     72   public static Usage build(java.util.function.Function<UsageBuilder<MissingId>, UsageBuilder<Good>> builder) {
     73     UsageBuilder<Good> built = 
     74       builder.apply(new UsageBuilder<MissingId>());
     75     return new Usage(built.id, built.name);
     76   }
     77 
     78   /**
     79    *  Required
     80    */
     81   @SuppressWarnings("unchecked")
     82   public UsageBuilder<Good> id(int id) {
     83     this.id = id;
     84     return (UsageBuilder<Good>) this;
     85   }
     86 
     87   /**
     88    *  Optional (default: none)
     89    */
     90   public UsageBuilder<ID> name(java.lang.String name) {
     91     this.name = name;
     92     return this;
     93   }
     94 
     95   public static class Good { }
     96   public static class MissingId { }
     97 }
     98 #+END_SRC
     99 
    100 *** The complex builder
    101 Input file =Complex.java=:
    102 #+BEGIN_SRC java
    103 public class Complex {
    104 
    105     @Builder("ComplexBuilder")
    106     public static String complex(@Required int id, 
    107                                  @Required String name,
    108                                  @Default("none") String comment,
    109                                  String comment2,
    110                                  @Default("java.time.LocalDate.now()") LocalDate date,
    111                                  @Required LocalDate birthDate,
    112                                  @Required String uuid,
    113                                  @Required String ssn,
    114                                  @RequiredOneOf("height") int heightInCm,
    115                                  @RequiredOneOf("height") double heightInFeet,
    116                                  @Required boolean isFalse) {
    117         return id + " " + name + " " + comment + " " + comment2 
    118             + " "  + date + " " + birthDate + " " + uuid + " " + ssn
    119             + " " + heightInCm + " " + heightInFeet + " " + isFalse;
    120     }
    121     
    122     public static void main(String[] args) {
    123         String compelx = ComplexBuilder.build(build -> build.id(42)
    124                                               // .birthDate(LocalDate.of(1, 1, 1970))
    125                                               .name("Foo")
    126                                               // .comment("none")
    127                                               // .comment2(null)
    128                                               // .date(LocalDate.now())
    129                                               .isFalse(false)
    130                                               .ssn("1337")
    131                                               .uuid("d3b07384d113edec49eaa6238ad5ff00")
    132                                               // .heightInCm(42)
    133                                               // .heightInFeet(1.38)
    134                                               );
    135     }
    136 }
    137 #+END_SRC
    138 
    139 
    140 And the generated =ComplexBuilder.java=:
    141 
    142 #+BEGIN_SRC java
    143 public class ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> {
    144   private int id;
    145   private java.lang.String name;
    146   private java.lang.String comment = "none";
    147   private java.lang.String comment2;
    148   private java.time.LocalDate date = java.time.LocalDate.now();
    149   private java.time.LocalDate birthDate;
    150   private java.lang.String uuid;
    151   private java.lang.String ssn;
    152   private int heightInCm;
    153   private double heightInFeet;
    154   private boolean isFalse;
    155 
    156   public static java.lang.String build(java.util.function.Function<ComplexBuilder<MissingId, MissingName, MissingBirthDate, MissingUuid, MissingSsn, MissingOneOfHeight, MissingIsFalse>, ComplexBuilder<Good, Good, Good, Good, Good, Good, Good>> builder) {
    157     ComplexBuilder<Good, Good, Good, Good, Good, Good, Good> built = 
    158       builder.apply(new ComplexBuilder<MissingId, MissingName, MissingBirthDate, MissingUuid, MissingSsn, MissingOneOfHeight, MissingIsFalse>());
    159     return Complex.complex(built.id, built.name, built.comment, built.comment2, built.date, built.birthDate, built.uuid, built.ssn, built.heightInCm, built.heightInFeet, built.isFalse);
    160   }
    161 
    162   /**
    163    *  Required.
    164    */
    165   @SuppressWarnings("unchecked")
    166   public ComplexBuilder<Good, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> id(int id) {
    167     this.id = id;
    168     return (ComplexBuilder<Good, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE>) this;
    169   }
    170 
    171   /**
    172    *  Required.
    173    */
    174   @SuppressWarnings("unchecked")
    175   public ComplexBuilder<MISSINGID, Good, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> name(java.lang.String name) {
    176     this.name = name;
    177     return (ComplexBuilder<MISSINGID, Good, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE>) this;
    178   }
    179 
    180   /**
    181    *  Optional (default: none).
    182    */
    183   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> comment(java.lang.String comment) {
    184     this.comment = comment;
    185     return this;
    186   }
    187 
    188   /**
    189    *  Optional.
    190    */
    191   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> comment2(java.lang.String comment2) {
    192     this.comment2 = comment2;
    193     return this;
    194   }
    195 
    196   /**
    197    *  Optional (default: java.time.LocalDate.now()).
    198    */
    199   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> date(java.time.LocalDate date) {
    200     this.date = date;
    201     return this;
    202   }
    203 
    204   /**
    205    *  Required.
    206    */
    207   @SuppressWarnings("unchecked")
    208   public ComplexBuilder<MISSINGID, MISSINGNAME, Good, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> birthDate(java.time.LocalDate birthDate) {
    209     this.birthDate = birthDate;
    210     return (ComplexBuilder<MISSINGID, MISSINGNAME, Good, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE>) this;
    211   }
    212 
    213   /**
    214    *  Required.
    215    */
    216   @SuppressWarnings("unchecked")
    217   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, Good, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE> uuid(java.lang.String uuid) {
    218     this.uuid = uuid;
    219     return (ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, Good, MISSINGSSN, MISSINGONEOFHEIGHT, MISSINGISFALSE>) this;
    220   }
    221 
    222   /**
    223    *  Required.
    224    */
    225   @SuppressWarnings("unchecked")
    226   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, Good, MISSINGONEOFHEIGHT, MISSINGISFALSE> ssn(java.lang.String ssn) {
    227     this.ssn = ssn;
    228     return (ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, Good, MISSINGONEOFHEIGHT, MISSINGISFALSE>) this;
    229   }
    230 
    231   /**
    232    *  Requires one of: heightInCm, heightInFeet.
    233    */
    234   @SuppressWarnings("unchecked")
    235   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, Good, MISSINGISFALSE> heightInCm(int heightInCm) {
    236     this.heightInCm = heightInCm;
    237     this.heightInFeet = 0.0;
    238     return (ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, Good, MISSINGISFALSE>) this;
    239   }
    240 
    241   /**
    242    *  Requires one of: heightInCm, heightInFeet.
    243    */
    244   @SuppressWarnings("unchecked")
    245   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, Good, MISSINGISFALSE> heightInFeet(double heightInFeet) {
    246     this.heightInFeet = heightInFeet;
    247     this.heightInCm = 0;
    248     return (ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, Good, MISSINGISFALSE>) this;
    249   }
    250 
    251   /**
    252    *  Required.
    253    */
    254   @SuppressWarnings("unchecked")
    255   public ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, Good> isFalse(boolean isFalse) {
    256     this.isFalse = isFalse;
    257     return (ComplexBuilder<MISSINGID, MISSINGNAME, MISSINGBIRTHDATE, MISSINGUUID, MISSINGSSN, MISSINGONEOFHEIGHT, Good>) this;
    258   }
    259 
    260   public static class Good { }
    261   public static class MissingId { }
    262   public static class MissingName { }
    263   public static class MissingBirthDate { }
    264   public static class MissingUuid { }
    265   public static class MissingSsn { }
    266   public static class MissingOneOfHeight { }
    267   public static class MissingIsFalse { }
    268 }
    269 #+END_SRC