- A Concurrent Affair - https://www.concurrentaffair.org -

Lombok @Builder with Required Parameters?

I would really like a Lombok @Builder [1] annotation that properly deals with required parameters at compile time.

Here is what I have in mind:

1
2
3
4
5
6
@lombok.Builder
class Foo {
    @lombok.Builder.Required final String [2] name;
    @lombok.Builder.Required final int age;
    final String [2] email;
}

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 [2] firstName;
    @lombok.Builder.Required final String [2] lastName;
    @lombok.Builder.Required final int age;
    final String [2] email;

    private Foo(String [2] firstName, String [2] lastName, int age, String [2] 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 [2] firstName = null;
        private String [2] lastName = null;
        private int age = 0;
        private String [2] email = null;
        private FooBuilderBase() {}
        private FooBuilderBase(String [2] firstName, String [2] lastName, int age, String [2] email) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.age = age;
            this.email = email;
        }
        public FooBuilder email(String [2] email) {
            this.email = email;
            return this;
        }
    }

    // builder with no value fixed

    private static class FooBuilder extends FooBuilderBase {
        private FooBuilder() {}
        public FooBuilderWithFirstName firstName(String [2] firstName) {
            this.firstName = firstName;
            return new FooBuilderWithFirstName(firstName, lastName, age, email);
        }
        public FooBuilderWithLastName lastName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] email) {
            super(firstName, lastName, age, email);
        }
        // no more firstName method, so the user can't clear it
        public FooBuilderWithFirstName_LastName lastName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] email) {
            super(firstName, lastName, age, email);
        }
        // no more lastName method, so the user can't clear it
        public FooBuilderWithFirstName_LastName firstName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] email) {
            super(firstName, lastName, age, email);
        }
        // no more age method, so the user can't clear it
        public FooBuilderWithFirstName_Age firstName(String [2] firstName) {
            this.firstName = firstName;
            return new FooBuilderWithFirstName_Age(firstName, lastName, age, email);
        }
        public FooBuilderWithLastName_Age lastName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] 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 [2] firstName, String [2] lastName, int age, String [2] email) {
            super(firstName, lastName, age, email);
        }
        // no more lastNam, age methods, so the user can't clear it
        public FooBuilderComplete firstName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] email) {
            super(firstName, lastName, age, email);
        }
        // no more firstName, age method, so the user can't clear it
        public FooBuilderComplete lastName(String [2] 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 [2] firstName, String [2] lastName, int age, String [2] 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 [3]. Not exactly the best example of well-structured code.

[4] [5]Share [6]