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