Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fff0ff1
Make @SimpleBuilder @Inherited to match documentation (#244)
AndreasIgel Aug 15, 2026
13a8fc8
Rename to BuilderAnnotationInheritanceTest and cover Template inherit…
AndreasIgel Aug 15, 2026
105310a
Refactoring code to move assertNoBuilderGenerated to common asserts
AndreasIgel Aug 15, 2026
2c83b78
fixing codeformat
AndreasIgel Aug 15, 2026
aca9ef6
Document @Inherited behavior of @SimpleBuilder.Template
AndreasIgel Aug 15, 2026
bd3bf93
Document options-inheritance limitation for inherited subclass builders
AndreasIgel Aug 15, 2026
e8e0352
Fix issue reference: #245 -> #248
AndreasIgel Aug 15, 2026
9776343
Clarify @SimpleBuilder vs @SimpleBuilder.Template usage in docs
AndreasIgel Aug 15, 2026
4b7723a
Link issue #248 from @SimpleBuilder.Template inheritance Javadoc
AndreasIgel Aug 15, 2026
218ea79
Remove redundant troubleshooting item about @SimpleBuilder.Template t…
AndreasIgel Aug 15, 2026
bd6020d
Merge remote-tracking branch 'upstream/main'
devin-ai-integration[bot] Aug 16, 2026
62d1269
Merge remote-tracking branch 'upstream/main'
devin-ai-integration[bot] Aug 16, 2026
390d926
fix: generate compilable collection helpers for primitive array field…
devin-ai-integration[bot] Aug 22, 2026
0d276e2
refactor: use Apache Commons ArrayUtils for primitive array conversion
devin-ai-integration[bot] Aug 23, 2026
c1d5415
refactor: use unqualified ArrayUtils/Arrays names in generated primit…
devin-ai-integration[bot] Aug 23, 2026
58d9ef7
test: assert full generated method bodies and build block for primiti…
devin-ai-integration[bot] Aug 23, 2026
aefb6f7
test: assert full method blocks in one contains per method for primit…
devin-ai-integration[bot] Aug 23, 2026
2116619
refactor: use Arrays.asList() in both branches of primitive array con…
devin-ai-integration[bot] Aug 23, 2026
509d5ed
refactor: copy method code-block imports via addAll instead of new ov…
devin-ai-integration[bot] Aug 23, 2026
7deb6ba
refactor: use Arrays.asList in all ArrayBuilderConsumer branches (#261)
devin-ai-integration[bot] Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@
import static org.javahelpers.simple.builders.core.util.TrackedValue.unsetValue;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
Expand DownExpand Up@@ -438,9 +439,12 @@ public PersonDtoBuilder nickNames2(List<String> nickNames2) {
* @return current instance of builder
*/
public PersonDtoBuilder nickNames2(Consumer<ArrayListBuilder<String>> nickNames2BuilderConsumer) {
ArrayListBuilder<String> builder = this.nickNames2.isSet()
? new ArrayListBuilder<String>(java.util.List.of(this.nickNames2.value()))
: new ArrayListBuilder<String>();
ArrayListBuilder<String> builder;
if (this.nickNames2.isSet()) {
builder = new ArrayListBuilder<String>(Arrays.asList(this.nickNames2.value()));
} else {
builder = new ArrayListBuilder<String>(Arrays.asList());
}
nickNames2BuilderConsumer.accept(builder);
this.nickNames2 = changedValue(builder.build().toArray(new String[0]));
return this;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -166,6 +166,9 @@ public static String mapInterfaceToTypeName(InterfaceName interfaceName) {
/**
* Resolves the JavaPoet-style named template used in CodeTemplateDto to plain Java source code.
*
* <p>Type placeholders support {@code $label:T} for the plain type and {@code $label:B} for its
* boxed representation.
*
* @param codeDto code template DTO
* @return resolved Java source code
*/
Expand All@@ -179,6 +182,7 @@ public static String resolveCodeTemplate(CodeTemplateDto codeDto) {
code = code.replace("$" + label + ":S", quote(stringPlaceholder.getValue()));
} else if (placeHolderValue instanceof MethodCodeTypePlaceholder typePlaceholder) {
code = code.replace("$" + label + ":T", mapType(typePlaceholder.getValue()));
code = code.replace("$" + label + ":B", mapBoxedType(typePlaceholder.getValue()));
} else {
throw new RoasterMapperException(
"Unsupported placeholder type: %s", placeHolderValue.getClass().getName());
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,6 +38,7 @@
import org.javahelpers.simple.builders.processor.model.type.TypeName;
import org.javahelpers.simple.builders.processor.model.type.TypeNameArray;
import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric;
import org.javahelpers.simple.builders.processor.model.type.TypeNamePrimitive;
import org.javahelpers.simple.builders.processor.processing.ProcessingContext;

/**
Expand All@@ -52,8 +53,8 @@
* converted to an array. This allows fluent array construction with the convenience of list
* operations.
*
* <p><b>Requirements:</b> Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}).
* Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}.
* <p><b>Requirements:</b> Only applies to array fields (e.g., {@code String[]}, {@code Integer[]},
* {@code int[]}, {@code boolean[]}).
*
* <p>This generator can be deactivated by setting the configuration flag {@code
* shouldGenerateBuilderConsumer()} to {@code false}. See the configuration documentation for
Expand DownExpand Up@@ -131,18 +132,36 @@ private BuilderMethodDto createFieldConsumerWithArrayBuilder(

BuilderMethodDto methodDto = createBuilderMethod(fieldName, returnBuilderType, context);
methodDto.addParameter(parameter);
methodDto.setCode(
"""
$helperType:T builder = this.$fieldName:N.isSet()
? new $helperType:T(java.util.List.of(this.$fieldName:N.value()))
: new $helperType:T();
$dtoMethodParam:N.accept(builder);
this.$fieldName:N = $builderFieldWrapper:T.changedValue(builder.build().toArray(new $elementType:T[0]));
return this;
""");

// Add code block import for java.util.List.of
methodDto.getMethodCodeDto().addCodeBlockImport(List.class);
methodDto.getMethodCodeDto().addCodeBlockImport(java.util.Arrays.class);
if (elementType instanceof TypeNamePrimitive) {
methodDto.setCode(
"""
$helperType:T builder;
if (this.$fieldName:N.isSet()) {
builder = new $helperType:T(Arrays.asList(ArrayUtils.toObject(this.$fieldName:N.value())));
} else {
builder = new $helperType:T(Arrays.asList());
}
$dtoMethodParam:N.accept(builder);
this.$fieldName:N = $builderFieldWrapper:T.changedValue(
ArrayUtils.toPrimitive(builder.build().toArray(new $elementType:B[0])));
return this;
""");
methodDto.getMethodCodeDto().addCodeBlockImport(org.apache.commons.lang3.ArrayUtils.class);
} else {
methodDto.setCode(
"""
$helperType:T builder;
if (this.$fieldName:N.isSet()) {
builder = new $helperType:T(Arrays.asList(this.$fieldName:N.value()));
} else {
builder = new $helperType:T(Arrays.asList());
}
$dtoMethodParam:N.accept(builder);
this.$fieldName:N = $builderFieldWrapper:T.changedValue(builder.build().toArray(new $elementType:T[0]));
return this;
""");
}

methodDto.addArgument("fieldName", fieldNameInBuilder);
methodDto.addArgument("dtoMethodParam", parameter.getParameterName());
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,7 @@
import org.javahelpers.simple.builders.processor.model.type.TypeName;
import org.javahelpers.simple.builders.processor.model.type.TypeNameArray;
import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric;
import org.javahelpers.simple.builders.processor.model.type.TypeNamePrimitive;
import org.javahelpers.simple.builders.processor.processing.ProcessingContext;

/**
Expand All@@ -50,8 +51,8 @@
* assigned to the field. This allows using List operations and utilities before converting to the
* required array type.
*
* <p><b>Requirements:</b> Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}).
* Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}.
* <p><b>Requirements:</b> Only applies to array fields (e.g., {@code String[]}, {@code Integer[]},
* {@code int[]}, {@code boolean[]}).
*
* <p>This generator cannot be deactivated as it provides essential convenience for array fields.
*
Expand DownExpand Up@@ -119,11 +120,21 @@ private BuilderMethodDto createFieldSetterForArrayFromList(

BuilderMethodDto methodDto = createBuilderMethod(fieldName, builderType, context);
methodDto.addParameter(parameter);
methodDto.setCode(
"""
this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N.toArray(new $elementType:T[0]));
return this;
""");
if (elementType instanceof TypeNamePrimitive) {
methodDto.setCode(
"""
this.$fieldName:N = $builderFieldWrapper:T.changedValue(
ArrayUtils.toPrimitive($dtoMethodParams:N.toArray(new $elementType:B[0])));
return this;
""");
methodDto.getMethodCodeDto().addCodeBlockImport(org.apache.commons.lang3.ArrayUtils.class);
} else {
methodDto.setCode(
"""
this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N.toArray(new $elementType:T[0]));
return this;
""");
}
methodDto.addArgument("fieldName", fieldNameInBuilder);
methodDto.addArgument("dtoMethodParams", fieldName);
methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,7 +141,8 @@ private static MethodDto toMethodDto(BuilderMethodDto classMethod) {
classMethod.getParameters().forEach(method::addParameter);
classMethod.getGenericParameters().forEach(method::addGenericParameter);

// Copy method code: set code format and copy all arguments
// Copy method code: set code format, copy all arguments, and preserve explicit code-block
// imports
if (classMethod.hasCode()) {
method.setCode(classMethod.getMethodCodeDto().getCodeFormat());
for (MethodCodePlaceholder<?> argument : classMethod.getMethodCodeDto().getCodeArguments()) {
Expand All@@ -151,6 +152,10 @@ private static MethodDto toMethodDto(BuilderMethodDto classMethod) {
method.addArgument(typePlaceholder.getLabel(), typePlaceholder.getValue());
}
}
method
.getMethodCodeDto()
.getCodeBlockImports()
.addAll(classMethod.getMethodCodeDto().getCodeBlockImports());
}

return method;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -434,6 +434,107 @@ public ArrayDto(String[] tags) {
contains("@param tagsBuilderConsumer consumer for tags"));
}

@Test
void primitiveArrayFields_shouldGenerateCompilableCollectionHelpers() {
JavaFileObject primitiveArrayDto =
ProcessorTestUtils.simpleBuilderClass(
"test",
"PrimitiveArrayDto",
"""
private final int[] scores;
private final boolean[] flags;
private final double[] values;

public PrimitiveArrayDto(int[] scores, boolean[] flags, double[] values) {
this.scores = scores;
this.flags = flags;
this.values = values;
}

public int[] getScores() { return scores; }
public boolean[] getFlags() { return flags; }
public double[] getValues() { return values; }
""");

Compilation compilation = compile(primitiveArrayDto);
String generatedCode =
ProcessorTestUtils.loadGeneratedSource(compilation, "PrimitiveArrayDtoBuilder");
assertGenerationSucceeded(compilation, "PrimitiveArrayDtoBuilder", generatedCode);

ProcessorAsserts.assertingResult(
generatedCode,
contains(
"""
public PrimitiveArrayDtoBuilder scores(List<Integer> scores) {
this.scores = changedValue(ArrayUtils.toPrimitive(scores.toArray(new Integer[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDtoBuilder scores(Consumer<ArrayListBuilder<Integer>> scoresBuilderConsumer) {
ArrayListBuilder<Integer> builder;
if (this.scores.isSet()) {
builder = new ArrayListBuilder<Integer>(Arrays.asList(ArrayUtils.toObject(this.scores.value())));
} else {
builder = new ArrayListBuilder<Integer>(Arrays.asList());
}
scoresBuilderConsumer.accept(builder);
this.scores = changedValue(ArrayUtils.toPrimitive(builder.build().toArray(new Integer[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDtoBuilder flags(List<Boolean> flags) {
this.flags = changedValue(ArrayUtils.toPrimitive(flags.toArray(new Boolean[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDtoBuilder flags(Consumer<ArrayListBuilder<Boolean>> flagsBuilderConsumer) {
ArrayListBuilder<Boolean> builder;
if (this.flags.isSet()) {
builder = new ArrayListBuilder<Boolean>(Arrays.asList(ArrayUtils.toObject(this.flags.value())));
} else {
builder = new ArrayListBuilder<Boolean>(Arrays.asList());
}
flagsBuilderConsumer.accept(builder);
this.flags = changedValue(ArrayUtils.toPrimitive(builder.build().toArray(new Boolean[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDtoBuilder values(List<Double> values) {
this.values = changedValue(ArrayUtils.toPrimitive(values.toArray(new Double[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDtoBuilder values(Consumer<ArrayListBuilder<Double>> valuesBuilderConsumer) {
ArrayListBuilder<Double> builder;
if (this.values.isSet()) {
builder = new ArrayListBuilder<Double>(Arrays.asList(ArrayUtils.toObject(this.values.value())));
} else {
builder = new ArrayListBuilder<Double>(Arrays.asList());
}
valuesBuilderConsumer.accept(builder);
this.values = changedValue(ArrayUtils.toPrimitive(builder.build().toArray(new Double[0])));
return this;
}
"""),
contains(
"""
public PrimitiveArrayDto build() {
PrimitiveArrayDto result = new PrimitiveArrayDto(this.scores.value(), this.flags.value(), this.values.value());
return result;
}
"""));
}

@Test
void unmodifiableListInConstructor_shouldHandleCorrectly() {
// DTO that creates unmodifiable list in constructor - builder should handle this safely
Expand Down
Loading