diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java new file mode 100644 index 00000000..c3968263 --- /dev/null +++ b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleMinimalBuilder.java @@ -0,0 +1,100 @@ +/* + * MIT License + * + * Copyright (c) 2026 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.core.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.javahelpers.simple.builders.core.enums.OptionState; + +/** + * Built-in minimal builder template annotation. + * + *

Generates the smallest possible fluent builder by disabling all optional features. Use this + * when you only need {@code create()}, field setters and {@code build()} without suppliers, + * consumers, varargs, collection helpers, {@code With} interface, Jackson integration or generated + * annotations. + * + *

This annotation is implemented purely as a {@link SimpleBuilder.Template} with every optional + * feature set to {@link OptionState#DISABLED}. Because the template is {@link Inherited}, + * subclasses of an annotated type also receive a minimal builder unless explicitly excluded by + * {@link Ignore4BuilderGeneration}. + * + *

Example: + * + *

{@code
+ * @SimpleMinimalBuilder
+ * public class PersonDto {
+ *     private String name;
+ *
+ *     public String getName() { return name; }
+ *     public void setName(String name) { this.name = name; }
+ * }
+ * }
+ * + *

Generated builder usage: + * + *

{@code
+ * PersonDto person = PersonDtoBuilder.create()
+ *     .name("John")
+ *     .build();
+ * }
+ * + * @see SimpleBuilder + * @see SimpleBuilder.Template + * @see SimpleBuilder.Options + * @see Ignore4BuilderGeneration + */ +@SimpleBuilder.Template( + options = + @SimpleBuilder.Options( + generateFieldSupplier = OptionState.DISABLED, + generateFieldConsumer = OptionState.DISABLED, + generateBuilderConsumer = OptionState.DISABLED, + generateConditionalHelper = OptionState.DISABLED, + generateVarArgsHelpers = OptionState.DISABLED, + generateStringFormatHelpers = OptionState.DISABLED, + generateAddToCollectionHelpers = OptionState.DISABLED, + generateUnboxedOptional = OptionState.DISABLED, + generateWithInterface = OptionState.DISABLED, + usingArrayListBuilder = OptionState.DISABLED, + usingArrayListBuilderWithElementBuilders = OptionState.DISABLED, + usingHashSetBuilder = OptionState.DISABLED, + usingHashSetBuilderWithElementBuilders = OptionState.DISABLED, + usingHashMapBuilder = OptionState.DISABLED, + usingGeneratedAnnotation = OptionState.DISABLED, + usingBuilderImplementationAnnotation = OptionState.DISABLED, + usingJacksonDeserializerAnnotation = OptionState.DISABLED, + generateJacksonModule = OptionState.DISABLED, + copyTypeAnnotations = OptionState.DISABLED, + implementsBuilderBase = OptionState.DISABLED, + builderSuffix = "Builder", + setterSuffix = "")) +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +@Inherited +public @interface SimpleMinimalBuilder {} diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 01a0ca35..70ede75f 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -85,9 +85,10 @@ public class PersonDto { | Need | Use | |------|-----| | Generate a builder for a single class/record | `@SimpleBuilder` on the class/record | +| Generate a minimal builder (no optional features) | `@SimpleMinimalBuilder` on the class/record | | Share the same configuration across many classes | `@SimpleBuilder.Template` on a custom `@interface`, then the custom annotation on each class | -Create reusable configuration presets with custom template annotations: +Create reusable configuration presets with custom template annotations. The built-in `@SimpleMinimalBuilder` already disables every optional feature; use the following pattern only when you need a differently named or customized template: ```java @SimpleBuilder.Template(options = @SimpleBuilder.Options( @@ -109,7 +110,8 @@ Create reusable configuration presets with custom template annotations: usingGeneratedAnnotation = OptionState.DISABLED, usingBuilderImplementationAnnotation = OptionState.DISABLED, implementsBuilderBase = OptionState.DISABLED, - usingJacksonDeserializerAnnotation = OptionState.DISABLED + usingJacksonDeserializerAnnotation = OptionState.DISABLED, + generateJacksonModule = OptionState.DISABLED )) @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) @@ -903,40 +905,23 @@ reported as warnings so compilation can continue. ### Minimal Builder -Generate only essential builder methods: +Use the built-in `@SimpleMinimalBuilder` template to generate only essential builder methods with a single annotation: ```java -@SimpleBuilder( - options = @SimpleBuilder.Options( - generateFieldSupplier = OptionState.DISABLED, - generateFieldConsumer = OptionState.DISABLED, - generateBuilderConsumer = OptionState.DISABLED, - generateConditionalHelper = OptionState.DISABLED, - generateVarArgsHelpers = OptionState.DISABLED, - generateStringFormatHelpers = OptionState.DISABLED, - generateAddToCollectionHelpers = OptionState.DISABLED, - generateUnboxedOptional = OptionState.DISABLED, - copyTypeAnnotations = OptionState.DISABLED, - usingArrayListBuilder = OptionState.DISABLED, - usingArrayListBuilderWithElementBuilders = OptionState.DISABLED, - usingHashSetBuilder = OptionState.DISABLED, - usingHashSetBuilderWithElementBuilders = OptionState.DISABLED, - usingHashMapBuilder = OptionState.DISABLED, - generateWithInterface = OptionState.DISABLED, - usingGeneratedAnnotation = OptionState.DISABLED, - usingBuilderImplementationAnnotation = OptionState.DISABLED, - implementsBuilderBase = OptionState.DISABLED, - usingJacksonDeserializerAnnotation = OptionState.DISABLED -) +import org.javahelpers.simple.builders.core.annotations.SimpleMinimalBuilder; + +@SimpleMinimalBuilder public class MinimalDto { private String name; - + public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` -**Generated**: Only basic builder methods (`create()`, field setters, `build()`) +**Generated**: Only basic builder methods (`create()`, field setters, `build()`). + +If you need a different name or extra customization, you can still build a custom `@SimpleBuilder.Template` with all optional features disabled. ### Internal API Builder @@ -993,7 +978,7 @@ TeamDto team = TeamDtoBuilder.create() ### Minimal Builder Template -Create a reusable template for lightweight builders: +The built-in `@SimpleMinimalBuilder` is the simplest way to get a lightweight builder. If you need a differently named template or want to build your own preset, create a custom annotation meta-annotated with `@SimpleBuilder.Template`: ```java @SimpleBuilder.Template(options = @SimpleBuilder.Options( @@ -1015,7 +1000,8 @@ Create a reusable template for lightweight builders: usingGeneratedAnnotation = OptionState.DISABLED, usingBuilderImplementationAnnotation = OptionState.DISABLED, implementsBuilderBase = OptionState.DISABLED, - usingJacksonDeserializerAnnotation = OptionState.DISABLED + usingJacksonDeserializerAnnotation = OptionState.DISABLED, + generateJacksonModule = OptionState.DISABLED )) @Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) diff --git a/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java b/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java new file mode 100644 index 00000000..bbcebcd2 --- /dev/null +++ b/example/generated-example-builder/org/javahelpers/simple/builders/example/CustomerDtoBuilder.java @@ -0,0 +1,218 @@ +package org.javahelpers.simple.builders.example; + +import static org.javahelpers.simple.builders.core.util.TrackedValue.changedValue; +import static org.javahelpers.simple.builders.core.util.TrackedValue.initialValue; +import static org.javahelpers.simple.builders.core.util.TrackedValue.unsetValue; +import java.util.List; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.javahelpers.simple.builders.core.util.BuilderToStringStyle; +import org.javahelpers.simple.builders.core.util.TrackedValue; + +/** + * Builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. + *

+ * This builder provides a fluent API for creating instances of org.javahelpers.simple.builders.example.CustomerDto with + * method chaining and validation. Use the static {@code create()} method to obtain a new builder instance, configure + * the desired properties using the setter methods, and then call {@code build()} to create the final DTO. + * + *

Example:

+ * + *
{@code
+ * CustomerDto result = CustomerDtoBuilder.create()
+ *     .email("example value")
+ *     .id(42L)
+ *     .name("example value")
+ *     .tags(List.of("example value"))
+ *     .build();
+ * }
+ */ +public class CustomerDtoBuilder { + + /** + * Tracked value for email: email. + */ + private TrackedValue email = unsetValue(); + /** + * Tracked value for id: id. + */ + private TrackedValue id = unsetValue(); + /** + * Tracked value for name: name. + */ + private TrackedValue name = unsetValue(); + /** + * Tracked value for tags: tags. + */ + private TrackedValue> tags = unsetValue(); + + /** + * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. + */ + public CustomerDtoBuilder() { + } + + /** + * Initialisation of builder for {@code org.javahelpers.simple.builders.example.CustomerDto} by a instance. + * + * @param instance object instance for initialisiation + */ + public CustomerDtoBuilder(CustomerDto instance) { + this.email = initialValue(instance.getEmail()); + this.id = initialValue(instance.getId()); + this.name = initialValue(instance.getName()); + this.tags = initialValue(instance.getTags()); + } + + /** + * Creating a new builder for {@code org.javahelpers.simple.builders.example.CustomerDto}. + * + *

Example:

+ * + *
{@code
+   * CustomerDtoBuilder builder = CustomerDtoBuilder.create();
+   * }
+ * + * @return builder for {@code org.javahelpers.simple.builders.example.CustomerDto} + */ + public static CustomerDtoBuilder create() { + return new CustomerDtoBuilder(); + } + + /** + * Sets the value for email. + *

+ * Generated from setter {@link CustomerDto#setEmail(String) setEmail(String email)} + * + *

Example:

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

+ * Generated from setter {@link CustomerDto#setId(Long) setId(Long id)} + * + *

Example:

+ * + *
{@code
+   * builder.id(42L);
+   * }
+ * + * @param id id + * @return current instance of builder + */ + public CustomerDtoBuilder id(Long id) { + this.id = changedValue(id); + return this; + } + + /** + * Sets the value for name. + *

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

Example:

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

+ * Generated from setter {@link CustomerDto#setTags(List) setTags(List tags)} + * + *

Example:

+ * + *
{@code
+   * builder.tags(List.of("example value"));
+   * }
+ * + * @param tags tags + * @return current instance of builder + */ + public CustomerDtoBuilder tags(List tags) { + this.tags = changedValue(tags); + return this; + } + + /** + * Validates that the email field is not null or empty. + *

+ * Generated from setter {@link CustomerDto#setEmail(String) setEmail(String email)} + * + * @return this builder instance for chaining + * @throws IllegalArgumentException if email is null or empty + */ + CustomerDtoBuilder validateEmail() { + if (!email.isSet() || email.value().trim().isEmpty()) { + throw new IllegalArgumentException("Email cannot be null or empty"); + } + return this; + } + + /** + * Validates that the name field is not null or empty. + *

+ * Generated from setter {@link CustomerDto#setName(String) setName(String name)} + * + * @return this builder instance for chaining + * @throws IllegalArgumentException if name is null or empty + */ + CustomerDtoBuilder validateName() { + if (!name.isSet() || name.value().trim().isEmpty()) { + throw new IllegalArgumentException("Name cannot be null or empty"); + } + return this; + } + + /** + * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+   * CustomerDto result = builder.build();
+   * }
+ */ + public CustomerDto build() { + CustomerDto result = new CustomerDto(); + this.email.ifSet(result::setEmail); + this.id.ifSet(result::setId); + this.name.ifSet(result::setName); + this.tags.ifSet(result::setTags); + 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("email", this.email) + .append("id", this.id) + .append("name", this.name) + .append("tags", this.tags) + .toString(); + } +} \ No newline at end of file diff --git a/example/src/main/java/org/javahelpers/simple/builders/example/CustomerDto.java b/example/src/main/java/org/javahelpers/simple/builders/example/CustomerDto.java new file mode 100644 index 00000000..58edb8ce --- /dev/null +++ b/example/src/main/java/org/javahelpers/simple/builders/example/CustomerDto.java @@ -0,0 +1,73 @@ +/* + * MIT License + * + * Copyright (c) 2026 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.example; + +import java.util.List; +import org.javahelpers.simple.builders.core.annotations.SimpleMinimalBuilder; + +/** + * Example DTO demonstrating the built-in {@link SimpleMinimalBuilder} template. + * + *

This generates a minimal builder with only {@code create()}, field setters and {@code build()}. + */ +@SimpleMinimalBuilder +public class CustomerDto { + private Long id; + private String name; + private String email; + private List tags; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } +} diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java new file mode 100644 index 00000000..c81d9e4f --- /dev/null +++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/SimpleMinimalBuilderTest.java @@ -0,0 +1,272 @@ +/* + * MIT License + * + * Copyright (c) 2026 Andreas Igel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.javahelpers.simple.builders.processor; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertContaining; +import static org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertGenerationSucceeded; +import static org.javahelpers.simple.builders.processor.testing.ProcessorAsserts.assertNotContaining; +import static org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils.loadGeneratedSource; + +import com.google.testing.compile.Compilation; +import javax.tools.JavaFileObject; +import org.javahelpers.simple.builders.processor.testing.ProcessorAsserts; +import org.javahelpers.simple.builders.processor.testing.ProcessorTestUtils; +import org.junit.jupiter.api.Test; + +/** + * Tests for the built-in {@code @SimpleMinimalBuilder} template annotation. + * + *

Verifies that {@code @SimpleMinimalBuilder} disables all optional features and produces a + * builder with only the basic fluent API: constructors, {@code create()}, field setters, {@code + * build()} and {@code toString()}. + */ +class SimpleMinimalBuilderTest { + + @Test + void simpleMinimalBuilderGeneratesMinimalBuilder() { + JavaFileObject source = + ProcessorTestUtils.forSource( + """ + package test; + import org.javahelpers.simple.builders.core.annotations.SimpleMinimalBuilder; + + @SimpleMinimalBuilder + public class PersonDto { + private String name; + private java.util.List tags; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public java.util.List getTags() { return tags; } + public void setTags(java.util.List tags) { this.tags = tags; } + } + """); + + Compilation compilation = ProcessorTestUtils.createCompiler().compile(source); + + assertThat(compilation).succeededWithoutWarnings(); + + String generatedCode = loadGeneratedSource(compilation, "PersonDtoBuilder"); + assertGenerationSucceeded(compilation, "PersonDtoBuilder", generatedCode); + + String expectedCode = + """ + package test; + + import static org.javahelpers.simple.builders.core.util.TrackedValue.changedValue; + import static org.javahelpers.simple.builders.core.util.TrackedValue.initialValue; + import static org.javahelpers.simple.builders.core.util.TrackedValue.unsetValue; + import java.util.List; + import org.apache.commons.lang3.builder.ToStringBuilder; + import org.javahelpers.simple.builders.core.util.BuilderToStringStyle; + import org.javahelpers.simple.builders.core.util.TrackedValue; + + /** + * Builder for {@code test.PersonDto}. + *

+ * This builder provides a fluent API for creating instances of test.PersonDto with method chaining and validation. Use + * the static {@code create()} method to obtain a new builder instance, configure the desired properties using the + * setter methods, and then call {@code build()} to create the final DTO. + * + *

Example:

+ * + *
{@code
+         * PersonDto result = PersonDtoBuilder.create().name("example value").tags(List.of("example value")).build();
+         * }
+ */ + public class PersonDtoBuilder { + + /** + * Tracked value for name: name. + */ + private TrackedValue name = unsetValue(); + /** + * Tracked value for tags: tags. + */ + private TrackedValue> tags = unsetValue(); + + /** + * Empty constructor of builder for {@code test.PersonDto}. + */ + public PersonDtoBuilder() { + } + + /** + * Initialisation of builder for {@code test.PersonDto} by a instance. + * + * @param instance object instance for initialisiation + */ + public PersonDtoBuilder(PersonDto instance) { + this.name = initialValue(instance.getName()); + this.tags = initialValue(instance.getTags()); + } + + /** + * Creating a new builder for {@code test.PersonDto}. + * + *

Example:

+ * + *
{@code
+           * PersonDtoBuilder builder = PersonDtoBuilder.create();
+           * }
+ * + * @return builder for {@code test.PersonDto} + */ + public static PersonDtoBuilder create() { + return new PersonDtoBuilder(); + } + + /** + * Sets the value for name. + *

+ * Generated from setter {@link PersonDto#setName(String) setName(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 tags. + *

+ * Generated from setter {@link PersonDto#setTags(List) setTags(List tags)} + * + *

Example:

+ * + *
{@code
+           * builder.tags(List.of("example value"));
+           * }
+ * + * @param tags tags + * @return current instance of builder + */ + public PersonDtoBuilder tags(List tags) { + this.tags = changedValue(tags); + return this; + } + + /** + * Builds the configured DTO instance. + * + *

Example:

+ * + *
{@code
+           * PersonDto result = builder.build();
+           * }
+ */ + public PersonDto build() { + PersonDto result = new PersonDto(); + this.name.ifSet(result::setName); + this.tags.ifSet(result::setTags); + 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("name", this.name) + .append("tags", this.tags) + .toString(); + } + } + """; + + ProcessorAsserts.assertNormalizedEquals( + expectedCode, + generatedCode, + "Generated minimal builder does not match expected. All optional features should be disabled."); + } + + @Test + void inheritedSimpleMinimalBuilderPropagatesToSubclass() { + JavaFileObject parentSource = + ProcessorTestUtils.forSource( + """ + package test; + import org.javahelpers.simple.builders.core.annotations.SimpleMinimalBuilder; + + @SimpleMinimalBuilder + public class PersonDto { + private String name; + + public String getName() { return name; } + public void setName(String name) { this.name = name; } + } + """); + + JavaFileObject childSource = + ProcessorTestUtils.forSource( + """ + package test; + + public class ChildDto extends PersonDto { + private int age; + + public int getAge() { return age; } + public void setAge(int age) { this.age = age; } + } + """); + + Compilation compilation = + ProcessorTestUtils.createCompiler().compile(parentSource, childSource); + + assertThat(compilation).succeededWithoutWarnings(); + + String parentBuilder = loadGeneratedSource(compilation, "PersonDtoBuilder"); + assertGenerationSucceeded(compilation, "PersonDtoBuilder", parentBuilder); + + String childBuilder = loadGeneratedSource(compilation, "ChildDtoBuilder"); + assertGenerationSucceeded(compilation, "ChildDtoBuilder", childBuilder); + + assertContaining(childBuilder, "public ChildDtoBuilder name(String name)"); + assertContaining(childBuilder, "public ChildDtoBuilder age(int age)"); + + assertNotContaining( + childBuilder, + "Supplier<", + "Consumer<", + "WithChildDto", + "@Generated", + "@BuilderImplementation", + "IBuilderBase", + "@JsonPOJOBuilder", + "ChildDtoBuilder name(String... name)", + "ChildDtoBuilder tags(String... tags)"); + } +}