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