Skip to content

Commit 79e8707

Browse files
feat: add getters to generated builders (#680)
Signed-off-by: Anthony Petrov <anthony@swirldslabs.com>
1 parent 35d8eae commit 79e8707

File tree

2 files changed

+59
-4
lines changed

2 files changed

+59
-4
lines changed

pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/generators/ModelGenerator.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public void generate(
205205
bodyContent += "\n";
206206

207207
// record style getters
208-
bodyContent += generateRecordStyleGetters(fieldsNoPrecomputed);
208+
bodyContent += generateRecordStyleGetters(fieldsNoPrecomputed, false);
209209
bodyContent += "\n";
210210

211211
bodyContent +=
@@ -356,11 +356,17 @@ private void generateClass(
356356

357357
/**
358358
* Generating method that generates getters for the record style accessors. Needed now we use class not record.
359+
* <p>
360+
* We generate the getters in both the model itself and in its builder. The builder already has a no-arg build()
361+
* method defined. If a protobuf model defines a field named "build", then the getter method name would clash
362+
* with it. So we use the `guardBuildFieldName` argument to help prefix such getter method name with "_"
363+
* to avoid a clash.
359364
*
360365
* @param fields the fields to use for the code generation
366+
* @param guardBuildFieldName if true and the field name is "build", then prefix the getter with "_"
361367
* @return the generated code
362368
*/
363-
private static String generateRecordStyleGetters(final List<Field> fields) {
369+
private static String generateRecordStyleGetters(final List<Field> fields, final boolean guardBuildFieldName) {
364370
return fields.stream()
365371
.map(field -> {
366372
String fieldComment = field.comment();
@@ -372,11 +378,15 @@ private static String generateRecordStyleGetters(final List<Field> fields) {
372378
*
373379
* @return the value of the $fieldName field
374380
*/
375-
public $fieldType $fieldName() {
381+
public $fieldType $methodName() {
376382
return $fieldName;
377383
}
378384
"""
379385
.replace("$fieldCommentLowerFirst", fieldCommentLowerFirst)
386+
.replace(
387+
"$methodName",
388+
(guardBuildFieldName && "build".equals(field.nameCamelFirstLower()) ? "_" : "")
389+
+ field.nameCamelFirstLower())
380390
.replace("$fieldName", field.nameCamelFirstLower())
381391
.replace("$fieldType", field.javaFieldType())
382392
.indent(DEFAULT_INDENT);
@@ -1154,7 +1164,19 @@ public static final class Builder {
11541164
return new $javaRecordName($recordParams);
11551165
}
11561166
1157-
$builderMethods}"""
1167+
$builderMethods
1168+
1169+
1170+
// Generated record-style getters for the current state of the builder follow below.
1171+
// NOTE: for performance reasons, repeated fields are initialized with unmodifiable lists. Similarly,
1172+
// Builder.repeatedField(Type... values) methods would wrap the values into unmodifiable lists.
1173+
// If an application needs to dynamically modify repeated fields in builders, then it must first call
1174+
// the Builder.repeatedField(List<Type> list) method and supply a modifiable list, such as an ArrayList
1175+
// instance. After that, the application must only use the getter method to access the list.
1176+
// If the application wants to finalize the list after the building is finished, then it may call
1177+
// Builder.repeatedField(builder.repeatedField().toArray(new Type[0])) as one last final step.
1178+
1179+
$getterMethods}"""
11581180
.replace("$fields", fields.stream().map(field ->
11591181
getFieldAnnotations(field)
11601182
+ "private " + field.javaFieldType()
@@ -1166,6 +1188,7 @@ public static final class Builder {
11661188
.replace("$javaRecordName",javaRecordName)
11671189
.replace("$recordParams",fields.stream().map(Field::nameCamelFirstLower).collect(Collectors.joining(", ")))
11681190
.replace("$builderMethods", String.join("\n", builderMethods))
1191+
.replace("$getterMethods", generateRecordStyleGetters(fields, true))
11691192
.indent(DEFAULT_INDENT);
11701193
// spotless:on
11711194
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
package com.hedera.pbj.integration.test;
3+
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import com.hedera.pbj.test.proto.pbj.Everything;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import org.junit.jupiter.api.Test;
12+
13+
public class BuilderGetterTest {
14+
@Test
15+
void getterTest() {
16+
Everything obj = Everything.DEFAULT;
17+
18+
assertTrue(obj.sfixed32NumberList().isEmpty());
19+
20+
final Everything.Builder builder = obj.copyBuilder();
21+
assertTrue(builder.sfixed32NumberList().isEmpty());
22+
assertThrows(UnsupportedOperationException.class, () -> builder.sfixed32NumberList()
23+
.add(666));
24+
25+
builder.sfixed32NumberList(new ArrayList<>());
26+
builder.sfixed32NumberList().add(666);
27+
assertEquals(List.of(666), builder.sfixed32NumberList());
28+
29+
Everything obj2 = builder.build();
30+
assertEquals(List.of(666), obj2.sfixed32NumberList());
31+
}
32+
}

0 commit comments

Comments
 (0)