aboutsummaryrefslogtreecommitdiff
path: root/value/userguide/autobuilder.md
blob: ccd191ca9a6d46dcbc467ccc9c9c04fd827f4c10 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# AutoBuilder


AutoBuilder makes it easy to create a generalized builder, with setter methods
that accumulate values, and a build method that calls a constructor or static
method with those values as parameters. Callers don't need to know the order of
those parameters. Parameters can also have default values. There can be
validation before the constructor or method call.

If you are familiar with [AutoValue builders](builders.md) then AutoBuilder
should also be familiar. Where an `@AutoValue.Builder` has setter methods
corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder`
has setter methods corresponding to the parameters of a constructor or static
method. Apart from that, the two are very similar.

AutoBuilder is **unstable** and it is possible that its API
may change. We do not recommend depending on it for production code yet.

## Example: calling a constructor

Here is a simple example:

```
@AutoBuilder(ofClass = Person.class)
abstract class PersonBuilder {
  static PersonBuilder personBuilder() {
    return new AutoBuilder_PersonBuilder();
  }

  abstract PersonBuilder setName(String name);
  abstract PersonBuilder setId(int id);
  abstract Person build();
}
```

It might be used like this:

```
Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build();
```

That would have the same effect as this:

```
Person p = new Person("Priz", 6);
```

But it doesn't require you to know what order the constructor parameters are in.

Here, `setName` and `setId` are _setter methods_. Calling
`builder.setName("Priz")` records the value `"Priz"` for the parameter `name`,
and likewise with `setId`.

There is also a `build()` method. Calling that method invokes the `Person`
constructor with the parameters that were previously set.

## Example: calling a Kotlin constructor

Kotlin has named arguments and default arguments for constructors and functions,
which means there is not much need for anything like AutoBuilder there. But if
you are constructing an instance of a Kotlin data class from Java code,
AutoBuilder can help.

Given this trivial Kotlin data class:

```
class KotlinData(val int: Int, val string: String?)
```

You might make a builder for it like this:

```
@AutoBuilder(ofClass = KotlinData.class)
public abstract class KotlinDataBuilder {
  public static KotlinDataBuilder kotlinDataBuilder() {
    return new AutoBuilder_KotlinDataBuilder();
  }

  public abstract setInt(int x);
  public abstract setString(@Nullable String x);
  public abstract KotlinData build();
}
```

The Kotlin type `String?` corresponds to `@Nullable String` in the AutoBuilder
class, where `@Nullable` is any annotation with that name, such as
`org.jetbrains.annotations.Nullable`.

## The generated subclass

Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a
concrete subclass. In the example above, this will be `class
AutoBuilder_PersonBuilder extends PersonBuilder`. It is common to have a static
`builder()` method, as in the example, which calls `new AutoBuilder_...()`. That
will typically be the only reference to the generated class.

If the `@AutoBuilder` type is nested then the name of the generated class
reflects that nesting. For example:

```
class Outer {
  static class Inner {
    @AutoBuilder
    abstract static class Builder {...}
  }
  static Inner.Builder builder() {
    return new AutoBuilder_Outer_Inner_Builder();
  }
}
```

## `@AutoBuilder` annotation parameters

`@AutoBuilder` has two annotation parameters, `ofClass` and `callMethod`.

If `ofClass` is specified, then `build()` will call a constructor or static
method of that class. Otherwise it will call a constructor or static method of
the class _containing_ the `@AutoBuilder` class.

If `callMethod` is specified, then `build()` will call a static method with that
name. Otherwise `build()` will call a constructor.

The following examples illustrate the various possibilities. These examples use
an interface for the `@AutoBuilder` type. You can also use an abstract class; if
it is nested then it must be static.

### Both `callMethod` and `ofClass`

```
@AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
interface LocalTimeBuilder {
  ...
  LocalTime build(); // calls: LocalTime.of(...)
}
```

### Only `ofClass`

```
@AutoBuilder(ofClass = Thread.class)
interface ThreadBuilder {
  ...
  Thread build(); // calls: new Thread(...)
}
```

### Only `callMethod`

```
class Foo {
  static String concat(String first, String middle, String last) {...}

  @AutoBuilder(callMethod = "concat")
  interface ConcatBuilder {
    ...
    String build(); // calls: Foo.concat(first, middle, last)
  }
}
```

Notice in this example that the static method returns `String`. The implicit
`ofClass` is `Foo`, but the static method can return any type.

### Neither `callMethod` nor `ofClass`

```
class Person {
  Person(String name, int id) {...}

