I would really like a Lombok @Builder annotation that properly deals with required parameters at compile time.
Here is what I have in mind:
1 2 3 4 5 6 |
This should generate the following:
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 | @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); } } } |
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 @Builder
annotation is almost 1000 lines long. Not exactly the best example of well-structured code.