• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# AutoBuilder
2
3
4AutoBuilder makes it easy to create a generalized builder, with setter methods
5that accumulate values, and a build method that calls a constructor or static
6method with those values as parameters. Callers don't need to know the order of
7those parameters. Parameters can also have default values. There can be
8validation before the constructor or method call.
9
10If you are familiar with [AutoValue builders](builders.md) then AutoBuilder
11should also be familiar. Where an `@AutoValue.Builder` has setter methods
12corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder`
13has setter methods corresponding to the parameters of a constructor or static
14method. Apart from that, the two are very similar.
15
16AutoBuilder is **unstable** and it is possible that its API
17may change. We do not recommend depending on it for production code yet.
18
19## Example: calling a constructor
20
21Here is a simple example:
22
23```
24@AutoBuilder(ofClass = Person.class)
25abstract class PersonBuilder {
26  static PersonBuilder personBuilder() {
27    return new AutoBuilder_PersonBuilder();
28  }
29
30  abstract PersonBuilder setName(String name);
31  abstract PersonBuilder setId(int id);
32  abstract Person build();
33}
34```
35
36It might be used like this:
37
38```
39Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build();
40```
41
42That would have the same effect as this:
43
44```
45Person p = new Person("Priz", 6);
46```
47
48But it doesn't require you to know what order the constructor parameters are in.
49
50Here, `setName` and `setId` are _setter methods_. Calling
51`builder.setName("Priz")` records the value `"Priz"` for the parameter `name`,
52and likewise with `setId`.
53
54There is also a `build()` method. Calling that method invokes the `Person`
55constructor with the parameters that were previously set.
56
57## Example: calling a Kotlin constructor
58
59Kotlin has named arguments and default arguments for constructors and functions,
60which means there is not much need for anything like AutoBuilder there. But if
61you are constructing an instance of a Kotlin data class from Java code,
62AutoBuilder can help.
63
64Given this trivial Kotlin data class:
65
66```
67class KotlinData(val int: Int, val string: String?)
68```
69
70You might make a builder for it like this:
71
72```
73@AutoBuilder(ofClass = KotlinData.class)
74public abstract class KotlinDataBuilder {
75  public static KotlinDataBuilder kotlinDataBuilder() {
76    return new AutoBuilder_KotlinDataBuilder();
77  }
78
79  public abstract setInt(int x);
80  public abstract setString(@Nullable String x);
81  public abstract KotlinData build();
82}
83```
84
85The Kotlin type `String?` corresponds to `@Nullable String` in the AutoBuilder
86class, where `@Nullable` is any annotation with that name, such as
87`org.jetbrains.annotations.Nullable`.
88
89## The generated subclass
90
91Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a
92concrete subclass. In the example above, this will be `class
93AutoBuilder_PersonBuilder extends PersonBuilder`. It is common to have a static
94`builder()` method, as in the example, which calls `new AutoBuilder_...()`. That
95will typically be the only reference to the generated class.
96
97If the `@AutoBuilder` type is nested then the name of the generated class
98reflects that nesting. For example:
99
100```
101class Outer {
102  static class Inner {
103    @AutoBuilder
104    abstract static class Builder {...}
105  }
106  static Inner.Builder builder() {
107    return new AutoBuilder_Outer_Inner_Builder();
108  }
109}
110```
111
112## `@AutoBuilder` annotation parameters
113
114`@AutoBuilder` has two annotation parameters, `ofClass` and `callMethod`.
115
116If `ofClass` is specified, then `build()` will call a constructor or static
117method of that class. Otherwise it will call a constructor or static method of
118the class _containing_ the `@AutoBuilder` class.
119
120If `callMethod` is specified, then `build()` will call a static method with that
121name. Otherwise `build()` will call a constructor.
122
123The following examples illustrate the various possibilities. These examples use
124an interface for the `@AutoBuilder` type. You can also use an abstract class; if
125it is nested then it must be static.
126
127### Both `callMethod` and `ofClass`
128
129```
130@AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
131interface LocalTimeBuilder {
132  ...
133  LocalTime build(); // calls: LocalTime.of(...)
134}
135```
136
137### Only `ofClass`
138
139```
140@AutoBuilder(ofClass = Thread.class)
141interface ThreadBuilder {
142  ...
143  Thread build(); // calls: new Thread(...)
144}
145```
146
147### Only `callMethod`
148
149```
150class Foo {
151  static String concat(String first, String middle, String last) {...}
152
153  @AutoBuilder(callMethod = "concat")
154  interface ConcatBuilder {
155    ...
156    String build(); // calls: Foo.concat(first, middle, last)
157  }
158}
159```
160
161Notice in this example that the static method returns `String`. The implicit
162`ofClass` is `Foo`, but the static method can return any type.
163
164### Neither `callMethod` nor `ofClass`
165
166```
167class Person {
168  Person(String name, int id) {...}
169
170  @AutoBuilder
171  interface Builder {
172    ...
173    Person build(); // calls: new Person(name, id)
174  }
175}
176```
177
178## The build method
179
180The build method must have a certain return type. If it calls a constructor then
181its return type must be the type of the constructed class. If it calls a static
182method then its return type must be the return type of the static method.
183
184The build method is often called `build()` but it does not have to be. The only
185requirement is that there must be exactly one no-arg abstract method that has
186the return type just described and that does not correspond to a parameter name.
187
188The following example uses the name `call()` since that more accurately reflects
189what it does:
190
191```
192public class LogUtil {
193  public static void log(Level severity, String message, Object... params) {...}
194
195  @AutoBuilder(callMethod = "log")
196  public interface Caller {
197    Caller setSeverity(Level level);
198    Caller setMessage(String message);
199    Caller setParams(Object... params);
200    void call(); // calls: LogUtil.log(severity, message, params)
201  }
202```
203
204## Overloaded constructors or methods
205
206There might be more than one constructor or static method that matches the
207`callMethod` and `ofClass`. AutoBuilder will ignore any that are not visible to
208the generated class, meaning private, or package-private and in a different
209package. Of the others, it will pick the one whose parameter names match the
210`@AutoBuilder` setter methods. It is a compilation error if there is not exactly
211one such method or constructor.
212
213## Generics
214
215If the builder calls the constructor of a generic type, then it must have the
216same type parameters as that type, as in this example:
217
218```
219class NumberPair<T extends Number> {
220  NumberPair(T first, T second) {...}
221
222  @AutoBuilder
223  interface Builder<T extends Number> {
224    Builder<T> setFirst(T x);
225    Builder<T> setSecond(T x);
226    NumberPair<T> build();
227  }
228}
229```
230
231If the builder calls a static method with type parameters, then it must have the
232same type parameters, as in this example:
233
234```
235class Utils {
236  static <K extends Number, V> Map<K, V> singletonNumberMap(K key, V value) {...}
237
238  @AutoBuilder(callMethod = "singletonNumberMap")
239  interface Builder<K extends Number, V> {
240    Builder<K, V> setKey(K x);
241    Builder<K, V> setValue(V x);
242    Map<K, V> build();
243  }
244}
245```
246
247Although it's unusual, a Java constructor can have its own type parameters,
248separately from any that its containing class might have. A builder that calls a
249constructor like that must have the type parameters of the class followed by the
250type parameters of the constructor:
251
252```
253class CheckedSet<E> implements Set<E> {
254  <T extends E> CheckedSet(Class<T> type) {...}
255
256  @AutoBuilder
257  interface Builder<E, T extends E> {
258    Builder<E, T> setType(Class<T> type);
259    CheckedSet<E> build();
260  }
261}
262```
263
264## Required, optional, and nullable parameters
265
266Parameters that are annotated `@Nullable` are null by default. Parameters of
267type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty
268by default. Every other parameter is _required_, meaning that the build method
269will throw `IllegalStateException` if any are omitted.
270
271To establish default values for parameters, set them in the `builder()` method
272before returning the builder.
273
274```
275class Foo {
276  Foo(String bar, @Nullable String baz, String buh) {...}
277
278  static Builder builder() {
279    return new AutoBuilder_Foo_Builder()
280        .setBar(DEFAULT_BAR);
281  }
282
283  @AutoBuilder
284  interface Builder {
285    Builder setBar(String x);
286    Builder setBaz(String x);
287    Builder setBuh(String x);
288    Foo build();
289  }
290
291  {
292     builder().build(); // IllegalStateException, buh is not set
293     builder().setBuh("buh").build(); // OK, bar=DEFAULT_BAR and baz=null
294     builder().setBaz(null).setBuh("buh").build(); // OK
295     builder().setBar(null); // NullPointerException, bar is not @Nullable
296  }
297}
298```
299
300Trying to set a parameter that is _not_ annotated `@Nullable` to `null` will
301produce a `NullPointerException`.
302
303`@Nullable` here is any annotation with that name, such as
304`javax.annotation.Nullable` or
305`org.checkerframework.checker.nullness.qual.Nullable`.
306
307## Getters
308
309The `@AutoBuilder` class or interface can also have _getter_ methods. A getter
310method returns the value that has been set for a certain parameter. Its return
311type can be either the same as the parameter type, or an `Optional` wrapping
312that type. Calling the getter before any value has been set will throw an
313exception in the first case or return an empty `Optional` in the second.
314
315In this example, the `nickname` parameter defaults to the same value as the
316`name` parameter but can also be set to a different value:
317
318```
319public class Named {
320  Named(String name, String nickname) {...}
321
322  @AutoBuilder
323  public abstract static class Builder {
324    public abstract Builder setName(String x);
325    public abstract Builder setNickname(String x);
326    abstract String getName();
327    abstract Optional<String> getNickname();
328    abstract Named autoBuild();
329
330    public Named build() {
331      if (!getNickname().isPresent()) {
332        setNickname(getName());
333      }
334      return autoBuild();
335    }
336  }
337}
338```
339
340The example illustrates having a package-private `autoBuild()` method that
341AutoBuilder implements. The public `build()` method calls it after adjusting the
342nickname if necessary.
343
344The builder in the example is an abstract class rather than an interface. An
345abstract class allows us to distinguish between public methods for users of the
346builder to call, and package-private methods that the builder's own logic uses.
347
348## Naming conventions
349
350A setter method for the parameter `foo` can be called either `setFoo` or `foo`.
351A getter method can be called either `getFoo` or `foo`, and for a `boolean`
352parameter it can also be called `isFoo`. The choice for getters and setters is
353independent. For example your getter might be `foo()` while your setter is
354`setFoo(T)`.
355
356By convention, the parameter name of a setter method either echoes the parameter
357being set:<br>
358`Builder setName(String name);`<br>
359or it is just `x`:<br>
360`Builder setName(String x);`<br>
361
362If class `Foo` has a nested `@AutoBuilder` that builds instances of `Foo`, then
363conventionally that type is called `Builder`, and instances of it are obtained
364by calling a static `Foo.builder()` method:
365
366```
367Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build();
368Foo.Builder fooBuilder = Foo.builder();
369```
370
371If an `@AutoBuilder` for `Foo` is its own top-level class then that class will
372typically be called `FooBuilder` and it will have a static `fooBuilder()` method
373that returns an instance of `FooBuilder`. That way callers can statically import
374`FooBuilder.fooBuilder` and just write `fooBuilder()` in their code.
375
376```
377@AutoBuilder(ofClass = Foo.class)
378public abstract class FooBuilder {
379  public static FooBuilder fooBuilder() {
380    return new AutoBuilder_FooBuilder();
381  }
382  ...
383  public abstract Foo build();
384}
385```
386
387If an `@AutoBuilder` is designed to call a static method that is not a factory
388method, the word "call" is better than "build" in the name of the type
389(`FooCaller`), the static method (`fooCaller()`), and the "build" method (`call()`).
390
391```
392@AutoBuilder(callMethod = "log", ofClass = MyLogger.class)
393public abstract class LogCaller {
394  public static LogCaller logCaller() {
395    return new AutoBuilder_LogCaller();
396  }
397  ...
398  public abstract void call();
399}
400
401// used as:
402logCaller().setLevel(Level.INFO).setMessage("oops").call();
403```
404
405## Other builder features
406
407There are a number of other builder features that have not been detailed here
408because they are the same as for `@AutoValue.Builder`. They include:
409
410*   [Special treatment of collections](builders-howto.md#collection)
411*   [Handling of nested builders](builders-howto.md#nested_builders)
412
413There is currently no equivalent of AutoValue's
414[`toBuilder()`](builders-howto.md#to_builder). Unlike AutoValue, there is not
415generally a mapping back from the result of the constructor or method to its
416parameters.
417
418## When parameter names are unavailable
419
420AutoBuilder depends on knowing the names of parameters. But parameter names are
421not always available in Java. They _are_ available in these cases, at least:
422
423*   In code that is being compiled at the same time as the `@AutoBuilder` class
424    or interface.
425*   In _records_ (from Java 16 onwards).
426*   In the constructors of Kotlin data classes.
427*   In code that was compiled with the [`-parameters`] option.
428
429A Java compiler bug means that parameter names are not available to AutoBuilder
430when compiling with JDK versions before 11, in any of these cases except the
431first. We recommend building with a recent JDK, using the `--release` option if
432necessary to produce code that can run on earlier versions.
433
434If parameter names are unavailable, you always have the option of introducing a
435static method in the same class as the `@AutoBuilder` type, and having it call
436the method you want. Since it is compiled at the same time, its parameter names
437are available.
438
439Here's an example of fixing a problem this way. The code here typically will not
440compile, since parameter names of JDK methods are not available:
441
442```
443import java.time.LocalTime;
444
445public class TimeUtils {
446  // Does not work, since parameter names from LocalTime.of are unavailable.
447  @AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
448  public interface TimeBuilder {
449    TimeBuilder setHour(int x);
450    TimeBuilder setMinute(int x);
451    TimeBuilder setSecond(int x);
452    LocalTime build();
453  }
454}
455```
456
457It will produce an error message like this:
458
459```
460error: [AutoBuilderNoMatch] Property names do not correspond to the parameter names of any static method named "of":
461  public interface TimeBuilder {
462  ^
463    of(int arg0, int arg1)
464    of(int arg0, int arg1, int arg2)
465    of(int arg0, int arg1, int arg2, int arg3)
466```
467
468The names `arg0`, `arg1`, etc are concocted by the compiler because it doesn't
469have the real names.
470
471Introducing a static method fixes the problem:
472
473```
474import java.time.LocalTime;
475
476public class TimeUtils {
477  static LocalTime localTimeOf(int hour, int second, int second) {
478    return LocalTime.of(hour, minute, second);
479  }
480
481  @AutoBuilder(callMethod = "localTimeOf")
482  public interface TimeBuilder {
483    TimeBuilder setHour(int x);
484    TimeBuilder setMinute(int x);
485    TimeBuilder setSecond(int x);
486    LocalTime build();
487  }
488}
489```
490
491[`-parameters`]: https://docs.oracle.com/en/java/javase/16/docs/specs/man/javac.html#option-parameters
492