  @AutoBuilder
  interface Builder {
    ...
    Person build(); // calls: new Person(name, id)
  }
}
```

## The build method

The build method must have a certain return type. If it calls a constructor then
its return type must be the type of the constructed class. If it calls a static
method then its return type must be the return type of the static method.

The build method is often called `build()` but it does not have to be. The only
requirement is that there must be exactly one no-arg abstract method that has
the return type just described and that does not correspond to a parameter name.

The following example uses the name `call()` since that more accurately reflects
what it does:

```
public class LogUtil {
  public static void log(Level severity, String message, Object... params) {...}

  @AutoBuilder(callMethod = "log")
  public interface Caller {
    Caller setSeverity(Level level);
    Caller setMessage(String message);
    Caller setParams(Object... params);
    void call(); // calls: LogUtil.log(severity, message, params)
  }
```

## Overloaded constructors or methods

There might be more than one constructor or static method that matches the
`callMethod` and `ofClass`. AutoBuilder will ignore any that are not visible to
the generated class, meaning private, or package-private and in a different
package. Of the others, it will pick the one whose parameter names match the
`@AutoBuilder` setter methods. It is a compilation error if there is not exactly
one such method or constructor.

## Generics

If the builder calls the constructor of a generic type, then it must have the
same type parameters as that type, as in this example:

```
class NumberPair<T extends Number> {
  NumberPair(T first, T second) {...}

  @AutoBuilder
  interface Builder<T extends Number> {
    Builder<T> setFirst(T x);
    Builder<T> setSecond(T x);
    NumberPair<T> build();
  }
}
```

If the builder calls a static method with type parameters, then it must have the
same type parameters, as in this example:

```
class Utils {
  static <K extends Number, V> Map<K, V> singletonNumberMap(K key, V value) {...}

  @AutoBuilder(callMethod = "singletonNumberMap")
  interface Builder<K extends Number, V> {
    Builder<K, V> setKey(K x);
    Builder<K, V> setValue(V x);
    Map<K, V> build();
  }
}
```

Although it's unusual, a Java constructor can have its own type parameters,
separately from any that its containing class might have. A builder that calls a
constructor like that must have the type parameters of the class followed by the
type parameters of the constructor:

```
class CheckedSet<E> implements Set<E> {
  <T extends E> CheckedSet(Class<T> type) {...}

  @AutoBuilder
  interface Builder<E, T extends E> {
    Builder<E, T> setType(Class<T> type);
    CheckedSet<E> build();
  }
}
```

## Required, optional, and nullable parameters

Parameters that are annotated `@Nullable` are null by default. Parameters of
type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty
by default. Every other parameter is _required_, meaning that the build method
will throw `IllegalStateException` if any are omitted.

To establish default values for parameters, set them in the `builder()` method
before returning the builder.

```
class Foo {
  Foo(String bar, @Nullable String baz, String buh) {...}

  static Builder builder() {
    return new AutoBuilder_Foo_Builder()
        .setBar(DEFAULT_BAR);
  }

  @AutoBuilder
  interface Builder {
    Builder setBar(String x);
    Builder setBaz(String x);
    Builder setBuh(String x);
    Foo build();
  }

  {
     builder().build(); // IllegalStateException, buh is not set
     builder().setBuh("buh").build(); // OK, bar=DEFAULT_BAR and baz=null
     builder().setBaz(null).setBuh("buh").build(); // OK
     builder().setBar(null); // NullPointerException, bar is not @Nullable
  }
}
```

Trying to set a parameter that is _not_ annotated `@Nullable` to `null` will
produce a `NullPointerException`.

`@Nullable` here is any annotation with that name, such as
`javax.annotation.Nullable` or
`org.checkerframework.checker.nullness.qual.Nullable`.

## Getters

The `@AutoBuilder` class or interface can also have _getter_ methods. A getter
method returns the value that has been set for a certain parameter. Its return
type can be either the same as the parameter type, or an `Optional` wrapping
that type. Calling the getter before any value has been set will throw an
exception in the first case or return an empty `Optional` in the second.

In this example, the `nickname` parameter defaults to the same value as the
`name` parameter but can also be set to a different value:

```
public class Named {
  Named(String name, String nickname) {...}

  @AutoBuilder
  public abstract static class Builder {
    public abstract Builder setName(String x);
    public abstract Builder setNickname(String x);
    abstract String getName();
    abstract Optional<String> getNickname();
    abstract Named autoBuild();

