From afb03e037cbb22694c9cc80de73da78250b9e3d0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 09:47:05 +0000 Subject: [PATCH 1/5] Order generated builder members: constructors, visibility, scope, field, name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a member ordering that keeps each field's methods together and emits the direct property setter last within its overload group, so consumers that resolve overloaded builder methods last-wins (e.g. MapStruct's builder detection) bind to the plain setter. The emission order is decided on the generation side: BuilderToGenerationTypeMapper sorts the (field, method) pairs by ordering value, access modifier (public first), non-static before static, originating field (alphabetically, builder-level methods last), method name, direct setter last, then the existing tie-breakers, and emits already-ordered MethodDtos. MethodDto drops its ordering field and comparator — it no longer carries generation-scope metadata. The generator-side signature safety net degrades to keep-first-occurrence with a warning (error in strict mode); real conflict resolution stays priority-based in BuilderDefinitionCreator. Regenerates example builders and golden test fixtures. --- docs/CUSTOMIZING.md | 5 + .../example/ExternalAddressBuilder.java | 198 ++--- .../example/JacksonIntegrationDtoBuilder.java | 60 +- .../example/MannschaftDtoBuilder.java | 102 +-- .../example/OrderWithDefaultsBuilder.java | 96 +-- .../builders/example/PersonDtoBuilder.java | 218 ++--- .../example/ProductRecordBuilder.java | 94 +-- .../example/ProductWithDefaultsBuilder.java | 116 +-- .../builders/example/SponsorDtoBuilder.java | 38 +- .../scoping/ScopedOwnerDtoBuilder.java | 114 +-- .../scoping/TrustedHelperDtoBuilder.java | 38 +- .../roaster/RoasterCodeGenerator.java | 29 +- .../core/BuilderToGenerationTypeMapper.java | 134 +++- .../processor/model/method/MethodDto.java | 122 --- .../processor/AnnotationCopyTest.java | 3 +- .../ComprehensiveFeatureIntegrationTest.java | 748 ++++++++++-------- .../ConfigurationProcessingTest.java | 10 +- .../processor/FormattingModeTest.java | 508 ++++++------ 18 files changed, 1336 insertions(+), 1297 deletions(-) diff --git a/docs/CUSTOMIZING.md b/docs/CUSTOMIZING.md index b52cdbe7..488a43a3 100644 --- a/docs/CUSTOMIZING.md +++ b/docs/CUSTOMIZING.md @@ -275,11 +275,14 @@ compileJava { | [`StringFormatHelperGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringFormatHelperGenerator.java) | String.format helpers | 80 | | [`SupplierMethodGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/SupplierMethodGenerator.java) | Supplier-based setters | 80 | | [`VarArgsHelperGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/VarArgsHelperGenerator.java) | Varargs helpers | 50 | +| [`StringBuilderConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/StringBuilderConsumerGenerator.java) | StringBuilder consumer methods | 45 | | [`ArrayConversionGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayConversionGenerator.java) | Array-from-List conversion methods | 35 | | [`UpdateHelperGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/UpdateHelperGenerator.java) | UnaryOperator update helpers | 31 | | [`AddToCollectionGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/AddToCollectionGenerator.java) | add2FieldName methods for List/Set | 30 | | [`ArrayBuilderConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/field/ArrayBuilderConsumerGenerator.java) | ArrayListBuilder consumer methods for arrays | 25 | +The column is the generator's *execution* priority. Emission order inside the generated class is governed separately: constructors first, then methods sorted by their `ordering` value (creation methods 200, fluent field methods 1000, conditional helpers 1100, build 1200, toString 2000, private helpers 2100), then by access modifier (public first, private last), non-static before static, then grouped by originating field, and finally by method name. Within a same-named overload group the direct property setter is emitted last, so consumers that resolve overloaded builder methods last-wins — e.g. MapStruct's builder detection — bind to the plain setter. + ### Builder-Level Generators | Generator | Purpose | Priority | @@ -287,8 +290,10 @@ compileJava { | [`ConditionalEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConditionalEnhancer.java) | Conditional logic methods | 100 | | [`JacksonAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/JacksonAnnotationEnhancer.java) | Jackson annotations | 100 | | [`CoreMethodsEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/CoreMethodsEnhancer.java) | Core builder methods (build, create, toString) | 100 | +| [`ConstructorEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ConstructorEnhancer.java) | Builder constructors (empty + from-instance) | 95 | | [`WithInterfaceEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/WithInterfaceEnhancer.java) | With interface implementation | 90 | | [`InterfaceEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/InterfaceEnhancer.java) | Builder interface implementation | 90 | +| [`FieldPrefillEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/FieldPrefillEnhancer.java) | Field-function prefilling for referenced builders | 15 | | [`GeneratedAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/GeneratedAnnotationEnhancer.java) | @Generated annotation | 10 | | [`BuilderImplementationAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/BuilderImplementationAnnotationEnhancer.java) | @BuilderImplementation annotation | 10 | | [`ClassJavaDocEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/builder/ClassJavaDocEnhancer.java) | Class-level JavaDoc | 10 | diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/ExternalAddressBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/ExternalAddressBuilder.java index f077e84e..7c8de9c9 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/ExternalAddressBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/ExternalAddressBuilder.java @@ -95,25 +95,6 @@ public static ExternalAddressBuilder create() { return new ExternalAddressBuilder(); } - /** - * Sets the value for city. - *

- * Generated from setter {@link ExternalAddress#setCity(String) setCity(String city)} - * - *

Example:

- * - *
{@code
-   * builder.city("example value");
-   * }
- * - * @param city city - * @return current instance of builder - */ - public ExternalAddressBuilder city(String city) { - this.city = changedValue(city); - return this; - } - /** * Sets the value for city by executing the provided consumer. *

@@ -176,47 +157,47 @@ public ExternalAddressBuilder city(String format, Object... args) { } /** - * Updates the current value of city in place by applying the given operator, instead of reading it out, - * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for city. *

* Generated from setter {@link ExternalAddress#setCity(String) setCity(String city)} * *

Example:

* *
{@code
-   * builder.city("example value").cityUpdate(String::trim);
+   * builder.city("example value");
    * }
* - * @param cityUpdater operator applied to the current value; its result becomes the new value + * @param city city * @return current instance of builder - * @throws IllegalStateException if city has not been set yet */ - public ExternalAddressBuilder cityUpdate(UnaryOperator cityUpdater) { - if (!this.city.isSet()) { - throw new IllegalStateException("Cannot update 'city' before it is set"); - } - this.city = changedValue(cityUpdater.apply(this.city.value())); + public ExternalAddressBuilder city(String city) { + this.city = changedValue(city); return this; } /** - * Sets the value for street. + * Updates the current value of city in place by applying the given operator, instead of reading it out, + * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). *

- * Generated from setter {@link ExternalAddress#setStreet(String) setStreet(String street)} + * Generated from setter {@link ExternalAddress#setCity(String) setCity(String city)} * *

Example:

* *
{@code
-   * builder.street("example value");
+   * builder.city("example value").cityUpdate(String::trim);
    * }
* - * @param street street + * @param cityUpdater operator applied to the current value; its result becomes the new value * @return current instance of builder + * @throws IllegalStateException if city has not been set yet */ - public ExternalAddressBuilder street(String street) { - this.street = changedValue(street); + public ExternalAddressBuilder cityUpdate(UnaryOperator cityUpdater) { + if (!this.city.isSet()) { + throw new IllegalStateException("Cannot update 'city' before it is set"); + } + this.city = changedValue(cityUpdater.apply(this.city.value())); return this; } @@ -282,92 +263,47 @@ public ExternalAddressBuilder street(String format, Object... args) { } /** - * Updates the current value of street in place by applying the given operator, instead of reading it - * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for street. *

* Generated from setter {@link ExternalAddress#setStreet(String) setStreet(String street)} * *

Example:

* *
{@code
-   * builder.street("example value").streetUpdate(String::trim);
+   * builder.street("example value");
    * }
* - * @param streetUpdater operator applied to the current value; its result becomes the new value + * @param street street * @return current instance of builder - * @throws IllegalStateException if street has not been set yet */ - public ExternalAddressBuilder streetUpdate(UnaryOperator streetUpdater) { - if (!this.street.isSet()) { - throw new IllegalStateException("Cannot update 'street' before it is set"); - } - this.street = changedValue(streetUpdater.apply(this.street.value())); - return this; - } - - /** - * Validates that the city field is not null or empty. - *

- * Generated from setter {@link ExternalAddress#setCity(String) setCity(String city)} - * - * @return this builder instance for chaining - * @throws IllegalArgumentException if city is null or empty - */ - ExternalAddressBuilder validateCity() { - if (!city.isSet() || city.value().trim().isEmpty()) { - throw new IllegalArgumentException("City cannot be null or empty"); - } + public ExternalAddressBuilder street(String street) { + this.street = changedValue(street); return this; } /** - * Validates that the street field is not null or empty. + * Updates the current value of street in place by applying the given operator, instead of reading it + * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). *

* Generated from setter {@link ExternalAddress#setStreet(String) setStreet(String street)} * - * @return this builder instance for chaining - * @throws IllegalArgumentException if street is null or empty - */ - ExternalAddressBuilder validateStreet() { - if (!street.isSet() || street.value().trim().isEmpty()) { - throw new IllegalArgumentException("Street cannot be null or empty"); - } - return this; - } - - /** - * Validates that the zipCode field is not null or empty. - *

- * Generated from setter {@link ExternalAddress#setZipCode(String) setZipCode(String zipCode)} - * - * @return this builder instance for chaining - * @throws IllegalArgumentException if zipCode is null or empty - */ - ExternalAddressBuilder validateZipCode() { - if (!zipCode.isSet() || zipCode.value().trim().isEmpty()) { - throw new IllegalArgumentException("ZipCode cannot be null or empty"); - } - return this; - } - - /** - * Sets the value for zipCode. - *

- * Generated from setter {@link ExternalAddress#setZipCode(String) setZipCode(String zipCode)} - * *

Example:

* *
{@code
-   * builder.zipCode("example value");
+   * builder.street("example value").streetUpdate(String::trim);
    * }
* - * @param zipCode zipCode + * @param streetUpdater operator applied to the current value; its result becomes the new value * @return current instance of builder + * @throws IllegalStateException if street has not been set yet */ - public ExternalAddressBuilder zipCode(String zipCode) { - this.zipCode = changedValue(zipCode); + public ExternalAddressBuilder streetUpdate(UnaryOperator streetUpdater) { + if (!this.street.isSet()) { + throw new IllegalStateException("Cannot update 'street' before it is set"); + } + this.street = changedValue(streetUpdater.apply(this.street.value())); return this; } @@ -432,6 +368,25 @@ public ExternalAddressBuilder zipCode(String format, Object... args) { return this; } + /** + * Sets the value for zipCode. + *

+ * Generated from setter {@link ExternalAddress#setZipCode(String) setZipCode(String zipCode)} + * + *

Example:

+ * + *
{@code
+   * builder.zipCode("example value");
+   * }
+ * + * @param zipCode zipCode + * @return current instance of builder + */ + public ExternalAddressBuilder zipCode(String zipCode) { + this.zipCode = changedValue(zipCode); + return this; + } + /** * Updates the current value of zipCode in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -458,6 +413,51 @@ public ExternalAddressBuilder zipCodeUpdate(UnaryOperator zipCodeUpdater return this; } + /** + * Validates that the city field is not null or empty. + *

+ * Generated from setter {@link ExternalAddress#setCity(String) setCity(String city)} + * + * @return this builder instance for chaining + * @throws IllegalArgumentException if city is null or empty + */ + ExternalAddressBuilder validateCity() { + if (!city.isSet() || city.value().trim().isEmpty()) { + throw new IllegalArgumentException("City cannot be null or empty"); + } + return this; + } + + /** + * Validates that the street field is not null or empty. + *

+ * Generated from setter {@link ExternalAddress#setStreet(String) setStreet(String street)} + * + * @return this builder instance for chaining + * @throws IllegalArgumentException if street is null or empty + */ + ExternalAddressBuilder validateStreet() { + if (!street.isSet() || street.value().trim().isEmpty()) { + throw new IllegalArgumentException("Street cannot be null or empty"); + } + return this; + } + + /** + * Validates that the zipCode field is not null or empty. + *

+ * Generated from setter {@link ExternalAddress#setZipCode(String) setZipCode(String zipCode)} + * + * @return this builder instance for chaining + * @throws IllegalArgumentException if zipCode is null or empty + */ + ExternalAddressBuilder validateZipCode() { + if (!zipCode.isSet() || zipCode.value().trim().isEmpty()) { + throw new IllegalArgumentException("ZipCode cannot be null or empty"); + } + return this; + } + /** * Conditionally applies builder modifications if the condition is true. * diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java index 7ec144f0..7e7671f0 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/JacksonIntegrationDtoBuilder.java @@ -84,7 +84,7 @@ public static JacksonIntegrationDtoBuilder create() { } /** - * Sets the value for age. + * Sets the value for age by invoking the provided supplier. *

* Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) * JacksonIntegrationDto(String name, int age)} @@ -92,19 +92,19 @@ public static JacksonIntegrationDtoBuilder create() { *

Example:

* *
{@code
-   * builder.age(42);
+   * builder.age(() -> 42);
    * }
* - * @param age age + * @param ageSupplier supplier for age * @return current instance of builder */ - public JacksonIntegrationDtoBuilder age(int age) { - this.age = changedValue(age); + public JacksonIntegrationDtoBuilder age(Supplier ageSupplier) { + this.age = changedValue(ageSupplier.get()); return this; } /** - * Sets the value for age by invoking the provided supplier. + * Sets the value for age. *

* Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) * JacksonIntegrationDto(String name, int age)} @@ -112,14 +112,14 @@ public JacksonIntegrationDtoBuilder age(int age) { *

Example:

* *
{@code
-   * builder.age(() -> 42);
+   * builder.age(42);
    * }
* - * @param ageSupplier supplier for age + * @param age age * @return current instance of builder */ - public JacksonIntegrationDtoBuilder age(Supplier ageSupplier) { - this.age = changedValue(ageSupplier.get()); + public JacksonIntegrationDtoBuilder age(int age) { + this.age = changedValue(age); return this; } @@ -150,26 +150,6 @@ public JacksonIntegrationDtoBuilder ageUpdate(UnaryOperator ageUpdater) return this; } - /** - * Sets the value for name. - *

- * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) - * JacksonIntegrationDto(String name, int age)} - * - *

Example:

- * - *
{@code
-   * builder.name("example value");
-   * }
- * - * @param name name - * @return current instance of builder - */ - public JacksonIntegrationDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

@@ -234,6 +214,26 @@ public JacksonIntegrationDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from parameter in constructor {@link JacksonIntegrationDto#JacksonIntegrationDto(String, int) + * JacksonIntegrationDto(String name, int age)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public JacksonIntegrationDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java index 3bd8b580..170884ed 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java @@ -84,45 +84,6 @@ public static MannschaftDtoBuilder create() { return new MannschaftDtoBuilder(); } - /** - * Adds a single element to sponsoren. - *

- * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} - * - * @param element the element to add - * @return current instance of builder - */ - public MannschaftDtoBuilder add2Sponsoren(SponsorDto element) { - Set newCollection; - if (this.sponsoren.isSet()) { - newCollection = new HashSet<>(this.sponsoren.value()); - } else { - newCollection = new HashSet<>(); - } - newCollection.add(element); - this.sponsoren = changedValue(newCollection); - return this; - } - - /** - * Sets the value for name. - *

- * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} - * - *

Example:

- * - *
{@code
-   * builder.name("example value");
-   * }
- * - * @param name name - * @return current instance of builder - */ - public MannschaftDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

@@ -184,6 +145,25 @@ public MannschaftDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from setter {@link MannschaftDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public MannschaftDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -211,15 +191,22 @@ public MannschaftDtoBuilder nameUpdate(UnaryOperator nameUpdater) { } /** - * Sets the value for sponsoren. + * Adds a single element to sponsoren. *

* Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * - * @param sponsoren sponsoren + * @param element the element to add * @return current instance of builder */ - public MannschaftDtoBuilder sponsoren(SponsorDto... sponsoren) { - this.sponsoren = changedValue(Set.of(sponsoren)); + public MannschaftDtoBuilder add2Sponsoren(SponsorDto element) { + Set newCollection; + if (this.sponsoren.isSet()) { + newCollection = new HashSet<>(this.sponsoren.value()); + } else { + newCollection = new HashSet<>(); + } + newCollection.add(element); + this.sponsoren = changedValue(newCollection); return this; } @@ -228,17 +215,11 @@ public MannschaftDtoBuilder sponsoren(SponsorDto... sponsoren) { *

* Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} * - *

Example:

- * - *
{@code
-   * builder.sponsoren(Set.of(SponsorDtoBuilder.create().build()));
-   * }
- * * @param sponsoren sponsoren * @return current instance of builder */ - public MannschaftDtoBuilder sponsoren(Set sponsoren) { - this.sponsoren = changedValue(sponsoren); + public MannschaftDtoBuilder sponsoren(SponsorDto... sponsoren) { + this.sponsoren = changedValue(Set.of(sponsoren)); return this; } @@ -286,6 +267,25 @@ public MannschaftDtoBuilder sponsoren(Supplier> sponsorenSupplie return this; } + /** + * Sets the value for sponsoren. + *

+ * Generated from setter {@link MannschaftDto#setSponsoren(Set) setSponsoren(Set sponsoren)} + * + *

Example:

+ * + *
{@code
+   * builder.sponsoren(Set.of(SponsorDtoBuilder.create().build()));
+   * }
+ * + * @param sponsoren sponsoren + * @return current instance of builder + */ + public MannschaftDtoBuilder sponsoren(Set sponsoren) { + this.sponsoren = changedValue(sponsoren); + return this; + } + /** * Updates the current value of sponsoren in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/OrderWithDefaultsBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/OrderWithDefaultsBuilder.java index e69d6a04..d0c1e671 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/OrderWithDefaultsBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/OrderWithDefaultsBuilder.java @@ -91,25 +91,6 @@ public static OrderWithDefaultsBuilder create() { return new OrderWithDefaultsBuilder(); } - /** - * Sets the value for id. - *

- * Generated from setter {@link OrderWithDefaults#setId(String) setId(String id)} - * - *

Example:

- * - *
{@code
-   * builder.id("example value");
-   * }
- * - * @param id id - * @return current instance of builder - */ - public OrderWithDefaultsBuilder id(String id) { - this.id = changedValue(id); - return this; - } - /** * Sets the value for id by executing the provided consumer. *

@@ -171,6 +152,25 @@ public OrderWithDefaultsBuilder id(String format, Object... args) { return this; } + /** + * Sets the value for id. + *

+ * Generated from setter {@link OrderWithDefaults#setId(String) setId(String id)} + * + *

Example:

+ * + *
{@code
+   * builder.id("example value");
+   * }
+ * + * @param id id + * @return current instance of builder + */ + public OrderWithDefaultsBuilder id(String id) { + this.id = changedValue(id); + return this; + } + /** * Updates the current value of id in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -198,40 +198,40 @@ public OrderWithDefaultsBuilder idUpdate(UnaryOperator idUpdater) { } /** - * Sets the value for priority. + * Sets the value for priority by invoking the provided supplier. *

* Generated from setter {@link OrderWithDefaults#setPriority(int) setPriority(int priority)} * *

Example:

* *
{@code
-   * builder.priority(42);
+   * builder.priority(() -> 42);
    * }
* - * @param priority priority + * @param prioritySupplier supplier for priority * @return current instance of builder */ - public OrderWithDefaultsBuilder priority(int priority) { - this.priority = changedValue(priority); + public OrderWithDefaultsBuilder priority(Supplier prioritySupplier) { + this.priority = changedValue(prioritySupplier.get()); return this; } /** - * Sets the value for priority by invoking the provided supplier. + * Sets the value for priority. *

* Generated from setter {@link OrderWithDefaults#setPriority(int) setPriority(int priority)} * *

Example:

* *
{@code
-   * builder.priority(() -> 42);
+   * builder.priority(42);
    * }
* - * @param prioritySupplier supplier for priority + * @param priority priority * @return current instance of builder */ - public OrderWithDefaultsBuilder priority(Supplier prioritySupplier) { - this.priority = changedValue(prioritySupplier.get()); + public OrderWithDefaultsBuilder priority(int priority) { + this.priority = changedValue(priority); return this; } @@ -261,25 +261,6 @@ public OrderWithDefaultsBuilder priorityUpdate(UnaryOperator priorityUp return this; } - /** - * Sets the value for status. - *

- * Generated from setter {@link OrderWithDefaults#setStatus(String) setStatus(String status)} - * - *

Example:

- * - *
{@code
-   * builder.status("example value");
-   * }
- * - * @param status status - * @return current instance of builder - */ - public OrderWithDefaultsBuilder status(String status) { - this.status = changedValue(status); - return this; - } - /** * Sets the value for status by executing the provided consumer. *

@@ -341,6 +322,25 @@ public OrderWithDefaultsBuilder status(String format, Object... args) { return this; } + /** + * Sets the value for status. + *

+ * Generated from setter {@link OrderWithDefaults#setStatus(String) setStatus(String status)} + * + *

Example:

+ * + *
{@code
+   * builder.status("example value");
+   * }
+ * + * @param status status + * @return current instance of builder + */ + public OrderWithDefaultsBuilder status(String status) { + this.status = changedValue(status); + return this; + } + /** * Updates the current value of status in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java index b05bb3a5..327027f0 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoBuilder.java @@ -112,28 +112,21 @@ public static PersonDtoBuilder create() { } /** - * Adds a single element to nickNames. + * Sets the value for birthdate by invoking the provided supplier. *

- * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * Generated from setter {@link PersonDto#setBirthdate(LocalDate) setBirthdate(LocalDate birthdate)} * *

Example:

* *
{@code
-   * builder.add2NickNames("example value");
+   * builder.birthdate(() -> LocalDate.now());
    * }
* - * @param element the element to add + * @param birthdateSupplier supplier for birthdate * @return current instance of builder */ - public PersonDtoBuilder add2NickNames(String element) { - List newCollection; - if (this.nickNames.isSet()) { - newCollection = new ArrayList<>(this.nickNames.value()); - } else { - newCollection = new ArrayList<>(); - } - newCollection.add(element); - this.nickNames = changedValue(newCollection); + public PersonDtoBuilder birthdate(Supplier birthdateSupplier) { + this.birthdate = changedValue(birthdateSupplier.get()); return this; } @@ -156,25 +149,6 @@ public PersonDtoBuilder birthdate(LocalDate birthdate) { return this; } - /** - * Sets the value for birthdate by invoking the provided supplier. - *

- * Generated from setter {@link PersonDto#setBirthdate(LocalDate) setBirthdate(LocalDate birthdate)} - * - *

Example:

- * - *
{@code
-   * builder.birthdate(() -> LocalDate.now());
-   * }
- * - * @param birthdateSupplier supplier for birthdate - * @return current instance of builder - */ - public PersonDtoBuilder birthdate(Supplier birthdateSupplier) { - this.birthdate = changedValue(birthdateSupplier.get()); - return this; - } - /** * Updates the current value of birthdate in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -201,25 +175,6 @@ public PersonDtoBuilder birthdateUpdate(UnaryOperator birthdateUpdate return this; } - /** - * Sets the value for mannschaft. - *

- * Generated from setter {@link PersonDto#setMannschaft(MannschaftDto) setMannschaft(MannschaftDto mannschaft)} - * - *

Example:

- * - *
{@code
-   * builder.mannschaft(new MannschaftDto());
-   * }
- * - * @param mannschaft mannschaft - * @return current instance of builder - */ - public PersonDtoBuilder mannschaft(MannschaftDto mannschaft) { - this.mannschaft = changedValue(mannschaft); - return this; - } - /** * Sets the value for mannschaft using a builder consumer that produces the value. *

@@ -262,6 +217,25 @@ public PersonDtoBuilder mannschaft(Supplier mannschaftSupplier) { return this; } + /** + * Sets the value for mannschaft. + *

+ * Generated from setter {@link PersonDto#setMannschaft(MannschaftDto) setMannschaft(MannschaftDto mannschaft)} + * + *

Example:

+ * + *
{@code
+   * builder.mannschaft(new MannschaftDto());
+   * }
+ * + * @param mannschaft mannschaft + * @return current instance of builder + */ + public PersonDtoBuilder mannschaft(MannschaftDto mannschaft) { + this.mannschaft = changedValue(mannschaft); + return this; + } + /** * Updates the current value of mannschaft in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -283,25 +257,6 @@ public PersonDtoBuilder mannschaftUpdate(UnaryOperator mannschaft return this; } - /** - * Sets the value for name. - *

- * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} - * - *

Example:

- * - *
{@code
-   * builder.name("example value");
-   * }
- * - * @param name name - * @return current instance of builder - */ - public PersonDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

@@ -363,6 +318,25 @@ public PersonDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from parameter in constructor {@link PersonDto#PersonDto(String) PersonDto(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public PersonDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -390,21 +364,28 @@ public PersonDtoBuilder nameUpdate(UnaryOperator nameUpdater) { } /** - * Sets the value for nickNames. + * Adds a single element to nickNames. *

* Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} * *

Example:

* *
{@code
-   * builder.nickNames("example value", "example value");
+   * builder.add2NickNames("example value");
    * }
* - * @param nickNames nickNames + * @param element the element to add * @return current instance of builder */ - public PersonDtoBuilder nickNames(String... nickNames) { - this.nickNames = changedValue(List.of(nickNames)); + public PersonDtoBuilder add2NickNames(String element) { + List newCollection; + if (this.nickNames.isSet()) { + newCollection = new ArrayList<>(this.nickNames.value()); + } else { + newCollection = new ArrayList<>(); + } + newCollection.add(element); + this.nickNames = changedValue(newCollection); return this; } @@ -416,14 +397,14 @@ public PersonDtoBuilder nickNames(String... nickNames) { *

Example:

* *
{@code
-   * builder.nickNames(List.of("example value"));
+   * builder.nickNames("example value", "example value");
    * }
* * @param nickNames nickNames * @return current instance of builder */ - public PersonDtoBuilder nickNames(List nickNames) { - this.nickNames = changedValue(nickNames); + public PersonDtoBuilder nickNames(String... nickNames) { + this.nickNames = changedValue(List.of(nickNames)); return this; } @@ -470,21 +451,47 @@ public PersonDtoBuilder nickNames(Supplier> nickNamesSupplier) { } /** - * Sets the value for nickNames2. + * Sets the value for nickNames. *

- * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} + * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} * *

Example:

* *
{@code
-   * builder.nickNames2(new String[]{"example value"});
+   * builder.nickNames(List.of("example value"));
    * }
* - * @param nickNames2 nickNames2 + * @param nickNames nickNames * @return current instance of builder */ - public PersonDtoBuilder nickNames2(String... nickNames2) { - this.nickNames2 = changedValue(nickNames2); + public PersonDtoBuilder nickNames(List nickNames) { + this.nickNames = changedValue(nickNames); + return this; + } + + /** + * Updates the current value of nickNames in place by applying the given operator, instead of reading it + * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). + *

+ * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} + * + *

Example:

+ * + *
{@code
+   * builder.nickNames(List.of("example value")).nickNamesUpdate(list -> list.stream().sorted().toList());
+   * }
+ * + * @param nickNamesUpdater operator applied to the current value; its result becomes the new value + * @return current instance of builder + * @throws IllegalStateException if nickNames has not been set yet + */ + public PersonDtoBuilder nickNamesUpdate(UnaryOperator> nickNamesUpdater) { + if (!this.nickNames.isSet()) { + throw new IllegalStateException("Cannot update 'nickNames' before it is set"); + } + this.nickNames = changedValue(nickNamesUpdater.apply(this.nickNames.value())); return this; } @@ -547,48 +554,41 @@ public PersonDtoBuilder nickNames2(Supplier nickNames2Supplier) { } /** - * Updates the current value of nickNames2 in place by applying the given operator, instead of reading it - * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for nickNames2. *

* Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} * - * @param nickNames2Updater operator applied to the current value; its result becomes the new value + *

Example:

+ * + *
{@code
+   * builder.nickNames2(new String[]{"example value"});
+   * }
+ * + * @param nickNames2 nickNames2 * @return current instance of builder - * @throws IllegalStateException if nickNames2 has not been set yet */ - public PersonDtoBuilder nickNames2Update(UnaryOperator nickNames2Updater) { - if (!this.nickNames2.isSet()) { - throw new IllegalStateException("Cannot update 'nickNames2' before it is set"); - } - this.nickNames2 = changedValue(nickNames2Updater.apply(this.nickNames2.value())); + public PersonDtoBuilder nickNames2(String... nickNames2) { + this.nickNames2 = changedValue(nickNames2); return this; } /** - * Updates the current value of nickNames in place by applying the given operator, instead of reading it + * Updates the current value of nickNames2 in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The * value must have been set before (directly or via an existing instance). *

- * Generated from setter {@link PersonDto#setNickNames(List) setNickNames(List nickNames)} - * - *

Example:

- * - *
{@code
-   * builder.nickNames(List.of("example value")).nickNamesUpdate(list -> list.stream().sorted().toList());
-   * }
+ * Generated from setter {@link PersonDto#setNickNames2(String) setNickNames2(String nickNames2)} * - * @param nickNamesUpdater operator applied to the current value; its result becomes the new value + * @param nickNames2Updater operator applied to the current value; its result becomes the new value * @return current instance of builder - * @throws IllegalStateException if nickNames has not been set yet + * @throws IllegalStateException if nickNames2 has not been set yet */ - public PersonDtoBuilder nickNamesUpdate(UnaryOperator> nickNamesUpdater) { - if (!this.nickNames.isSet()) { - throw new IllegalStateException("Cannot update 'nickNames' before it is set"); + public PersonDtoBuilder nickNames2Update(UnaryOperator nickNames2Updater) { + if (!this.nickNames2.isSet()) { + throw new IllegalStateException("Cannot update 'nickNames2' before it is set"); } - this.nickNames = changedValue(nickNamesUpdater.apply(this.nickNames.value())); + this.nickNames2 = changedValue(nickNames2Updater.apply(this.nickNames2.value())); return this; } diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java index 7214ef8a..9316d7fc 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductRecordBuilder.java @@ -90,26 +90,6 @@ public static ProductRecordBuilder create() { return new ProductRecordBuilder(); } - /** - * Sets the value for category. - *

- * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) - * ProductRecord(String name, double price, String category)} - * - *

Example:

- * - *
{@code
-   * builder.category("example value");
-   * }
- * - * @param category category - * @return current instance of builder - */ - public ProductRecordBuilder category(String category) { - this.category = changedValue(category); - return this; - } - /** * Sets the value for category by executing the provided consumer. *

@@ -175,10 +155,7 @@ public ProductRecordBuilder category(String format, Object... args) { } /** - * Updates the current value of category in place by applying the given operator, instead of reading it - * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for category. *

* Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) * ProductRecord(String name, double price, String category)} @@ -186,23 +163,22 @@ public ProductRecordBuilder category(String format, Object... args) { *

Example:

* *
{@code
-   * builder.category("example value").categoryUpdate(String::trim);
+   * builder.category("example value");
    * }
* - * @param categoryUpdater operator applied to the current value; its result becomes the new value + * @param category category * @return current instance of builder - * @throws IllegalStateException if category has not been set yet */ - public ProductRecordBuilder categoryUpdate(UnaryOperator categoryUpdater) { - if (!this.category.isSet()) { - throw new IllegalStateException("Cannot update 'category' before it is set"); - } - this.category = changedValue(categoryUpdater.apply(this.category.value())); + public ProductRecordBuilder category(String category) { + this.category = changedValue(category); return this; } /** - * Sets the value for name. + * Updates the current value of category in place by applying the given operator, instead of reading it + * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) * ProductRecord(String name, double price, String category)} @@ -210,14 +186,18 @@ public ProductRecordBuilder categoryUpdate(UnaryOperator categoryUpdater *

Example:

* *
{@code
-   * builder.name("example value");
+   * builder.category("example value").categoryUpdate(String::trim);
    * }
* - * @param name name + * @param categoryUpdater operator applied to the current value; its result becomes the new value * @return current instance of builder + * @throws IllegalStateException if category has not been set yet */ - public ProductRecordBuilder name(String name) { - this.name = changedValue(name); + public ProductRecordBuilder categoryUpdate(UnaryOperator categoryUpdater) { + if (!this.category.isSet()) { + throw new IllegalStateException("Cannot update 'category' before it is set"); + } + this.category = changedValue(categoryUpdater.apply(this.category.value())); return this; } @@ -285,6 +265,26 @@ public ProductRecordBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) + * ProductRecord(String name, double price, String category)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public ProductRecordBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -313,7 +313,7 @@ public ProductRecordBuilder nameUpdate(UnaryOperator nameUpdater) { } /** - * Sets the value for price. + * Sets the value for price by invoking the provided supplier. *

* Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) * ProductRecord(String name, double price, String category)} @@ -321,19 +321,19 @@ public ProductRecordBuilder nameUpdate(UnaryOperator nameUpdater) { *

Example:

* *
{@code
-   * builder.price(3.14);
+   * builder.price(() -> 3.14);
    * }
* - * @param price price + * @param priceSupplier supplier for price * @return current instance of builder */ - public ProductRecordBuilder price(double price) { - this.price = changedValue(price); + public ProductRecordBuilder price(Supplier priceSupplier) { + this.price = changedValue(priceSupplier.get()); return this; } /** - * Sets the value for price by invoking the provided supplier. + * Sets the value for price. *

* Generated from parameter in constructor {@link ProductRecord#ProductRecord(String, double, String) * ProductRecord(String name, double price, String category)} @@ -341,14 +341,14 @@ public ProductRecordBuilder price(double price) { *

Example:

* *
{@code
-   * builder.price(() -> 3.14);
+   * builder.price(3.14);
    * }
* - * @param priceSupplier supplier for price + * @param price price * @return current instance of builder */ - public ProductRecordBuilder price(Supplier priceSupplier) { - this.price = changedValue(priceSupplier.get()); + public ProductRecordBuilder price(double price) { + this.price = changedValue(price); return this; } diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductWithDefaultsBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductWithDefaultsBuilder.java index 661e2a33..84c9f536 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductWithDefaultsBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/ProductWithDefaultsBuilder.java @@ -100,7 +100,7 @@ public static ProductWithDefaultsBuilder create() { } /** - * Sets the value for active. + * Sets the value for active by invoking the provided supplier. *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -109,19 +109,19 @@ public static ProductWithDefaultsBuilder create() { *

Example:

* *
{@code
-   * builder.active(true);
+   * builder.active(() -> true);
    * }
* - * @param active active + * @param activeSupplier supplier for active * @return current instance of builder */ - public ProductWithDefaultsBuilder active(boolean active) { - this.active = changedValue(active); + public ProductWithDefaultsBuilder active(Supplier activeSupplier) { + this.active = changedValue(activeSupplier.get()); return this; } /** - * Sets the value for active by invoking the provided supplier. + * Sets the value for active. *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -130,14 +130,14 @@ public ProductWithDefaultsBuilder active(boolean active) { *

Example:

* *
{@code
-   * builder.active(() -> true);
+   * builder.active(true);
    * }
* - * @param activeSupplier supplier for active + * @param active active * @return current instance of builder */ - public ProductWithDefaultsBuilder active(Supplier activeSupplier) { - this.active = changedValue(activeSupplier.get()); + public ProductWithDefaultsBuilder active(boolean active) { + this.active = changedValue(active); return this; } @@ -169,27 +169,6 @@ public ProductWithDefaultsBuilder activeUpdate(UnaryOperator activeUpda return this; } - /** - * Sets the value for category. - *

- * Generated from parameter in constructor - * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, - * double price, String category, boolean active)} - * - *

Example:

- * - *
{@code
-   * builder.category("example value");
-   * }
- * - * @param category category - * @return current instance of builder - */ - public ProductWithDefaultsBuilder category(String category) { - this.category = changedValue(category); - return this; - } - /** * Sets the value for category by executing the provided consumer. *

@@ -258,10 +237,7 @@ public ProductWithDefaultsBuilder category(String format, Object... args) { } /** - * Updates the current value of category in place by applying the given operator, instead of reading it - * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for category. *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -270,23 +246,22 @@ public ProductWithDefaultsBuilder category(String format, Object... args) { *

Example:

* *
{@code
-   * builder.category("example value").categoryUpdate(String::trim);
+   * builder.category("example value");
    * }
* - * @param categoryUpdater operator applied to the current value; its result becomes the new value + * @param category category * @return current instance of builder - * @throws IllegalStateException if category has not been set yet */ - public ProductWithDefaultsBuilder categoryUpdate(UnaryOperator categoryUpdater) { - if (!this.category.isSet()) { - throw new IllegalStateException("Cannot update 'category' before it is set"); - } - this.category = changedValue(categoryUpdater.apply(this.category.value())); + public ProductWithDefaultsBuilder category(String category) { + this.category = changedValue(category); return this; } /** - * Sets the value for name. + * Updates the current value of category in place by applying the given operator, instead of reading it + * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -295,14 +270,18 @@ public ProductWithDefaultsBuilder categoryUpdate(UnaryOperator categoryU *

Example:

* *
{@code
-   * builder.name("example value");
+   * builder.category("example value").categoryUpdate(String::trim);
    * }
* - * @param name name + * @param categoryUpdater operator applied to the current value; its result becomes the new value * @return current instance of builder + * @throws IllegalStateException if category has not been set yet */ - public ProductWithDefaultsBuilder name(String name) { - this.name = changedValue(name); + public ProductWithDefaultsBuilder categoryUpdate(UnaryOperator categoryUpdater) { + if (!this.category.isSet()) { + throw new IllegalStateException("Cannot update 'category' before it is set"); + } + this.category = changedValue(categoryUpdater.apply(this.category.value())); return this; } @@ -373,6 +352,27 @@ public ProductWithDefaultsBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from parameter in constructor + * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, + * double price, String category, boolean active)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public ProductWithDefaultsBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -402,7 +402,7 @@ public ProductWithDefaultsBuilder nameUpdate(UnaryOperator nameUpdater) } /** - * Sets the value for price. + * Sets the value for price by invoking the provided supplier. *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -411,19 +411,19 @@ public ProductWithDefaultsBuilder nameUpdate(UnaryOperator nameUpdater) *

Example:

* *
{@code
-   * builder.price(3.14);
+   * builder.price(() -> 3.14);
    * }
* - * @param price price + * @param priceSupplier supplier for price * @return current instance of builder */ - public ProductWithDefaultsBuilder price(double price) { - this.price = changedValue(price); + public ProductWithDefaultsBuilder price(Supplier priceSupplier) { + this.price = changedValue(priceSupplier.get()); return this; } /** - * Sets the value for price by invoking the provided supplier. + * Sets the value for price. *

* Generated from parameter in constructor * {@link ProductWithDefaults#ProductWithDefaults(String, double, String, boolean) ProductWithDefaults(String name, @@ -432,14 +432,14 @@ public ProductWithDefaultsBuilder price(double price) { *

Example:

* *
{@code
-   * builder.price(() -> 3.14);
+   * builder.price(3.14);
    * }
* - * @param priceSupplier supplier for price + * @param price price * @return current instance of builder */ - public ProductWithDefaultsBuilder price(Supplier priceSupplier) { - this.price = changedValue(priceSupplier.get()); + public ProductWithDefaultsBuilder price(double price) { + this.price = changedValue(price); return this; } diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java index 5b8c9506..74ae41ad 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java @@ -72,25 +72,6 @@ public static SponsorDtoBuilder create() { return new SponsorDtoBuilder(); } - /** - * Sets the value for name. - *

- * Generated from setter {@link SponsorDto#setName(String) setName(String name)} - * - *

Example:

- * - *
{@code
-   * builder.name("example value");
-   * }
- * - * @param name name - * @return current instance of builder - */ - public SponsorDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

@@ -152,6 +133,25 @@ public SponsorDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from setter {@link SponsorDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public SponsorDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java index 9c660869..9abe8e2e 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java @@ -87,25 +87,6 @@ public static ScopedOwnerDtoBuilder create() { return new ScopedOwnerDtoBuilder(); } - /** - * Sets the value for library. - *

- * Generated from setter {@link ScopedOwnerDto#setLibrary(LibraryHelperDto) setLibrary(LibraryHelperDto library)} - * - *

Example:

- * - *
{@code
-   * builder.library(new LibraryHelperDto());
-   * }
- * - * @param library library - * @return current instance of builder - */ - public ScopedOwnerDtoBuilder library(LibraryHelperDto library) { - this.library = changedValue(library); - return this; - } - /** * Sets the value for library by executing the provided consumer. *

@@ -140,6 +121,25 @@ public ScopedOwnerDtoBuilder library(Supplier librarySupplier) return this; } + /** + * Sets the value for library. + *

+ * Generated from setter {@link ScopedOwnerDto#setLibrary(LibraryHelperDto) setLibrary(LibraryHelperDto library)} + * + *

Example:

+ * + *
{@code
+   * builder.library(new LibraryHelperDto());
+   * }
+ * + * @param library library + * @return current instance of builder + */ + public ScopedOwnerDtoBuilder library(LibraryHelperDto library) { + this.library = changedValue(library); + return this; + } + /** * Updates the current value of library in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -160,25 +160,6 @@ public ScopedOwnerDtoBuilder libraryUpdate(UnaryOperator libra return this; } - /** - * Sets the value for sponsor. - *

- * Generated from setter {@link ScopedOwnerDto#setSponsor(SponsorDto) setSponsor(SponsorDto sponsor)} - * - *

Example:

- * - *
{@code
-   * builder.sponsor(new SponsorDto());
-   * }
- * - * @param sponsor sponsor - * @return current instance of builder - */ - public ScopedOwnerDtoBuilder sponsor(SponsorDto sponsor) { - this.sponsor = changedValue(sponsor); - return this; - } - /** * Sets the value for sponsor by executing the provided consumer. *

@@ -213,6 +194,25 @@ public ScopedOwnerDtoBuilder sponsor(Supplier sponsorSupplier) { return this; } + /** + * Sets the value for sponsor. + *

+ * Generated from setter {@link ScopedOwnerDto#setSponsor(SponsorDto) setSponsor(SponsorDto sponsor)} + * + *

Example:

+ * + *
{@code
+   * builder.sponsor(new SponsorDto());
+   * }
+ * + * @param sponsor sponsor + * @return current instance of builder + */ + public ScopedOwnerDtoBuilder sponsor(SponsorDto sponsor) { + this.sponsor = changedValue(sponsor); + return this; + } + /** * Updates the current value of sponsor in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -233,25 +233,6 @@ public ScopedOwnerDtoBuilder sponsorUpdate(UnaryOperator sponsorUpda return this; } - /** - * Sets the value for trusted. - *

- * Generated from setter {@link ScopedOwnerDto#setTrusted(TrustedHelperDto) setTrusted(TrustedHelperDto trusted)} - * - *

Example:

- * - *
{@code
-   * builder.trusted(new TrustedHelperDto());
-   * }
- * - * @param trusted trusted - * @return current instance of builder - */ - public ScopedOwnerDtoBuilder trusted(TrustedHelperDto trusted) { - this.trusted = changedValue(trusted); - return this; - } - /** * Sets the value for trusted using a builder consumer that produces the value. *

@@ -294,6 +275,25 @@ public ScopedOwnerDtoBuilder trusted(Supplier trustedSupplier) return this; } + /** + * Sets the value for trusted. + *

+ * Generated from setter {@link ScopedOwnerDto#setTrusted(TrustedHelperDto) setTrusted(TrustedHelperDto trusted)} + * + *

Example:

+ * + *
{@code
+   * builder.trusted(new TrustedHelperDto());
+   * }
+ * + * @param trusted trusted + * @return current instance of builder + */ + public ScopedOwnerDtoBuilder trusted(TrustedHelperDto trusted) { + this.trusted = changedValue(trusted); + return this; + } + /** * Updates the current value of trusted in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/TrustedHelperDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/TrustedHelperDtoBuilder.java index c7b643b8..94fdf114 100644 --- a/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/TrustedHelperDtoBuilder.java +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/TrustedHelperDtoBuilder.java @@ -74,25 +74,6 @@ public static TrustedHelperDtoBuilder create() { return new TrustedHelperDtoBuilder(); } - /** - * Sets the value for name. - *

- * Generated from setter {@link TrustedHelperDto#setName(String) setName(String name)} - * - *

Example:

- * - *
{@code
-   * builder.name("example value");
-   * }
- * - * @param name name - * @return current instance of builder - */ - public TrustedHelperDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

@@ -154,6 +135,25 @@ public TrustedHelperDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from setter {@link TrustedHelperDto#setName(String) setName(String name)} + * + *

Example:

+ * + *
{@code
+   * builder.name("example value");
+   * }
+ * + * @param name name + * @return current instance of builder + */ + public TrustedHelperDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java index 56d994d7..498a5550 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java @@ -289,32 +289,19 @@ private void appendMethods(JavaClassSource source, GenerationTargetClassDto clas * org.javahelpers.simple.builders.processor.processing.BuilderDefinitionCreator#resolveMethodConflicts}. * *

Since conflicts should already be resolved by the builder-specific step, this safety net - * simply keeps the first occurrence for any remaining duplicate signatures and logs a generic - * warning. + * simply keeps the first occurrence for any remaining duplicate signatures and reports it as a + * warning (an error in strict mode). */ private List resolveMethodConflicts(List methods) { - MethodDto.MethodComparator comparator = new MethodDto.MethodComparator(); - - // Sort methods by comparator for consistent ordering - List sortedMethods = methods.stream().sorted(comparator).toList(); - Map signatureToMethod = new java.util.LinkedHashMap<>(); - - for (MethodDto method : sortedMethods) { - String signature = method.getSignatureKey(); - - MethodDto existing = signatureToMethod.get(signature); - if (existing == null) { - signatureToMethod.put(signature, method); - } else { - context.warning( - " Unexpected duplicate method signature: '%s' — keeping first occurrence (safety net)", - signature); + for (MethodDto method : methods) { + if (signatureToMethod.putIfAbsent(method.getSignatureKey(), method) != null) { + context.reportBasedOnStrictMode( + "Unexpected duplicate method signature: '%s' — keeping first occurrence (safety net)", + method.getSignatureKey()); } } - - // Sort the final result for reproducible output - return signatureToMethod.values().stream().sorted(comparator).toList(); + return List.copyOf(signatureToMethod.values()); } private void appendMethod( diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java index a56be04f..7005740f 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -24,6 +24,9 @@ package org.javahelpers.simple.builders.processor.model.core; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; @@ -50,6 +53,16 @@ */ public class BuilderToGenerationTypeMapper { + /** + * Emission order for generated methods, applied while mapping to the rendering DTOs: ordering + * value (create/fluent/conditional/build/toString/helper sections), access modifier (public + * first, private last), non-static before static, originating field (alphabetically, + * builder-level methods last), method name, direct field setter last within a same-named overload + * group, parameter count, non-generic first, full signature. + */ + private static final Comparator METHOD_EMISSION_ORDER = + BuilderToGenerationTypeMapper::compareFieldMethods; + private final BuilderConfiguration configuration; /** @@ -99,22 +112,25 @@ public GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto builderDto) // Copy interfaces builderDto.getInterfaces().forEach(renderingDto::addInterface); - // Map and copy methods from fields + // Map and copy methods in emission order (see METHOD_EMISSION_ORDER): field-originated + // methods stay grouped per field, builder-level methods come after grouped ones. + List fieldMethods = new ArrayList<>(); for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { for (BuilderMethodDto method : field.getMethods()) { - renderingDto.addMethod(toMethodDto(method)); + fieldMethods.add(new FieldMethod(field, method)); } } for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { for (BuilderMethodDto method : field.getMethods()) { - renderingDto.addMethod(toMethodDto(method)); + fieldMethods.add(new FieldMethod(field, method)); } } - - // Map and copy builder-level methods from enhancers for (BuilderMethodDto classMethod : builderDto.getMethods()) { - renderingDto.addMethod(toMethodDto(classMethod)); + fieldMethods.add(new FieldMethod(null, classMethod)); } + fieldMethods.stream() + .sorted(METHOD_EMISSION_ORDER) + .forEach(pair -> renderingDto.addMethod(toMethodDto(pair.method()))); builderDto .getNestedTypes() @@ -137,7 +153,6 @@ private MethodDto toMethodDto(BuilderMethodDto classMethod) { MethodDto method = new MethodDto(classMethod.getMethodName(), classMethod.getReturnType()); method.setModifier(classMethod.getModifier().orElse(null)); method.setStatic(classMethod.isStatic()); - method.setOrdering(classMethod.getOrdering()); if (configuration.shouldGenerateJavaDoc()) { method.setJavadoc(buildMethodJavadoc(classMethod)); @@ -166,6 +181,111 @@ private MethodDto toMethodDto(BuilderMethodDto classMethod) { return method; } + /** + * Pair of a builder method and the field it originates from; {@code field} is {@code null} for + * builder-level methods created by enhancers. + */ + private record FieldMethod(FieldDto field, BuilderMethodDto method) {} + + private static int compareFieldMethods(FieldMethod p1, FieldMethod p2) { + BuilderMethodDto m1 = p1.method(); + BuilderMethodDto m2 = p2.method(); + + int orderingCompare = Integer.compare(m1.getOrdering(), m2.getOrdering()); + if (orderingCompare != 0) { + return orderingCompare; + } + int modifierCompare = Integer.compare(accessModifierRank(m1), accessModifierRank(m2)); + if (modifierCompare != 0) { + return modifierCompare; + } + int staticCompare = Boolean.compare(m1.isStatic(), m2.isStatic()); + if (staticCompare != 0) { + return staticCompare; + } + int groupCompare = compareGroupKeys(groupKeyOf(p1), groupKeyOf(p2)); + if (groupCompare != 0) { + return groupCompare; + } + int nameCompare = m1.getMethodName().compareTo(m2.getMethodName()); + if (nameCompare != 0) { + return nameCompare; + } + // Within a same-named overload group the direct setter is emitted last, so consumers that + // resolve overloaded builder methods last-wins (e.g. MapStruct) bind to it. + int directSetterCompare = Boolean.compare(isDirectFieldSetter(p1), isDirectFieldSetter(p2)); + if (directSetterCompare != 0) { + return directSetterCompare; + } + int paramCountCompare = Integer.compare(m1.getParameters().size(), m2.getParameters().size()); + if (paramCountCompare != 0) { + return paramCountCompare; + } + boolean m1Generic = hasGenericParameters(m1); + boolean m2Generic = hasGenericParameters(m2); + if (m1Generic != m2Generic) { + return m1Generic ? 1 : -1; + } + return m1.getSignatureKey().compareTo(m2.getSignatureKey()); + } + + /** Group key clustering field-originated methods; {@code null} for builder-level methods. */ + private static String groupKeyOf(FieldMethod pair) { + return pair.field() == null ? null : pair.field().getFieldNameInBuilder(); + } + + /** Visibility rank: public first, private last; absent modifier means package-private. */ + private static int accessModifierRank(BuilderMethodDto method) { + return method + .getModifier() + .map( + modifier -> + switch (modifier) { + case DEFAULT, PUBLIC -> 0; + case PROTECTED -> 1; + case PACKAGE_PRIVATE -> 2; + case PRIVATE -> 3; + }) + .orElse(2); + } + + /** Grouped methods first (alphabetical by key), ungrouped methods last. */ + private static int compareGroupKeys(String groupKey1, String groupKey2) { + if (groupKey1 == null && groupKey2 == null) { + return 0; + } + if (groupKey1 == null) { + return 1; + } + if (groupKey2 == null) { + return -1; + } + return groupKey1.compareTo(groupKey2); + } + + private static boolean isDirectFieldSetter(FieldMethod pair) { + return pair.field() != null && isDirectFieldSetter(pair.method(), pair.field()); + } + + private static boolean hasGenericParameters(BuilderMethodDto method) { + return method.getParameters().stream() + .anyMatch(param -> param.getParameterType().getFullQualifiedName().contains("<")); + } + + /** + * Whether {@code method} is the direct setter of {@code field}: exactly one parameter of the + * field's declared type. + */ + private static boolean isDirectFieldSetter(BuilderMethodDto method, FieldDto field) { + return method.getParameters().size() == 1 + && method + .getParameters() + .get(0) + .getParameterType() + .getFullQualifiedName() + .equals(field.getFieldType().getFullQualifiedName()); + } + /** * Returns the given class field, clearing its Javadoc when Javadoc generation is disabled. * diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java index e9d3a845..ff563cd3 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/method/MethodDto.java @@ -50,9 +50,6 @@ public class MethodDto { /** Whether the method is static. */ private boolean isStatic = false; - /** Ordering for method generation. Lower values appear first in generated class. */ - private int ordering = 1000; - /** Name of method. */ private String methodName; @@ -90,33 +87,6 @@ public MethodDto(String methodName, TypeName returnType) { this.returnType = returnType; } - /** - * Sets the ordering for this method. - * - *

Lower values appear first in the generated class. Methods with the same ordering and name - * are sorted using the following enhanced rules: - * - *

    - *
  1. Methods with fewer parameters come first - *
  2. Non-generic methods come before generic methods - *
  3. Full method signature (name(paramType1,paramType2,...)) used for final ordering - *
- * - * @param ordering the ordering value (lower values appear first) - */ - public void setOrdering(int ordering) { - this.ordering = ordering; - } - - /** - * Returns the ordering of this method. - * - * @return the ordering value - */ - public int getOrdering() { - return ordering; - } - /** * Setting the inner implementation of a method. Supports placeholders which has to be set by * addArgument. @@ -329,98 +299,6 @@ public void addAnnotation(AnnotationDto annotation) { this.annotations.add(annotation); } - /** - * Comparator for sorting MethodDto instances with sophisticated ordering rules. - * - *

Sorting order for methods with same ordering and name: - * - *

    - *
  1. Methods with fewer parameters come first - *
  2. Non-generic methods come before generic methods - *
  3. Full method signature (name(paramType1,paramType2,...)) used for final ordering - *
- */ - public static class MethodComparator implements java.util.Comparator { - - @Override - public int compare(MethodDto m1, MethodDto m2) { - // Primary sort: ordering value - int orderingCompare = Integer.compare(m1.getOrdering(), m2.getOrdering()); - if (orderingCompare != 0) { - return orderingCompare; - } - - // Secondary sort: method name - int nameCompare = m1.getMethodName().compareTo(m2.getMethodName()); - if (nameCompare != 0) { - return nameCompare; - } - - // Tertiary sort: parameter count (fewer parameters first) - int paramCountCompare = Integer.compare(m1.getParameters().size(), m2.getParameters().size()); - if (paramCountCompare != 0) { - return paramCountCompare; - } - - // Quaternary sort: generic vs non-generic (non-generic first) - boolean m1Generic = hasGenericParameters(m1); - boolean m2Generic = hasGenericParameters(m2); - if (m1Generic != m2Generic) { - return m1Generic ? 1 : -1; // non-generic comes first - } - - // Final sort: full method signature - String signature1 = createMethodSignature(m1); - String signature2 = createMethodSignature(m2); - return signature1.compareTo(signature2); - } - - /** - * Creates a qualified name string for a TypeName. - * - * @param typeName the type name - * @return qualified name using the type's own formatting logic - */ - private String getQualifiedName(TypeName typeName) { - return typeName.getFullQualifiedName(); - } - - /** - * Creates a method signature string for sorting purposes. - * - *

The signature includes method name and parameter types in the format: - * methodName(paramType1,paramType2,...) - * - * @param method the method to create signature for - * @return signature string for comparison - */ - private String createMethodSignature(MethodDto method) { - StringBuilder signature = new StringBuilder(method.getMethodName()); - signature.append("("); - - java.util.List paramTypes = - method.getParameters().stream() - .map(param -> getQualifiedName(param.getParameterType())) - .toList(); - - signature.append(String.join(",", paramTypes)); - signature.append(")"); - - return signature.toString(); - } - - /** - * Checks if a method has generic parameters. - * - * @param method the method to check - * @return true if any parameter is generic (contains type parameters) - */ - private boolean hasGenericParameters(MethodDto method) { - return method.getParameters().stream() - .anyMatch(param -> getQualifiedName(param.getParameterType()).contains("<")); - } - } - /** * Returns a string representation of this method in Java method signature format. * diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java index 6a761b4d..b16b0c38 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java @@ -597,8 +597,7 @@ public MyDto build() { public String toString() { return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("name", this.name).toString(); } - } - """); + }"""); } @Test diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java index ffcdd35e..3132b7d4 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ComprehensiveFeatureIntegrationTest.java @@ -25,8 +25,11 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils.loadGeneratedSource; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.google.testing.compile.Compilation; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.tools.JavaFileObject; import org.javahelpers.simple.builders.processor.testing.ProcessorAsserts; import org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils; @@ -65,79 +68,7 @@ private static Compilation compile(JavaFileObject... sources) { @Test void allFeatures_generatedCorrectly() { - // Create nested DTO for testing builder consumers - JavaFileObject addressDto = - ProcessorTestUtils.forSource( - """ - package test; - @org.javahelpers.simple.builders.core.annotations.SimpleBuilder - public class AddressDto { - private final String street; - private final String city; - public AddressDto(String street, String city) { - this.street = street; - this.city = city; - } - public String getStreet() { return street; } - public String getCity() { return city; } - } - """); - - // Create comprehensive DTO with all features - JavaFileObject personDto = - ProcessorTestUtils.forSource( - """ - package test; - import java.util.LinkedList; - import java.util.List; - import java.util.Set; - import java.util.Map; - import java.util.Optional; - import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; - import org.javahelpers.simple.builders.core.annotations.Default; - - @SimpleBuilder - public class PersonDto { - private final String name; - private final int age; - private final Optional email; - private final List nicknames; - private final Set tags; - private final Map metadata; - private final AddressDto address; - private final List previousAddresses; - private final LinkedList phoneNumbers; - - public PersonDto(String name, int age, - @Default("Optional.empty()") Optional email, - List nicknames, Set tags, - Map metadata, AddressDto address, - List previousAddresses, - LinkedList phoneNumbers) { - this.name = name; - this.age = age; - this.email = email; - this.nicknames = nicknames; - this.tags = tags; - this.metadata = metadata; - this.address = address; - this.previousAddresses = previousAddresses; - this.phoneNumbers = phoneNumbers; - } - - public String getName() { return name; } - public int getAge() { return age; } - public Optional getEmail() { return email; } - public List getNicknames() { return nicknames; } - public Set getTags() { return tags; } - public Map getMetadata() { return metadata; } - public AddressDto getAddress() { return address; } - public List getPreviousAddresses() { return previousAddresses; } - public LinkedList getPhoneNumbers() { return phoneNumbers; } - } - """); - - Compilation compilation = compile(addressDto, personDto); + Compilation compilation = compile(addressDto(), personDto()); assertThat(compilation).succeeded(); String generatedCode = loadGeneratedSource(compilation, "PersonDtoBuilder"); @@ -311,112 +242,50 @@ public static PersonDtoBuilder create() { } /** - * Adds a single element to nicknames. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - *

Example:

- * - *
{@code
-           * builder.add2Nicknames("example value");
-           * }
- * - * @param element the element to add - * @return current instance of builder - */ - public PersonDtoBuilder add2Nicknames(String element) { - List newCollection; - if (this.nicknames.isSet()) { - newCollection = new ArrayList<>(this.nicknames.value()); - } else { - newCollection = new ArrayList<>(); - } - newCollection.add(element); - this.nicknames = changedValue(newCollection); - return this; - } - - /** - * Adds a single element to phoneNumbers. + * Sets the value for address using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.add2PhoneNumbers("example value");
+           * builder.address(addressDtoBuilder -> addressDtoBuilder);
            * }
* - * @param element the element to add - * @return current instance of builder - */ - public PersonDtoBuilder add2PhoneNumbers(String element) { - LinkedList newCollection; - if (this.phoneNumbers.isSet()) { - newCollection = new LinkedList<>(this.phoneNumbers.value()); - } else { - newCollection = new LinkedList<>(); - } - newCollection.add(element); - this.phoneNumbers = changedValue(newCollection); - return this; - } - - /** - * Adds a single element to previousAddresses. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - * @param element the element to add + * @param addressBuilderConsumer consumer providing an instance of a builder for address * @return current instance of builder */ - public PersonDtoBuilder add2PreviousAddresses(AddressDto element) { - List newCollection; - if (this.previousAddresses.isSet()) { - newCollection = new ArrayList<>(this.previousAddresses.value()); - } else { - newCollection = new ArrayList<>(); - } - newCollection.add(element); - this.previousAddresses = changedValue(newCollection); + public PersonDtoBuilder address(Consumer addressBuilderConsumer) { + AddressDtoBuilder builder = this.address.isSet() + ? new AddressDtoBuilder(this.address.value()) + : AddressDtoBuilder.create(); + addressBuilderConsumer.accept(builder); + this.address = changedValue(builder.build()); return this; } /** - * Adds a single element to tags. + * Sets the value for address by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.add2Tags("example value");
+           * builder.address(() -> AddressDtoBuilder.create().build());
            * }
* - * @param element the element to add + * @param addressSupplier supplier for address * @return current instance of builder */ - public PersonDtoBuilder add2Tags(String element) { - Set newCollection; - if (this.tags.isSet()) { - newCollection = new HashSet<>(this.tags.value()); - } else { - newCollection = new HashSet<>(); - } - newCollection.add(element); - this.tags = changedValue(newCollection); + public PersonDtoBuilder address(Supplier addressSupplier) { + this.address = changedValue(addressSupplier.get()); return this; } @@ -424,7 +293,7 @@ public PersonDtoBuilder add2Tags(String element) { * Sets the value for address. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -442,54 +311,6 @@ public PersonDtoBuilder address(AddressDto address) { return this; } - /** - * Sets the value for address using a builder consumer that produces the value. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - *

Example:

- * - *
{@code
-           * builder.address(addressDtoBuilder -> addressDtoBuilder);
-           * }
- * - * @param addressBuilderConsumer consumer providing an instance of a builder for address - * @return current instance of builder - */ - public PersonDtoBuilder address(Consumer addressBuilderConsumer) { - AddressDtoBuilder builder = this.address.isSet() - ? new AddressDtoBuilder(this.address.value()) - : AddressDtoBuilder.create(); - addressBuilderConsumer.accept(builder); - this.address = changedValue(builder.build()); - return this; - } - - /** - * Sets the value for address by invoking the provided supplier. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - *

Example:

- * - *
{@code
-           * builder.address(() -> AddressDtoBuilder.create().build());
-           * }
- * - * @param addressSupplier supplier for address - * @return current instance of builder - */ - public PersonDtoBuilder address(Supplier addressSupplier) { - this.address = changedValue(addressSupplier.get()); - return this; - } - /** * Updates the current value of address in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -498,7 +319,7 @@ public PersonDtoBuilder address(Supplier addressSupplier) { * DTO, prefer the builder-consumer helper {@link #address(Consumer)}. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -515,46 +336,46 @@ public PersonDtoBuilder addressUpdate(UnaryOperator addressUpdater) } /** - * Sets the value for age. + * Sets the value for age by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.age(42);
+           * builder.age(() -> 42);
            * }
* - * @param age age + * @param ageSupplier supplier for age * @return current instance of builder */ - public PersonDtoBuilder age(int age) { - this.age = changedValue(age); + public PersonDtoBuilder age(Supplier ageSupplier) { + this.age = changedValue(ageSupplier.get()); return this; } /** - * Sets the value for age by invoking the provided supplier. + * Sets the value for age. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.age(() -> 42);
+           * builder.age(42);
            * }
* - * @param ageSupplier supplier for age + * @param age age * @return current instance of builder */ - public PersonDtoBuilder age(Supplier ageSupplier) { - this.age = changedValue(ageSupplier.get()); + public PersonDtoBuilder age(int age) { + this.age = changedValue(age); return this; } @@ -565,7 +386,7 @@ public PersonDtoBuilder age(Supplier ageSupplier) { * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -591,7 +412,7 @@ public PersonDtoBuilder ageUpdate(UnaryOperator ageUpdater) { * Sets the value for email. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -609,33 +430,11 @@ public PersonDtoBuilder email(String email) { return this; } - /** - * Sets the value for email. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - *

Example:

- * - *
{@code
-           * builder.email(Optional.of("example value"));
-           * }
- * - * @param email email - * @return current instance of builder - */ - public PersonDtoBuilder email(Optional email) { - this.email = changedValue(email); - return this; - } - /** * Sets the value for email by executing the provided consumer. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -659,7 +458,7 @@ public PersonDtoBuilder email(Consumer emailStringBuilderConsumer * Sets the value for email by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -682,7 +481,7 @@ public PersonDtoBuilder email(Supplier> emailSupplier) { * {@link String#format(String, Object...)} for details. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -701,6 +500,28 @@ public PersonDtoBuilder email(String format, Object... args) { return this; } + /** + * Sets the value for email. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.email(Optional.of("example value"));
+           * }
+ * + * @param email email + * @return current instance of builder + */ + public PersonDtoBuilder email(Optional email) { + this.email = changedValue(email); + return this; + } + /** * Updates the current value of email in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -708,7 +529,7 @@ public PersonDtoBuilder email(String format, Object... args) { * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -728,7 +549,7 @@ public PersonDtoBuilder emailUpdate(UnaryOperator> emailUpdater * Sets the value for metadata. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -746,33 +567,11 @@ public PersonDtoBuilder metadata(Entry... metadata) { return this; } - /** - * Sets the value for metadata. - *

- * Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String - * name, int age, Optional email, List nicknames, Set tags, Map metadata, - * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} - * - *

Example:

- * - *
{@code
-           * builder.metadata(Map.of("example value", "example value"));
-           * }
- * - * @param metadata metadata - * @return current instance of builder - */ - public PersonDtoBuilder metadata(Map metadata) { - this.metadata = changedValue(metadata); - return this; - } - /** * Sets the value for metadata using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -792,7 +591,7 @@ public PersonDtoBuilder metadata(Consumer> metada * Sets the value for metadata by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -811,53 +610,53 @@ public PersonDtoBuilder metadata(Supplier> metadataSupplier) } /** - * Updates the current value of metadata in place by applying the given operator, instead of reading it - * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). + * Sets the value for metadata. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.metadata(Map.of("example value", "example value")).metadataUpdate(Map::copyOf);
+           * builder.metadata(Map.of("example value", "example value"));
            * }
* - * @param metadataUpdater operator applied to the current value; its result becomes the new value + * @param metadata metadata * @return current instance of builder - * @throws IllegalStateException if metadata has not been set yet */ - public PersonDtoBuilder metadataUpdate(UnaryOperator> metadataUpdater) { - if (!this.metadata.isSet()) { - throw new IllegalStateException("Cannot update 'metadata' before it is set"); - } - this.metadata = changedValue(metadataUpdater.apply(this.metadata.value())); + public PersonDtoBuilder metadata(Map metadata) { + this.metadata = changedValue(metadata); return this; } /** - * Sets the value for name. + * Updates the current value of metadata in place by applying the given operator, instead of reading it + * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.name("example value");
+           * builder.metadata(Map.of("example value", "example value")).metadataUpdate(Map::copyOf);
            * }
* - * @param name name + * @param metadataUpdater operator applied to the current value; its result becomes the new value * @return current instance of builder + * @throws IllegalStateException if metadata has not been set yet */ - public PersonDtoBuilder name(String name) { - this.name = changedValue(name); + public PersonDtoBuilder metadataUpdate(UnaryOperator> metadataUpdater) { + if (!this.metadata.isSet()) { + throw new IllegalStateException("Cannot update 'metadata' before it is set"); + } + this.metadata = changedValue(metadataUpdater.apply(this.metadata.value())); return this; } @@ -865,7 +664,7 @@ public PersonDtoBuilder name(String name) { * Sets the value for name by executing the provided consumer. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -889,7 +688,7 @@ public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) * Sets the value for name by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -912,7 +711,7 @@ public PersonDtoBuilder name(Supplier nameSupplier) { * {@link String#format(String, Object...)} for details. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -931,6 +730,28 @@ public PersonDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.name("example value");
+           * }
+ * + * @param name name + * @return current instance of builder + */ + public PersonDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -938,7 +759,7 @@ public PersonDtoBuilder name(String format, Object... args) { * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -961,24 +782,31 @@ public PersonDtoBuilder nameUpdate(UnaryOperator nameUpdater) { } /** - * Sets the value for nicknames. + * Adds a single element to nicknames. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.nicknames("example value", "example value");
+           * builder.add2Nicknames("example value");
            * }
* - * @param nicknames nicknames + * @param element the element to add * @return current instance of builder */ - public PersonDtoBuilder nicknames(String... nicknames) { - this.nicknames = changedValue(List.of(nicknames)); + public PersonDtoBuilder add2Nicknames(String element) { + List newCollection; + if (this.nicknames.isSet()) { + newCollection = new ArrayList<>(this.nicknames.value()); + } else { + newCollection = new ArrayList<>(); + } + newCollection.add(element); + this.nicknames = changedValue(newCollection); return this; } @@ -986,21 +814,21 @@ public PersonDtoBuilder nicknames(String... nicknames) { * Sets the value for nicknames. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.nicknames(List.of("example value"));
+           * builder.nicknames("example value", "example value");
            * }
* * @param nicknames nicknames * @return current instance of builder */ - public PersonDtoBuilder nicknames(List nicknames) { - this.nicknames = changedValue(nicknames); + public PersonDtoBuilder nicknames(String... nicknames) { + this.nicknames = changedValue(List.of(nicknames)); return this; } @@ -1008,7 +836,7 @@ public PersonDtoBuilder nicknames(List nicknames) { * Sets the value for nicknames using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1034,7 +862,7 @@ public PersonDtoBuilder nicknames(Consumer> nicknamesBu * Sets the value for nicknames by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1052,6 +880,28 @@ public PersonDtoBuilder nicknames(Supplier> nicknamesSupplier) { return this; } + /** + * Sets the value for nicknames. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.nicknames(List.of("example value"));
+           * }
+ * + * @param nicknames nicknames + * @return current instance of builder + */ + public PersonDtoBuilder nicknames(List nicknames) { + this.nicknames = changedValue(nicknames); + return this; + } + /** * Updates the current value of nicknames in place by applying the given operator, instead of reading it * out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -1059,7 +909,7 @@ public PersonDtoBuilder nicknames(Supplier> nicknamesSupplier) { * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1082,24 +932,31 @@ public PersonDtoBuilder nicknamesUpdate(UnaryOperator> nicknamesUpd } /** - * Sets the value for phoneNumbers. + * Adds a single element to phoneNumbers. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.phoneNumbers("example value", "example value");
+           * builder.add2PhoneNumbers("example value");
            * }
* - * @param phoneNumbers phoneNumbers + * @param element the element to add * @return current instance of builder */ - public PersonDtoBuilder phoneNumbers(String... phoneNumbers) { - this.phoneNumbers = changedValue(new LinkedList<>(java.util.List.of(phoneNumbers))); + public PersonDtoBuilder add2PhoneNumbers(String element) { + LinkedList newCollection; + if (this.phoneNumbers.isSet()) { + newCollection = new LinkedList<>(this.phoneNumbers.value()); + } else { + newCollection = new LinkedList<>(); + } + newCollection.add(element); + this.phoneNumbers = changedValue(newCollection); return this; } @@ -1107,21 +964,21 @@ public PersonDtoBuilder phoneNumbers(String... phoneNumbers) { * Sets the value for phoneNumbers. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.phoneNumbers(List.of("example value"));
+           * builder.phoneNumbers("example value", "example value");
            * }
* * @param phoneNumbers phoneNumbers * @return current instance of builder */ - public PersonDtoBuilder phoneNumbers(LinkedList phoneNumbers) { - this.phoneNumbers = changedValue(phoneNumbers); + public PersonDtoBuilder phoneNumbers(String... phoneNumbers) { + this.phoneNumbers = changedValue(new LinkedList<>(java.util.List.of(phoneNumbers))); return this; } @@ -1129,7 +986,7 @@ public PersonDtoBuilder phoneNumbers(LinkedList phoneNumbers) { * Sets the value for phoneNumbers using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1155,7 +1012,7 @@ public PersonDtoBuilder phoneNumbers(Consumer> phoneNum * Sets the value for phoneNumbers by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1173,6 +1030,28 @@ public PersonDtoBuilder phoneNumbers(Supplier> phoneNumbersSu return this; } + /** + * Sets the value for phoneNumbers. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.phoneNumbers(List.of("example value"));
+           * }
+ * + * @param phoneNumbers phoneNumbers + * @return current instance of builder + */ + public PersonDtoBuilder phoneNumbers(LinkedList phoneNumbers) { + this.phoneNumbers = changedValue(phoneNumbers); + return this; + } + /** * Updates the current value of phoneNumbers in place by applying the given operator, instead of reading * it out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -1180,7 +1059,7 @@ public PersonDtoBuilder phoneNumbers(Supplier> phoneNumbersSu * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1188,7 +1067,7 @@ public PersonDtoBuilder phoneNumbers(Supplier> phoneNumbersSu * *

{@code
            * builder.phoneNumbers(List.of("example value"))
-           *        .phoneNumbersUpdate(list -> new LinkedList<>(list.stream().sorted().toList()));
+           *     .phoneNumbersUpdate(list -> new LinkedList<>(list.stream().sorted().toList()));
            * }
* * @param phoneNumbersUpdater operator applied to the current value; its result becomes the new value @@ -1204,18 +1083,25 @@ public PersonDtoBuilder phoneNumbersUpdate(UnaryOperator> pho } /** - * Sets the value for previousAddresses. + * Adds a single element to previousAddresses. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * - * @param previousAddresses previousAddresses + * @param element the element to add * @return current instance of builder */ - public PersonDtoBuilder previousAddresses(AddressDto... previousAddresses) { - this.previousAddresses = changedValue(List.of(previousAddresses)); + public PersonDtoBuilder add2PreviousAddresses(AddressDto element) { + List newCollection; + if (this.previousAddresses.isSet()) { + newCollection = new ArrayList<>(this.previousAddresses.value()); + } else { + newCollection = new ArrayList<>(); + } + newCollection.add(element); + this.previousAddresses = changedValue(newCollection); return this; } @@ -1223,21 +1109,15 @@ public PersonDtoBuilder previousAddresses(AddressDto... previousAddresses) { * Sets the value for previousAddresses. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * - *

Example:

- * - *
{@code
-           * builder.previousAddresses(List.of(AddressDtoBuilder.create().build()));
-           * }
- * * @param previousAddresses previousAddresses * @return current instance of builder */ - public PersonDtoBuilder previousAddresses(List previousAddresses) { - this.previousAddresses = changedValue(previousAddresses); + public PersonDtoBuilder previousAddresses(AddressDto... previousAddresses) { + this.previousAddresses = changedValue(List.of(previousAddresses)); return this; } @@ -1245,7 +1125,7 @@ public PersonDtoBuilder previousAddresses(List previousAddresses) { * Sets the value for previousAddresses using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1273,7 +1153,7 @@ public PersonDtoBuilder previousAddresses( * Sets the value for previousAddresses by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1291,6 +1171,28 @@ public PersonDtoBuilder previousAddresses(Supplier> previousAdd return this; } + /** + * Sets the value for previousAddresses. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.previousAddresses(List.of(AddressDtoBuilder.create().build()));
+           * }
+ * + * @param previousAddresses previousAddresses + * @return current instance of builder + */ + public PersonDtoBuilder previousAddresses(List previousAddresses) { + this.previousAddresses = changedValue(previousAddresses); + return this; + } + /** * Updates the current value of previousAddresses in place by applying the given operator, instead of * reading it out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. @@ -1299,7 +1201,7 @@ public PersonDtoBuilder previousAddresses(Supplier> previousAdd * a nested DTO, prefer the builder-consumer helper {@link #previousAddresses(Consumer)}. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1307,7 +1209,7 @@ public PersonDtoBuilder previousAddresses(Supplier> previousAdd * *

{@code
            * builder.previousAddresses(List.of(AddressDtoBuilder.create().build()))
-           *        .previousAddressesUpdate(list -> list.stream().sorted().toList());
+           *     .previousAddressesUpdate(list -> list.stream().sorted().toList());
            * }
* * @param previousAddressesUpdater operator applied to the current value; its result becomes the new value @@ -1323,24 +1225,31 @@ public PersonDtoBuilder previousAddressesUpdate(UnaryOperator> } /** - * Sets the value for tags. + * Adds a single element to tags. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.tags("example value", "example value");
+           * builder.add2Tags("example value");
            * }
* - * @param tags tags + * @param element the element to add * @return current instance of builder */ - public PersonDtoBuilder tags(String... tags) { - this.tags = changedValue(Set.of(tags)); + public PersonDtoBuilder add2Tags(String element) { + Set newCollection; + if (this.tags.isSet()) { + newCollection = new HashSet<>(this.tags.value()); + } else { + newCollection = new HashSet<>(); + } + newCollection.add(element); + this.tags = changedValue(newCollection); return this; } @@ -1348,21 +1257,21 @@ public PersonDtoBuilder tags(String... tags) { * Sets the value for tags. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * *

Example:

* *
{@code
-           * builder.tags(Set.of("example value"));
+           * builder.tags("example value", "example value");
            * }
* * @param tags tags * @return current instance of builder */ - public PersonDtoBuilder tags(Set tags) { - this.tags = changedValue(tags); + public PersonDtoBuilder tags(String... tags) { + this.tags = changedValue(Set.of(tags)); return this; } @@ -1370,7 +1279,7 @@ public PersonDtoBuilder tags(Set tags) { * Sets the value for tags using a builder consumer that produces the value. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1396,7 +1305,7 @@ public PersonDtoBuilder tags(Consumer> tagsBuilderConsume * Sets the value for tags by invoking the provided supplier. *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1414,6 +1323,28 @@ public PersonDtoBuilder tags(Supplier> tagsSupplier) { return this; } + /** + * Sets the value for tags. + *

+ * Generated from parameter in constructor + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String + * name, int age, Optional email, List nicknames, Set tags, Map metadata, + * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} + * + *

Example:

+ * + *
{@code
+           * builder.tags(Set.of("example value"));
+           * }
+ * + * @param tags tags + * @return current instance of builder + */ + public PersonDtoBuilder tags(Set tags) { + this.tags = changedValue(tags); + return this; + } + /** * Updates the current value of tags in place by applying the given operator, instead of reading it out, * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, @@ -1421,7 +1352,7 @@ public PersonDtoBuilder tags(Supplier> tagsSupplier) { * value must have been set before (directly or via an existing instance). *

* Generated from parameter in constructor - * {@link PersonDto#PersonDto(String,int,Optional,List,Set,Map,AddressDto,List,LinkedList) PersonDto(String + * {@link PersonDto#PersonDto(String, int, Optional, List, Set, Map, AddressDto, List, LinkedList) PersonDto(String * name, int age, Optional email, List nicknames, Set tags, Map metadata, * AddressDto address, List previousAddresses, LinkedList phoneNumbers)} * @@ -1559,8 +1490,7 @@ default PersonDtoBuilder with() { } } } - } - """; + }"""; ProcessorAsserts.assertNormalizedEquals( expectedCode, @@ -1569,4 +1499,124 @@ default PersonDtoBuilder with() { + "are correctly generated. If this fails after adding a new feature, the expectedCode " + "template MUST be updated to include the new feature!"); } + + /** + * Verifies that the direct setter is the last overload of its property name for every field. + * Consumers that resolve overloaded builder methods last-wins (e.g. MapStruct's builder + * detection) rely on this order to bind the plain setter. + */ + @Test + void directSetters_emittedAfterHelperOverloads() { + Compilation compilation = compile(addressDto(), personDto()); + assertThat(compilation).succeeded(); + + String generatedCode = loadGeneratedSource(compilation, "PersonDtoBuilder"); + + assertDirectSetterEmittedLast(generatedCode, "name", "String "); + assertDirectSetterEmittedLast(generatedCode, "age", "int "); + assertDirectSetterEmittedLast(generatedCode, "email", "Optional "); + assertDirectSetterEmittedLast(generatedCode, "nicknames", "List "); + assertDirectSetterEmittedLast(generatedCode, "tags", "Set "); + assertDirectSetterEmittedLast(generatedCode, "metadata", "Map "); + assertDirectSetterEmittedLast(generatedCode, "address", "AddressDto "); + assertDirectSetterEmittedLast(generatedCode, "previousAddresses", "List "); + assertDirectSetterEmittedLast(generatedCode, "phoneNumbers", "LinkedList "); + } + + private static void assertDirectSetterEmittedLast( + String generatedCode, String property, String expectedParameterPrefix) { + Pattern overloadPattern = + Pattern.compile("public PersonDtoBuilder " + property + "\\(([^)]*)\\)"); + Matcher matcher = overloadPattern.matcher(generatedCode); + int overloadCount = 0; + String lastParameters = ""; + while (matcher.find()) { + overloadCount++; + lastParameters = matcher.group(1); + } + assertTrue( + overloadCount > 1, + "expected multiple '" + property + "' overloads, found " + overloadCount); + assertTrue( + lastParameters.startsWith(expectedParameterPrefix), + "expected last '" + + property + + "' overload to be the direct setter (" + + expectedParameterPrefix + + "...), but was (" + + lastParameters + + ")"); + } + + private static JavaFileObject addressDto() { + return ProcessorTestUtils.forSource( + """ + package test; + @org.javahelpers.simple.builders.core.annotations.SimpleBuilder + public class AddressDto { + private final String street; + private final String city; + public AddressDto(String street, String city) { + this.street = street; + this.city = city; + } + public String getStreet() { return street; } + public String getCity() { return city; } + } + """); + } + + private static JavaFileObject personDto() { + return ProcessorTestUtils.forSource( + """ + package test; + import java.util.LinkedList; + import java.util.List; + import java.util.Set; + import java.util.Map; + import java.util.Optional; + import org.javahelpers.simple.builders.core.annotations.SimpleBuilder; + import org.javahelpers.simple.builders.core.annotations.Default; + + @SimpleBuilder + public class PersonDto { + private final String name; + private final int age; + private final Optional email; + private final List nicknames; + private final Set tags; + private final Map metadata; + private final AddressDto address; + private final List previousAddresses; + private final LinkedList phoneNumbers; + + public PersonDto(String name, int age, + @Default("Optional.empty()") Optional email, + List nicknames, Set tags, + Map metadata, AddressDto address, + List previousAddresses, + LinkedList phoneNumbers) { + this.name = name; + this.age = age; + this.email = email; + this.nicknames = nicknames; + this.tags = tags; + this.metadata = metadata; + this.address = address; + this.previousAddresses = previousAddresses; + this.phoneNumbers = phoneNumbers; + } + + public String getName() { return name; } + public int getAge() { return age; } + public Optional getEmail() { return email; } + public List getNicknames() { return nicknames; } + public Set getTags() { return tags; } + public Map getMetadata() { return metadata; } + public AddressDto getAddress() { return address; } + public List getPreviousAddresses() { return previousAddresses; } + public LinkedList getPhoneNumbers() { return phoneNumbers; } + } + """); + } } diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java index 27c60a4b..24801cb5 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/ConfigurationProcessingTest.java @@ -468,11 +468,6 @@ public static PersonDtoBuilder create() { return new PersonDtoBuilder(); } - public PersonDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) { StringBuilder builder = new StringBuilder(); nameStringBuilderConsumer.accept(builder); @@ -490,6 +485,11 @@ public PersonDtoBuilder name(String format, Object... args) { return this; } + public PersonDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + public PersonDtoBuilder nameUpdate(UnaryOperator nameUpdater) { if (!this.name.isSet()) { throw new IllegalStateException("Cannot update 'name' before it is set"); diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java index 3efb56a4..20122c87 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java @@ -173,32 +173,32 @@ public static FormatTestDtoBuilder create() { } /** - * Sets the value for count. + * Sets the value for count by invoking the provided supplier. *

Generated from parameter in constructor {@link FormatTestDto#FormatTestDto(String, int) FormatTestDto(String name, int count)} * *

Example:

{@code
-           * builder.count(42);
+           * builder.count(() -> 42);
            * }
- * @param count count + * @param countSupplier supplier for count * @return current instance of builder */ - public FormatTestDtoBuilder count(int count) { - this.count = changedValue(count); + public FormatTestDtoBuilder count(Supplier countSupplier) { + this.count = changedValue(countSupplier.get()); return this; } /** - * Sets the value for count by invoking the provided supplier. + * Sets the value for count. *

Generated from parameter in constructor {@link FormatTestDto#FormatTestDto(String, int) FormatTestDto(String name, int count)} * *

Example:

{@code
-           * builder.count(() -> 42);
+           * builder.count(42);
            * }
- * @param countSupplier supplier for count + * @param count count * @return current instance of builder */ - public FormatTestDtoBuilder count(Supplier countSupplier) { - this.count = changedValue(countSupplier.get()); + public FormatTestDtoBuilder count(int count) { + this.count = changedValue(count); return this; } @@ -223,21 +223,6 @@ public FormatTestDtoBuilder countUpdate(UnaryOperator countUpdater) { return this; } - /** - * Sets the value for name. - *

Generated from parameter in constructor {@link FormatTestDto#FormatTestDto(String, int) FormatTestDto(String name, int count)} - * - *

Example:

{@code
-           * builder.name("example value");
-           * }
- * @param name name - * @return current instance of builder - */ - public FormatTestDtoBuilder name(String name) { - this.name = changedValue(name); - return this; - } - /** * Sets the value for name by executing the provided consumer. *

Generated from parameter in constructor {@link FormatTestDto#FormatTestDto(String, int) FormatTestDto(String name, int count)} @@ -287,6 +272,21 @@ public FormatTestDtoBuilder name(String format, Object... args) { return this; } + /** + * Sets the value for name. + *

Generated from parameter in constructor {@link FormatTestDto#FormatTestDto(String, int) FormatTestDto(String name, int count)} + * + *

Example:

{@code
+           * builder.name("example value");
+           * }
+ * @param name name + * @return current instance of builder + */ + public FormatTestDtoBuilder name(String name) { + this.name = changedValue(name); + return this; + } + /** * Updates the current value of name in place by applying the given operator, instead of reading it out, changing it and setting it again. * Useful for adjustments relative to the current value, e.g. trimming, upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. @@ -494,249 +494,249 @@ public class DefaultFormatDtoBuilder implements IBuilderBase { // Section 2: class body — the closing brace at 0 indentation anchors the common prefix String section2 = String.join("\n", java.util.Arrays.copyOfRange(lines, 38, lines.length)); String expectedSection2 = - """ - - /** - * Tracked value for value: value. - */ - private TrackedValue value = unsetValue(); +""" - /** - * Empty constructor of builder for {@code test.DefaultFormatDto}. - */ - public DefaultFormatDtoBuilder() { - } + /** + * Tracked value for value: value. + */ + private TrackedValue value = unsetValue(); - /** - * Initialisation of builder for {@code test.DefaultFormatDto} by a instance. - * - * @param instance object instance for initialisiation - */ - public DefaultFormatDtoBuilder(DefaultFormatDto instance) { - this.value = initialValue(instance.getValue()); - } + /** + * Empty constructor of builder for {@code test.DefaultFormatDto}. + */ + public DefaultFormatDtoBuilder() { + } - /** - * Creating a new builder for {@code test.DefaultFormatDto}. - * - *

Example:

- * - *
{@code
-           * DefaultFormatDtoBuilder builder = DefaultFormatDtoBuilder.create();
-           * }
- * - * @return builder for {@code test.DefaultFormatDto} - */ - public static DefaultFormatDtoBuilder create() { - return new DefaultFormatDtoBuilder(); - } + /** + * Initialisation of builder for {@code test.DefaultFormatDto} by a instance. + * + * @param instance object instance for initialisiation + */ + public DefaultFormatDtoBuilder(DefaultFormatDto instance) { + this.value = initialValue(instance.getValue()); + } - /** - * Sets the value for value. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-           * builder.value("example value");
-           * }
- * - * @param value value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(String value) { - this.value = changedValue(value); - return this; - } + /** + * Creating a new builder for {@code test.DefaultFormatDto}. + * + *

Example:

+ * + *
{@code
+   * DefaultFormatDtoBuilder builder = DefaultFormatDtoBuilder.create();
+   * }
+ * + * @return builder for {@code test.DefaultFormatDto} + */ + public static DefaultFormatDtoBuilder create() { + return new DefaultFormatDtoBuilder(); + } - /** - * Sets the value for value by executing the provided consumer. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-           * builder.value(sb -> sb.append("text"));
-           * }
- * - * @param valueStringBuilderConsumer consumer providing an instance of value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(Consumer valueStringBuilderConsumer) { - StringBuilder builder = new StringBuilder(); - valueStringBuilderConsumer.accept(builder); - this.value = changedValue(builder.toString()); - return this; - } + /** + * Sets the value for value by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+   * builder.value(sb -> sb.append("text"));
+   * }
+ * + * @param valueStringBuilderConsumer consumer providing an instance of value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(Consumer valueStringBuilderConsumer) { + StringBuilder builder = new StringBuilder(); + valueStringBuilderConsumer.accept(builder); + this.value = changedValue(builder.toString()); + return this; + } - /** - * Sets the value for value by invoking the provided supplier. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-           * builder.value(() -> "example value");
-           * }
- * - * @param valueSupplier supplier for value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(Supplier valueSupplier) { - this.value = changedValue(valueSupplier.get()); - return this; - } + /** + * Sets the value for value by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+   * builder.value(() -> "example value");
+   * }
+ * + * @param valueSupplier supplier for value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(Supplier valueSupplier) { + this.value = changedValue(valueSupplier.get()); + return this; + } - /** - * Sets the String value for value by using String.format(format, args). See - * {@link String#format(String, Object...)} for details. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-           * builder.value("Hello %s", "World");
-           * }
- * - * @param format A format string - * @param args Arguments referenced by the format specifiers in the format string. - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(String format, Object... args) { - this.value = changedValue(String.format(format, args)); - return this; - } + /** + * Sets the String value for value by using String.format(format, args). See + * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+   * builder.value("Hello %s", "World");
+   * }
+ * + * @param format A format string + * @param args Arguments referenced by the format specifiers in the format string. + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(String format, Object... args) { + this.value = changedValue(String.format(format, args)); + return this; + } - /** - * Updates the current value of value in place by applying the given operator, instead of reading it out, - * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-           * builder.value("example value").valueUpdate(String::trim);
-           * }
- * - * @param valueUpdater operator applied to the current value; its result becomes the new value - * @return current instance of builder - * @throws IllegalStateException if value has not been set yet - */ - public DefaultFormatDtoBuilder valueUpdate(UnaryOperator valueUpdater) { - if (!this.value.isSet()) { - throw new IllegalStateException("Cannot update 'value' before it is set"); - } - this.value = changedValue(valueUpdater.apply(this.value.value())); - return this; - } + /** + * Sets the value for value. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+   * builder.value("example value");
+   * }
+ * + * @param value value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(String value) { + this.value = changedValue(value); + return this; + } - /** - * Conditionally applies builder modifications if the condition is true. - * - * @param condition the condition to evaluate - * @param yesCondition the consumer to apply if condition is true - * @return this builder instance - */ - public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, - Consumer yesCondition) { - return conditional(condition, yesCondition, null); - } + /** + * Updates the current value of value in place by applying the given operator, instead of reading it out, + * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+   * builder.value("example value").valueUpdate(String::trim);
+   * }
+ * + * @param valueUpdater operator applied to the current value; its result becomes the new value + * @return current instance of builder + * @throws IllegalStateException if value has not been set yet + */ + public DefaultFormatDtoBuilder valueUpdate(UnaryOperator valueUpdater) { + if (!this.value.isSet()) { + throw new IllegalStateException("Cannot update 'value' before it is set"); + } + this.value = changedValue(valueUpdater.apply(this.value.value())); + return this; + } - /** - * Conditionally applies builder modifications based on a condition evaluation. - * - * @param condition the condition to evaluate - * @param trueCase the consumer to apply if condition is true - * @param falseCase the consumer to apply if condition is false (can be null) - * @return this builder instance - */ - public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, Consumer trueCase, - Consumer falseCase) { - if (condition.getAsBoolean()) { - trueCase.accept(this); - } else if (falseCase != null) { - falseCase.accept(this); - } - return this; - } + /** + * Conditionally applies builder modifications if the condition is true. + * + * @param condition the condition to evaluate + * @param yesCondition the consumer to apply if condition is true + * @return this builder instance + */ + public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, + Consumer yesCondition) { + return conditional(condition, yesCondition, null); + } - /** - * Builds the configured DTO instance. - * - *

Example:

- * - *
{@code
-           * DefaultFormatDto result = builder.build();
-           * }
- */ - @Override - public DefaultFormatDto build() { - DefaultFormatDto result = new DefaultFormatDto(this.value.value()); - return result; - } + /** + * Conditionally applies builder modifications based on a condition evaluation. + * + * @param condition the condition to evaluate + * @param trueCase the consumer to apply if condition is true + * @param falseCase the consumer to apply if condition is false (can be null) + * @return this builder instance + */ + public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, Consumer trueCase, + Consumer falseCase) { + if (condition.getAsBoolean()) { + trueCase.accept(this); + } else if (falseCase != null) { + falseCase.accept(this); + } + return this; + } - /** - * Returns a string representation of this builder, including only fields that have been set. - * - * @return string representation of the builder - */ - @Override - public String toString() { - return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("value", this.value).toString(); - } + /** + * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+   * DefaultFormatDto result = builder.build();
+   * }
+ */ + @Override + public DefaultFormatDto build() { + DefaultFormatDto result = new DefaultFormatDto(this.value.value()); + return result; + } - /** - * Interface that can be implemented by the DTO to provide fluent modification methods. - */ - public interface With { - /** - * Initializes a builder from an instance of this class, using methods of this builder to change values and returns - * the new built object. - * - * @param b the consumer to apply modifications - * @return the modified instance - */ - default DefaultFormatDto with(Consumer b) { - DefaultFormatDtoBuilder builder; - try { - builder = new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); - } catch (ClassCastException ex) { - throw new IllegalArgumentException( - "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", - ex); - } - b.accept(builder); - return builder.build(); - } + /** + * Returns a string representation of this builder, including only fields that have been set. + * + * @return string representation of the builder + */ + @Override + public String toString() { + return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("value", this.value).toString(); + } - /** - * Creates a builder initialized from this instance. - * - * @return a builder initialized with this instance's values - */ - default DefaultFormatDtoBuilder with() { - try { - return new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); - } catch (ClassCastException ex) { - throw new IllegalArgumentException( - "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", - ex); - } - } - } - }"""; + /** + * Interface that can be implemented by the DTO to provide fluent modification methods. + */ + public interface With { + /** + * Initializes a builder from an instance of this class, using methods of this builder to change values and returns + * the new built object. + * + * @param b the consumer to apply modifications + * @return the modified instance + */ + default DefaultFormatDto with(Consumer b) { + DefaultFormatDtoBuilder builder; + try { + builder = new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", + ex); + } + b.accept(builder); + return builder.build(); + } + + /** + * Creates a builder initialized from this instance. + * + * @return a builder initialized with this instance's values + */ + default DefaultFormatDtoBuilder with() { + try { + return new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", + ex); + } + } + } +}"""; assertEquals(expectedSection2, section2, "Eclipse-formatted section 2 (class body) mismatch"); } } From 49efe2c380f132a44ebbfe323ed13cc253bd6ff0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:24:19 +0000 Subject: [PATCH 2/5] Address review: restore test block indentation, StringUtils nulls-last compare - FormattingModeTest: keep expectedSection2's original block indentation so the diff shows only ordering changes. - BuilderToGenerationTypeMapper: use StringUtils.compare(a, b, false) for the nulls-last group-key comparison instead of a hand-rolled helper. - RoasterCodeGenerator: note that LinkedHashMap preserves the mapper's emission order through the safety-net dedup. --- .../roaster/RoasterCodeGenerator.java | 1 + .../core/BuilderToGenerationTypeMapper.java | 16 +- .../processor/FormattingModeTest.java | 458 +++++++++--------- 3 files changed, 231 insertions(+), 244 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java index 498a5550..030f2f68 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java @@ -293,6 +293,7 @@ private void appendMethods(JavaClassSource source, GenerationTargetClassDto clas * warning (an error in strict mode). */ private List resolveMethodConflicts(List methods) { + // LinkedHashMap keeps the mapper's emission order for values(). Map signatureToMethod = new java.util.LinkedHashMap<>(); for (MethodDto method : methods) { if (signatureToMethod.putIfAbsent(method.getSignatureKey(), method) != null) { diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java index 7005740f..0a54dfd5 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -203,7 +203,7 @@ private static int compareFieldMethods(FieldMethod p1, FieldMethod p2) { if (staticCompare != 0) { return staticCompare; } - int groupCompare = compareGroupKeys(groupKeyOf(p1), groupKeyOf(p2)); + int groupCompare = StringUtils.compare(groupKeyOf(p1), groupKeyOf(p2), false); if (groupCompare != 0) { return groupCompare; } @@ -249,20 +249,6 @@ private static int accessModifierRank(BuilderMethodDto method) { .orElse(2); } - /** Grouped methods first (alphabetical by key), ungrouped methods last. */ - private static int compareGroupKeys(String groupKey1, String groupKey2) { - if (groupKey1 == null && groupKey2 == null) { - return 0; - } - if (groupKey1 == null) { - return 1; - } - if (groupKey2 == null) { - return -1; - } - return groupKey1.compareTo(groupKey2); - } - private static boolean isDirectFieldSetter(FieldMethod pair) { return pair.field() != null && isDirectFieldSetter(pair.method(), pair.field()); } diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java index 20122c87..f6e3b789 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/FormattingModeTest.java @@ -494,249 +494,249 @@ public class DefaultFormatDtoBuilder implements IBuilderBase { // Section 2: class body — the closing brace at 0 indentation anchors the common prefix String section2 = String.join("\n", java.util.Arrays.copyOfRange(lines, 38, lines.length)); String expectedSection2 = -""" + """ - /** - * Tracked value for value: value. - */ - private TrackedValue value = unsetValue(); + /** + * Tracked value for value: value. + */ + private TrackedValue value = unsetValue(); - /** - * Empty constructor of builder for {@code test.DefaultFormatDto}. - */ - public DefaultFormatDtoBuilder() { - } + /** + * Empty constructor of builder for {@code test.DefaultFormatDto}. + */ + public DefaultFormatDtoBuilder() { + } - /** - * Initialisation of builder for {@code test.DefaultFormatDto} by a instance. - * - * @param instance object instance for initialisiation - */ - public DefaultFormatDtoBuilder(DefaultFormatDto instance) { - this.value = initialValue(instance.getValue()); - } + /** + * Initialisation of builder for {@code test.DefaultFormatDto} by a instance. + * + * @param instance object instance for initialisiation + */ + public DefaultFormatDtoBuilder(DefaultFormatDto instance) { + this.value = initialValue(instance.getValue()); + } - /** - * Creating a new builder for {@code test.DefaultFormatDto}. - * - *

Example:

- * - *
{@code
-   * DefaultFormatDtoBuilder builder = DefaultFormatDtoBuilder.create();
-   * }
- * - * @return builder for {@code test.DefaultFormatDto} - */ - public static DefaultFormatDtoBuilder create() { - return new DefaultFormatDtoBuilder(); - } + /** + * Creating a new builder for {@code test.DefaultFormatDto}. + * + *

Example:

+ * + *
{@code
+           * DefaultFormatDtoBuilder builder = DefaultFormatDtoBuilder.create();
+           * }
+ * + * @return builder for {@code test.DefaultFormatDto} + */ + public static DefaultFormatDtoBuilder create() { + return new DefaultFormatDtoBuilder(); + } - /** - * Sets the value for value by executing the provided consumer. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-   * builder.value(sb -> sb.append("text"));
-   * }
- * - * @param valueStringBuilderConsumer consumer providing an instance of value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(Consumer valueStringBuilderConsumer) { - StringBuilder builder = new StringBuilder(); - valueStringBuilderConsumer.accept(builder); - this.value = changedValue(builder.toString()); - return this; - } + /** + * Sets the value for value by executing the provided consumer. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+           * builder.value(sb -> sb.append("text"));
+           * }
+ * + * @param valueStringBuilderConsumer consumer providing an instance of value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(Consumer valueStringBuilderConsumer) { + StringBuilder builder = new StringBuilder(); + valueStringBuilderConsumer.accept(builder); + this.value = changedValue(builder.toString()); + return this; + } - /** - * Sets the value for value by invoking the provided supplier. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-   * builder.value(() -> "example value");
-   * }
- * - * @param valueSupplier supplier for value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(Supplier valueSupplier) { - this.value = changedValue(valueSupplier.get()); - return this; - } + /** + * Sets the value for value by invoking the provided supplier. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+           * builder.value(() -> "example value");
+           * }
+ * + * @param valueSupplier supplier for value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(Supplier valueSupplier) { + this.value = changedValue(valueSupplier.get()); + return this; + } - /** - * Sets the String value for value by using String.format(format, args). See - * {@link String#format(String, Object...)} for details. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-   * builder.value("Hello %s", "World");
-   * }
- * - * @param format A format string - * @param args Arguments referenced by the format specifiers in the format string. - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(String format, Object... args) { - this.value = changedValue(String.format(format, args)); - return this; - } + /** + * Sets the String value for value by using String.format(format, args). See + * {@link String#format(String, Object...)} for details. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+           * builder.value("Hello %s", "World");
+           * }
+ * + * @param format A format string + * @param args Arguments referenced by the format specifiers in the format string. + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(String format, Object... args) { + this.value = changedValue(String.format(format, args)); + return this; + } - /** - * Sets the value for value. - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-   * builder.value("example value");
-   * }
- * - * @param value value - * @return current instance of builder - */ - public DefaultFormatDtoBuilder value(String value) { - this.value = changedValue(value); - return this; - } + /** + * Sets the value for value. + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+           * builder.value("example value");
+           * }
+ * + * @param value value + * @return current instance of builder + */ + public DefaultFormatDtoBuilder value(String value) { + this.value = changedValue(value); + return this; + } - /** - * Updates the current value of value in place by applying the given operator, instead of reading it out, - * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, - * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The - * value must have been set before (directly or via an existing instance). - *

- * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String - * value)} - * - *

Example:

- * - *
{@code
-   * builder.value("example value").valueUpdate(String::trim);
-   * }
- * - * @param valueUpdater operator applied to the current value; its result becomes the new value - * @return current instance of builder - * @throws IllegalStateException if value has not been set yet - */ - public DefaultFormatDtoBuilder valueUpdate(UnaryOperator valueUpdater) { - if (!this.value.isSet()) { - throw new IllegalStateException("Cannot update 'value' before it is set"); - } - this.value = changedValue(valueUpdater.apply(this.value.value())); - return this; - } + /** + * Updates the current value of value in place by applying the given operator, instead of reading it out, + * changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming, + * upper-casing, clamping or incrementing, and in combination with the With copy-and-modify flow. The + * value must have been set before (directly or via an existing instance). + *

+ * Generated from parameter in constructor {@link DefaultFormatDto#DefaultFormatDto(String) DefaultFormatDto(String + * value)} + * + *

Example:

+ * + *
{@code
+           * builder.value("example value").valueUpdate(String::trim);
+           * }
+ * + * @param valueUpdater operator applied to the current value; its result becomes the new value + * @return current instance of builder + * @throws IllegalStateException if value has not been set yet + */ + public DefaultFormatDtoBuilder valueUpdate(UnaryOperator valueUpdater) { + if (!this.value.isSet()) { + throw new IllegalStateException("Cannot update 'value' before it is set"); + } + this.value = changedValue(valueUpdater.apply(this.value.value())); + return this; + } - /** - * Conditionally applies builder modifications if the condition is true. - * - * @param condition the condition to evaluate - * @param yesCondition the consumer to apply if condition is true - * @return this builder instance - */ - public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, - Consumer yesCondition) { - return conditional(condition, yesCondition, null); - } + /** + * Conditionally applies builder modifications if the condition is true. + * + * @param condition the condition to evaluate + * @param yesCondition the consumer to apply if condition is true + * @return this builder instance + */ + public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, + Consumer yesCondition) { + return conditional(condition, yesCondition, null); + } - /** - * Conditionally applies builder modifications based on a condition evaluation. - * - * @param condition the condition to evaluate - * @param trueCase the consumer to apply if condition is true - * @param falseCase the consumer to apply if condition is false (can be null) - * @return this builder instance - */ - public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, Consumer trueCase, - Consumer falseCase) { - if (condition.getAsBoolean()) { - trueCase.accept(this); - } else if (falseCase != null) { - falseCase.accept(this); - } - return this; - } + /** + * Conditionally applies builder modifications based on a condition evaluation. + * + * @param condition the condition to evaluate + * @param trueCase the consumer to apply if condition is true + * @param falseCase the consumer to apply if condition is false (can be null) + * @return this builder instance + */ + public DefaultFormatDtoBuilder conditional(BooleanSupplier condition, Consumer trueCase, + Consumer falseCase) { + if (condition.getAsBoolean()) { + trueCase.accept(this); + } else if (falseCase != null) { + falseCase.accept(this); + } + return this; + } - /** - * Builds the configured DTO instance. - * - *

Example:

- * - *
{@code
-   * DefaultFormatDto result = builder.build();
-   * }
- */ - @Override - public DefaultFormatDto build() { - DefaultFormatDto result = new DefaultFormatDto(this.value.value()); - return result; - } + /** + * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+           * DefaultFormatDto result = builder.build();
+           * }
+ */ + @Override + public DefaultFormatDto build() { + DefaultFormatDto result = new DefaultFormatDto(this.value.value()); + return result; + } - /** - * Returns a string representation of this builder, including only fields that have been set. - * - * @return string representation of the builder - */ - @Override - public String toString() { - return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("value", this.value).toString(); - } + /** + * Returns a string representation of this builder, including only fields that have been set. + * + * @return string representation of the builder + */ + @Override + public String toString() { + return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("value", this.value).toString(); + } - /** - * Interface that can be implemented by the DTO to provide fluent modification methods. - */ - public interface With { - /** - * Initializes a builder from an instance of this class, using methods of this builder to change values and returns - * the new built object. - * - * @param b the consumer to apply modifications - * @return the modified instance - */ - default DefaultFormatDto with(Consumer b) { - DefaultFormatDtoBuilder builder; - try { - builder = new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); - } catch (ClassCastException ex) { - throw new IllegalArgumentException( - "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", - ex); - } - b.accept(builder); - return builder.build(); - } - - /** - * Creates a builder initialized from this instance. - * - * @return a builder initialized with this instance's values - */ - default DefaultFormatDtoBuilder with() { - try { - return new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); - } catch (ClassCastException ex) { - throw new IllegalArgumentException( - "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", - ex); - } - } - } -}"""; + /** + * Interface that can be implemented by the DTO to provide fluent modification methods. + */ + public interface With { + /** + * Initializes a builder from an instance of this class, using methods of this builder to change values and returns + * the new built object. + * + * @param b the consumer to apply modifications + * @return the modified instance + */ + default DefaultFormatDto with(Consumer b) { + DefaultFormatDtoBuilder builder; + try { + builder = new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", + ex); + } + b.accept(builder); + return builder.build(); + } + + /** + * Creates a builder initialized from this instance. + * + * @return a builder initialized with this instance's values + */ + default DefaultFormatDtoBuilder with() { + try { + return new DefaultFormatDtoBuilder(DefaultFormatDto.class.cast(this)); + } catch (ClassCastException ex) { + throw new IllegalArgumentException( + "The interface 'DefaultFormatDtoBuilder.With' should only be implemented by classes, which could be casted to 'DefaultFormatDto'", + ex); + } + } + } + }"""; assertEquals(expectedSection2, section2, "Eclipse-formatted section 2 (class body) mismatch"); } } From b31b3443caed4fdda32a1c75c8b1a1c39a1c1898 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:25:30 +0000 Subject: [PATCH 3/5] Simplify safety-net dedup to Set+List, preserving emission order Keeps a HashSet of seen signature keys and appends each unseen method to the result list, so duplicate signatures keep the first occurrence in emission order and no map indirection is needed. --- .../classgen/roaster/RoasterCodeGenerator.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java index 030f2f68..24e5b136 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java @@ -30,8 +30,10 @@ import java.io.IOException; import java.io.Writer; +import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -293,16 +295,18 @@ private void appendMethods(JavaClassSource source, GenerationTargetClassDto clas * warning (an error in strict mode). */ private List resolveMethodConflicts(List methods) { - // LinkedHashMap keeps the mapper's emission order for values(). - Map signatureToMethod = new java.util.LinkedHashMap<>(); + Set seenSignatures = new HashSet<>(); + List deduplicated = new ArrayList<>(methods.size()); for (MethodDto method : methods) { - if (signatureToMethod.putIfAbsent(method.getSignatureKey(), method) != null) { + if (seenSignatures.add(method.getSignatureKey())) { + deduplicated.add(method); + } else { context.reportBasedOnStrictMode( "Unexpected duplicate method signature: '%s' — keeping first occurrence (safety net)", method.getSignatureKey()); } } - return List.copyOf(signatureToMethod.values()); + return deduplicated; } private void appendMethod( From 1f2ccbfeeaad926b3fc9f5c54d344d02d3e27249 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:32:52 +0000 Subject: [PATCH 4/5] Cover the generator safety-net dedup in RoasterCodeGeneratorResilienceTest The duplicate-signature branch is unreachable through the builder pipeline because BuilderDefinitionCreator resolves conflicts first; feeding identical MethodDto signatures straight into the generator verifies the take-first semantics and the strict-mode warning. --- .../RoasterCodeGeneratorResilienceTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java index fd729e32..8422b2c8 100644 --- a/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/RoasterCodeGeneratorResilienceTest.java @@ -29,6 +29,7 @@ import javax.annotation.processing.ProcessingEnvironment; import org.javahelpers.simple.builders.core.enums.AccessModifier; +import org.javahelpers.simple.builders.core.enums.FormattingMode; import org.javahelpers.simple.builders.processor.classgen.roaster.RoasterCodeGenerator; import org.javahelpers.simple.builders.processor.classgen.roaster.exceptions.RoasterMapperException; import org.javahelpers.simple.builders.processor.exceptions.BuilderException; @@ -37,9 +38,12 @@ import org.javahelpers.simple.builders.processor.model.method.ConstructorDto; import org.javahelpers.simple.builders.processor.model.method.MethodCodeDto; import org.javahelpers.simple.builders.processor.model.method.MethodCodePlaceholder; +import org.javahelpers.simple.builders.processor.model.method.MethodDto; +import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto; import org.javahelpers.simple.builders.processor.model.type.TypeName; import org.javahelpers.simple.builders.processor.processing.ProcessingContext; import org.javahelpers.simple.builders.processor.processing.logging.ProcessingLogger; +import org.javahelpers.simple.builders.processor.testing.CapturingProcessingLogger; import org.javahelpers.simple.builders.processor.testing.ProcessingEnvironmentStub; import org.junit.jupiter.api.Test; @@ -84,4 +88,45 @@ void shouldWrapRenderingRuntimeExceptionInBuilderException() { assertThrows(BuilderException.class, () -> generator.generateClass(classDef)); assertInstanceOf(RoasterMapperException.class, thrown.getCause()); } + + /** + * Two methods with identical signature keys must not produce duplicate members in the emitted + * source: the safety-net resolution keeps the first occurrence and reports the dropped one as a + * warning (an error in strict mode). Conflicts are normally resolved earlier by {@code + * BuilderDefinitionCreator}, so this path is only reachable when methods are fed directly to the + * generator. + */ + @Test + void shouldKeepFirstMethodAndWarnOnDuplicateSignature() { + GenerationTargetClassDto classDef = new GenerationTargetClassDto(); + classDef.setTypeName(new TypeName("com.example", "DuplicateSignatureBuilder")); + classDef.setClassAccessModifier(AccessModifier.PUBLIC); + classDef.setFormattingMode(FormattingMode.NONE); + classDef.addMethod(duplicateSignatureMethod("first")); + classDef.addMethod(duplicateSignatureMethod("second")); + + CapturingProcessingLogger capturing = CapturingProcessingLogger.create(); + ProcessingEnvironment env = ProcessingEnvironmentStub.createEmpty(); + ProcessingContext context = + new ProcessingContext(capturing.logger(), BuilderConfiguration.DEFAULT, env); + RoasterCodeGenerator generator = new RoasterCodeGenerator(context, env); + + // generation itself succeeds; only the file-write step fails on the stub's null filer + assertThrows(NullPointerException.class, () -> generator.generateClass(classDef)); + capturing.assertMessage( + "WARNING: Unexpected duplicate method signature: 'name(java.lang.String)'" + + " — keeping first occurrence (safety net)"); + } + + private static MethodDto duplicateSignatureMethod(String marker) { + MethodDto method = + new MethodDto("name", new TypeName("com.example", "DuplicateSignatureBuilder")); + method.setModifier(AccessModifier.PUBLIC); + MethodParameterDto parameter = new MethodParameterDto(); + parameter.setParameterName("value"); + parameter.setParameterTypeName(new TypeName("java.lang", "String")); + method.addParameter(parameter); + method.setCode("return this; // " + marker); + return method; + } } From f34100eef922129210abeb2bae128b2f1f3b1865 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 10:39:21 +0000 Subject: [PATCH 5/5] Collect field-method pairs via streams (Sonar java:S9391) Replaces the three accumulation loops with a Stream.concat pipeline feeding the emission-order sort; semantics unchanged. --- .../core/BuilderToGenerationTypeMapper.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java index 0a54dfd5..67112658 100644 --- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java +++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/BuilderToGenerationTypeMapper.java @@ -24,9 +24,9 @@ package org.javahelpers.simple.builders.processor.model.core; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto; import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto; @@ -114,21 +114,17 @@ public GenerationTargetClassDto toRenderingDto(BuilderDefinitionDto builderDto) // Map and copy methods in emission order (see METHOD_EMISSION_ORDER): field-originated // methods stay grouped per field, builder-level methods come after grouped ones. - List fieldMethods = new ArrayList<>(); - for (FieldDto field : builderDto.getConstructorFieldsForBuilder()) { - for (BuilderMethodDto method : field.getMethods()) { - fieldMethods.add(new FieldMethod(field, method)); - } - } - for (FieldDto field : builderDto.getSetterFieldsForBuilder()) { - for (BuilderMethodDto method : field.getMethods()) { - fieldMethods.add(new FieldMethod(field, method)); - } - } - for (BuilderMethodDto classMethod : builderDto.getMethods()) { - fieldMethods.add(new FieldMethod(null, classMethod)); - } - fieldMethods.stream() + Stream fieldMethods = + Stream.concat( + Stream.of( + builderDto.getConstructorFieldsForBuilder(), + builderDto.getSetterFieldsForBuilder()) + .flatMap(List::stream) + .flatMap( + field -> + field.getMethods().stream().map(method -> new FieldMethod(field, method))), + builderDto.getMethods().stream().map(method -> new FieldMethod(null, method))); + fieldMethods .sorted(METHOD_EMISSION_ORDER) .forEach(pair -> renderingDto.addMethod(toMethodDto(pair.method())));