diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java index a7514d66..9dfa2d31 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java @@ -54,6 +54,8 @@ import org.javahelpers.simple.builders.processor.dtos.*; import org.javahelpers.simple.builders.processor.exceptions.BuilderException; +import com.google.common.graph.ElementOrder.Type; + /** Class for creating a specific BuilderDefinitionDto for an annotated DTO class. */ public class BuilderDefinitionCreator { private static final String BUILDER_SUFFIX = "Builder"; @@ -252,6 +254,22 @@ private static void addAdditionalHelperMethodsForField( fieldName, "String.format(format, args)", annotations, builderType)); } + if ((fieldType instanceof TypeNameArray arrayType)) { + TypeName elementType = arrayType.getTypeOfArray(); + + // Add method accepting List and converting to array + TypeNameGeneric listType = new TypeNameGeneric(map2TypeName(List.class), elementType); + result.addMethod( + createFieldSetterForArrayFromList(fieldName, listType, elementType, builderType)); + + // Add Consumer> method + TypeName collectionBuilderType = map2TypeName(ArrayListBuilder.class); + result.addMethod( + createFieldConsumerWithArrayBuilder( + fieldName, collectionBuilderType, elementType, builderType)); + return; + } + // Only process generic types (List, Set, Map, Optional, etc.) if (!(fieldType instanceof TypeNameGeneric fieldTypeGeneric)) { return; @@ -855,6 +873,78 @@ private static MethodDto createStringFormatMethodWithTransform( return methodDto; } + /** + * Creates a field setter method that accepts a List and converts it to an array. + * + * @param fieldName the field name + * @param listType the List parameter type + * @param elementType the element type of the array + * @param builderType the builder type to return + * @return the method DTO for the setter + */ + private static MethodDto createFieldSetterForArrayFromList( + String fieldName, TypeName listType, TypeName elementType, TypeName builderType) { + MethodParameterDto parameter = new MethodParameterDto(); + parameter.setParameterName(fieldName); + parameter.setParameterTypeName(listType); + + MethodDto methodDto = new MethodDto(); + methodDto.setMethodName(fieldName); + methodDto.setReturnType(builderType); + methodDto.addParameter(parameter); + methodDto.setModifier(Modifier.PUBLIC); + methodDto.setMethodType(MethodTypes.PROXY); + methodDto.setCode( + """ + this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N.toArray(new $elementType:T[0])); + return this; + """); + methodDto.addArgument(ARG_FIELD_NAME, fieldName); + methodDto.addArgument(ARG_DTO_METHOD_PARAMS, fieldName); + methodDto.addArgument(ARG_BUILDER_FIELD_WRAPPER, TRACKED_VALUE_TYPE); + methodDto.addArgument("elementType", elementType); + return methodDto; + } + + /** + * Creates a consumer method for array fields with ArrayListBuilder. This allows building arrays + * using the fluent ArrayListBuilder API. + */ + private static MethodDto createFieldConsumerWithArrayBuilder( + String fieldName, + TypeName collectionBuilderType, + TypeName elementType, + TypeName returnBuilderType) { + TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(collectionBuilderType, elementType); + TypeNameGeneric consumerType = + new TypeNameGeneric(map2TypeName(Consumer.class), builderTypeGeneric); + + MethodParameterDto parameter = new MethodParameterDto(); + parameter.setParameterName(fieldName + BUILDER_SUFFIX + SUFFIX_CONSUMER); + parameter.setParameterTypeName(consumerType); + + MethodDto methodDto = new MethodDto(); + methodDto.setMethodName(fieldName); + methodDto.setReturnType(returnBuilderType); + methodDto.addParameter(parameter); + methodDto.setModifier(Modifier.PUBLIC); + methodDto.setMethodType(MethodTypes.CONSUMER_BY_BUILDER); + + 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; + """); + methodDto.addArgument(ARG_FIELD_NAME, fieldName); + methodDto.addArgument(ARG_DTO_METHOD_PARAM, parameter.getParameterName()); + methodDto.addArgument(ARG_HELPER_TYPE, builderTypeGeneric); + methodDto.addArgument(ARG_BUILDER_FIELD_WRAPPER, TRACKED_VALUE_TYPE); + methodDto.addArgument("elementType", elementType); + return methodDto; + } + /** * Checks if a StringBuilder consumer should be generated for the given field type. This applies * to plain String fields and Optional<String> fields. diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java index f176be0f..86bafd00 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java @@ -1681,7 +1681,7 @@ void shouldIgnoreIfAnnotatedWithIgnoreInBuilder() { } @Test - void shouldNotGenerateConsumersForPrimitiveAndArrayFields() { + void shouldNotGenerateConsumersForPrimitiveFields() { // Given String packageName = "test"; String className = "PrimAndArray"; @@ -1708,13 +1708,13 @@ void shouldNotGenerateConsumersForPrimitiveAndArrayFields() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // Expect simple setters and suppliers only; no consumer methods for primitive/array + // Primitives should not get consumer methods, but arrays now get ArrayListBuilder consumers ProcessorAsserts.assertingResult( generatedCode, contains("public PrimAndArrayBuilder count(Supplier"), contains("public PrimAndArrayBuilder names(Supplier"), notContains("public PrimAndArrayBuilder count(Consumer"), - notContains("public PrimAndArrayBuilder names(Consumer")); + contains("public PrimAndArrayBuilder names(Consumer>")); } @Test @@ -1896,9 +1896,12 @@ void shouldHandleMixedFields() { // primitive int: direct and supplier (boxed) contains("public HasMixedBuilder a(int a)"), contains("public HasMixedBuilder a(Supplier aSupplier)"), - // array String[]: direct and supplier only (no varargs, no collection-builder consumers) + // array String[]: varargs, supplier, List parameter, and ArrayListBuilder consumer contains("public HasMixedBuilder names(String... names)"), contains("public HasMixedBuilder names(Supplier namesSupplier)"), + contains("public HasMixedBuilder names(List names)"), + contains( + "public HasMixedBuilder names(Consumer> namesBuilderConsumer)"), // List: direct, supplier, varargs convenience and consumer with ArrayListBuilder contains("public HasMixedBuilder list(List list)"), contains("public HasMixedBuilder list(Supplier> listSupplier)"), @@ -1937,14 +1940,14 @@ void shouldHandleObjectArrayField() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // Arrays should get setter and supplier + // Arrays should get setter, supplier, List parameter, and ArrayListBuilder consumer ProcessorAsserts.assertingResult( generatedCode, contains("public HasObjectArrayBuilder names(Supplier namesSupplier)"), contains("public HasObjectArrayBuilder names(String... names)"), notContains("public HasObjectArrayBuilder names(String[] names)"), - notContains("public HasObjectArrayBuilder names(List names)"), // TODO: feature - notContains("Consumer names)"), + contains("Consumer> namesBuilderConsumer"), notContains("Consumer