    public Named build() {
      if (!getNickname().isPresent()) {
        setNickname(getName());
      }
      return autoBuild();
    }
  }
}
```

The example illustrates having a package-private `autoBuild()` method that
AutoBuilder implements. The public `build()` method calls it after adjusting the
nickname if necessary.

The builder in the example is an abstract class rather than an interface. An
abstract class allows us to distinguish between public methods for users of the
builder to call, and package-private methods that the builder's own logic uses.

## Naming conventions

A setter method for the parameter `foo` can be called either `setFoo` or `foo`.
A getter method can be called either `getFoo` or `foo`, and for a `boolean`
parameter it can also be called `isFoo`. The choice for getters and setters is
independent. For example your getter might be `foo()` while your setter is
`setFoo(T)`.

By convention, the parameter name of a setter method either echoes the parameter
being set:<br>
`Builder setName(String name);`<br>
or it is just `x`:<br>
`Builder setName(String x);`<br>

If class `Foo` has a nested `@AutoBuilder` that builds instances of `Foo`, then
conventionally that type is called `Builder`, and instances of it are obtained
by calling a static `Foo.builder()` method:

```
Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build();
Foo.Builder fooBuilder = Foo.builder();
```

If an `@AutoBuilder` for `Foo` is its own top-level class then that class will
typically be called `FooBuilder` and it will have a static `fooBuilder()` method
that returns an instance of `FooBuilder`. That way callers can statically import
`FooBuilder.fooBuilder` and just write `fooBuilder()` in their code.

```
@AutoBuilder(ofClass = Foo.class)
public abstract class FooBuilder {
  public static FooBuilder fooBuilder() {
    return new AutoBuilder_FooBuilder();
  }
  ...
  public abstract Foo build();
}
```

If an `@AutoBuilder` is designed to call a static method that is not a factory
method, the word "call" is better than "build" in the name of the type
(`FooCaller`), the static method (`fooCaller()`), and the "build" method (`call()`).

```
@AutoBuilder(callMethod = "log", ofClass = MyLogger.class)
public abstract class LogCaller {
  public static LogCaller logCaller() {
    return new AutoBuilder_LogCaller();
  }
  ...
  public abstract void call();
}

// used as:
logCaller().setLevel(Level.INFO).setMessage("oops").call();
```

## Other builder features

There are a number of other builder features that have not been detailed here
because they are the same as for `@AutoValue.Builder`. They include:

*   [Special treatment of collections](builders-howto.md#collection)
*   [Handling of nested builders](builders-howto.md#nested_builders)

There is currently no equivalent of AutoValue's
[`toBuilder()`](builders-howto.md#to_builder). Unlike AutoValue, there is not
generally a mapping back from the result of the constructor or method to its
parameters.

## When parameter names are unavailable

AutoBuilder depends on knowing the names of parameters. But parameter names are
not always available in Java. They _are_ available in these cases, at least:

*   In code that is being compiled at the same time as the `@AutoBuilder` class
    or interface.
*   In _records_ (from Java 16 onwards).
*   In the constructors of Kotlin data classes.
*   In code that was compiled with the [`-parameters`] option.

A Java compiler bug means that parameter names are not available to AutoBuilder
when compiling with JDK versions before 11, in any of these cases except the
first. We recommend building with a recent JDK, using the `--release` option if
necessary to produce code that can run on earlier versions.

If parameter names are unavailable, you always have the option of introducing a
static method in the same class as the `@AutoBuilder` type, and having it call
the method you want. Since it is compiled at the same time, its parameter names
are available.

Here's an example of fixing a problem this way. The code here typically will not
compile, since parameter names of JDK methods are not available:

```
import java.time.LocalTime;

public class TimeUtils {
  // Does not work, since parameter names from LocalTime.of are unavailable.
  @AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
  public interface TimeBuilder {
    TimeBuilder setHour(int x);
    TimeBuilder setMinute(int x);
    TimeBuilder setSecond(int x);
    LocalTime build();
  }
}
```

It will produce an error message like this:

```
error: [AutoBuilderNoMatch] Property names do not correspond to the parameter names of any static method named "of":
  public interface TimeBuilder {
  ^
    of(int arg0, int arg1)
    of(int arg0, int arg1, int arg2)
    of(int arg0, int arg1, int arg2, int arg3)
```

The names `arg0`, `arg1`, etc are concocted by the compiler because it doesn't
have the real names.

Introducing a static method fixes the problem:

```
import java.time.LocalTime;

public class TimeUtils {
  static LocalTime localTimeOf(int hour, int second, int second) {
    return LocalTime.of(hour, minute, second);
  }

  @AutoBuilder(callMethod = "localTimeOf")
  public interface TimeBuilder {
    TimeBuilder setHour(int x);
    TimeBuilder setMinute(int x);
    TimeBuilder setSecond(int x);
    LocalTime build();
  }
}
```

[`-parameters`]: https://docs.oracle.com/en/java/javase/16/docs/specs/man/javac.html#option-parameters