From 37e85dfe68ece89d7ecfc84b3372e542e60facc2 Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Fri, 9 May 2025 21:31:38 +0200 Subject: [PATCH 01/18] adding explicit setter property in builder definition --- .../simple/builders/processor/dtos/FieldDto.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java index 8189de4d..e0c074b7 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java @@ -37,6 +37,9 @@ public class FieldDto { /** Type of field. Containing generic, name of package and class */ private TypeName fieldType; + + /** Name of setter in target object. Entry may be {@code null} if field is only been set via constructor */ + private final String setterName; /** List of all methods in builder, which change the field. */ private final List fieldSetterMethodsList = new ArrayList<>(); @@ -58,6 +61,14 @@ public String getFieldName() { public void setFieldName(String fieldName) { this.fieldName = fieldName; } + + public Optional getSetterName() { + return Optional.ofNullable(setterName); + } + + public void setSetterName(String setterName) { + this.setterName = setterName; + } /** * Getting type of field. From 209e76f6c06fd287337c12428c082ed81dfb8777 Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Fri, 9 May 2025 21:32:41 +0200 Subject: [PATCH 02/18] Improving Javadoc --- .../simple/builders/processor/dtos/BuilderDefinitionDto.java | 2 +- .../javahelpers/simple/builders/processor/dtos/FieldDto.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java index 9a5746bc..ef651671 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java @@ -42,7 +42,7 @@ public class BuilderDefinitionDto { /** * List of all fields of target DTO which are supported by builder. A field could be supported by - * several functions in builder. + * multiple functions in builder. */ private final LinkedList setterFieldsForBuilder = new LinkedList<>(); diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java index e0c074b7..b314a942 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java @@ -41,7 +41,7 @@ public class FieldDto { /** Name of setter in target object. Entry may be {@code null} if field is only been set via constructor */ private final String setterName; - /** List of all methods in builder, which change the field. */ + /** List of all methods in builder, which provide helpers to change the field. */ private final List fieldSetterMethodsList = new ArrayList<>(); /** From 6f4cccce4b5b5521cd08f2d224c8024267898c90 Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Fri, 9 May 2025 21:34:00 +0200 Subject: [PATCH 03/18] Renaming fields in BuilderDefinition for better understanding --- .../processor/dtos/BuilderDefinitionDto.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java index ef651671..62e679cb 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java @@ -38,13 +38,13 @@ public class BuilderDefinitionDto { * List of all methods of target DTO which are supported by builder and not targeting a specific * DTO field. */ - private final LinkedList methodsForBuilder = new LinkedList<>(); + private final LinkedList methods = new LinkedList<>(); /** * List of all fields of target DTO which are supported by builder. A field could be supported by * multiple functions in builder. */ - private final LinkedList setterFieldsForBuilder = new LinkedList<>(); + private final LinkedList fields = new LinkedList<>(); /** * Getting type of builder. @@ -90,7 +90,7 @@ public void setBuildingTargetTypeName(TypeName buildingTargetTypeName) { * @param member {@code org.javahelpers.simple.builders.internal.dtos.MethodDto} to be added */ public void addMethod(MethodDto member) { - methodsForBuilder.add(member); + methods.add(member); } /** @@ -100,7 +100,7 @@ public void addMethod(MethodDto member) { * org.javahelpers.simple.builders.internal.dtos.MethodDto} */ public LinkedList getMethodsForBuilder() { - return methodsForBuilder; + return methods; } /** @@ -109,7 +109,7 @@ public LinkedList getMethodsForBuilder() { * @param field {@code org.javahelpers.simple.builders.internal.dtos.FieldDto} to be added */ public void addField(FieldDto field) { - setterFieldsForBuilder.add(field); + fields.add(field); } /** @@ -118,6 +118,6 @@ public void addField(FieldDto field) { * @return list of fields with type {@code org.javahelpers.simple.builders.internal.dtos.FieldDto} */ public LinkedList getSetterFieldsForBuilder() { - return setterFieldsForBuilder; + return fields; } } From 65a90d8ffd7591a1a153a386b80357ac7478ad74 Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Fri, 9 May 2025 21:40:15 +0200 Subject: [PATCH 04/18] Adding fields of constructor to BuilderDefintion too --- .../simple/builders/processor/dtos/BuilderDefinitionDto.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java index 62e679cb..7bf304f6 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java @@ -33,6 +33,9 @@ public class BuilderDefinitionDto { /** Type of builder. Containing package and name of DTO. */ private TypeName builderTypeName; + + /** Fields in constructor. Containing all fields which need to be given for calling the constructor. Ordering in LinkedList is matching orderung of parameters in constructor call. Instances of fields are added in LinkedList of all fields too. */ + private final LinkedList fieldsInConstructor = new LinkedList<>(); /** * List of all methods of target DTO which are supported by builder and not targeting a specific From a8bdfa38b2147c46afc687bba43f9a64f22ff1e8 Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:35:33 +0200 Subject: [PATCH 05/18] Updating tests according to desired functionality --- .../processor/BuilderProcessorTest.java | 228 ++++++++---------- 1 file changed, 96 insertions(+), 132 deletions(-) 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 6a96fba7..78c779e7 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 @@ -74,8 +74,7 @@ void shouldFailWhenAnnotationPlacedOnAbstractClass() { } @Test - @Disabled("Constructor-Parameters are not supported yet") - void shouldIgnoreConstructorParametersAndOnlyUseSetters() { + void shouldUseConstructorParameters() { // Given String packageName = "test"; String className = "CtorAndSetter"; @@ -86,7 +85,7 @@ void shouldIgnoreConstructorParametersAndOnlyUseSetters() { packageName, className, """ - private int a; // has no setter, only in constructor + private final int a; // has no setter, only in constructor private String name; // will be set via setter public CtorAndSetter() {} @@ -107,14 +106,15 @@ public CtorAndSetter() {} ProcessorAsserts.assertingResult( generatedCode, contains("public CtorAndSetterBuilder a(int a)"), - contains("instance.setA(a);"), + contains("this.a = a;"), contains("public CtorAndSetterBuilder name(String name)"), - contains("instance.setName(name);")); + contains("this.name = name;"), + contains("CtorAndSetter instance = new CtorAndSetter(this.a);"), + contains("instance.setName(this.name);")); } @Test - @Disabled("Records are not supported for builder generation yet") - void shouldHandleRecordWithoutGeneratingBuilder() { + void shouldHandleRecordWithBuilder() { // Given String packageName = "test"; String recordName = "PersonRecord"; @@ -145,9 +145,10 @@ public record PersonRecord(String name, int age) { ProcessorAsserts.assertingResult( generatedCode, contains("public PersonRecordBuilder name(String name)"), - contains("instance.setName(name);"), + contains("this.name = name;"), contains("public PersonRecordBuilder age(int age)"), - contains("instance.setAge(age);")); + contains("this.age = age;"), + contains("PersonRecord instance = new PersonRecord(this.name,this.age);")); } @Test @@ -189,18 +190,18 @@ void shouldGenerateCollectionSettersAndProviders() { contains("public WithCollectionsBuilder names(List names)"), contains("public WithCollectionsBuilder names(Supplier> namesSupplier)"), contains("public WithCollectionsBuilder names(String... names)"), - contains("instance.setNames(List.of(names));"), + contains("this.names = List.of(names);"), contains("public WithCollectionsBuilder tags(Set tags)"), contains("public WithCollectionsBuilder tags(Supplier> tagsSupplier)"), contains("public WithCollectionsBuilder tags(String... tags)"), - contains("instance.setTags(Set.of(tags));"), + contains("this.tags = Set.of(tags);"), contains("public WithCollectionsBuilder map(Map map)"), contains("public WithCollectionsBuilder map(Supplier> mapSupplier)"), - contains("instance.setMap(map);")); + contains("this.map = map;")); } @Test - void shouldDeclareInstanceFieldAsPrivateFinalWithJavadoc() { + void shouldDeclareFieldAsBuilderFieldsWithJavadoc() { // Given String packageName = "test"; String className = "FieldDoc"; @@ -211,8 +212,11 @@ void shouldDeclareInstanceFieldAsPrivateFinalWithJavadoc() { packageName, className, """ - private int x; public int getX(){return x;} public void setX(int x){this.x=x;} - """); + /** Field X is an example for having javadoc **/ + private int x; + public int getX(){return x;} + public void setX(int x){this.x=x;} + """); // When Compilation compilation = compile(sourceFile); @@ -223,8 +227,8 @@ void shouldDeclareInstanceFieldAsPrivateFinalWithJavadoc() { ProcessorAsserts.assertingResult( generatedCode, - contains("Inner instance of builder."), - contains("private final FieldDoc instance;")); + contains("Field X is an example for having javadoc"), + contains("private int x;")); } @Test @@ -239,8 +243,10 @@ void shouldGenerateCreateMethodWithModifiersAndJavadoc() { packageName, className, """ - private int x; public int getX(){return x;} public void setX(int x){this.x=x;} - """); + private int x; + public int getX(){return x;} + public void setX(int x){this.x=x;} + """); // When Compilation compilation = compile(sourceFile); @@ -252,8 +258,8 @@ void shouldGenerateCreateMethodWithModifiersAndJavadoc() { ProcessorAsserts.assertingResult( generatedCode, contains("public static CreateDocBuilder create()"), - contains("Creating a new builder for {"), - contains("@return builder for {")); + contains("Creating a new builder for {@code CreateDoc}"), + contains("@return builder for {@code CreateDoc}")); } @Test @@ -268,8 +274,10 @@ void shouldGenerateBuildOverrideAndReturnInstance() { packageName, className, """ - private int x; public int getX(){return x;} public void setX(int x){this.x=x;} - """); + private int x; + public int getX(){return x;} + public void setX(int x){this.x=x;} + """); // When Compilation compilation = compile(sourceFile); @@ -308,7 +316,10 @@ void shouldReturnThisFromFluentMethods() { assertGenerationSucceeded(compilation, builderClassName, generatedCode); ProcessorAsserts.assertingResult( - generatedCode, contains("instance.setName(name);"), contains("return this;")); + generatedCode, + contains("public FluentReturnBuilder name(String name)"), + contains("this.name = name;"), + contains("return this;")); } @Test @@ -369,7 +380,7 @@ void shouldContainReturnJavadocForConsumerSupplierAndConsumerByBuilder() { } @Test - void shouldGenerateProxyWithMixedParamsAndArrayVarargs() { + void nonSetterMethodsShouldNotBeInBuilder() { // Given String packageName = "test"; String className = "MixedProxy"; @@ -380,6 +391,8 @@ void shouldGenerateProxyWithMixedParamsAndArrayVarargs() { packageName, className, """ + private String name; + public String getName(){return name;} public void setName(String name){this.name=name;} public void doMixed(int a, String b, int[] nums) { /* no-op */ } """); @@ -393,8 +406,9 @@ public void doMixed(int a, String b, int[] nums) { /* no-op */ } // Expect last parameter mapped to varargs in builder signature and proper call ProcessorAsserts.assertingResult( generatedCode, - contains("doMixed(int a, String b, int... nums)"), - contains("instance.doMixed(a,b,nums);")); + contains("public MixedProxyBuilder name(String name)"), + containsNot("doMixed(int a, String b, int... nums)"), + containsNot("instance.doMixed(a,b,nums);")); } @Test @@ -483,8 +497,7 @@ void shouldGenerateBothConstructorsWithJavadocs() { contains("Empty constructor of builder for"), contains("Initialisation of builder for"), contains("@param instance object instance for initialisiation"), - contains("this.instance = instance;"), - contains("this.instance = new CtorDoc();")); + contains("this.x = instance.getX();")); } @Test @@ -524,13 +537,10 @@ public void doSomething(int a, String b) { /* no-op */ } String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // PROXY method javadoc has @param for each parameter and @return + // PROXY method is not in builder ProcessorAsserts.assertingResult( generatedCode, - contains("Calling doSomething on dto-instance with parameters."), - contains("@param a value for a."), - contains("@param b value for b."), - contains("@return current instance of builder")); + containsNot("doSomething")); // SUPPLIER method javadoc and code ProcessorAsserts.assertingResult( @@ -586,7 +596,7 @@ void shouldGenerateConsumerByBuilderForAnnotatedField() { contains("helperBuilderConsumer"), contains("HelperAnnoBuilder builder = new HelperAnnoBuilder();"), contains("helperBuilderConsumer.accept(builder);"), - contains("instance.setHelper(builder.build());")); + contains("this.helper = builder.build();")); } @Test @@ -627,9 +637,9 @@ void shouldGenerateConsumerForCustomTypeWithEmptyConstructor() { // Expect consumer method with local var named 'consumer' of HelperPlain ProcessorAsserts.assertingResult( generatedCode, - contains("HelperPlain consumer = new HelperPlain();"), - contains("helperPlainConsumer.accept(consumer);"), - contains("instance.setHelperPlain(consumer);")); + contains("HelperPlain consumerInstance = new HelperPlain();"), + contains("helperPlainConsumer.accept(consumerInstance);"), + contains("this.helperPlain = consumerInstance;")); } @Test @@ -659,7 +669,7 @@ void shouldGenerateSupplierMethodForField() { // Expect supplier-based setter usage ProcessorAsserts.assertingResult( - generatedCode, contains("nameSupplier"), contains("instance.setName(nameSupplier.get());")); + generatedCode, contains("nameSupplier"), contains("this.name = nameSupplier.get();")); } protected Compilation compile(JavaFileObject... sourceFiles) { @@ -733,11 +743,18 @@ public void setAge(int age) { // Verify the generated code contains builder methods (build/create are checked centrally) ProcessorAsserts.assertingResult( generatedCode, + contains("private String name;"), + contains("private int age;"), contains("public PersonBuilder name(String name)"), - contains("public PersonBuilder age(int age)")); + contains("public PersonBuilder age(int age)"), + contains("public Person build()"), + contains("Person instance = new Person();"), + contains("instance.setName(this.name);"), + contains("instance.setAge(this.age);")); } @Test + //Todo: what should that test validate? void shouldGenerateSetterForSetOfStrings_whenImplemented() { // Given String packageName = "test"; @@ -766,6 +783,7 @@ void shouldGenerateSetterForSetOfStrings_whenImplemented() { } @Test + //Todo: what should that test validate? void shouldHandleSetOfStrings() { // Given String packageName = "test"; @@ -825,7 +843,8 @@ void shouldGenerateSetterForHelperInDifferentPackage_whenImplemented() { generatedCode, contains("public UsesOtherPackageHelperBuilder helper(Helper helper)")); } - @Test + @Test + //Todo: what should that test validate? void shouldHandleSetOfCustomType() { // Given String packageName = "test"; @@ -944,14 +963,15 @@ void shouldFilterOutNonRelevantMethods() { packageName, className, """ - // valid proxy candidate - public void ok() {} + // valid setter + public void setOk(int ok) {} // should be filtered - private void hidden() {} - public static void util() {} - public void risky() throws Exception {} + private void setHidden(int hidden) {} + public static void setUtil(int util) {} + public void setRisky(int risk) throws Exception {} public int returnsInt() { return 42; } + public int getJustGetter() { return 25; } """); // When @@ -963,15 +983,15 @@ public void risky() throws Exception {} // Expect only ok() proxy to be present ProcessorAsserts.assertingResult( generatedCode, - contains("public HasVariousMethodsBuilder ok()"), - contains("instance.ok();"), - contains("return this;")); + contains("public HasVariousMethodsBuilder ok(int ok)"), + contains("this.ok = ok;")); ProcessorAsserts.assertNotContaining( generatedCode, - ProcessorAsserts.notContains("hidden()"), - ProcessorAsserts.notContains("util()"), - ProcessorAsserts.notContains("risky()"), - ProcessorAsserts.notContains("returnsInt(")); + ProcessorAsserts.notContains("hidden"), + ProcessorAsserts.notContains("util"), + ProcessorAsserts.notContains("risky"), + ProcessorAsserts.notContains("returnsInt"), + ProcessorAsserts.notContains("justGetter")); } @Test @@ -1005,16 +1025,12 @@ void shouldNotGenerateConsumersForPrimitiveAndArrayFields() { // Expect simple setters and suppliers only; no consumer methods for primitive/array ProcessorAsserts.assertingResult( generatedCode, - contains("public PrimAndArrayBuilder count("), - contains("countSupplier)"), - contains("public PrimAndArrayBuilder names("), - contains("namesSupplier)")); + contains("public PrimAndArrayBuilder count(Supplier countSupplier"), + contains("public PrimAndArrayBuilder names(Supplier> namesSupplier")); ProcessorAsserts.assertNotContaining( generatedCode, - ProcessorAsserts.notContains("countConsumer"), - ProcessorAsserts.notContains("namesConsumer"), - ProcessorAsserts.notContains("countBuilderConsumer"), - ProcessorAsserts.notContains("namesBuilderConsumer")); + ProcessorAsserts.notContains("public PrimAndArrayBuilder count(Consumer"), + ProcessorAsserts.notContains("public PrimAndArrayBuilder names(Consumer")); } @Test @@ -1123,7 +1139,7 @@ void shouldGenerateAnnotationsAndCreateBuildMethods() { contains("@Generated("), contains("@BuilderImplementation("), contains("public static AnnoTargetBuilder create()"), - contains("return new AnnoTargetBuilder(instance);"), + contains("return new AnnoTargetBuilder();"), contains("public AnnoTarget build()"), contains("return instance;")); } @@ -1159,41 +1175,9 @@ void shouldGenerateVarargsConvenienceForCollections() { ProcessorAsserts.assertingResult( generatedCode, contains("public HasCollectionsConvenienceBuilder names(String... names)"), - contains("instance.setNames(List.of(names));"), + contains("this.names = List.of(names);"), contains("public HasCollectionsConvenienceBuilder tags(String... tags)"), - contains("instance.setTags(Set.of(tags));")); - } - - @Test - void shouldGenerateProxyMethodIncludingVarargsParameters() { - // Given - String packageName = "test"; - String className = "HasActionMethods"; - String builderClassName = className + "Builder"; - - JavaFileObject sourceFile = - ProcessorTestUtils.simpleBuilderClass( - packageName, - className, - """ - public void reset(int level, String[] notes) {} - public void clearAll() {} - """); - - // When - Compilation compilation = compile(sourceFile); - - // Then - String generatedCode = loadGeneratedSource(compilation, builderClassName); - assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // Method proxies return builder and forward to instance, last array parameter becomes varargs - ProcessorAsserts.assertingResult( - generatedCode, - contains("public HasActionMethodsBuilder reset(int level, String... notes)"), - contains("instance.reset(level,notes);"), - contains("public HasActionMethodsBuilder clearAll()"), - contains("instance.clearAll();"), - contains("return this;")); + contains("this.tags = Set.of(tags);")); } @Test @@ -1217,38 +1201,14 @@ void shouldHandleJavaTimeField() { // When Compilation compilation = compile(sourceFile); - // Then - String generatedCode = loadGeneratedSource(compilation, builderClassName); - // Currently no positive assertion; future expectation covered by @Disabled test below - assertGenerationSucceeded(compilation, builderClassName, generatedCode); - } - - @Test - void shouldGenerateSetterForJavaTime_whenImplemented() { - // Given - String packageName = "test"; - String className = "HasLocalDate"; - String builderClassName = className + "Builder"; - - JavaFileObject sourceFile = - ProcessorTestUtils.simpleBuilderClass( - packageName, - className, - """ - private java.time.LocalDate date; - - public java.time.LocalDate getDate() { return date; } - public void setDate(java.time.LocalDate date) { this.date = date; } - """); - - // When - Compilation compilation = compile(sourceFile); - // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); ProcessorAsserts.assertingResult( - generatedCode, contains("public HasLocalDateBuilder date(LocalDate date)")); + generatedCode, + contains("private LocalDate date;"), + contains("public HasLocalDateBuilder date(LocalDate date)"), + contsins("this.date = date;")); } @Test @@ -1291,7 +1251,7 @@ void shouldHandleMixedFields() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO add asserts here for fields and build method } @Test @@ -1318,7 +1278,7 @@ void shouldHandleObjectArrayField() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO: check test is needed? } @Test @@ -1347,6 +1307,7 @@ void shouldHandleClassWithOnlyGetters() { assertGenerationSucceeded(compilation, builderClassName, generatedCode); // Even without setters, builder should exist (build/create checked centrally). No extra // asserts. + // TODO Without Setters or Constructors there is no need for a builder. we should have a warning in build } @Test @@ -1370,7 +1331,7 @@ void shouldHandleEmptyClass() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // No setters expected; build/create checked centrally. + // TODO Without Setters or Constructors there is no need for a builder. we should have a warning in build } @Test @@ -1424,10 +1385,11 @@ void shouldHandleListField() { // Then String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO: add assterts } @Test + //TODO: class code does not match testname void shouldBoxPrimitiveTypeArgumentsInGenerics() { // Given String packageName = "test"; @@ -1494,7 +1456,7 @@ void shouldHandleNestedTypeWithEmptyConstructor() { String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO: add asserts } @Test @@ -1535,9 +1497,11 @@ void shouldHandleNestedTypeWithoutEmptyConstructor() { String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + //TODO: sdd asserts, because of missing empty constructor there could be no consumer } +//TODO adding list of custom types without empty constructor + @Test void shouldHandleListOfCustomType() { // Given @@ -1574,7 +1538,7 @@ void shouldHandleListOfCustomType() { String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO: Adding asserts } @Test @@ -1613,6 +1577,6 @@ void shouldHandleMapWithCustomValueType() { String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); - // build/create are checked centrally; no additional builder setter checks here + // TODO: Adding asserts } } From d9f69a8a028f9e21f7d22d925e668b459f34d73d Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:14:18 +0200 Subject: [PATCH 06/18] adding test for exact usage examples in Readme.MD --- .../builders/processor/ReadmeExampleTest.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index 768311af..918a052a 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -48,8 +48,25 @@ public class Person { public void setEmailAddresses(List emailAddresses) { this.emailAddresses = emailAddresses; } } """); + + JavaFileObject usage = + JavaFileObjects.forSourceString( + packageName + ".Usage", + """ + package readme; + public class Usage { + public static void main(){ + Person person = PersonBuilder.create() + .name("John Doe") + .age(30) + .emailAddresses("john@example.com", "j.doe@example.com") + .build(); + } + } + """); Compilation compilation = compiler.compile(person); + Compilation compilationWithUsage = compiler.compile(person, usage); String generatedCode = loadGeneratedSource(compilation, builderClassName); assertGenerationSucceeded(compilation, builderClassName, generatedCode); @@ -61,6 +78,9 @@ public class Person { contains("public PersonBuilder age(int age)"), contains("public PersonBuilder emailAddresses(String... emailAddresses)"), contains("public Person build()")); + + // Validate the full usage too + assertGenerationSucceeded(compilationWithUsage); } @Test @@ -119,8 +139,36 @@ public class Task { package readme; public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } """); + + JavaFileObject usage = + JavaFileObjects.forSourceString( + packageName + ".Usage", + """ + package readme; + import java.util.Map; + public class Usage { + public static void main(){ + Project project = ProjectBuilder.create() + .name("Simple Builders with a bit of complexity") + .status(ProjectStatus.IN_PROGRESS) + .tasks(tasks -> tasks + .add(TaskBuilder.create() + .title("Implement core functionality") + .completed(true) + .build()) + .add(TaskBuilder.create() + .title("Add documentation") + .description("Update README and add Javadocs") + .build()) + ) + .metadata(Map.of("version", "1.0.0", "owner", "dev-team")) + .build(); + } + } + """); Compilation compilation = compiler.compile(project, task, statusEnum); + Compilation compilationWithUsage = compiler.compile(project, task, statusEnum, usage); String projectBuilder = loadGeneratedSource(compilation, "ProjectBuilder"); assertGenerationSucceeded(compilation, "ProjectBuilder", projectBuilder); @@ -132,7 +180,7 @@ public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } contains("public ProjectBuilder name(String name)"), contains( "public ProjectBuilder tasks(Consumer> tasksBuilderConsumer)"), - contains("public ProjectBuilder metadata(Map metadata)"), + contains("public ProjectBuilder metadata(HashMapBuilder metadataBuilder)"), contains("public ProjectBuilder status(ProjectStatus status)"), contains("public Project build()")); @@ -146,5 +194,8 @@ public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } contains("public TaskBuilder description(String description)"), contains("public TaskBuilder completed(boolean completed)"), contains("public Task build()")); + + // Validate the full usage too + assertGenerationSucceeded(compilationWithUsage); } } From 779be11330354fda2f291558b37086ec4c539fda Mon Sep 17 00:00:00 2001 From: Andreas Igel <193066328+AndreasIgel@users.noreply.github.com> Date: Thu, 28 Aug 2025 17:16:58 +0200 Subject: [PATCH 07/18] changing example in Readme to use HashMapBuilder instead of Map.of --- README.md | 4 +++- .../simple/builders/processor/ReadmeExampleTest.java | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f63c5399..67765c7e 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,9 @@ Project project = ProjectBuilder.create() .description("Update README and add Javadocs") .build()) ) - .metadata(Map.of("version", "1.0.0", "owner", "dev-team")) + .metadata(metadata -> metadata + .put("version", "1.0.0") + .put("owner", "dev-team")) .build(); ``` diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index 918a052a..aeafb6c8 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -145,7 +145,6 @@ public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } packageName + ".Usage", """ package readme; - import java.util.Map; public class Usage { public static void main(){ Project project = ProjectBuilder.create() @@ -161,7 +160,9 @@ public static void main(){ .description("Update README and add Javadocs") .build()) ) - .metadata(Map.of("version", "1.0.0", "owner", "dev-team")) + .metadata(metadata -> metadata + .put("version", "1.0.0") + .put("owner", "dev-team")) .build(); } } From 15f4e5d3bc530bcaf5a741cfed2abed36b70a354 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 15:52:58 +0200 Subject: [PATCH 08/18] Fixing failure in merging main --- .../builders/processor/dtos/BuilderDefinitionDto.java | 8 ++++++-- .../simple/builders/processor/dtos/FieldDto.java | 11 ----------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java index b8609021..dd5abea2 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java @@ -34,8 +34,12 @@ public class BuilderDefinitionDto { /** Type of builder. Containing package and name of DTO. */ private TypeName builderTypeName; - - /** Fields in constructor. Containing all fields which need to be given for calling the constructor. Ordering in LinkedList is matching orderung of parameters in constructor call. Instances of fields are added in LinkedList of all fields too. */ + + /** + * Fields in constructor. Containing all fields which need to be given for calling the + * constructor. Ordering in LinkedList is matching orderung of parameters in constructor call. + * Instances of fields are added in LinkedList of all fields too. + */ private final LinkedList fieldsInConstructor = new LinkedList<>(); /** Generic parameters declared on the target DTO (e.g., ). */ diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java index 4b5a744a..a51bb292 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java @@ -37,9 +37,6 @@ public class FieldDto { /** Type of field. Containing generic, name of package and class */ private TypeName fieldType; - - /** Name of setter in target object. Entry may be {@code null} if field is only been set via constructor */ - private final String setterName; /** List of all methods in builder, which provide helpers to change the field. */ private final List fieldSetterMethodsList = new ArrayList<>(); @@ -67,14 +64,6 @@ public String getFieldName() { public void setFieldName(String fieldName) { this.fieldName = fieldName; } - - public Optional getSetterName() { - return Optional.ofNullable(setterName); - } - - public void setSetterName(String setterName) { - this.setterName = setterName; - } /** * Getting type of field. From 1a4c16b5efb5b9e5a752aa0d6c8b827204eb5514 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 16:06:43 +0200 Subject: [PATCH 09/18] Fixing tests (double compilation in one test) --- .../builders/processor/ReadmeExampleTest.java | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index aeafb6c8..7bffb2c3 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -1,6 +1,5 @@ package org.javahelpers.simple.builders.processor; -import static org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertGenerationSucceeded; import static org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.contains; import static org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils.loadGeneratedSource; @@ -8,18 +7,17 @@ import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import javax.tools.JavaFileObject; + +import org.javahelpers.simple.builders.processor.testing.ProcessorAsserts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ReadmeExampleTest { - protected BuilderProcessor processor; - protected Compiler compiler; - - @BeforeEach - protected void setUp() { - processor = new BuilderProcessor(); - compiler = Compiler.javac().withProcessors(processor); + private Compilation compileSources(JavaFileObject... sources){ + BuilderProcessor processor = new BuilderProcessor(); + Compiler compiler = Compiler.javac().withProcessors(processor); + return compiler.compile(sources); } @Test @@ -48,8 +46,8 @@ public class Person { public void setEmailAddresses(List emailAddresses) { this.emailAddresses = emailAddresses; } } """); - - JavaFileObject usage = + + JavaFileObject usage = JavaFileObjects.forSourceString( packageName + ".Usage", """ @@ -65,10 +63,10 @@ public static void main(){ } """); - Compilation compilation = compiler.compile(person); - Compilation compilationWithUsage = compiler.compile(person, usage); + // Test 1: Builder generation (without usage) + Compilation compilation = compileSources(person); String generatedCode = loadGeneratedSource(compilation, builderClassName); - assertGenerationSucceeded(compilation, builderClassName, generatedCode); + ProcessorAsserts.assertGenerationSucceeded(compilation, builderClassName, generatedCode); // Fixed expected strings based on README usage snippet org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( @@ -78,9 +76,12 @@ public static void main(){ contains("public PersonBuilder age(int age)"), contains("public PersonBuilder emailAddresses(String... emailAddresses)"), contains("public Person build()")); - - // Validate the full usage too - assertGenerationSucceeded(compilationWithUsage); + + // Test 2: Usage compilation (with usage) + Compilation compilationWithUsage = compileSources(person, usage); + + // Validate the full usage too + assertGenerationSucceeded(compilationWithUsage, "PersonBuilder"); } @Test @@ -139,7 +140,7 @@ public class Task { package readme; public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } """); - + JavaFileObject usage = JavaFileObjects.forSourceString( packageName + ".Usage", @@ -168,11 +169,10 @@ public static void main(){ } """); - Compilation compilation = compiler.compile(project, task, statusEnum); - Compilation compilationWithUsage = compiler.compile(project, task, statusEnum, usage); + Compilation compilation = compileSources(project, task, statusEnum); String projectBuilder = loadGeneratedSource(compilation, "ProjectBuilder"); - assertGenerationSucceeded(compilation, "ProjectBuilder", projectBuilder); + ProcessorAsserts.assertGenerationSucceeded(compilation, "ProjectBuilder", projectBuilder); // Assert key capabilities corresponding to README usage org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( @@ -187,7 +187,7 @@ public static void main(){ // Also validate TaskBuilder exists and typical fluent API String taskBuilder = loadGeneratedSource(compilation, "TaskBuilder"); - assertGenerationSucceeded(compilation, "TaskBuilder", taskBuilder); + ProcessorAsserts.assertGenerationSucceeded(compilation, "TaskBuilder", taskBuilder); org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( taskBuilder, contains("public static TaskBuilder create()"), @@ -195,8 +195,19 @@ public static void main(){ contains("public TaskBuilder description(String description)"), contains("public TaskBuilder completed(boolean completed)"), contains("public Task build()")); - - // Validate the full usage too - assertGenerationSucceeded(compilationWithUsage); + + // Validate the full usage too + Compilation compilationWithUsage = compileSources(project, task, statusEnum, usage); + assertGenerationSucceeded(compilationWithUsage,"TaskBuilder"); + } + + + /** + * Asserts compilation succeeded and that the basic builder API exists in the provided generated + * source (build() and static create()). + */ + public static void assertGenerationSucceeded( + Compilation compilation, String builderSimpleName) { + ProcessorAsserts.assertGenerationSucceeded(compilation, builderSimpleName, loadGeneratedSource(compilation, builderSimpleName)); } } From bad20054d3edbd5ccc2557635008eac5729e93af Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 16:09:10 +0200 Subject: [PATCH 10/18] Codeformat --- .../builders/processor/ReadmeExampleTest.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index 7bffb2c3..581383b4 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -7,14 +7,12 @@ import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; import javax.tools.JavaFileObject; - import org.javahelpers.simple.builders.processor.testing.ProcessorAsserts; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class ReadmeExampleTest { - private Compilation compileSources(JavaFileObject... sources){ + private Compilation compileSources(JavaFileObject... sources) { BuilderProcessor processor = new BuilderProcessor(); Compiler compiler = Compiler.javac().withProcessors(processor); return compiler.compile(sources); @@ -198,16 +196,15 @@ public static void main(){ // Validate the full usage too Compilation compilationWithUsage = compileSources(project, task, statusEnum, usage); - assertGenerationSucceeded(compilationWithUsage,"TaskBuilder"); + assertGenerationSucceeded(compilationWithUsage, "TaskBuilder"); } - /** * Asserts compilation succeeded and that the basic builder API exists in the provided generated * source (build() and static create()). */ - public static void assertGenerationSucceeded( - Compilation compilation, String builderSimpleName) { - ProcessorAsserts.assertGenerationSucceeded(compilation, builderSimpleName, loadGeneratedSource(compilation, builderSimpleName)); + public static void assertGenerationSucceeded(Compilation compilation, String builderSimpleName) { + ProcessorAsserts.assertGenerationSucceeded( + compilation, builderSimpleName, loadGeneratedSource(compilation, builderSimpleName)); } } From 7a0fe9a62f73b74f71242cb3633aa39fc566434c Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 18:29:24 +0200 Subject: [PATCH 11/18] Implementation of helper-functions and HashMapBuilder supplier adding additional checks for typesafety --- .../util/BuilderDefinitionCreator.java | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) 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 2930faf9..fa539112 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 @@ -32,6 +32,7 @@ import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.mapRelevantModifier; import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -45,6 +46,7 @@ import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; import org.javahelpers.simple.builders.core.builders.ArrayListBuilder; +import org.javahelpers.simple.builders.core.builders.HashMapBuilder; import org.javahelpers.simple.builders.core.builders.HashSetBuilder; import org.javahelpers.simple.builders.processor.dtos.*; import org.javahelpers.simple.builders.processor.exceptions.BuilderException; @@ -134,26 +136,37 @@ private static Optional createMethodDto( private static void addAdditionalHelperMethodsForField( FieldDto result, String fieldName, TypeName fieldType) { - if (isList(fieldType)) { + List innerTypes; + if (fieldType instanceof TypeNameGeneric fieldTypeGeneric) { + innerTypes = fieldTypeGeneric.getInnerTypeArguments(); + + } else { + innerTypes = Arrays.asList(new TypeName("java.lang", "Object")); + } + int innerTypesCnt = innerTypes.size(); + + if (isList(fieldType) && innerTypesCnt == 1) { result.addMethod( createFieldSetterWithTransform( fieldName, "List.of(%s)", - new TypeNameArray(fieldType.getInnerType().get(), false), + new TypeNameArray(innerTypes.get(0), false), result.getFieldGenerics())); - return; - } - if (isSet(fieldType)) { + } else if (isSet(fieldType) && innerTypesCnt == 1) { result.addMethod( createFieldSetterWithTransform( fieldName, "Set.of(%s)", - new TypeNameArray(fieldType.getInnerType().get(), true), + new TypeNameArray(innerTypes.get(0), true), result.getFieldGenerics())); - return; - } - if (isMap(fieldType)) { - // TODO MAP (having 2 inner classes, TypeNameGeneric is not able to address that yet) + } else if (isMap(fieldType) && innerTypesCnt == 2) { + TypeName mapEntryType = + new TypeNameArray( + new TypeNameGeneric("java.util", "Map.Entry", innerTypes.get(0), innerTypes.get(1)), + false); + result.addMethod( + createFieldSetterWithTransform( + fieldName, "Map.ofEntries(%s)", mapEntryType, result.getFieldGenerics())); } } @@ -185,16 +198,33 @@ private static void addConsumerMethodsForField( && hasEmptyConstructor(fieldTypeElement, elementUtils)) { // Only generate a Consumer for concrete classes with an accessible empty constructor result.addMethod(createFieldConsumer(fieldName, fieldType)); - } else if (isList(fieldType)) { + } else if (isList(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 1) { result.addMethod( createFieldConsumerWithBuilder( - fieldName, map2TypeName(ArrayListBuilder.class), fieldType.getInnerType().get())); - } else if (isMap(fieldType)) { - // TODO MAP (having 2 inner classes, TypeNameGeneric is not able to adress that yet) - } else if (isSet(fieldType)) { + fieldName, + map2TypeName(ArrayListBuilder.class), + fieldTypeGeneric.getInnerTypeArguments().get(0))); + } else if (isMap(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 2) { + TypeName builderTargetTypeName = + new TypeNameGeneric( + map2TypeName(HashMapBuilder.class), + fieldTypeGeneric.getInnerTypeArguments().get(0), + fieldTypeGeneric.getInnerTypeArguments().get(1)); + MethodDto mapConsumerWithBuilder = + BuilderDefinitionCreator.createFieldConsumerWithBuilder(fieldName, builderTargetTypeName); + result.addMethod(mapConsumerWithBuilder); + } else if (isSet(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 1) { result.addMethod( createFieldConsumerWithBuilder( - fieldName, map2TypeName(HashSetBuilder.class), fieldType.getInnerType().get())); + fieldName, + map2TypeName(HashSetBuilder.class), + fieldTypeGeneric.getInnerTypeArguments().get(0))); } } From c6ee710125ad7068201500329406b266e668f527 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 18:32:11 +0200 Subject: [PATCH 12/18] Adepting tests to final implementation of functionality --- .../processor/BuilderProcessorTest.java | 36 ------------------- .../builders/processor/ReadmeExampleTest.java | 3 +- 2 files changed, 2 insertions(+), 37 deletions(-) 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 2670f420..bab98386 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 @@ -1004,42 +1004,6 @@ void shouldNotGenerateConsumersForPrimitiveAndArrayFields() { notContains("public PrimAndArrayBuilder names(Consumer")); } - @Test - void shouldNotGenerateSpecialMethodsForMapBeyondSetterAndSupplier() { - // Given - String packageName = "test"; - String className = "HasMap"; - String builderClassName = className + "Builder"; - - JavaFileObject sourceFile = - ProcessorTestUtils.simpleBuilderClass( - packageName, - className, - """ - private java.util.Map map; - - public java.util.Map getMap() { return map; } - public void setMap(java.util.Map map) { this.map = map; } - """); - - // When - Compilation compilation = compile(sourceFile); - - // Then - String generatedCode = loadGeneratedSource(compilation, builderClassName); - assertGenerationSucceeded(compilation, builderClassName, generatedCode); - - // Expect only direct setter and supplier; no varargs/consumer for Map - ProcessorAsserts.assertingResult( - generatedCode, - contains("public HasMapBuilder map("), - contains("mapSupplier)"), - notContains("map(String..."), - notContains("map(java.lang.String..."), - notContains("mapBuilderConsumer"), - notContains("mapConsumer")); - } - @Test void shouldNotGenerateConsumerForAbstractHelper() { // Given diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index 581383b4..7426caea 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -179,7 +179,8 @@ public static void main(){ contains("public ProjectBuilder name(String name)"), contains( "public ProjectBuilder tasks(Consumer> tasksBuilderConsumer)"), - contains("public ProjectBuilder metadata(HashMapBuilder metadataBuilder)"), + contains( + "public ProjectBuilder metadata(Consumer> metadataBuilderConsumer"), contains("public ProjectBuilder status(ProjectStatus status)"), contains("public Project build()")); From 4dcae4beb66937e88ae1e3d00c1c792895c373c7 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 18:35:30 +0200 Subject: [PATCH 13/18] Fixing codestyle --- .../simple/builders/processor/dtos/FieldDto.java | 2 +- .../simple/builders/processor/ReadmeExampleTest.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java index a51bb292..77a5359b 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java @@ -38,7 +38,7 @@ public class FieldDto { /** Type of field. Containing generic, name of package and class */ private TypeName fieldType; - /** List of all methods in builder, which provide helpers to change the field. */ + /** List of all methods in builder, which change the field. */ private final List fieldSetterMethodsList = new ArrayList<>(); /** Optional Javadoc of the field extracted from setter or constructor. */ diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java index 7426caea..3ce9eafd 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ReadmeExampleTest.java @@ -67,7 +67,7 @@ public static void main(){ ProcessorAsserts.assertGenerationSucceeded(compilation, builderClassName, generatedCode); // Fixed expected strings based on README usage snippet - org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( + ProcessorAsserts.assertingResult( generatedCode, contains("public static PersonBuilder create()"), contains("public PersonBuilder name(String name)"), @@ -173,7 +173,7 @@ public static void main(){ ProcessorAsserts.assertGenerationSucceeded(compilation, "ProjectBuilder", projectBuilder); // Assert key capabilities corresponding to README usage - org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( + ProcessorAsserts.assertingResult( projectBuilder, contains("public static ProjectBuilder create()"), contains("public ProjectBuilder name(String name)"), @@ -187,7 +187,7 @@ public static void main(){ // Also validate TaskBuilder exists and typical fluent API String taskBuilder = loadGeneratedSource(compilation, "TaskBuilder"); ProcessorAsserts.assertGenerationSucceeded(compilation, "TaskBuilder", taskBuilder); - org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertingResult( + ProcessorAsserts.assertingResult( taskBuilder, contains("public static TaskBuilder create()"), contains("public TaskBuilder title(String title)"), From 6439fb16fbda28baa0b33a541a3046b1391cd358 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 18:36:55 +0200 Subject: [PATCH 14/18] Removed unused functionality --- .../builders/processor/dtos/BuilderDefinitionDto.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java index dd5abea2..5fe8c644 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java @@ -35,13 +35,6 @@ public class BuilderDefinitionDto { /** Type of builder. Containing package and name of DTO. */ private TypeName builderTypeName; - /** - * Fields in constructor. Containing all fields which need to be given for calling the - * constructor. Ordering in LinkedList is matching orderung of parameters in constructor call. - * Instances of fields are added in LinkedList of all fields too. - */ - private final LinkedList fieldsInConstructor = new LinkedList<>(); - /** Generic parameters declared on the target DTO (e.g., ). */ private final List generics = new LinkedList<>(); From 4c0c1d63a8459c6b4a4e9ae4a338d8e0627715e0 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 18:51:36 +0200 Subject: [PATCH 15/18] Extending coverage by adding test for raw type --- .../util/BuilderDefinitionCreator.java | 8 +-- .../processor/BuilderProcessorTest.java | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) 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 fa539112..7b6a2bfb 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 @@ -32,6 +32,7 @@ import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.mapRelevantModifier; import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -137,13 +138,14 @@ private static Optional createMethodDto( private static void addAdditionalHelperMethodsForField( FieldDto result, String fieldName, TypeName fieldType) { List innerTypes; + int innerTypesCnt; if (fieldType instanceof TypeNameGeneric fieldTypeGeneric) { innerTypes = fieldTypeGeneric.getInnerTypeArguments(); - + innerTypesCnt = innerTypes.size(); } else { - innerTypes = Arrays.asList(new TypeName("java.lang", "Object")); + innerTypes = null; + innerTypesCnt = 0; } - int innerTypesCnt = innerTypes.size(); if (isList(fieldType) && innerTypesCnt == 1) { result.addMethod( 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 bab98386..6e901812 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 @@ -1923,4 +1923,57 @@ void shouldHandleOverloadedSettersForSameFieldWithoutConflicts() { "public OverloadedNamesBuilder names(Supplier> namesSupplier)", "public OverloadedNamesBuilder names(String... names)"); } + + @Test + void collectionsWithRawTypes_shouldGenerateBuilders() { + + JavaFileObject rawCollectionsClass = + ProcessorTestUtils.forSource( + """ + package test; + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + import java.util.List; + import java.util.Set; + import java.util.Map; + @SimpleBuilder + public class RawCollections { + private List rawList; + private Set rawSet; + private Map rawMap; + + public List getRawList() { return rawList; } + public void setRawList(List rawList) { this.rawList = rawList; } + + public Set getRawSet() { return rawSet; } + public void setRawSet(Set rawSet) { this.rawSet = rawSet; } + + public Map getRawMap() { return rawMap; } + public void setRawMap(Map rawMap) { this.rawMap = rawMap; } + } + """); + + // Test builder generation (without usage) + Compilation compilation = compile(rawCollectionsClass); + String generatedCode = loadGeneratedSource(compilation, "RawCollectionsBuilder"); + assertGenerationSucceeded(compilation, "RawCollectionsBuilder", generatedCode); + + // Verify basic builder methods are generated + ProcessorAsserts.assertContaining( + generatedCode, + "public static RawCollectionsBuilder create()", + "public RawCollectionsBuilder rawList(List rawList)", + "public RawCollectionsBuilder rawSet(Set rawSet)", + "public RawCollectionsBuilder rawMap(Map rawMap)", + "public RawCollections build()"); + + // Verify no additional helper methods are generated for raw types + ProcessorAsserts.assertNotContaining( + generatedCode, + "rawListBuilderConsumer", + "rawSetBuilderConsumer", + "rawMapBuilderConsumer", + "rawList(String...)", + "rawSet(String...)", + "rawMap(Map.Entry)"); + } } From b68a11d34298a19adfb6870fd465f5b0bfd58210 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 19:12:17 +0200 Subject: [PATCH 16/18] Adding a test for generic properties without being lists,sets,maps --- .../processor/BuilderProcessorTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) 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 6e901812..23dfeaf9 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 @@ -762,6 +762,60 @@ public void setAge(int age) { "public PersonBuilder age(int age)"); } + @Test + void shouldHandleGenericWrapperTypes() { + + JavaFileObject optionalWrapperClass = + ProcessorTestUtils.forSource( + """ + package test; + import java.util.Optional; + import java.time.LocalDate; + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + + @SimpleBuilder + public class OptionalWrapper { + private Optional name; + private Optional count; + private Optional date; + + public Optional getName() { return name; } + public void setName(Optional name) { this.name = name; } + + public Optional getCount() { return count; } + public void setCount(Optional count) { this.count = count; } + + public Optional getDate() { return date; } + public void setDate(Optional date) { this.date = date; } + } + """); + + // Test builder generation (without usage) + Compilation compilation = compile(optionalWrapperClass); + String optionalBuilder = loadGeneratedSource(compilation, "OptionalWrapperBuilder"); + + // Verify Optional wrapper generates basic methods + assertGenerationSucceeded(compilation, "OptionalWrapperBuilder", optionalBuilder); + ProcessorAsserts.assertContaining( + optionalBuilder, + "public static OptionalWrapperBuilder create()", + "public OptionalWrapperBuilder name(Optional name)", + "public OptionalWrapperBuilder count(Optional count)", + "public OptionalWrapperBuilder date(Optional date)", + "public OptionalWrapper build()"); + + // Verify no additional helper methods are generated for these generic types + // (since they don't match List/Set/Map patterns) + ProcessorAsserts.assertNotContaining( + optionalBuilder, + "nameBuilderConsumer", + "countBuilderConsumer", + "dateBuilderConsumer", + "name(String...)", + "count(Integer...)", + "date(LocalDate...)"); + } + @Test void shouldGenerateSetterForClassInDifferentPackage() { // Given From 8603f4128b6c5430255cab0da7ad0e398c8a7db3 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 19:12:25 +0200 Subject: [PATCH 17/18] Fixing codeformat --- .../builders/processor/util/BuilderDefinitionCreator.java | 2 -- 1 file changed, 2 deletions(-) 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 7b6a2bfb..f4c96885 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 @@ -32,8 +32,6 @@ import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.mapRelevantModifier; import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; From 92d275b9e7c28cbee89925e28f5d6168faf72703 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Sun, 7 Sep 2025 19:31:23 +0200 Subject: [PATCH 18/18] Updating coverage calculation. according to common sense 80% should be enough. And a drop of 5% for changed lines in a single Pull request is fully ok --- codecov.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index 23ac5f69..46dd4db4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,10 +5,11 @@ coverage: status: project: default: - target: auto + target: 80% patch: default: - target: auto + target: 90% + threshold: 5% comment: layout: "condensed_header, condensed_files, condensed_footer"