From 0a2cd7118fb1215e9ff8fc79dabd3e61e5e0a8a8 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Mon, 20 Oct 2025 22:02:47 +0200 Subject: [PATCH 01/14] Adding builders for lists and sets, which support interfaces with builders --- .../ArrayListBuilderWithElementBuilders.java | 111 +++++++++ .../HashSetBuilderWithElementBuilders.java | 107 +++++++++ ...rayListBuilderWithElementBuildersTest.java | 196 ++++++++++++++++ ...HashSetBuilderWithElementBuildersTest.java | 215 ++++++++++++++++++ 4 files changed, 629 insertions(+) create mode 100644 core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java create mode 100644 core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java create mode 100644 core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java create mode 100644 core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java b/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java new file mode 100644 index 00000000..86d0311b --- /dev/null +++ b/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java @@ -0,0 +1,111 @@ +/* + * MIT License + * + * Copyright (c) 2025 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.core.builders; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.javahelpers.simple.builders.core.interfaces.IBuilderBase; + +/** + * Enhanced builder for Lists that supports adding elements via builder consumers. This class + * extends {@link ArrayListBuilder} and adds methods to accept {@link Consumer} functions that + * configure element builders, eliminating the need to manually call {@code build()} on each element + * builder. + * + *

Example usage: + * + *

{@code
+ * List people = new ArrayListBuilderWithElementBuilders<>(PersonBuilder::create)
+ *     .add(personBuilder -> personBuilder
+ *         .name("John Doe")
+ *         .age(30))
+ *     .add(personBuilder -> personBuilder
+ *         .name("Jane Smith")
+ *         .age(25))
+ *     .build();
+ * }
+ * + * @param the type of elements in the targeting list + * @param the type of builder used to create elements, must implement {@link IBuilderBase} and building objects of type T + */ +public class ArrayListBuilderWithElementBuilders> + extends ArrayListBuilder { + private final Supplier elementBuilderProvider; + + /** + * Creates an empty ArrayListBuilderWithElementBuilders. + * + * @param elementBuilderProvider supplier that provides new instances of element builders + */ + public ArrayListBuilderWithElementBuilders(Supplier elementBuilderProvider) { + this.elementBuilderProvider = elementBuilderProvider; + } + + /** + * Creates an ArrayListBuilderWithElementBuilders initialized with the elements from the given + * list. + * + * @param initialList list to initialize from + * @param elementBuilderProvider supplier that provides new instances of element builders + */ + public ArrayListBuilderWithElementBuilders( + List initialList, Supplier elementBuilderProvider) { + super(initialList); + this.elementBuilderProvider = elementBuilderProvider; + } + + /** + * Adds an element to the list by providing a consumer that configures an element builder. The + * builder is automatically created using the elementBuilderProvider and built after + * configuration. + * + * @param elementBuilderConsumer consumer that configures the element builder + * @return this builder instance for method chaining + */ + public ArrayListBuilderWithElementBuilders add(Consumer elementBuilderConsumer) { + B elementBuilder = elementBuilderProvider.get(); + elementBuilderConsumer.accept(elementBuilder); + super.add(elementBuilder.build()); + return this; + } + + /** + * Adds multiple elements to the list by providing consumers that configure element builders. Each + * builder is automatically created using the elementBuilderProvider and built after + * configuration. + * + * @param elementBuilderConsumers varargs of consumers that configure element builders + * @return this builder instance for method chaining + */ + @SafeVarargs + public final ArrayListBuilderWithElementBuilders add( + Consumer... elementBuilderConsumers) { + for (Consumer elementBuilderConsumer : elementBuilderConsumers) { + this.add(elementBuilderConsumer); + } + return this; + } +} diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java b/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java new file mode 100644 index 00000000..1ba129df --- /dev/null +++ b/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java @@ -0,0 +1,107 @@ +/* + * MIT License + * + * Copyright (c) 2025 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.core.builders; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.javahelpers.simple.builders.core.interfaces.IBuilderBase; + +/** + * Enhanced builder for Sets that supports adding elements via builder consumers. This class extends + * {@link HashSetBuilder} and adds methods to accept {@link Consumer} functions that configure + * element builders, eliminating the need to manually call {@code build()} on each element builder. + * + *

Example usage: + * + *

{@code
+ * Set people = new HashSetBuilderWithElementBuilders<>(PersonBuilder::create)
+ *     .add(personBuilder -> personBuilder
+ *         .name("John Doe")
+ *         .age(30))
+ *     .add(personBuilder -> personBuilder
+ *         .name("Jane Smith")
+ *         .age(25))
+ *     .build();
+ * }
+ * + * @param the type of elements in the targeting set + * @param the type of builder used to create elements, must implement {@link IBuilderBase} and building objects of type T + */ +public class HashSetBuilderWithElementBuilders> + extends HashSetBuilder { + private final Supplier elementBuilderProvider; + + /** + * Creates an empty HashSetBuilderWithElementBuilders. + * + * @param elementBuilderProvider supplier that provides new instances of element builders + */ + public HashSetBuilderWithElementBuilders(Supplier elementBuilderProvider) { + this.elementBuilderProvider = elementBuilderProvider; + } + + /** + * Creates a HashSetBuilderWithElementBuilders initialized with the elements from the given set. + * + * @param initialSet set to initialize from + * @param elementBuilderProvider supplier that provides new instances of element builders + */ + public HashSetBuilderWithElementBuilders(Set initialSet, Supplier elementBuilderProvider) { + super(initialSet); + this.elementBuilderProvider = elementBuilderProvider; + } + + /** + * Adds an element to the set by providing a consumer that configures an element builder. The + * builder is automatically created using the elementBuilderProvider and built after + * configuration. + * + * @param elementBuilderConsumer consumer that configures the element builder + * @return this builder instance for method chaining + */ + public HashSetBuilderWithElementBuilders add(Consumer elementBuilderConsumer) { + B elementBuilder = elementBuilderProvider.get(); + elementBuilderConsumer.accept(elementBuilder); + super.add(elementBuilder.build()); + return this; + } + + /** + * Adds multiple elements to the set by providing consumers that configure element builders. Each + * builder is automatically created using the elementBuilderProvider and built after + * configuration. + * + * @param elementBuilderConsumers varargs of consumers that configure element builders + * @return this builder instance for method chaining + */ + @SafeVarargs + public final HashSetBuilderWithElementBuilders add(Consumer... elementBuilderConsumers) { + for (Consumer elementBuilderConsumer : elementBuilderConsumers) { + this.add(elementBuilderConsumer); + } + return this; + } +} diff --git a/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java b/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java new file mode 100644 index 00000000..b3c47d6e --- /dev/null +++ b/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java @@ -0,0 +1,196 @@ +/* + * MIT License + * + * Copyright (c) 2025 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.core.builders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.javahelpers.simple.builders.core.interfaces.IBuilderBase; +import org.junit.jupiter.api.Test; + +class ArrayListBuilderWithElementBuildersTest { + + @Test + void shouldCreateEmptyBuilder() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + List result = builder.build(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void shouldCreateBuilderWithInitialList() { + List initial = + Arrays.asList(new TestPerson("Alice", 30), new TestPerson("Bob", 25)); + + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(initial, TestPersonBuilder::new); + List result = builder.build(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals("Alice", result.get(0).name); + assertEquals(30, result.get(0).age); + assertEquals("Bob", result.get(1).name); + assertEquals(25, result.get(1).age); + } + + @Test + void shouldAddSingleElementViaConsumer() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add(person -> person.name("John").age(35)); + List result = builder.build(); + + assertEquals(1, result.size()); + assertEquals("John", result.get(0).name); + assertEquals(35, result.get(0).age); + } + + @Test + void shouldAddMultipleElementsViaConsumers() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder + .add(person -> person.name("Alice").age(30)) + .add(person -> person.name("Bob").age(25)) + .add(person -> person.name("Charlie").age(40)); + + List result = builder.build(); + + assertEquals(3, result.size()); + assertEquals("Alice", result.get(0).name); + assertEquals(30, result.get(0).age); + assertEquals("Bob", result.get(1).name); + assertEquals(25, result.get(1).age); + assertEquals("Charlie", result.get(2).name); + assertEquals(40, result.get(2).age); + } + + @Test + void shouldAddMultipleElementsViaVarargs() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add( + person -> person.name("Alice").age(30), + person -> person.name("Bob").age(25), + person -> person.name("Charlie").age(40)); + + List result = builder.build(); + + assertEquals(3, result.size()); + assertEquals("Alice", result.get(0).name); + assertEquals(30, result.get(0).age); + assertEquals("Bob", result.get(1).name); + assertEquals(25, result.get(1).age); + assertEquals("Charlie", result.get(2).name); + assertEquals(40, result.get(2).age); + } + + @Test + void shouldCombineInitialListAndConsumers() { + List initial = Arrays.asList(new TestPerson("Initial", 20)); + + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(initial, TestPersonBuilder::new); + + builder.add(person -> person.name("Added").age(30)); + + List result = builder.build(); + + assertEquals(2, result.size()); + assertEquals("Initial", result.get(0).name); + assertEquals(20, result.get(0).age); + assertEquals("Added", result.get(1).name); + assertEquals(30, result.get(1).age); + } + + @Test + void shouldReturnThisForFluentInterface() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + + ArrayListBuilderWithElementBuilders result = + builder.add(person -> person.name("Test").age(25)); + + assertEquals(builder, result); + } + + @Test + void shouldAllowMixingConsumerAndDirectAdd() { + ArrayListBuilderWithElementBuilders builder = + new ArrayListBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add(person -> person.name("Consumer").age(30)).add(new TestPerson("Direct", 25)); + + List result = builder.build(); + + assertEquals(2, result.size()); + assertEquals("Consumer", result.get(0).name); + assertEquals(30, result.get(0).age); + assertEquals("Direct", result.get(1).name); + assertEquals(25, result.get(1).age); + } + + // Test helper classes + static class TestPerson { + String name; + int age; + + TestPerson() {} + + TestPerson(String name, int age) { + this.name = name; + this.age = age; + } + } + + static class TestPersonBuilder implements IBuilderBase { + private final TestPerson person = new TestPerson(); + + TestPersonBuilder name(String name) { + person.name = name; + return this; + } + + TestPersonBuilder age(int age) { + person.age = age; + return this; + } + + @Override + public TestPerson build() { + return person; + } + } +} diff --git a/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java b/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java new file mode 100644 index 00000000..8664448e --- /dev/null +++ b/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java @@ -0,0 +1,215 @@ +/* + * MIT License + * + * Copyright (c) 2025 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.core.builders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import org.javahelpers.simple.builders.core.interfaces.IBuilderBase; +import org.junit.jupiter.api.Test; + +class HashSetBuilderWithElementBuildersTest { + + @Test + void shouldCreateEmptyBuilder() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + Set result = builder.build(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void shouldCreateBuilderWithInitialSet() { + Set initial = + new HashSet<>(Arrays.asList(new TestPerson("Alice", 30), new TestPerson("Bob", 25))); + + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(initial, TestPersonBuilder::new); + Set result = builder.build(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(new TestPerson("Alice", 30))); + assertTrue(result.contains(new TestPerson("Bob", 25))); + } + + @Test + void shouldAddSingleElementViaConsumer() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add(person -> person.name("John").age(35)); + Set result = builder.build(); + + assertEquals(1, result.size()); + assertTrue(result.contains(new TestPerson("John", 35))); + } + + @Test + void shouldAddMultipleElementsViaConsumers() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder + .add(person -> person.name("Alice").age(30)) + .add(person -> person.name("Bob").age(25)) + .add(person -> person.name("Charlie").age(40)); + + Set result = builder.build(); + + assertEquals(3, result.size()); + assertTrue(result.contains(new TestPerson("Alice", 30))); + assertTrue(result.contains(new TestPerson("Bob", 25))); + assertTrue(result.contains(new TestPerson("Charlie", 40))); + } + + @Test + void shouldAddMultipleElementsViaVarargs() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add( + person -> person.name("Alice").age(30), + person -> person.name("Bob").age(25), + person -> person.name("Charlie").age(40)); + + Set result = builder.build(); + + assertEquals(3, result.size()); + assertTrue(result.contains(new TestPerson("Alice", 30))); + assertTrue(result.contains(new TestPerson("Bob", 25))); + assertTrue(result.contains(new TestPerson("Charlie", 40))); + } + + @Test + void shouldCombineInitialSetAndConsumers() { + Set initial = new HashSet<>(Arrays.asList(new TestPerson("Initial", 20))); + + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(initial, TestPersonBuilder::new); + + builder.add(person -> person.name("Added").age(30)); + + Set result = builder.build(); + + assertEquals(2, result.size()); + assertTrue(result.contains(new TestPerson("Initial", 20))); + assertTrue(result.contains(new TestPerson("Added", 30))); + } + + @Test + void shouldReturnThisForFluentInterface() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + HashSetBuilderWithElementBuilders result = + builder.add(person -> person.name("Test").age(25)); + + assertEquals(builder, result); + } + + @Test + void shouldAllowMixingConsumerAndDirectAdd() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder.add(person -> person.name("Consumer").age(30)).add(new TestPerson("Direct", 25)); + + Set result = builder.build(); + + assertEquals(2, result.size()); + assertTrue(result.contains(new TestPerson("Consumer", 30))); + assertTrue(result.contains(new TestPerson("Direct", 25))); + } + + @Test + void shouldHandleDuplicateElementsFromConsumers() { + HashSetBuilderWithElementBuilders builder = + new HashSetBuilderWithElementBuilders<>(TestPersonBuilder::new); + + builder + .add(person -> person.name("Duplicate").age(30)) + .add(person -> person.name("Duplicate").age(30)) + .add(person -> person.name("Unique").age(25)); + + Set result = builder.build(); + + assertEquals(2, result.size()); + assertTrue(result.contains(new TestPerson("Duplicate", 30))); + assertTrue(result.contains(new TestPerson("Unique", 25))); + } + + // Test helper classes + static class TestPerson { + String name; + int age; + + TestPerson() {} + + TestPerson(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestPerson that = (TestPerson) o; + return age == that.age && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, age); + } + } + + static class TestPersonBuilder implements IBuilderBase { + private final TestPerson person = new TestPerson(); + + TestPersonBuilder name(String name) { + person.name = name; + return this; + } + + TestPersonBuilder age(int age) { + person.age = age; + return this; + } + + @Override + public TestPerson build() { + return person; + } + } +} From 0f0ea320309a948c12b20b23541d66d8b27189eb Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Mon, 20 Oct 2025 22:05:43 +0200 Subject: [PATCH 02/14] Adapting tests --- .../core/builders/ArrayListBuilderWithElementBuildersTest.java | 3 ++- .../core/builders/HashSetBuilderWithElementBuildersTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java b/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java index b3c47d6e..b85d1ab0 100644 --- a/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java +++ b/core/src/test/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuildersTest.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; @@ -143,7 +144,7 @@ void shouldReturnThisForFluentInterface() { ArrayListBuilderWithElementBuilders result = builder.add(person -> person.name("Test").age(25)); - assertEquals(builder, result); + assertSame(builder, result); } @Test diff --git a/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java b/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java index 8664448e..df010d93 100644 --- a/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java +++ b/core/src/test/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuildersTest.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Arrays; @@ -134,7 +135,7 @@ void shouldReturnThisForFluentInterface() { HashSetBuilderWithElementBuilders result = builder.add(person -> person.name("Test").age(25)); - assertEquals(builder, result); + assertSame(builder, result); } @Test From 19c1d5002c47c1606ef42e2d970a2251ab73b645 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Mon, 20 Oct 2025 22:05:52 +0200 Subject: [PATCH 03/14] Fixing codeformat --- .../core/builders/ArrayListBuilderWithElementBuilders.java | 3 ++- .../core/builders/HashSetBuilderWithElementBuilders.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java b/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java index 86d0311b..3354e27c 100644 --- a/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java +++ b/core/src/main/java/org/javahelpers/simple/builders/core/builders/ArrayListBuilderWithElementBuilders.java @@ -49,7 +49,8 @@ * } * * @param the type of elements in the targeting list - * @param the type of builder used to create elements, must implement {@link IBuilderBase} and building objects of type T + * @param the type of builder used to create elements, must implement {@link IBuilderBase} and + * building objects of type T */ public class ArrayListBuilderWithElementBuilders> extends ArrayListBuilder { diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java b/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java index 1ba129df..3176f390 100644 --- a/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java +++ b/core/src/main/java/org/javahelpers/simple/builders/core/builders/HashSetBuilderWithElementBuilders.java @@ -48,7 +48,8 @@ * } * * @param the type of elements in the targeting set - * @param the type of builder used to create elements, must implement {@link IBuilderBase} and building objects of type T + * @param the type of builder used to create elements, must implement {@link IBuilderBase} and + * building objects of type T */ public class HashSetBuilderWithElementBuilders> extends HashSetBuilder { From a01e9011af85754e2daafbedfabd5c4a4bf7ec65 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Tue, 21 Oct 2025 21:13:03 +0200 Subject: [PATCH 04/14] 1st implementation of BuilderDefinitionCreator to use dto-builder-supplier in constructor of collection-builders --- .../util/BuilderDefinitionCreator.java | 169 ++++++++++++++++-- 1 file changed, 157 insertions(+), 12 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 1499b520..6cb8b5ac 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 @@ -34,6 +34,7 @@ import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -308,11 +309,35 @@ && hasEmptyConstructor(fieldTypeElement, context)) { } else if (isList(fieldType) && fieldType instanceof TypeNameGeneric fieldTypeGeneric && fieldTypeGeneric.getInnerTypeArguments().size() == 1) { - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, - map2TypeName(ArrayListBuilder.class), - fieldTypeGeneric.getInnerTypeArguments().get(0))); + TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); + + // Get the TypeMirror of the element type from the parameter's type + TypeMirror fieldTypeMirror = fieldParameter.asType(); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + + Optional elementBuilderType = + elementTypeMirror != null + ? findBuilderTypeForElementType(elementType, elementTypeMirror, context) + : Optional.empty(); + + if (elementBuilderType.isPresent()) { + // Element type has a builder - use ArrayListBuilderWithElementBuilders + TypeName arrayListBuilderWithElementBuildersType = + new TypeName( + "org.javahelpers.simple.builders.core.builders", + "ArrayListBuilderWithElementBuilders"); + TypeName builderType = + new TypeNameGeneric( + arrayListBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + result.addMethod( + createFieldConsumerWithElementBuilders( + fieldName, builderType, elementBuilderType.get())); + } else { + // Regular ArrayListBuilder + result.addMethod( + createFieldConsumerWithBuilder( + fieldName, map2TypeName(ArrayListBuilder.class), elementType)); + } } else if (isMap(fieldType) && fieldType instanceof TypeNameGeneric fieldTypeGeneric && fieldTypeGeneric.getInnerTypeArguments().size() == 2) { @@ -327,11 +352,35 @@ && hasEmptyConstructor(fieldTypeElement, context)) { } else if (isSet(fieldType) && fieldType instanceof TypeNameGeneric fieldTypeGeneric && fieldTypeGeneric.getInnerTypeArguments().size() == 1) { - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, - map2TypeName(HashSetBuilder.class), - fieldTypeGeneric.getInnerTypeArguments().get(0))); + TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); + + // Get the TypeMirror of the element type from the parameter's type + TypeMirror fieldTypeMirror = fieldParameter.asType(); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + + Optional elementBuilderType = + elementTypeMirror != null + ? findBuilderTypeForElementType(elementType, elementTypeMirror, context) + : Optional.empty(); + + if (elementBuilderType.isPresent()) { + // Element type has a builder - use HashSetBuilderWithElementBuilders + TypeName hashSetBuilderWithElementBuildersType = + new TypeName( + "org.javahelpers.simple.builders.core.builders", + "HashSetBuilderWithElementBuilders"); + TypeName builderType = + new TypeNameGeneric( + hashSetBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + result.addMethod( + createFieldConsumerWithElementBuilders( + fieldName, builderType, elementBuilderType.get())); + } else { + // Regular HashSetBuilder + result.addMethod( + createFieldConsumerWithBuilder( + fieldName, map2TypeName(HashSetBuilder.class), elementType)); + } } else if (shouldGenerateStringBuilderConsumer(fieldType)) { // Generate StringBuilder consumer for String or Optional String transform = @@ -534,6 +583,41 @@ private static MethodDto createFieldConsumerWithBuilder( } private static MethodDto createFieldConsumerWithBuilder(String fieldName, TypeName builderType) { + return createFieldConsumerWithBuilder( + fieldName, builderType, "this.$fieldName:N.value()", "", Map.of()); + } + + /** + * Creates a consumer method for collection builders with element builders. Used for + * ArrayListBuilderWithElementBuilders and HashSetBuilderWithElementBuilders. + */ + private static MethodDto createFieldConsumerWithElementBuilders( + String fieldName, TypeName collectionBuilderType, TypeName elementBuilderType) { + return createFieldConsumerWithBuilder( + fieldName, + collectionBuilderType, + "this.$fieldName:N.value(), $elementBuilderType:T::create", + "$elementBuilderType:T::create", + Map.of("elementBuilderType", elementBuilderType)); + } + + /** + * Creates a consumer method for a field with a builder type. + * + * @param fieldName the field name + * @param builderType the builder type (e.g., ArrayListBuilder or + * ArrayListBuilderWithElementBuilders) + * @param constructorArgsWithValue constructor arguments when field is already set + * @param constructorArgsEmpty constructor arguments when field is empty + * @param additionalArguments additional template arguments to add to the method (must be TypeName + * values) + */ + private static MethodDto createFieldConsumerWithBuilder( + String fieldName, + TypeName builderType, + String constructorArgsWithValue, + String constructorArgsEmpty, + Map additionalArguments) { TypeNameGeneric consumerType = new TypeNameGeneric(map2TypeName(Consumer.class), builderType); MethodParameterDto parameter = new MethodParameterDto(); parameter.setParameterName(fieldName + BUILDER_SUFFIX + SUFFIX_CONSUMER); @@ -545,14 +629,16 @@ private static MethodDto createFieldConsumerWithBuilder(String fieldName, TypeNa methodDto.setMethodType(MethodTypes.CONSUMER_BY_BUILDER); methodDto.setCode( """ - $helperType:T builder = this.$fieldName:N.isSet() ? new $helperType:T(this.$fieldName:N.value()) : new $helperType:T(); + $helperType:T builder = this.$fieldName:N.isSet() ? new $helperType:T(%s) : new $helperType:T(%s); $dtoMethodParam:N.accept(builder); this.$fieldName:N = $builderFieldWrapper:T.changedValue(builder.build()); return this; - """); + """ + .formatted(constructorArgsWithValue, constructorArgsEmpty)); methodDto.addArgument(ARG_FIELD_NAME, fieldName); methodDto.addArgument(ARG_DTO_METHOD_PARAM, parameter.getParameterName()); methodDto.addArgument(ARG_HELPER_TYPE, builderType); + additionalArguments.forEach(methodDto::addArgument); methodDto.addArgument(ARG_BUILDER_FIELD_WRAPPER, TRACKED_VALUE_TYPE); return methodDto; } @@ -645,4 +731,63 @@ private static Optional findBuilderType( } return Optional.empty(); } + + /** + * Finds the builder type for a given element type (used for collection elements). + * + * @param elementType the type of the collection element + * @param elementTypeMirror the TypeMirror of the element type (from the parameter) + * @param context processing context + * @return Optional containing the builder TypeName if the element type has @SimpleBuilder + */ + private static Optional findBuilderTypeForElementType( + TypeName elementType, TypeMirror elementTypeMirror, ProcessingContext context) { + // Skip type variables and primitives + if (elementType instanceof TypeNameVariable || elementType instanceof TypeNamePrimitive) { + context.debug(" -> Skipping type variable or primitive: %s", elementType); + return Optional.empty(); + } + + // Try to get the element from the TypeMirror + Element element = context.asElement(elementTypeMirror); + if (!(element instanceof TypeElement typeElement)) { + context.debug(" -> Element is not a TypeElement: %s", element); + return Optional.empty(); + } + + // Check if element type has @SimpleBuilder annotation + Optional foundBuilderAnnotation = + findAnnotation(typeElement, SimpleBuilder.class); + + if (foundBuilderAnnotation.isPresent()) { + String packageName = context.getPackageName(typeElement); + String simpleClassName = typeElement.getSimpleName().toString(); + context.debug( + " -> Found @SimpleBuilder on element type %s.%s, will use %sBuilder", + packageName, simpleClassName, simpleClassName); + return Optional.of(new TypeName(packageName, simpleClassName + BUILDER_SUFFIX)); + } + + context.debug(" -> Element type %s does not have @SimpleBuilder", typeElement.getSimpleName()); + return Optional.empty(); + } + + /** + * Extracts the first type argument from a parameterized type mirror. For example, from + * List<Task>, this extracts the Task TypeMirror. + * + * @param typeMirror the parameterized type mirror + * @param context processing context + * @return the first type argument, or null if not a parameterized type + */ + private static TypeMirror extractFirstTypeArgument( + TypeMirror typeMirror, ProcessingContext context) { + if (typeMirror instanceof javax.lang.model.type.DeclaredType declaredType) { + List typeArguments = declaredType.getTypeArguments(); + if (!typeArguments.isEmpty()) { + return typeArguments.get(0); + } + } + return null; + } } From 30139965474e127b61bfab7a9c566dd134eebc2c Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Tue, 21 Oct 2025 21:32:37 +0200 Subject: [PATCH 05/14] Improving normalizing of whitespaces in comparing of texts in tests --- .../simple/builders/processor/testing/ProcessorAsserts.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java index ec219347..4fddc926 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/testing/ProcessorAsserts.java @@ -19,7 +19,7 @@ private ProcessorAsserts() {} /** Collapse all whitespace (spaces, tabs, newlines) to single spaces and trim. */ private static String normalizeWhitespace(String input) { if (input == null) return null; - return input.replaceAll("\\s+", " ").trim(); + return input.replaceAll("[\\t\\n\\r\\s]+", "").trim(); } /** From 090a9c82a862f7776bcf18107efa659203ddd1c6 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Tue, 21 Oct 2025 21:34:14 +0200 Subject: [PATCH 06/14] Replacing ArrayListBuilders by ArrayListBuilderWithElementBuilders in tests --- .../simple/builders/processor/ReadmeExampleTest.java | 2 +- 1 file changed, 1 insertion(+), 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 3ce9eafd..fde22e60 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 @@ -178,7 +178,7 @@ public static void main(){ contains("public static ProjectBuilder create()"), contains("public ProjectBuilder name(String name)"), contains( - "public ProjectBuilder tasks(Consumer> tasksBuilderConsumer)"), + "public ProjectBuilder tasks(Consumer> tasksBuilderConsumer)"), contains( "public ProjectBuilder metadata(Consumer> metadataBuilderConsumer"), contains("public ProjectBuilder status(ProjectStatus status)"), From d01db8751d429fdbe1b254f7cfb9feeb825941b8 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Tue, 21 Oct 2025 21:43:18 +0200 Subject: [PATCH 07/14] Adding additional tests for ArrayListBuilderWithElementBuilders and HashSetBuilderWithElementBuilders --- .../processor/BuilderProcessorTest.java | 156 ++++++++++++++++++ 1 file changed, 156 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 c86be7db..85a8e6d3 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 @@ -2696,4 +2696,160 @@ public class RawCollections { "rawSet(String...)", "rawMap(Map.Entry)"); } + + @Test + void shouldGenerateArrayListBuilderWithElementBuildersForAnnotatedElements() { + // Given: List of custom type where element has @SimpleBuilder + String packageName = "test"; + String className = "HasListWithBuilders"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private java.util.List tasks; + + public java.util.List getTasks() { return tasks; } + public void setTasks(java.util.List tasks) { this.tasks = tasks; } + """); + + JavaFileObject task = + ProcessorTestUtils.forSource( + """ + package test; + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + @SimpleBuilder + public class Task { + private String title; + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + } + """); + + // When + Compilation compilation = compile(dto, task); + + // Then + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // Verify WithElementBuilders consumer is generated instead of regular ArrayListBuilder + ProcessorAsserts.assertingResult( + generatedCode, + contains("public HasListWithBuildersBuilder tasks(List tasks)"), + contains("public HasListWithBuildersBuilder tasks(Supplier> tasksSupplier)"), + contains("public HasListWithBuildersBuilder tasks(Task... tasks)"), + // Key assertion: uses ArrayListBuilderWithElementBuilders with TaskBuilder + contains( + "public HasListWithBuildersBuilder tasks(Consumer> tasksBuilderConsumer)"), + contains( + "new ArrayListBuilderWithElementBuilders(this.tasks.value(), TaskBuilder::create)"), + contains("new ArrayListBuilderWithElementBuilders(TaskBuilder::create)"), + // Should NOT use regular ArrayListBuilder + notContains("Consumer>")); + } + + @Test + void shouldGenerateHashSetBuilderWithElementBuildersForAnnotatedElements() { + // Given: Set of custom type where element has @SimpleBuilder + String packageName = "test"; + String className = "HasSetWithBuilders"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private java.util.Set tags; + + public java.util.Set getTags() { return tags; } + public void setTags(java.util.Set tags) { this.tags = tags; } + """); + + JavaFileObject tag = + ProcessorTestUtils.forSource( + """ + package test; + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + @SimpleBuilder + public class Tag { + private String name; + public String getName() { return name; } + public void setName(String name) { this.name = name; } + } + """); + + // When + Compilation compilation = compile(dto, tag); + + // Then + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // Verify WithElementBuilders consumer is generated for Set + ProcessorAsserts.assertingResult( + generatedCode, + contains("public HasSetWithBuildersBuilder tags(Set tags)"), + contains("public HasSetWithBuildersBuilder tags(Supplier> tagsSupplier)"), + contains("public HasSetWithBuildersBuilder tags(Tag... tags)"), + // Key assertion: uses HashSetBuilderWithElementBuilders with TagBuilder + contains( + "public HasSetWithBuildersBuilder tags(Consumer> tagsBuilderConsumer)"), + contains( + "new HashSetBuilderWithElementBuilders(this.tags.value(), TagBuilder::create)"), + contains("new HashSetBuilderWithElementBuilders(TagBuilder::create)"), + // Should NOT use regular HashSetBuilder + notContains("Consumer>")); + } + + @Test + void shouldUseRegularBuilderWhenElementTypeHasNoSimpleBuilder() { + // Given: List of custom type WITHOUT @SimpleBuilder annotation + String packageName = "test"; + String className = "HasListWithoutBuilders"; + String builderClassName = className + "Builder"; + + JavaFileObject dto = + ProcessorTestUtils.simpleBuilderClass( + packageName, + className, + """ + private java.util.List items; + + public java.util.List getItems() { return items; } + public void setItems(java.util.List items) { this.items = items; } + """); + + JavaFileObject plain = + ProcessorTestUtils.forSource( + """ + package test; + public class PlainClass { + private String value; + public PlainClass() {} + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + } + """); + + // When + Compilation compilation = compile(dto, plain); + + // Then + String generatedCode = loadGeneratedSource(compilation, builderClassName); + assertGenerationSucceeded(compilation, builderClassName, generatedCode); + + // Verify regular ArrayListBuilder is used (not WithElementBuilders) + ProcessorAsserts.assertingResult( + generatedCode, + contains("public HasListWithoutBuildersBuilder items(List items)"), + contains( + "public HasListWithoutBuildersBuilder items(Consumer> itemsBuilderConsumer)"), + // Should NOT use WithElementBuilders version + notContains("ArrayListBuilderWithElementBuilders"), + notContains("PlainClassBuilder")); + } } From 06fd9b84c319fa1fe505faaa5071fea6a10168f2 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 20:28:37 +0200 Subject: [PATCH 08/14] Adding helper-functions for checking on genericTypes --- .../util/BuilderDefinitionCreator.java | 2 +- .../processor/util/JavaLangAnalyser.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) 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 6cb8b5ac..cf84e1d4 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 @@ -416,7 +416,7 @@ private static Optional createFieldFromSetter( // Serializable> // If there are field-specific generics, no field in builder could be generated for it, so it // needs to be ignored - if (CollectionUtils.isNotEmpty(mth.getTypeParameters())) { + if (JavaLangAnalyser.hasGenericTypes(mth)) { context.warning( mth.getEnclosingElement(), "Field '%s' has field-specific generics, so it will be ignored", diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java index 68cbf071..59657eae 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java @@ -130,6 +130,33 @@ public static boolean isNotStatic(ExecutableElement mth) { return !mth.getModifiers().contains(STATIC); } + /** + * Checks whether the given {@link TypeElement} declares generic type parameters. + * + * @param typeElement the type element to inspect + * @return {@code true} if the type declares one or more type parameters; {@code false} otherwise + */ + public static boolean hasGenericTypes(TypeElement typeElement) { + if (typeElement == null) { + return false; + } + return CollectionUtils.isNotEmpty(typeElement.getTypeParameters()); + } + + /** + * Checks whether the given {@link ExecutableElement} declares generic type parameters. + * + * @param executableElement the executable element to inspect + * @return {@code true} if the executable declares one or more type parameters; {@code false} + * otherwise + */ + public static boolean hasGenericTypes(ExecutableElement executableElement) { + if (executableElement == null) { + return false; + } + return CollectionUtils.isNotEmpty(executableElement.getTypeParameters()); + } + /** * Helper to check if the method is a setter for a field. * From 1594d022676728bf8693c9d4dbb66b6ff89122ff Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 20:38:49 +0200 Subject: [PATCH 09/14] Extracting logic of resolving builders --- .../util/BuilderDefinitionCreator.java | 71 +++++++++++-------- .../processor/util/JavaLangAnalyser.java | 2 + 2 files changed, 42 insertions(+), 31 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 cf84e1d4..85526180 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 @@ -294,7 +294,7 @@ private static void addConsumerMethodsForField( return; } - Optional builderTypeOpt = findBuilderType(fieldParameter, context); + Optional builderTypeOpt = resolveBuilderType(fieldParameter, context); if (builderTypeOpt.isPresent()) { TypeName builderType = builderTypeOpt.get(); result.addMethod( @@ -315,10 +315,7 @@ && hasEmptyConstructor(fieldTypeElement, context)) { TypeMirror fieldTypeMirror = fieldParameter.asType(); TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); - Optional elementBuilderType = - elementTypeMirror != null - ? findBuilderTypeForElementType(elementType, elementTypeMirror, context) - : Optional.empty(); + Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); if (elementBuilderType.isPresent()) { // Element type has a builder - use ArrayListBuilderWithElementBuilders @@ -358,10 +355,7 @@ && hasEmptyConstructor(fieldTypeElement, context)) { TypeMirror fieldTypeMirror = fieldParameter.asType(); TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); - Optional elementBuilderType = - elementTypeMirror != null - ? findBuilderTypeForElementType(elementType, elementTypeMirror, context) - : Optional.empty(); + Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); if (elementBuilderType.isPresent()) { // Element type has a builder - use HashSetBuilderWithElementBuilders @@ -710,26 +704,24 @@ private static boolean shouldGenerateStringBuilderConsumer(TypeName fieldType) { return isOptionalString(fieldType); } - private static Optional findBuilderType( + private static Optional resolveBuilderType( VariableElement param, ProcessingContext context) { TypeMirror typeOfParameter = param.asType(); if (typeOfParameter.getKind() == ARRAY || typeOfParameter.getKind().isPrimitive()) { return Optional.empty(); } Element elementOfParameter = context.asElement(typeOfParameter); - if (elementOfParameter == null) { + if (!(elementOfParameter instanceof TypeElement typeElement)) { // Can happen for primitives or certain compiler-internal types; nothing to build return Optional.empty(); } - String simpleClassName = elementOfParameter.getSimpleName().toString(); - String packageName = context.getPackageName(elementOfParameter); - Optional foundBuilderAnnotation = - findAnnotation(elementOfParameter, SimpleBuilder.class); - boolean isClassWithoutGenerics = StringUtils.containsNone(simpleClassName, "<"); - if (foundBuilderAnnotation.isPresent() && isClassWithoutGenerics) { - return Optional.of(new TypeName(packageName, simpleClassName + BUILDER_SUFFIX)); + // For direct field types disallow generics here in the caller + if (JavaLangAnalyser.hasGenericTypes(typeElement)) { + context.debug( + " -> Skipping builder lookup for generic type %s", typeElement.getSimpleName()); + return Optional.empty(); } - return Optional.empty(); + return resolveBuilderTypeFromTypeElement(typeElement, context); } /** @@ -740,8 +732,14 @@ private static Optional findBuilderType( * @param context processing context * @return Optional containing the builder TypeName if the element type has @SimpleBuilder */ - private static Optional findBuilderTypeForElementType( + private static Optional resolveBuilderType( TypeName elementType, TypeMirror elementTypeMirror, ProcessingContext context) { + + // Skip cases without type mirror + if (elementTypeMirror == null) { + return Optional.empty(); + } + // Skip type variables and primitives if (elementType instanceof TypeNameVariable || elementType instanceof TypeNamePrimitive) { context.debug(" -> Skipping type variable or primitive: %s", elementType); @@ -754,22 +752,33 @@ private static Optional findBuilderTypeForElementType( context.debug(" -> Element is not a TypeElement: %s", element); return Optional.empty(); } + // For collection element types: delegate to shared helper + return resolveBuilderTypeFromTypeElement(typeElement, context); + } - // Check if element type has @SimpleBuilder annotation + /** + * Shared helper resolving a builder {@link TypeName} from a {@link TypeElement} if it is + * annotated with {@code @SimpleBuilder}. Generics policy is enforced by the callers. + * + * @param typeElement the type element to inspect + * @param context processing context + */ + private static Optional resolveBuilderTypeFromTypeElement( + TypeElement typeElement, ProcessingContext context) { + // Check annotation presence first Optional foundBuilderAnnotation = findAnnotation(typeElement, SimpleBuilder.class); - - if (foundBuilderAnnotation.isPresent()) { - String packageName = context.getPackageName(typeElement); - String simpleClassName = typeElement.getSimpleName().toString(); - context.debug( - " -> Found @SimpleBuilder on element type %s.%s, will use %sBuilder", - packageName, simpleClassName, simpleClassName); - return Optional.of(new TypeName(packageName, simpleClassName + BUILDER_SUFFIX)); + if (foundBuilderAnnotation.isEmpty()) { + context.debug(" -> Type %s has no @SimpleBuilder", typeElement.getSimpleName()); + return Optional.empty(); } - context.debug(" -> Element type %s does not have @SimpleBuilder", typeElement.getSimpleName()); - return Optional.empty(); + String packageName = context.getPackageName(typeElement); + String simpleClassName = typeElement.getSimpleName().toString(); + context.debug( + " -> Found @SimpleBuilder on type %s.%s, will use %sBuilder", + packageName, simpleClassName, simpleClassName); + return Optional.of(new TypeName(packageName, simpleClassName + BUILDER_SUFFIX)); } /** diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java index 59657eae..06245c1d 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java @@ -38,6 +38,8 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; + +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.javahelpers.simple.builders.core.annotations.SimpleBuilderConstructor; From b9a059a94e1af296c934cfe1b060fa78be76c829 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 21:01:47 +0200 Subject: [PATCH 10/14] Refactoring addConsumerMethodsForField to have individual helper functions for each consumer type --- .../util/BuilderDefinitionCreator.java | 221 ++++++++++++------ .../processor/util/JavaLangAnalyser.java | 1 - 2 files changed, 148 insertions(+), 74 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 85526180..4f2d0b3d 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 @@ -41,7 +41,6 @@ import java.util.function.Supplier; import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.javahelpers.simple.builders.core.annotations.IgnoreInBuilder; @@ -294,93 +293,169 @@ private static void addConsumerMethodsForField( return; } + if (tryAddBuilderConsumer(result, fieldName, fieldType, fieldParameter, context)) { + // handled by direct builder helper + } else if (tryAddFieldConsumer(result, fieldName, fieldType, fieldTypeElement, context)) { + // handled by field helper + } else if (tryAddListConsumer(result, fieldName, fieldType, fieldParameter, context)) { + // handled by list helper + } else if (tryAddMapConsumer(result, fieldName, fieldType)) { + // handled by map helper + } else if (tryAddSetConsumer(result, fieldName, fieldType, fieldParameter, context)) { + // handled by set helper + } else if (tryAddStringBuilderConsumer(result, fieldName, fieldType)) { + // handled by string builder helper + } + } + + /** Tries to add a direct builder-based consumer when the field type itself has a builder. */ + private static boolean tryAddBuilderConsumer( + FieldDto result, + String fieldName, + TypeName fieldType, + VariableElement fieldParameter, + ProcessingContext context) { Optional builderTypeOpt = resolveBuilderType(fieldParameter, context); if (builderTypeOpt.isPresent()) { TypeName builderType = builderTypeOpt.get(); result.addMethod( BuilderDefinitionCreator.createFieldConsumerWithBuilder(fieldName, builderType)); - } else if (!isJavaClass(fieldType) + return true; + } + return false; + } + + /** Tries to add a consumer using an empty constructor of a concrete non-java class. */ + private static boolean tryAddFieldConsumer( + FieldDto result, + String fieldName, + TypeName fieldType, + TypeElement fieldTypeElement, + ProcessingContext context) { + if (!isJavaClass(fieldType) && fieldTypeElement != null && fieldTypeElement.getKind() == javax.lang.model.element.ElementKind.CLASS && !fieldTypeElement.getModifiers().contains(Modifier.ABSTRACT) && hasEmptyConstructor(fieldTypeElement, context)) { // Only generate a Consumer for concrete classes with an accessible empty constructor result.addMethod(createFieldConsumer(fieldName, fieldType)); - } else if (isList(fieldType) - && fieldType instanceof TypeNameGeneric fieldTypeGeneric - && fieldTypeGeneric.getInnerTypeArguments().size() == 1) { - TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); - - // Get the TypeMirror of the element type from the parameter's type - TypeMirror fieldTypeMirror = fieldParameter.asType(); - TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); - - Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); - - if (elementBuilderType.isPresent()) { - // Element type has a builder - use ArrayListBuilderWithElementBuilders - TypeName arrayListBuilderWithElementBuildersType = - new TypeName( - "org.javahelpers.simple.builders.core.builders", - "ArrayListBuilderWithElementBuilders"); - TypeName builderType = - new TypeNameGeneric( - arrayListBuilderWithElementBuildersType, elementType, elementBuilderType.get()); - result.addMethod( - createFieldConsumerWithElementBuilders( - fieldName, builderType, elementBuilderType.get())); - } else { - // Regular ArrayListBuilder - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, map2TypeName(ArrayListBuilder.class), elementType)); - } - } 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) { - TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); - - // Get the TypeMirror of the element type from the parameter's type - TypeMirror fieldTypeMirror = fieldParameter.asType(); - TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); - - Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); - - if (elementBuilderType.isPresent()) { - // Element type has a builder - use HashSetBuilderWithElementBuilders - TypeName hashSetBuilderWithElementBuildersType = - new TypeName( - "org.javahelpers.simple.builders.core.builders", - "HashSetBuilderWithElementBuilders"); - TypeName builderType = - new TypeNameGeneric( - hashSetBuilderWithElementBuildersType, elementType, elementBuilderType.get()); - result.addMethod( - createFieldConsumerWithElementBuilders( - fieldName, builderType, elementBuilderType.get())); - } else { - // Regular HashSetBuilder - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, map2TypeName(HashSetBuilder.class), elementType)); - } - } else if (shouldGenerateStringBuilderConsumer(fieldType)) { - // Generate StringBuilder consumer for String or Optional + return true; + } + return false; + } + + /** Tries to add StringBuilder-based consumer for String and Optional. */ + private static boolean tryAddStringBuilderConsumer( + FieldDto result, String fieldName, TypeName fieldType) { + if (shouldGenerateStringBuilderConsumer(fieldType)) { String transform = isOptionalString(fieldType) ? "Optional.of(builder.toString())" : "builder.toString()"; result.addMethod(createStringBuilderConsumer(fieldName, transform)); + return true; + } + return false; + } + + /** Tries to add List-specific consumer methods. Returns true if handled. */ + private static boolean tryAddListConsumer( + FieldDto result, + String fieldName, + TypeName fieldType, + VariableElement fieldParameter, + ProcessingContext context) { + if (!(isList(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 1)) { + return false; + } + + TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); + + // Get the TypeMirror of the element type from the parameter's type + TypeMirror fieldTypeMirror = fieldParameter.asType(); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + + Optional elementBuilderType = + resolveBuilderType(elementType, elementTypeMirror, context); + + if (elementBuilderType.isPresent()) { + // Element type has a builder - use ArrayListBuilderWithElementBuilders + TypeName arrayListBuilderWithElementBuildersType = + new TypeName( + "org.javahelpers.simple.builders.core.builders", + "ArrayListBuilderWithElementBuilders"); + TypeName builderType = + new TypeNameGeneric( + arrayListBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + result.addMethod( + createFieldConsumerWithElementBuilders(fieldName, builderType, elementBuilderType.get())); + } else { + // Regular ArrayListBuilder + result.addMethod( + createFieldConsumerWithBuilder( + fieldName, map2TypeName(ArrayListBuilder.class), elementType)); + } + return true; + } + + /** Tries to add Map-specific consumer methods. Returns true if handled. */ + private static boolean tryAddMapConsumer(FieldDto result, String fieldName, TypeName fieldType) { + if (!(isMap(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 2)) { + return false; + } + + TypeName builderTargetTypeName = + new TypeNameGeneric( + map2TypeName(HashMapBuilder.class), + fieldTypeGeneric.getInnerTypeArguments().get(0), + fieldTypeGeneric.getInnerTypeArguments().get(1)); + MethodDto mapConsumerWithBuilder = + BuilderDefinitionCreator.createFieldConsumerWithBuilder(fieldName, builderTargetTypeName); + result.addMethod(mapConsumerWithBuilder); + return true; + } + + /** Tries to add Set-specific consumer methods. Returns true if handled. */ + private static boolean tryAddSetConsumer( + FieldDto result, + String fieldName, + TypeName fieldType, + VariableElement fieldParameter, + ProcessingContext context) { + if (!(isSet(fieldType) + && fieldType instanceof TypeNameGeneric fieldTypeGeneric + && fieldTypeGeneric.getInnerTypeArguments().size() == 1)) { + return false; } + + TypeName elementType = fieldTypeGeneric.getInnerTypeArguments().get(0); + + // Get the TypeMirror of the element type from the parameter's type + TypeMirror fieldTypeMirror = fieldParameter.asType(); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + + Optional elementBuilderType = + resolveBuilderType(elementType, elementTypeMirror, context); + + if (elementBuilderType.isPresent()) { + // Element type has a builder - use HashSetBuilderWithElementBuilders + TypeName hashSetBuilderWithElementBuildersType = + new TypeName( + "org.javahelpers.simple.builders.core.builders", "HashSetBuilderWithElementBuilders"); + TypeName builderType = + new TypeNameGeneric( + hashSetBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + result.addMethod( + createFieldConsumerWithElementBuilders(fieldName, builderType, elementBuilderType.get())); + } else { + // Regular HashSetBuilder + result.addMethod( + createFieldConsumerWithBuilder( + fieldName, map2TypeName(HashSetBuilder.class), elementType)); + } + return true; } private static void addSupplierMethodsForField( diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java index 06245c1d..bc33c47a 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/JavaLangAnalyser.java @@ -38,7 +38,6 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; From 477b5d75ac36adcaf33fc96d040585325aa16ed9 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 21:09:54 +0200 Subject: [PATCH 11/14] Revieweing and improving code in new consumerSetBuilder and consumerListBuilders --- .../util/BuilderDefinitionCreator.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 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 4f2d0b3d..62a99bb9 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 @@ -46,8 +46,10 @@ import org.javahelpers.simple.builders.core.annotations.IgnoreInBuilder; import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; import org.javahelpers.simple.builders.core.builders.ArrayListBuilder; +import org.javahelpers.simple.builders.core.builders.ArrayListBuilderWithElementBuilders; import org.javahelpers.simple.builders.core.builders.HashMapBuilder; import org.javahelpers.simple.builders.core.builders.HashSetBuilder; +import org.javahelpers.simple.builders.core.builders.HashSetBuilderWithElementBuilders; import org.javahelpers.simple.builders.core.util.TrackedValue; import org.javahelpers.simple.builders.processor.dtos.*; import org.javahelpers.simple.builders.processor.exceptions.BuilderException; @@ -380,20 +382,17 @@ private static boolean tryAddListConsumer( if (elementBuilderType.isPresent()) { // Element type has a builder - use ArrayListBuilderWithElementBuilders - TypeName arrayListBuilderWithElementBuildersType = - new TypeName( - "org.javahelpers.simple.builders.core.builders", - "ArrayListBuilderWithElementBuilders"); TypeName builderType = new TypeNameGeneric( - arrayListBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + map2TypeName(ArrayListBuilderWithElementBuilders.class), + elementType, + elementBuilderType.get()); result.addMethod( createFieldConsumerWithElementBuilders(fieldName, builderType, elementBuilderType.get())); } else { // Regular ArrayListBuilder - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, map2TypeName(ArrayListBuilder.class), elementType)); + TypeName builderType = map2TypeName(ArrayListBuilder.class); + result.addMethod(createFieldConsumerWithBuilder(fieldName, builderType, elementType)); } return true; } @@ -441,19 +440,17 @@ private static boolean tryAddSetConsumer( if (elementBuilderType.isPresent()) { // Element type has a builder - use HashSetBuilderWithElementBuilders - TypeName hashSetBuilderWithElementBuildersType = - new TypeName( - "org.javahelpers.simple.builders.core.builders", "HashSetBuilderWithElementBuilders"); TypeName builderType = new TypeNameGeneric( - hashSetBuilderWithElementBuildersType, elementType, elementBuilderType.get()); + map2TypeName(HashSetBuilderWithElementBuilders.class), + elementType, + elementBuilderType.get()); result.addMethod( createFieldConsumerWithElementBuilders(fieldName, builderType, elementBuilderType.get())); } else { // Regular HashSetBuilder - result.addMethod( - createFieldConsumerWithBuilder( - fieldName, map2TypeName(HashSetBuilder.class), elementType)); + TypeName builderType = map2TypeName(HashSetBuilder.class); + result.addMethod(createFieldConsumerWithBuilder(fieldName, builderType, elementType)); } return true; } From 7825481e939629e548424f3360a7195079b25319 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 21:21:31 +0200 Subject: [PATCH 12/14] Changing readme for new functionality --- README.md | 11 ++++++----- .../simple/builders/processor/ReadmeExampleTest.java | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 567ef660..9646c7db 100644 --- a/README.md +++ b/README.md @@ -152,18 +152,19 @@ public enum ProjectStatus { Usage with collections and nested builders: ```java +String version = "1.0.0"; Project project = ProjectBuilder.create() - .name("Simple Builders with a bit of complexity") + .name("Simple Builders in version '%s' with a bit of complexity", version) .status(ProjectStatus.IN_PROGRESS) .tasks(tasks -> tasks - .add(TaskBuilder.create() + .add(taskBuilder -> taskBuilder .title("Implement core functionality") .completed(true) - .build()) - .add(TaskBuilder.create() + ) + .add(taskBuilder -> taskBuilder .title("Add documentation") .description("Update README and add Javadocs") - .build()) + ) ) .metadata(metadata -> metadata .put("version", "1.0.0") 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 fde22e60..dcc6002b 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 @@ -146,18 +146,19 @@ public enum ProjectStatus { PLANNING, IN_PROGRESS, COMPLETED, ON_HOLD } package readme; public class Usage { public static void main(){ + String version = "1.0.0"; Project project = ProjectBuilder.create() - .name("Simple Builders with a bit of complexity") + .name("Simple Builders in version %s with a bit of complexity", version) .status(ProjectStatus.IN_PROGRESS) .tasks(tasks -> tasks - .add(TaskBuilder.create() + .add(taskBuilder -> taskBuilder .title("Implement core functionality") .completed(true) - .build()) - .add(TaskBuilder.create() + ) + .add(taskBuilder -> taskBuilder .title("Add documentation") .description("Update README and add Javadocs") - .build()) + ) ) .metadata(metadata -> metadata .put("version", "1.0.0") From 5b4698735831f6e1dc216c498df78de577926c49 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 21:30:39 +0200 Subject: [PATCH 13/14] Fixing codestyle by solving sonarQube issues --- .../util/BuilderDefinitionCreator.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 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 62a99bb9..8beaa47e 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 @@ -295,18 +295,12 @@ private static void addConsumerMethodsForField( return; } - if (tryAddBuilderConsumer(result, fieldName, fieldType, fieldParameter, context)) { - // handled by direct builder helper - } else if (tryAddFieldConsumer(result, fieldName, fieldType, fieldTypeElement, context)) { - // handled by field helper - } else if (tryAddListConsumer(result, fieldName, fieldType, fieldParameter, context)) { - // handled by list helper - } else if (tryAddMapConsumer(result, fieldName, fieldType)) { - // handled by map helper - } else if (tryAddSetConsumer(result, fieldName, fieldType, fieldParameter, context)) { - // handled by set helper - } else if (tryAddStringBuilderConsumer(result, fieldName, fieldType)) { - // handled by string builder helper + if (!tryAddBuilderConsumer(result, fieldName, fieldParameter, context) + && !tryAddFieldConsumer(result, fieldName, fieldType, fieldTypeElement, context) + && !tryAddListConsumer(result, fieldName, fieldType, fieldParameter, context) + && !tryAddMapConsumer(result, fieldName, fieldType) + && !tryAddSetConsumer(result, fieldName, fieldType, fieldParameter, context)) { + tryAddStringBuilderConsumer(result, fieldName, fieldType); } } @@ -314,7 +308,6 @@ private static void addConsumerMethodsForField( private static boolean tryAddBuilderConsumer( FieldDto result, String fieldName, - TypeName fieldType, VariableElement fieldParameter, ProcessingContext context) { Optional builderTypeOpt = resolveBuilderType(fieldParameter, context); From 964454758217dd7f7a39e49b108b0bc243becd83 Mon Sep 17 00:00:00 2001 From: AndreasIgel Date: Fri, 24 Oct 2025 21:34:45 +0200 Subject: [PATCH 14/14] Further clean code topic, removing unused parameter --- .../builders/processor/util/BuilderDefinitionCreator.java | 8 +++----- 1 file changed, 3 insertions(+), 5 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 8beaa47e..f2cab96f 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 @@ -368,7 +368,7 @@ private static boolean tryAddListConsumer( // Get the TypeMirror of the element type from the parameter's type TypeMirror fieldTypeMirror = fieldParameter.asType(); - TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror); Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); @@ -426,7 +426,7 @@ private static boolean tryAddSetConsumer( // Get the TypeMirror of the element type from the parameter's type TypeMirror fieldTypeMirror = fieldParameter.asType(); - TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror, context); + TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror); Optional elementBuilderType = resolveBuilderType(elementType, elementTypeMirror, context); @@ -851,11 +851,9 @@ private static Optional resolveBuilderTypeFromTypeElement( * List<Task>, this extracts the Task TypeMirror. * * @param typeMirror the parameterized type mirror - * @param context processing context * @return the first type argument, or null if not a parameterized type */ - private static TypeMirror extractFirstTypeArgument( - TypeMirror typeMirror, ProcessingContext context) { + private static TypeMirror extractFirstTypeArgument(TypeMirror typeMirror) { if (typeMirror instanceof javax.lang.model.type.DeclaredType declaredType) { List typeArguments = declaredType.getTypeArguments(); if (!typeArguments.isEmpty()) {