Lombok @Builder with Required Parameters?

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
@lombok.Builder
class Foo {
    @lombok.Builder.Required final String name;
    @lombok.Builder.Required final int age;
    final String 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 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.

Share

About Mathias

Software development engineer. Principal developer of DrJava. Recent Ph.D. graduate from the Department of Computer Science at Rice University.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply