I would really like a Lombok @Builder annotation that properly deals with required parameters at compile time.
Here is what I have in mind:
[cc lang=”java”]
@lombok.Builder
class Foo {
@lombok.Builder.Required final String name;
@lombok.Builder.Required final int age;
final String email;
}
[/cc]
This should generate the following:
[cc lang=”java”]
@lombok.Builder
class Foo {
@lombok.Builder.Required final String firstName;
@lombok.Builder.Required final String lastName;
@lombok.Builder.Required final int age;
final String email;
private Foo(String firstName, String lastName, int age, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.email = email;
}
public static FooBuilder builder() {
return new FooBuilder();
}
private static class FooBuilderBase {
private String firstName = null;
private String lastName = null;
private int age = 0;
private String email = null;
private FooBuilderBase() {}
private FooBuilderBase(String firstName, String lastName, int age, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.email = email;
}
public FooBuilder email(String email) {
this.email = email;
return this;
}
}
// builder with no value fixed
private static class FooBuilder extends FooBuilderBase {
private FooBuilder() {}
public FooBuilderWithFirstName firstName(String firstName) {
this.firstName = firstName;
return new FooBuilderWithFirstName(firstName, lastName, age, email);
}
public FooBuilderWithLastName lastName(String lastName) {
this.lastName = lastName;
return new FooBuilderWithLastName(firstName, lastName, age, email);
}
public FooBuilderWithAge age(int age) {
this.age = age;
return new FooBuilderWithAge(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
// builders with one value fixed
public static class FooBuilderWithFirstName extends FooBuilderBase {
private FooBuilderWithFirstName(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more firstName method, so the user can’t clear it
public FooBuilderWithFirstName_LastName lastName(String lastName) {
this.lastName = lastName;
return new FooBuilderWithFirstName_LastName(firstName, lastName, age, email);
}
public FooBuilderWithFirstName_Age age(int age) {
this.age = age;
return new FooBuilderWithFirstName_Age(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
public static class FooBuilderWithLastName extends FooBuilderBase {
private FooBuilderWithLastName(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more lastName method, so the user can’t clear it
public FooBuilderWithFirstName_LastName firstName(String firstName) {
this.firstName = firstName;
return new FooBuilderWithFirstName_LastName(firstName, lastName, age, email);
}
public FooBuilderWithLastName_Age age(int age) {
this.age = age;
return new FooBuilderWithLastName_Age(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
public static class FooBuilderWithAge extends FooBuilderBase {
private FooBuilderWithAge(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more age method, so the user can’t clear it
public FooBuilderWithFirstName_Age firstName(String firstName) {
this.firstName = firstName;
return new FooBuilderWithFirstName_Age(firstName, lastName, age, email);
}
public FooBuilderWithLastName_Age lastName(String lastName) {
this.lastName = lastName;
return new FooBuilderWithLastName_Age(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
// builders with two values fixed
public static class FooBuilderWithFirstName_LastName extends FooBuilderBase {
private FooBuilderWithFirstName_LastName(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more firstName, lastName methods, so the user can’t clear it
public FooBuilderComplete age(int age) {
this.age = age;
return new FooBuilderComplete(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
public static class FooBuilderWithLastName_Age extends FooBuilderBase {
private FooBuilderWithLastName_Age(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more lastNam, age methods, so the user can’t clear it
public FooBuilderComplete firstName(String firstName) {
this.firstName = firstName;
return new FooBuilderComplete(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
public static class FooBuilderWithFirstName_Age extends FooBuilderBase {
private FooBuilderWithFirstName_Age(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more firstName, age method, so the user can’t clear it
public FooBuilderComplete lastName(String lastName) {
this.lastName = lastName;
return new FooBuilderComplete(firstName, lastName, age, email);
}
// no build method yet, because the builder doesn’t have all the required info
}
// builders with three values fixed
public static class FooBuilderComplete extends FooBuilderBase {
private FooBuilderComplete(String firstName, String lastName, int age, String email) {
super(firstName, lastName, age, email);
}
// no more firstName, lastName, age methods, so the user can’t clear it
// but now we have a build method
public Foo build() {
return new Foo(firstName, lastName, age, email);
}
}
}
[/cc]
So basically, if we have n required parameters, we 2^n builder classes. For each r required parameters that have been set already, we need n-choose-r builders.
In the example above, we have three required parameters. We have one builder that has no required parameters set, three that have one value set, three that have two values set, and one that has all of them set.
Unfortunately, the source code for rewriting the bytecode in response to a [cci lang=”java”]@Builder[/cci] annotation is almost 1000 lines long. Not exactly the best example of well-structured code.