diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java index fd94d1d0..eec5a94d 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java @@ -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; @@ -438,9 +439,12 @@ public PersonDtoBuilder nickNames2(List nickNames2) { * @return current instance of builder */ public PersonDtoBuilder nickNames2(Consumer> nickNames2BuilderConsumer) { - ArrayListBuilder builder = this.nickNames2.isSet() - ? new ArrayListBuilder(java.util.List.of(this.nickNames2.value())) - : new ArrayListBuilder(); + ArrayListBuilder builder; + if (this.nickNames2.isSet()) { + builder = new ArrayListBuilder(Arrays.asList(this.nickNames2.value())); + } else { + builder = new ArrayListBuilder(Arrays.asList()); + } nickNames2BuilderConsumer.accept(builder); this.nickNames2 = changedValue(builder.build().toArray(new String[0])); return this; diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java index 69cd0c14..958ca3c7 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterMapper.java @@ -166,6 +166,9 @@ public static String mapInterfaceToTypeName(InterfaceName interfaceName) { /** * Resolves the JavaPoet-style named template used in CodeTemplateDto to plain Java source code. * + *

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 */ @@ -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()); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java index 6c951c5a..80e958e9 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java @@ -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; /** @@ -52,8 +53,8 @@ * converted to an array. This allows fluent array construction with the convenience of list * operations. * - *

Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}). - * Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}. + *

Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}, + * {@code int[]}, {@code boolean[]}). * *

This generator can be deactivated by setting the configuration flag {@code * shouldGenerateBuilderConsumer()} to {@code false}. See the configuration documentation for @@ -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()); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java index 772925af..6874ce7f 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java @@ -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; /** @@ -50,8 +51,8 @@ * assigned to the field. This allows using List operations and utilities before converting to the * required array type. * - *

Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}). - * Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}. + *

Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}, + * {@code int[]}, {@code boolean[]}). * *

This generator cannot be deactivated as it provides essential convenience for array fields. * @@ -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); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java index 4d67b6e8..7f4a0160 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -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()) { @@ -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; diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomCollectionTypeTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomCollectionTypeTest.java index 6f8263fc..6e2bfd5b 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomCollectionTypeTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/CustomCollectionTypeTest.java @@ -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 scores) { + this.scores = changedValue(ArrayUtils.toPrimitive(scores.toArray(new Integer[0]))); + return this; + } + """), + contains( + """ + public PrimitiveArrayDtoBuilder scores(Consumer> scoresBuilderConsumer) { + ArrayListBuilder builder; + if (this.scores.isSet()) { + builder = new ArrayListBuilder(Arrays.asList(ArrayUtils.toObject(this.scores.value()))); + } else { + builder = new ArrayListBuilder(Arrays.asList()); + } + scoresBuilderConsumer.accept(builder); + this.scores = changedValue(ArrayUtils.toPrimitive(builder.build().toArray(new Integer[0]))); + return this; + } + """), + contains( + """ + public PrimitiveArrayDtoBuilder flags(List flags) { + this.flags = changedValue(ArrayUtils.toPrimitive(flags.toArray(new Boolean[0]))); + return this; + } + """), + contains( + """ + public PrimitiveArrayDtoBuilder flags(Consumer> flagsBuilderConsumer) { + ArrayListBuilder builder; + if (this.flags.isSet()) { + builder = new ArrayListBuilder(Arrays.asList(ArrayUtils.toObject(this.flags.value()))); + } else { + builder = new ArrayListBuilder(Arrays.asList()); + } + flagsBuilderConsumer.accept(builder); + this.flags = changedValue(ArrayUtils.toPrimitive(builder.build().toArray(new Boolean[0]))); + return this; + } + """), + contains( + """ + public PrimitiveArrayDtoBuilder values(List values) { + this.values = changedValue(ArrayUtils.toPrimitive(values.toArray(new Double[0]))); + return this; + } + """), + contains( + """ + public PrimitiveArrayDtoBuilder values(Consumer> valuesBuilderConsumer) { + ArrayListBuilder builder; + if (this.values.isSet()) { + builder = new ArrayListBuilder(Arrays.asList(ArrayUtils.toObject(this.values.value()))); + } else { + builder = new ArrayListBuilder(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