}.
* For Optional fields, this generates a setter that accepts T and wraps it with
* Optional.ofNullable().
*
@@ -495,7 +495,7 @@
* Example:
*
*
{@code
- * @Generated("org.javahelpers.simple.builders.processor.BuilderProcessor")
+ * @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
* public class PersonDtoBuilder {
* // ...
* }
@@ -542,6 +542,8 @@
*
* Default: ENABLED
* Compiler option: -Asimplebuilder.implementsBuilderBase
+ *
+ * @return the option state for implementing BuilderBase interface
*/
OptionState implementsBuilderBase() default OptionState.UNSET;
@@ -565,6 +567,8 @@
*
* Default: ENABLED
* Compiler option: -Asimplebuilder.generateWithInterface
+ *
+ * @return the option state for generating With interface
*/
OptionState generateWithInterface() default OptionState.UNSET;
@@ -586,6 +590,8 @@
*
* Default: DISABLED
* Compiler option: -Asimplebuilder.usingJacksonDeserializerAnnotation
+ *
+ * @return the option state for using Jackson deserializer annotation
*/
OptionState usingJacksonDeserializerAnnotation() default OptionState.UNSET;
@@ -600,6 +606,8 @@
*
* Default: DISABLED
* Compiler option: -Asimplebuilder.generateJacksonModule
+ *
+ * @return the option state for generating Jackson module
*/
OptionState generateJacksonModule() default OptionState.UNSET;
@@ -614,6 +622,8 @@
*
*
Default: "" (empty - generate one module per package)
* Compiler option: -Asimplebuilder.jacksonModulePackage
+ *
+ * @return the package name for the Jackson module
*/
String jacksonModulePackage() default "";
@@ -635,6 +645,8 @@
*
* Default: "Builder"
* Compiler option: -Asimplebuilder.builderSuffix
+ *
+ * @return the suffix for the builder class name
*/
String builderSuffix() default "Builder";
@@ -661,6 +673,8 @@
*
* Default: "" (empty - no suffix)
* Compiler option: -Asimplebuilder.setterSuffix
+ *
+ * @return the suffix for setter method names
*/
String setterSuffix() default "";
}
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 372f524c..033c4621 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -16,6 +16,7 @@ Simple-builders supports fine-grained configuration through the `@SimpleBuilder.
- [Conditional Logic](#conditional-logic)
- [Access Control](#access-control)
- [Collection Helpers](#collection-helpers)
+ - [Component Filtering](#component-filtering)
- [Integration](#integration)
- [Examples](#examples)
- [Minimal Builder](#minimal-builder)
@@ -390,19 +391,26 @@ PersonDto person = PersonDtoBuilder.create()
**Default**: `ENABLED` | **Compiler Option**: `-Asimplebuilder.generateStringFormatHelpers=ENABLED|DISABLED`
-Generates `String.format()` helper methods for String fields.
+Generates `String.format()` helper methods for String and Optional fields.
**When ENABLED**:
```java
-// Generated method
-public PersonDtoBuilder name(String format, Object... args) {
- this.name = changedValue(String.format(format, args));
- return this;
+// For String title field:
+public BookDtoBuilder title(String format, Object... args) {
+ this.title = changedValue(String.format(format, args));
+ return this;
+}
+
+// For Optional subtitle field:
+public BookDtoBuilder subtitle(String format, Object... args) {
+ this.subtitle = changedValue(Optional.of(String.format(format, args)));
+ return this;
}
// Usage
-PersonDto person = PersonDtoBuilder.create()
- .name("Hello, %s %s!", firstName, lastName)
+BookDto book = BookDtoBuilder.create()
+ .title("The %s Guide", "Complete")
+ .subtitle("A comprehensive %s tutorial for %s", "Java", "beginners")
.build();
```
@@ -599,6 +607,34 @@ Generates methods using `HashMapBuilder` for fluent Map construction.
---
+### Component Filtering
+
+#### `deactivateGenerationComponents`
+
+**Default**: `""` (empty) | **Compiler Option**: `-Asimplebuilder.deactivateGenerationComponents=pattern1,pattern2,...`
+
+Deactivates specific method generators and builder enhancers by class name pattern. This allows you to override default generators/enhancers with your own custom implementations.
+
+**Primary Use Case**: Override Default Components
+
+When you want to replace a built-in generator or enhancer with your own custom implementation, you first deactivate the default component, then register your custom one via ServiceLoader.
+
+For detailed instructions on creating custom generators and enhancers, see [**CUSTOMIZING.md**](CUSTOMIZING.md).
+
+**Pattern Matching**:
+- **Exact match**: `ConditionalEnhancer` - deactivates exactly this class
+- **Wildcard suffix**: `*HelperGenerator` - deactivates all classes ending with HelperGenerator
+- **Wildcard prefix**: `String*` - deactivates all classes starting with String
+- **Wildcard anywhere**: `*Consumer*` - deactivates all classes containing Consumer
+
+**Feature Toggling vs Component Override**:
+- **Feature toggling**: Use `@SimpleBuilder.Options(generateConditionalHelper = DISABLED)`
+- **Component override**: Use `deactivateGenerationComponents` + custom ServiceLoader implementation
+
+See [**CUSTOMIZING.md**](CUSTOMIZING.md) for complete implementation examples and best practices.
+
+---
+
### Integration & Annotations
#### `generateWithInterface`
@@ -1183,6 +1219,9 @@ methodAccess = AccessModifier.PRIVATE
-Asimplebuilder.usingHashSetBuilderWithElementBuilders=ENABLED|DISABLED
-Asimplebuilder.usingHashMapBuilder=ENABLED|DISABLED
+# Component Filtering
+-Asimplebuilder.deactivateGenerationComponents=pattern1,pattern2,...
+
# Integration & Annotations
-Asimplebuilder.generateWithInterface=ENABLED|DISABLED
-Asimplebuilder.implementsBuilderBase=ENABLED|DISABLED
diff --git a/docs/CUSTOMIZING.md b/docs/CUSTOMIZING.md
new file mode 100644
index 00000000..ee11433c
--- /dev/null
+++ b/docs/CUSTOMIZING.md
@@ -0,0 +1,540 @@
+# Customizing Simple Builders
+
+This guide explains how to extend and customize simple-builders by creating custom generators and enhancers.
+
+## Table of Contents
+
+- [Overview](#overview)
+- [Generators vs Enhancers](#generators-vs-enhancers)
+- [Creating Custom Components](#creating-custom-components)
+ - [Custom Method Generators](#custom-method-generators)
+ - [Custom Builder Enhancers](#custom-builder-enhancers)
+- [ServiceLoader Registration](#serviceloader-registration)
+- [Component Override Workflow](#component-override-workflow)
+- [Available Default Components](#available-default-components)
+- [Best Practices](#best-practices)
+- [Examples](#examples)
+
+## Overview
+
+Simple-builders is designed to be extensible through custom generators and enhancers. You can:
+
+- **Override default behavior** by replacing built-in components with custom implementations
+- **Add new functionality** by creating generators for specific use cases
+- **Integrate with frameworks** by creating enhancers that add annotations or methods
+
+All generators are managed by a unified `GeneratorRegistry` that loads both method generators and builder enhancers from a single service file, automatically separating them based on their type (using Java's sealed interface feature)
+
+## Generator Interface
+
+Simple-builders uses a sealed `Generator` interface hierarchy that supports two types of functionality:
+
+```java
+public sealed interface Generator permits MethodGenerator, BuilderEnhancer {
+ int getPriority();
+}
+
+public non-sealed interface MethodGenerator extends Generator {
+ boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context);
+ List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context);
+}
+
+public non-sealed interface BuilderEnhancer extends Generator {
+ boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context);
+ void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context);
+}
+```
+
+This sealed hierarchy ensures type safety while allowing extensibility. **Custom generators should implement `MethodGenerator` or `BuilderEnhancer` directly, not `Generator`:**
+
+### Field-Level Method Generation
+
+Method generators create individual methods for builder fields by implementing `appliesTo(FieldDto, ...)` and `generateMethods()`.
+
+**Use cases**:
+- Custom setter methods (e.g., validation setters)
+- Domain-specific helper methods (e.g., date parsing setters)
+- Integration methods (e.g., with other builders)
+
+### Builder-Level Enhancement
+
+Builder enhancers modify the entire builder class after all field methods are generated by implementing `appliesTo(BuilderDefinitionDto, ...)` and `enhanceBuilder()`.
+
+**Use cases**:
+- Adding annotations (e.g., Jackson, validation)
+- Adding utility methods (e.g., conditional logic)
+- Implementing interfaces (e.g., With, Serializable)
+
+### Combined Generators
+
+A single generator can implement both field-level and builder-level functionality, allowing cohesive features that span both concerns (e.g., validation setters for fields plus a validate() method for the builder)
+
+## Creating Custom Components
+
+### Custom Field-Level Generator
+
+```java
+package com.yourpackage;
+
+import org.javahelpers.simple.builders.processor.generators.MethodGenerator;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodParameterDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+import java.util.List;
+
+public class CustomValidationGenerator implements MethodGenerator {
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ // Only apply to String fields with @Email annotation
+ return "java.lang.String".equals(field.getFieldType().getFullQualifiedName())
+ && field.hasAnnotation("javax.validation.constraints.Email");
+ }
+
+ @Override
+ public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) {
+ String fieldName = field.getFieldName();
+ String methodName = "validated" + capitalize(fieldName);
+
+ // Generate validation setter method
+ MethodDto method = new MethodDto(methodName, builderType);
+
+ // Add parameter
+ String parameterName = fieldName;
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(parameterName);
+ parameter.setParameterTypeName(new TypeName("java.lang", "String"));
+ method.addParameter(parameter);
+
+ method.setCode("""
+ if (!isValidEmail(%s != null && %s.contains("@"))) {
+ throw new IllegalArgumentException("Invalid email: " + %s);
+ }
+ this.%s = $builderFieldWrapper:T.changedValue(%s);
+ return this;
+ """.formatted(parameterName,
+ parameterName,
+ parameterName,
+ fieldName,
+ parameterName));
+ method.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+
+ return List.of(method);
+ }
+
+ @Override
+ public int getPriority() {
+ return 1000; // Higher than default generators
+ }
+}
+```
+
+### Custom Builder-Level Generator
+
+```java
+package com.yourpackage;
+
+import org.javahelpers.simple.builders.processor.generators.BuilderEnhancer;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+public class CustomValidationEnhancer implements BuilderEnhancer {
+
+ @Override
+ public boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ // Only apply to DTOs with validation annotations
+ return builderDto.getAllFieldsForBuilder().stream()
+ .flatMap(field -> field.getParameterAnnotations().stream())
+ .anyMatch(annotation ->
+ annotation.getAnnotationType() != null &&
+ org.apache.commons.lang3.StringUtils.startsWith(
+ annotation.getAnnotationType().getFullQualifiedName(),
+ "javax.validation.constraints."));
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add validation method to builder
+ MethodDto validateMethod = createValidateMethod();
+ builderDto.addCoreMethod(validateMethod);
+
+ // Add @Valid annotation if available
+ if (isValidationAvailable(context)) {
+ AnnotationDto validAnnotation = new AnnotationDto();
+ validAnnotation.setAnnotationType(new TypeName("javax.validation", "Valid"));
+ builderDto.addClassAnnotation(validAnnotation);
+ }
+
+ context.debug("Added validation enhancements to builder %s",
+ builderDto.getBuilderTypeName().getClassName());
+ }
+
+ @Override
+ public int getPriority() {
+ return 500; // Medium priority
+ }
+
+ private MethodDto createValidateMethod() {
+ TypeName returnType = new TypeName("java.lang", "Void");
+ MethodDto method = new MethodDto("validate", returnType);
+ method.setCode("// Validation logic here");
+ return method;
+ }
+
+ private boolean isValidationAvailable(ProcessingContext context) {
+ return context.getTypeElement("javax.validation.Valid") != null;
+ }
+}
+```
+
+## ServiceLoader Registration
+
+To make your custom generators discoverable, create a service file in `META-INF/services/`:
+
+### Generator Registration
+
+Create file: `META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator`
+
+```
+com.yourpackage.CustomValidationGenerator
+com.yourpackage.CustomValidationEnhancer
+com.yourpackage.AnotherCustomGenerator
+```
+
+All generators (whether they provide field-level generation, builder-level enhancement, or both) are registered in the same service file
+
+## Component Override Workflow
+
+To override default components:
+
+1. **Create Custom Generator**: Implement `Generator` interface
+2. **Register via ServiceLoader**: Add to `META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator`
+3. **Deactivate Default**: Use compiler option to disable the default component
+4. **Configure Build**: Add the compiler option to your build configuration
+
+### Example: Override Conditional Logic
+
+1. **Create Custom Generator** (see example above)
+2. **Register in ServiceLoader** (`META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator`):
+ ```
+ com.yourpackage.CustomConditionalEnhancer
+ ```
+3. **Deactivate Default**:
+ ```bash
+ -Asimplebuilder.deactivateGenerationComponents=ConditionalEnhancer
+ ```
+
+### Maven Configuration
+
+```xml
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+
+ -Asimplebuilder.deactivateGenerationComponents=ConditionalEnhancer
+
+
+
+```
+
+### Gradle Configuration
+
+```gradle
+compileJava {
+ options.compilerArgs += ["-Asimplebuilder.deactivateGenerationComponents=ConditionalEnhancer"]
+}
+```
+
+## Available Default Generators
+
+### Field-Level Generators
+
+| Generator | Purpose | Priority |
+|-----------|---------|----------|
+| [`BasicSetterGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BasicSetterGenerator.java) | Basic field setters | 100 |
+| [`SupplierMethodGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SupplierMethodGenerator.java) | Supplier-based setters | 80 |
+| [`FieldConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/FieldConsumerGenerator.java) | Consumer-based setters | 80 |
+| [`BuilderConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderConsumerGenerator.java) | Builder consumer methods | 80 |
+| [`MapConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MapConsumerGenerator.java) | Map consumer methods | 80 |
+| [`ListConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ListConsumerGenerator.java) | List consumer methods | 80 |
+| [`SetConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SetConsumerGenerator.java) | Set consumer methods | 80 |
+| [`StringFormatHelperGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringFormatHelperGenerator.java) | String.format helpers | 80 |
+| [`VarArgsHelperGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/VarArgsHelperGenerator.java) | Varargs helpers | 50 |
+| [`AddToCollectionGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/AddToCollectionGenerator.java) | add2FieldName methods for List/Set | 30 |
+| [`ArrayConversionGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayConversionGenerator.java) | Array-from-List conversion methods | 35 |
+| [`ArrayBuilderConsumerGenerator`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayBuilderConsumerGenerator.java) | ArrayListBuilder consumer methods for arrays | 25 |
+
+### Builder-Level Generators
+
+| Generator | Purpose | Priority |
+|----------|---------|----------|
+| [`ConditionalEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ConditionalEnhancer.java) | Conditional logic methods | 100 |
+| [`JacksonAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java) | Jackson annotations | 100 |
+| [`CoreMethodsEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java) | Core builder methods (build, create, toString) | 100 |
+| [`WithInterfaceEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java) | With interface implementation | 90 |
+| [`InterfaceEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java) | Builder interface implementation | 90 |
+| [`GeneratedAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java) | @Generated annotation | 10 |
+| [`BuilderImplementationAnnotationEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderImplementationAnnotationEnhancer.java) | @BuilderImplementation annotation | 10 |
+| [`ClassJavaDocEnhancer`](../processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java) | Class-level JavaDoc | 10 |
+
+## Best Practices
+
+### Understanding the Architecture
+
+- **Sealed Interface Hierarchy** - `Generator` is sealed and permits only `MethodGenerator` and `BuilderEnhancer`
+- **Unified Registry** - A single `GeneratorRegistry` loads all generators from one service file
+- **Type-Based Separation** - The registry automatically separates generators by type using `instanceof`
+- **Single Service File** - All generators (both method and builder) are registered in one place
+
+### Priority Management
+
+- **Higher priority** = executed first
+- Use priority `> 100` to override defaults
+- Use priority `< 10` for utility enhancers
+
+### Error Handling
+
+```java
+@Override
+public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) {
+ try {
+ // Your generation logic
+ return methods;
+ } catch (Exception e) {
+ context.error("Failed to generate method for field %s: %s", field.getFieldName(), e.getMessage());
+ return List.of(); // Return empty list on error
+ }
+}
+```
+
+### Conditional Application
+
+```java
+@Override
+public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ // Check if your component should apply
+ if (!shouldApply(field, context)) {
+ return false;
+ }
+
+ // Check configuration
+ return context.getConfiguration() != null;
+}
+```
+
+### Testing Custom Components
+
+When testing custom components, you have several approaches depending on your testing setup:
+
+#### Option 1: Integration Testing with Maven/Gradle
+
+Create a test project and verify the generated code manually:
+
+```bash
+# 1. Create a test DTO with your custom annotations
+# 2. Compile with your custom component and deactivated default
+mvn compile -Asimplebuilder.deactivateGenerationComponents=BasicSetterGenerator
+
+# 3. Check the generated builder code
+cat target/generated-sources/annotations/your/package/YourDtoBuilder.java
+```
+
+#### Option 2: Using google-compile-testing (Advanced)
+
+If you want to use the same testing framework as simple-builders:
+
+```xml
+
+
+ com.google.testing.compile
+ compile-testing
+ 0.21.0
+ test
+
+```
+
+```java
+@Test
+void customGeneratorIntegrationTest() {
+ Compilation compilation = javac()
+ .withProcessors(new BuilderProcessor())
+ .withOptions("-Asimplebuilder.deactivateGenerationComponents=BasicSetterGenerator")
+ .compile(forSourceString("test.TestDto", TEST_DTO));
+
+ assertThat(compilation).succeeded();
+
+ // Load and verify generated code
+ JavaFileObject generatedBuilder = compilation.generatedSource("test.TestDtoBuilder");
+ String content = generatedBuilder.getSourceContents();
+
+ // Verify your custom methods are present
+ assertThat(content).contains("validatedEmail(");
+ assertThat(content).contains("isValidEmail");
+}
+```
+
+#### Manual Verification Steps
+
+1. **Compile your project** with the custom component
+2. **Check generated builder code** in `target/generated-sources/annotations/`
+3. **Run integration tests** to ensure the builder works correctly
+4. **Verify edge cases** by testing with different field types and annotations
+
+## Examples
+
+### Example 1: Custom Date Parser Generator
+
+Creates setters that parse string dates into `LocalDate`:
+
+```java
+public class DateParserGenerator implements MethodGenerator {
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ return "java.time.LocalDate".equals(field.getFieldType().getFullQualifiedName())
+ && field.hasAnnotation("com.example.ParseFromString");
+ }
+
+ @Override
+ public List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context) {
+ String fieldName = field.getFieldName();
+ String methodName = fieldName + "FromString";
+
+ MethodDto method = new MethodDto(methodName, builderType);
+
+ // Add parameter
+ String parameterName = fieldName + "String";
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(parameterName);
+ parameter.setParameterTypeName(new TypeName("java.lang", "String"));
+ method.addParameter(parameter);
+
+ method.setCode("""
+ try {
+ return this.%s(LocalDate.parse(%s));
+ } catch (DateTimeParseException e) {
+ throw new IllegalArgumentException("Invalid date format: " + %s, e);
+ }
+ """.formatted(fieldName, parameterName, parameterName));
+
+ return List.of(method);
+ }
+
+ @Override
+ public int getPriority() {
+ return 200; // Higher than basic setters
+ }
+}
+```
+
+### Example 2: Custom Builder Factory Generator
+
+Adds static factory methods to builders:
+
+```java
+public class BuilderFactoryEnhancer implements BuilderEnhancer {
+
+ @Override
+ public boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return dtoType.hasAnnotation("com.example.BuilderFactory");
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ TypeName builderType = builderDto.getBuilderTypeName();
+ TypeName dtoType = builderDto.getTargetTypeName();
+
+ // Add static factory method
+ MethodDto factoryMethod = new MethodDto("from", builderType);
+ factoryMethod.setStatic(true);
+
+ // Add parameter
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName("template");
+ parameter.setParameterTypeName(dtoType);
+ factoryMethod.addParameter(parameter);
+
+ factoryMethod.setCode("return new %s(template);");
+ factoryMethod.addArgument("builderType", builderType.getClassName());
+
+ builderDto.addStaticMethod(factoryMethod);
+
+ context.debug("Added factory method to builder %s", builderType.getClassName());
+ }
+
+ @Override
+ public int getPriority() {
+ return 50; // Low priority, runs after most enhancements
+ }
+}
+```
+
+### Example 3: Custom Validation Integration
+
+Integrates with Bean Validation API:
+
+```java
+public class BeanValidationEnhancer implements BuilderEnhancer {
+
+ @Override
+ public boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return isBeanValidationAvailable(context);
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add validate() method
+ MethodDto validateMethod = createValidateMethod(builderDto);
+ builderDto.addCoreMethod(validateMethod);
+
+ // Add @Validated annotation
+ AnnotationDto validatedAnnotation = new AnnotationDto();
+ validatedAnnotation.setAnnotationType(new TypeName("org.springframework.validation.annotation", "Validated"));
+ builderDto.addClassAnnotation(validatedAnnotation);
+ }
+
+ @Override
+ public int getPriority() {
+ return 200; // High priority for validation
+ }
+
+ private boolean isBeanValidationAvailable(ProcessingContext context) {
+ return context.getTypeElement("javax.validation.Validator") != null;
+ }
+}
+```
+
+## Integration with Frameworks
+
+### Spring Integration
+
+```java
+public class SpringBuilderEnhancer implements BuilderEnhancer {
+
+ @Override
+ public boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return isSpringAvailable(context);
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add @Component annotation
+ AnnotationDto componentAnnotation = new AnnotationDto();
+ componentAnnotation.setAnnotationType(new TypeName("org.springframework.stereotype", "Component"));
+ componentAnnotation.addMember("value", "\"" + builderDto.getTargetTypeName().getClassName() + "Builder\"");
+ builderDto.addClassAnnotation(componentAnnotation);
+ }
+
+ private boolean isSpringAvailable(ProcessingContext context) {
+ return context.getTypeElement("org.springframework.stereotype.Component") != null;
+ }
+}
+```
+
+For more examples, see the source code of the built-in generators and enhancers.
diff --git a/example/pom.xml b/example/pom.xml
index e58291dc..a239bf2d 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -101,7 +101,7 @@
default-testCompile
-
+
none
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/BookDtoBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/BookDtoBuilder.java
deleted file mode 100644
index 704c96fd..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/BookDtoBuilder.java
+++ /dev/null
@@ -1,469 +0,0 @@
-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.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-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.BookDto}.
- */
-public class BookDtoBuilder {
- /**
- * Tracked value for title: the book title to set.
- */
- private TrackedValue title = unsetValue();
-
- /**
- * Tracked value for author: the book author to set.
- */
- private TrackedValue author = unsetValue();
-
- /**
- * Tracked value for isbn: the ISBN to set.
- */
- private TrackedValue isbn = unsetValue();
-
- /**
- * Tracked value for pages: the page count to set.
- */
- private TrackedValue pages = unsetValue();
-
- /**
- * Tracked value for price: the book price to set.
- */
- private TrackedValue price = unsetValue();
-
- /**
- * Tracked value for exactPrice: the exact book price to set.
- */
- private TrackedValue exactPrice = unsetValue();
-
- /**
- * Tracked value for available: true if available, false otherwise.
- */
- private TrackedValue available = unsetValue();
-
- /**
- * Tracked value for rating: the book rating to set.
- */
- private TrackedValue rating = unsetValue();
-
- /**
- * Tracked value for edition: the edition number to set.
- */
- private TrackedValue edition = unsetValue();
-
- /**
- * Tracked value for salesCount: the sales count to set.
- */
- private TrackedValue salesCount = unsetValue();
-
- /**
- * Tracked value for discount: the discount percentage to set.
- */
- private TrackedValue discount = unsetValue();
-
- /**
- * Tracked value for category: the category code to set.
- */
- private TrackedValue category = unsetValue();
-
- /**
- * Tracked value for publishDate: the publication date to set.
- */
- private TrackedValue publishDate = unsetValue();
-
- /**
- * Tracked value for lastUpdated: the last update timestamp to set.
- */
- private TrackedValue lastUpdated = unsetValue();
-
- /**
- * Tracked value for subtitle: an Optional containing the subtitle to set.
- */
- private TrackedValue> subtitle = unsetValue();
-
- /**
- * Tracked value for tags: the list of tags to set.
- */
- private TrackedValue> tags = unsetValue();
-
- /**
- * Tracked value for genres: the set of genres to set.
- */
- private TrackedValue> genres = unsetValue();
-
- /**
- * Tracked value for metadata: the metadata map to set.
- */
- private TrackedValue> metadata = unsetValue();
-
- /**
- * Tracked value for publisher: the publisher to set.
- */
- private TrackedValue publisher = unsetValue();
-
- /**
- * Initialisation of builder for {@code org.javahelpers.simple.builders.example.BookDto} by a instance.
- *
- * @param instance object instance for initialisiation
- */
- public BookDtoBuilder(BookDto instance) {
- this.title = initialValue(instance.getTitle());
- this.author = initialValue(instance.getAuthor());
- this.isbn = initialValue(instance.getIsbn());
- this.pages = initialValue(instance.getPages());
- if (this.pages.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'pages' is marked as non-null but source object has null value");
- }
- this.price = initialValue(instance.getPrice());
- if (this.price.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'price' is marked as non-null but source object has null value");
- }
- this.exactPrice = initialValue(instance.getExactPrice());
- this.available = initialValue(instance.isAvailable());
- if (this.available.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'available' is marked as non-null but source object has null value");
- }
- this.rating = initialValue(instance.getRating());
- if (this.rating.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'rating' is marked as non-null but source object has null value");
- }
- this.edition = initialValue(instance.getEdition());
- if (this.edition.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'edition' is marked as non-null but source object has null value");
- }
- this.salesCount = initialValue(instance.getSalesCount());
- if (this.salesCount.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'salesCount' is marked as non-null but source object has null value");
- }
- this.discount = initialValue(instance.getDiscount());
- if (this.discount.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'discount' is marked as non-null but source object has null value");
- }
- this.category = initialValue(instance.getCategory());
- if (this.category.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'category' is marked as non-null but source object has null value");
- }
- this.publishDate = initialValue(instance.getPublishDate());
- this.lastUpdated = initialValue(instance.getLastUpdated());
- this.subtitle = initialValue(instance.getSubtitle());
- this.tags = initialValue(instance.getTags());
- this.genres = initialValue(instance.getGenres());
- this.metadata = initialValue(instance.getMetadata());
- this.publisher = initialValue(instance.getPublisher());
- }
-
- /**
- * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.BookDto}.
- */
- public BookDtoBuilder() {
- }
-
- /**
- * Sets the value for author.
- *
- * @param author the book author to set
- * @return current instance of builder
- */
- public BookDtoBuilder author(String author) {
- this.author = changedValue(author);
- return this;
- }
-
- /**
- * Sets the value for available.
- *
- * @param available true if available, false otherwise
- * @return current instance of builder
- */
- public BookDtoBuilder available(boolean available) {
- this.available = changedValue(available);
- return this;
- }
-
- /**
- * Sets the value for category.
- *
- * @param category the category code to set
- * @return current instance of builder
- */
- public BookDtoBuilder category(char category) {
- this.category = changedValue(category);
- return this;
- }
-
- /**
- * Sets the value for discount.
- *
- * @param discount the discount percentage to set
- * @return current instance of builder
- */
- public BookDtoBuilder discount(float discount) {
- this.discount = changedValue(discount);
- return this;
- }
-
- /**
- * Sets the value for edition.
- *
- * @param edition the edition number to set
- * @return current instance of builder
- */
- public BookDtoBuilder edition(short edition) {
- this.edition = changedValue(edition);
- return this;
- }
-
- /**
- * Sets the value for exactPrice.
- *
- * @param exactPrice the exact book price to set
- * @return current instance of builder
- */
- public BookDtoBuilder exactPrice(BigDecimal exactPrice) {
- this.exactPrice = changedValue(exactPrice);
- return this;
- }
-
- /**
- * Sets the value for genres.
- *
- * @param genres the set of genres to set
- * @return current instance of builder
- */
- public BookDtoBuilder genres(Set genres) {
- this.genres = changedValue(genres);
- return this;
- }
-
- /**
- * Sets the value for isbn.
- *
- * @param isbn the ISBN to set
- * @return current instance of builder
- */
- public BookDtoBuilder isbn(String isbn) {
- this.isbn = changedValue(isbn);
- return this;
- }
-
- /**
- * Sets the value for lastUpdated.
- *
- * @param lastUpdated the last update timestamp to set
- * @return current instance of builder
- */
- public BookDtoBuilder lastUpdated(LocalDateTime lastUpdated) {
- this.lastUpdated = changedValue(lastUpdated);
- return this;
- }
-
- /**
- * Sets the value for metadata.
- *
- * @param metadata the metadata map to set
- * @return current instance of builder
- */
- public BookDtoBuilder metadata(Map metadata) {
- this.metadata = changedValue(metadata);
- return this;
- }
-
- /**
- * Sets the value for pages.
- *
- * @param pages the page count to set
- * @return current instance of builder
- */
- public BookDtoBuilder pages(int pages) {
- this.pages = changedValue(pages);
- return this;
- }
-
- /**
- * Sets the value for price.
- *
- * @param price the book price to set
- * @return current instance of builder
- */
- public BookDtoBuilder price(double price) {
- this.price = changedValue(price);
- return this;
- }
-
- /**
- * Sets the value for publishDate.
- *
- * @param publishDate the publication date to set
- * @return current instance of builder
- */
- public BookDtoBuilder publishDate(LocalDate publishDate) {
- this.publishDate = changedValue(publishDate);
- return this;
- }
-
- /**
- * Sets the value for publisher.
- *
- * @param publisher the publisher to set
- * @return current instance of builder
- */
- public BookDtoBuilder publisher(PersonDto publisher) {
- this.publisher = changedValue(publisher);
- return this;
- }
-
- /**
- * Sets the value for rating.
- *
- * @param rating the book rating to set
- * @return current instance of builder
- */
- public BookDtoBuilder rating(byte rating) {
- this.rating = changedValue(rating);
- return this;
- }
-
- /**
- * Sets the value for salesCount.
- *
- * @param salesCount the sales count to set
- * @return current instance of builder
- */
- public BookDtoBuilder salesCount(long salesCount) {
- this.salesCount = changedValue(salesCount);
- return this;
- }
-
- /**
- * Sets the value for subtitle.
- *
- * @param subtitle an Optional containing the subtitle to set
- * @return current instance of builder
- */
- public BookDtoBuilder subtitle(Optional subtitle) {
- this.subtitle = changedValue(subtitle);
- return this;
- }
-
- /**
- * Sets the value for tags.
- *
- * @param tags the list of tags to set
- * @return current instance of builder
- */
- public BookDtoBuilder tags(List tags) {
- this.tags = changedValue(tags);
- return this;
- }
-
- /**
- * Sets the value for title.
- *
- * @param title the book title to set
- * @return current instance of builder
- */
- public BookDtoBuilder title(String title) {
- this.title = changedValue(title);
- return this;
- }
-
- public BookDto build() {
- if (this.pages.isSet() && this.pages.value() == null) {
- throw new IllegalStateException("Field 'pages' is marked as non-null but null value was provided");
- }
- if (this.price.isSet() && this.price.value() == null) {
- throw new IllegalStateException("Field 'price' is marked as non-null but null value was provided");
- }
- if (this.available.isSet() && this.available.value() == null) {
- throw new IllegalStateException("Field 'available' is marked as non-null but null value was provided");
- }
- if (this.rating.isSet() && this.rating.value() == null) {
- throw new IllegalStateException("Field 'rating' is marked as non-null but null value was provided");
- }
- if (this.edition.isSet() && this.edition.value() == null) {
- throw new IllegalStateException("Field 'edition' is marked as non-null but null value was provided");
- }
- if (this.salesCount.isSet() && this.salesCount.value() == null) {
- throw new IllegalStateException("Field 'salesCount' is marked as non-null but null value was provided");
- }
- if (this.discount.isSet() && this.discount.value() == null) {
- throw new IllegalStateException("Field 'discount' is marked as non-null but null value was provided");
- }
- if (this.category.isSet() && this.category.value() == null) {
- throw new IllegalStateException("Field 'category' is marked as non-null but null value was provided");
- }
- BookDto result = new BookDto();
- this.title.ifSet(result::setTitle);
- this.author.ifSet(result::setAuthor);
- this.isbn.ifSet(result::setIsbn);
- this.pages.ifSet(result::setPages);
- this.price.ifSet(result::setPrice);
- this.exactPrice.ifSet(result::setExactPrice);
- this.available.ifSet(result::setAvailable);
- this.rating.ifSet(result::setRating);
- this.edition.ifSet(result::setEdition);
- this.salesCount.ifSet(result::setSalesCount);
- this.discount.ifSet(result::setDiscount);
- this.category.ifSet(result::setCategory);
- this.publishDate.ifSet(result::setPublishDate);
- this.lastUpdated.ifSet(result::setLastUpdated);
- this.subtitle.ifSet(result::setSubtitle);
- this.tags.ifSet(result::setTags);
- this.genres.ifSet(result::setGenres);
- this.metadata.ifSet(result::setMetadata);
- this.publisher.ifSet(result::setPublisher);
- return result;
- }
-
- /**
- * Creating a new builder for {@code org.javahelpers.simple.builders.example.BookDto}.
- *
- * @return builder for {@code org.javahelpers.simple.builders.example.BookDto}
- */
- public static BookDtoBuilder create() {
- return new BookDtoBuilder();
- }
-
- /**
- * 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("title", this.title)
- .append("author", this.author)
- .append("isbn", this.isbn)
- .append("pages", this.pages)
- .append("price", this.price)
- .append("exactPrice", this.exactPrice)
- .append("available", this.available)
- .append("rating", this.rating)
- .append("edition", this.edition)
- .append("salesCount", this.salesCount)
- .append("discount", this.discount)
- .append("category", this.category)
- .append("publishDate", this.publishDate)
- .append("lastUpdated", this.lastUpdated)
- .append("subtitle", this.subtitle)
- .append("tags", this.tags)
- .append("genres", this.genres)
- .append("metadata", this.metadata)
- .append("publisher", this.publisher)
- .toString();
- }
-}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java
deleted file mode 100644
index b52495c9..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java
+++ /dev/null
@@ -1,260 +0,0 @@
-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.HashSet;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import javax.annotation.processing.Generated;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
-import org.javahelpers.simple.builders.core.builders.HashSetBuilderWithElementBuilders;
-import org.javahelpers.simple.builders.core.interfaces.IBuilderBase;
-import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
-import org.javahelpers.simple.builders.core.util.TrackedValue;
-
-/**
- * Builder for {@code org.javahelpers.simple.builders.example.MannschaftDto}.
- */
-@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
-@BuilderImplementation(
- forClass = MannschaftDto.class
-)
-public class MannschaftDtoBuilder implements IBuilderBase {
- /**
- * Tracked value for name: name.
- */
- private TrackedValue name = unsetValue();
-
- /**
- * Tracked value for sponsoren: sponsoren.
- */
- private TrackedValue> sponsoren = unsetValue();
-
- /**
- * Initialisation of builder for {@code org.javahelpers.simple.builders.example.MannschaftDto} by a instance.
- *
- * @param instance object instance for initialisiation
- */
- public MannschaftDtoBuilder(MannschaftDto instance) {
- this.name = initialValue(instance.getName());
- this.sponsoren = initialValue(instance.getSponsoren());
- }
-
- /**
- * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.MannschaftDto}.
- */
- public MannschaftDtoBuilder() {
- }
-
- /**
- * Adds a single element to 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.
- *
- * @param name name
- * @return current instance of builder
- */
- public MannschaftDtoBuilder name(String name) {
- this.name = changedValue(name);
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param format name
- * @param args name
- * @return current instance of builder
- */
- public MannschaftDtoBuilder name(String format, Object... args) {
- this.name = changedValue(String.format(format, args));
- return this;
- }
-
- /**
- * Sets the value for name by executing the provided consumer.
- *
- * @param nameStringBuilderConsumer consumer providing an instance of name
- * @return current instance of builder
- */
- public MannschaftDtoBuilder name(Consumer nameStringBuilderConsumer) {
- StringBuilder builder = new StringBuilder();
- nameStringBuilderConsumer.accept(builder);
- this.name = changedValue(builder.toString());
- return this;
- }
-
- /**
- * Sets the value for name by invoking the provided supplier.
- *
- * @param nameSupplier supplier for name
- * @return current instance of builder
- */
- public MannschaftDtoBuilder name(Supplier nameSupplier) {
- this.name = changedValue(nameSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for sponsoren.
- *
- * @param sponsoren sponsoren
- * @return current instance of builder
- */
- public MannschaftDtoBuilder sponsoren(Set sponsoren) {
- this.sponsoren = changedValue(sponsoren);
- return this;
- }
-
- /**
- * Sets the value for sponsoren using a builder consumer that produces the value.
- *
- * @param sponsorenBuilderConsumer consumer providing an instance of a builder for sponsoren
- * @return current instance of builder
- */
- public MannschaftDtoBuilder sponsoren(
- Consumer> sponsorenBuilderConsumer) {
- HashSetBuilderWithElementBuilders builder = this.sponsoren.isSet() ? new HashSetBuilderWithElementBuilders(this.sponsoren.value(), SponsorDtoBuilder::create) : new HashSetBuilderWithElementBuilders(SponsorDtoBuilder::create);
- sponsorenBuilderConsumer.accept(builder);
- this.sponsoren = changedValue(builder.build());
- return this;
- }
-
- /**
- * Sets the value for sponsoren by invoking the provided supplier.
- *
- * @param sponsorenSupplier supplier for sponsoren
- * @return current instance of builder
- */
- public MannschaftDtoBuilder sponsoren(Supplier> sponsorenSupplier) {
- this.sponsoren = changedValue(sponsorenSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for sponsoren.
- *
- * @param sponsoren sponsoren
- * @return current instance of builder
- */
- public MannschaftDtoBuilder sponsoren(SponsorDto... sponsoren) {
- this.sponsoren = changedValue(Set.of(sponsoren));
- return this;
- }
-
- @Override
- public MannschaftDto build() {
- MannschaftDto result = new MannschaftDto();
- this.name.ifSet(result::setName);
- this.sponsoren.ifSet(result::setSponsoren);
- return result;
- }
-
- /**
- * Creating a new builder for {@code org.javahelpers.simple.builders.example.MannschaftDto}.
- *
- * @return builder for {@code org.javahelpers.simple.builders.example.MannschaftDto}
- */
- public static MannschaftDtoBuilder create() {
- return new MannschaftDtoBuilder();
- }
-
- /**
- * 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 MannschaftDtoBuilder 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 MannschaftDtoBuilder conditional(BooleanSupplier condition,
- Consumer yesCondition) {
- return conditional(condition, yesCondition, null);
- }
-
- /**
- * 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("sponsoren", this.sponsoren)
- .toString();
- }
-
- /**
- * Interface that can be implemented by the DTO to provide fluent modification methods.
- */
- public interface With {
- /**
- * Applies modifications to a builder initialized from this instance and returns the built object.
- *
- * @param b the consumer to apply modifications
- * @return the modified instance
- */
- default MannschaftDto with(Consumer b) {
- MannschaftDtoBuilder builder;
- try {
- builder = new MannschaftDtoBuilder(MannschaftDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'MannschaftDtoBuilder.With' should only be implemented by classes, which could be casted to 'MannschaftDto'", ex);
- }
- b.accept(builder);
- return builder.build();
- }
-
- /**
- * Creates a builder initialized from this instance.
- *
- * @return a builder initialized with this instance's values
- */
- default MannschaftDtoBuilder with() {
- try {
- return new MannschaftDtoBuilder(MannschaftDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'MannschaftDtoBuilder.With' should only be implemented by classes, which could be casted to 'MannschaftDto'", ex);
- }
- }
- }
-}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/PersonDtoBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/PersonDtoBuilder.java
deleted file mode 100644
index 1941b335..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/PersonDtoBuilder.java
+++ /dev/null
@@ -1,385 +0,0 @@
-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.time.LocalDate;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import javax.annotation.processing.Generated;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
-import org.javahelpers.simple.builders.core.builders.ArrayListBuilder;
-import org.javahelpers.simple.builders.core.interfaces.IBuilderBase;
-import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
-import org.javahelpers.simple.builders.core.util.TrackedValue;
-
-/**
- * Builder for {@code org.javahelpers.simple.builders.example.PersonDto}.
- */
-@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
-@BuilderImplementation(
- forClass = PersonDto.class
-)
-public class PersonDtoBuilder implements IBuilderBase {
- /**
- * Tracked value for name: name.
- */
- private TrackedValue name = unsetValue();
-
- /**
- * Tracked value for nickNames: nickNames.
- */
- private TrackedValue> nickNames = unsetValue();
-
- /**
- * Tracked value for nickNames2: nickNames2.
- */
- private TrackedValue nickNames2 = unsetValue();
-
- /**
- * Tracked value for birthdate: birthdate.
- */
- private TrackedValue birthdate = unsetValue();
-
- /**
- * Tracked value for mannschaft: mannschaft.
- */
- private TrackedValue mannschaft = unsetValue();
-
- /**
- * Initialisation of builder for {@code org.javahelpers.simple.builders.example.PersonDto} by a instance.
- *
- * @param instance object instance for initialisiation
- */
- public PersonDtoBuilder(PersonDto instance) {
- this.name = initialValue(instance.getName());
- this.nickNames = initialValue(instance.getNickNames());
- this.birthdate = initialValue(instance.getBirthdate());
- this.mannschaft = initialValue(instance.getMannschaft());
- }
-
- /**
- * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.PersonDto}.
- */
- public PersonDtoBuilder() {
- }
-
- /**
- * Adds a single element to nickNames.
- *
- * @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;
- }
-
- /**
- * Sets the value for birthdate.
- *
- * @param birthdate birthdate
- * @return current instance of builder
- */
- public PersonDtoBuilder birthdate(LocalDate birthdate) {
- this.birthdate = changedValue(birthdate);
- return this;
- }
-
- /**
- * Sets the value for birthdate by invoking the provided supplier.
- *
- * @param birthdateSupplier supplier for birthdate
- * @return current instance of builder
- */
- public PersonDtoBuilder birthdate(Supplier birthdateSupplier) {
- this.birthdate = changedValue(birthdateSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for mannschaft using a builder consumer that produces the value.
- *
- * @param mannschaftBuilderConsumer consumer providing an instance of a builder for mannschaft
- * @return current instance of builder
- */
- public PersonDtoBuilder mannschaft(Consumer mannschaftBuilderConsumer) {
- MannschaftDtoBuilder builder = this.mannschaft.isSet() ? new MannschaftDtoBuilder(this.mannschaft.value()) : new MannschaftDtoBuilder();
- mannschaftBuilderConsumer.accept(builder);
- this.mannschaft = changedValue(builder.build());
- return this;
- }
-
- /**
- * Sets the value for mannschaft by invoking the provided supplier.
- *
- * @param mannschaftSupplier supplier for mannschaft
- * @return current instance of builder
- */
- public PersonDtoBuilder mannschaft(Supplier mannschaftSupplier) {
- this.mannschaft = changedValue(mannschaftSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for mannschaft.
- *
- * @param mannschaft mannschaft
- * @return current instance of builder
- */
- public PersonDtoBuilder mannschaft(MannschaftDto mannschaft) {
- this.mannschaft = changedValue(mannschaft);
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param name name
- * @return current instance of builder
- */
- public PersonDtoBuilder name(String name) {
- this.name = changedValue(name);
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param format name
- * @param args name
- * @return current instance of builder
- */
- public PersonDtoBuilder name(String format, Object... args) {
- this.name = changedValue(String.format(format, args));
- return this;
- }
-
- /**
- * Sets the value for name by executing the provided consumer.
- *
- * @param nameStringBuilderConsumer consumer providing an instance of name
- * @return current instance of builder
- */
- public PersonDtoBuilder name(Consumer nameStringBuilderConsumer) {
- StringBuilder builder = new StringBuilder();
- nameStringBuilderConsumer.accept(builder);
- this.name = changedValue(builder.toString());
- return this;
- }
-
- /**
- * Sets the value for name by invoking the provided supplier.
- *
- * @param nameSupplier supplier for name
- * @return current instance of builder
- */
- public PersonDtoBuilder name(Supplier nameSupplier) {
- this.name = changedValue(nameSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for nickNames.
- *
- * @param nickNames nickNames
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames(String... nickNames) {
- this.nickNames = changedValue(List.of(nickNames));
- return this;
- }
-
- /**
- * Sets the value for nickNames.
- *
- * @param nickNames nickNames
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames(List nickNames) {
- this.nickNames = changedValue(nickNames);
- return this;
- }
-
- /**
- * Sets the value for nickNames using a builder consumer that produces the value.
- *
- * @param nickNamesBuilderConsumer consumer providing an instance of a builder for nickNames
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames(Consumer> nickNamesBuilderConsumer) {
- ArrayListBuilder builder = this.nickNames.isSet() ? new ArrayListBuilder(this.nickNames.value()) : new ArrayListBuilder();
- nickNamesBuilderConsumer.accept(builder);
- this.nickNames = changedValue(builder.build());
- return this;
- }
-
- /**
- * Sets the value for nickNames by invoking the provided supplier.
- *
- * @param nickNamesSupplier supplier for nickNames
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames(Supplier> nickNamesSupplier) {
- this.nickNames = changedValue(nickNamesSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for nickNames2.
- *
- * @param nickNames2 nickNames2
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames2(String... nickNames2) {
- this.nickNames2 = changedValue(nickNames2);
- return this;
- }
-
- /**
- * Sets the value for nickNames2.
- *
- * @param nickNames2 nickNames2
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames2(List nickNames2) {
- this.nickNames2 = changedValue(nickNames2.toArray(new String[0]));
- return this;
- }
-
- /**
- * Sets the value for nickNames2 using the fluent builder consumer.
- *
- * @param nickNames2BuilderConsumer consumer for nickNames2
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames2(Consumer> nickNames2BuilderConsumer) {
- ArrayListBuilder builder = this.nickNames2.isSet() ? new ArrayListBuilder(java.util.List.of(this.nickNames2.value())) : new ArrayListBuilder();
- nickNames2BuilderConsumer.accept(builder);
- this.nickNames2 = changedValue(builder.build().toArray(new String[0]));
- return this;
- }
-
- /**
- * Sets the value for nickNames2 by invoking the provided supplier.
- *
- * @param nickNames2Supplier supplier for nickNames2
- * @return current instance of builder
- */
- public PersonDtoBuilder nickNames2(Supplier nickNames2Supplier) {
- this.nickNames2 = changedValue(nickNames2Supplier.get());
- return this;
- }
-
- @Override
- public PersonDto build() {
- PersonDto result = new PersonDto(this.name.value());
- this.nickNames.ifSet(result::setNickNames);
- this.nickNames2.ifSet(result::setNickNames2);
- this.birthdate.ifSet(result::setBirthdate);
- this.mannschaft.ifSet(result::setMannschaft);
- return result;
- }
-
- /**
- * Creating a new builder for {@code org.javahelpers.simple.builders.example.PersonDto}.
- *
- * @return builder for {@code org.javahelpers.simple.builders.example.PersonDto}
- */
- public static PersonDtoBuilder create() {
- return new PersonDtoBuilder();
- }
-
- /**
- * 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 PersonDtoBuilder 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 PersonDtoBuilder conditional(BooleanSupplier condition,
- Consumer yesCondition) {
- return conditional(condition, yesCondition, null);
- }
-
- /**
- * 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("nickNames", this.nickNames)
- .append("nickNames2", this.nickNames2)
- .append("birthdate", this.birthdate)
- .append("mannschaft", this.mannschaft)
- .toString();
- }
-
- /**
- * Interface that can be implemented by the DTO to provide fluent modification methods.
- */
- public interface With {
- /**
- * Applies modifications to a builder initialized from this instance and returns the built object.
- *
- * @param b the consumer to apply modifications
- * @return the modified instance
- */
- default PersonDto with(Consumer b) {
- PersonDtoBuilder builder;
- try {
- builder = new PersonDtoBuilder(PersonDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'PersonDtoBuilder.With' should only be implemented by classes, which could be casted to 'PersonDto'", ex);
- }
- b.accept(builder);
- return builder.build();
- }
-
- /**
- * Creates a builder initialized from this instance.
- *
- * @return a builder initialized with this instance's values
- */
- default PersonDtoBuilder with() {
- try {
- return new PersonDtoBuilder(PersonDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'PersonDtoBuilder.With' should only be implemented by classes, which could be casted to 'PersonDto'", ex);
- }
- }
- }
-}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/ProductRecordBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/ProductRecordBuilder.java
deleted file mode 100644
index eb6fdee0..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/ProductRecordBuilder.java
+++ /dev/null
@@ -1,275 +0,0 @@
-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.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import javax.annotation.processing.Generated;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
-import org.javahelpers.simple.builders.core.interfaces.IBuilderBase;
-import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
-import org.javahelpers.simple.builders.core.util.TrackedValue;
-
-/**
- * Builder for {@code org.javahelpers.simple.builders.example.ProductRecord}.
- */
-@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
-@BuilderImplementation(
- forClass = ProductRecord.class
-)
-public class ProductRecordBuilder implements IBuilderBase {
- /**
- * Tracked value for name: name.
- */
- private TrackedValue name = unsetValue();
-
- /**
- * Tracked value for price: price.
- */
- private TrackedValue price = unsetValue();
-
- /**
- * Tracked value for category: category.
- */
- private TrackedValue category = unsetValue();
-
- /**
- * Initialisation of builder for {@code org.javahelpers.simple.builders.example.ProductRecord} by a instance.
- *
- * @param instance object instance for initialisiation
- */
- public ProductRecordBuilder(ProductRecord instance) {
- this.name = initialValue(instance.name());
- this.price = initialValue(instance.price());
- if (this.price.value() == null) {
- throw new IllegalArgumentException("Cannot initialize builder from instance: field 'price' is marked as non-null but source object has null value");
- }
- this.category = initialValue(instance.category());
- }
-
- /**
- * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.ProductRecord}.
- */
- public ProductRecordBuilder() {
- }
-
- /**
- * Sets the value for category.
- *
- * @param category category
- * @return current instance of builder
- */
- public ProductRecordBuilder category(String category) {
- this.category = changedValue(category);
- return this;
- }
-
- /**
- * Sets the value for category.
- *
- * @param format category
- * @param args category
- * @return current instance of builder
- */
- public ProductRecordBuilder category(String format, Object... args) {
- this.category = changedValue(String.format(format, args));
- return this;
- }
-
- /**
- * Sets the value for category by executing the provided consumer.
- *
- * @param categoryStringBuilderConsumer consumer providing an instance of category
- * @return current instance of builder
- */
- public ProductRecordBuilder category(Consumer categoryStringBuilderConsumer) {
- StringBuilder builder = new StringBuilder();
- categoryStringBuilderConsumer.accept(builder);
- this.category = changedValue(builder.toString());
- return this;
- }
-
- /**
- * Sets the value for category by invoking the provided supplier.
- *
- * @param categorySupplier supplier for category
- * @return current instance of builder
- */
- public ProductRecordBuilder category(Supplier categorySupplier) {
- this.category = changedValue(categorySupplier.get());
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param name name
- * @return current instance of builder
- */
- public ProductRecordBuilder name(String name) {
- this.name = changedValue(name);
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param format name
- * @param args name
- * @return current instance of builder
- */
- public ProductRecordBuilder name(String format, Object... args) {
- this.name = changedValue(String.format(format, args));
- return this;
- }
-
- /**
- * Sets the value for name by executing the provided consumer.
- *
- * @param nameStringBuilderConsumer consumer providing an instance of name
- * @return current instance of builder
- */
- public ProductRecordBuilder name(Consumer nameStringBuilderConsumer) {
- StringBuilder builder = new StringBuilder();
- nameStringBuilderConsumer.accept(builder);
- this.name = changedValue(builder.toString());
- return this;
- }
-
- /**
- * Sets the value for name by invoking the provided supplier.
- *
- * @param nameSupplier supplier for name
- * @return current instance of builder
- */
- public ProductRecordBuilder name(Supplier nameSupplier) {
- this.name = changedValue(nameSupplier.get());
- return this;
- }
-
- /**
- * Sets the value for price.
- *
- * @param price price
- * @return current instance of builder
- */
- public ProductRecordBuilder price(double price) {
- this.price = changedValue(price);
- return this;
- }
-
- /**
- * Sets the value for price by invoking the provided supplier.
- *
- * @param priceSupplier supplier for price
- * @return current instance of builder
- */
- public ProductRecordBuilder price(Supplier priceSupplier) {
- this.price = changedValue(priceSupplier.get());
- return this;
- }
-
- @Override
- public ProductRecord build() {
- if (!this.price.isSet()) {
- throw new IllegalStateException("Required field 'price' must be set before calling build()");
- }
- if (this.price.value() == null) {
- throw new IllegalStateException("Field 'price' is marked as non-null but null value was provided");
- }
- ProductRecord result = new ProductRecord(this.name.value(), this.price.value(), this.category.value());
- return result;
- }
-
- /**
- * Creating a new builder for {@code org.javahelpers.simple.builders.example.ProductRecord}.
- *
- * @return builder for {@code org.javahelpers.simple.builders.example.ProductRecord}
- */
- public static ProductRecordBuilder create() {
- return new ProductRecordBuilder();
- }
-
- /**
- * 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 ProductRecordBuilder 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 ProductRecordBuilder conditional(BooleanSupplier condition,
- Consumer yesCondition) {
- return conditional(condition, yesCondition, null);
- }
-
- /**
- * 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("price", this.price)
- .append("category", this.category)
- .toString();
- }
-
- /**
- * Interface that can be implemented by the DTO to provide fluent modification methods.
- */
- public interface With {
- /**
- * Applies modifications to a builder initialized from this instance and returns the built object.
- *
- * @param b the consumer to apply modifications
- * @return the modified instance
- */
- default ProductRecord with(Consumer b) {
- ProductRecordBuilder builder;
- try {
- builder = new ProductRecordBuilder(ProductRecord.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'ProductRecordBuilder.With' should only be implemented by classes, which could be casted to 'ProductRecord'", ex);
- }
- b.accept(builder);
- return builder.build();
- }
-
- /**
- * Creates a builder initialized from this instance.
- *
- * @return a builder initialized with this instance's values
- */
- default ProductRecordBuilder with() {
- try {
- return new ProductRecordBuilder(ProductRecord.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'ProductRecordBuilder.With' should only be implemented by classes, which could be casted to 'ProductRecord'", ex);
- }
- }
- }
-}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SimpleBuildersJacksonModule.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SimpleBuildersJacksonModule.java
deleted file mode 100644
index 5766b3ae..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SimpleBuildersJacksonModule.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.javahelpers.simple.builders.example;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-
-public class SimpleBuildersJacksonModule extends SimpleModule {
- public SimpleBuildersJacksonModule() {
- setMixInAnnotation(JacksonIntegrationDto.class, JacksonIntegrationDtoMixin.class);
- }
-
- @JsonDeserialize(
- builder = JacksonIntegrationDtoBuilder.class
- )
- private interface JacksonIntegrationDtoMixin {
- }
-}
diff --git a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java b/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java
deleted file mode 100644
index 5bbfaed8..00000000
--- a/example/target/generated-sources/annotations/org/javahelpers/simple/builders/example/SponsorDtoBuilder.java
+++ /dev/null
@@ -1,184 +0,0 @@
-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.function.BooleanSupplier;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-import javax.annotation.processing.Generated;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
-import org.javahelpers.simple.builders.core.interfaces.IBuilderBase;
-import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
-import org.javahelpers.simple.builders.core.util.TrackedValue;
-
-/**
- * Builder for {@code org.javahelpers.simple.builders.example.SponsorDto}.
- */
-@Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
-@BuilderImplementation(
- forClass = SponsorDto.class
-)
-public class SponsorDtoBuilder implements IBuilderBase {
- /**
- * Tracked value for name: name.
- */
- private TrackedValue name = unsetValue();
-
- /**
- * Initialisation of builder for {@code org.javahelpers.simple.builders.example.SponsorDto} by a instance.
- *
- * @param instance object instance for initialisiation
- */
- public SponsorDtoBuilder(SponsorDto instance) {
- this.name = initialValue(instance.getName());
- }
-
- /**
- * Empty constructor of builder for {@code org.javahelpers.simple.builders.example.SponsorDto}.
- */
- public SponsorDtoBuilder() {
- }
-
- /**
- * Sets the value for name.
- *
- * @param name name
- * @return current instance of builder
- */
- public SponsorDtoBuilder name(String name) {
- this.name = changedValue(name);
- return this;
- }
-
- /**
- * Sets the value for name.
- *
- * @param format name
- * @param args name
- * @return current instance of builder
- */
- public SponsorDtoBuilder name(String format, Object... args) {
- this.name = changedValue(String.format(format, args));
- return this;
- }
-
- /**
- * Sets the value for name by executing the provided consumer.
- *
- * @param nameStringBuilderConsumer consumer providing an instance of name
- * @return current instance of builder
- */
- public SponsorDtoBuilder name(Consumer nameStringBuilderConsumer) {
- StringBuilder builder = new StringBuilder();
- nameStringBuilderConsumer.accept(builder);
- this.name = changedValue(builder.toString());
- return this;
- }
-
- /**
- * Sets the value for name by invoking the provided supplier.
- *
- * @param nameSupplier supplier for name
- * @return current instance of builder
- */
- public SponsorDtoBuilder name(Supplier nameSupplier) {
- this.name = changedValue(nameSupplier.get());
- return this;
- }
-
- @Override
- public SponsorDto build() {
- SponsorDto result = new SponsorDto();
- this.name.ifSet(result::setName);
- return result;
- }
-
- /**
- * Creating a new builder for {@code org.javahelpers.simple.builders.example.SponsorDto}.
- *
- * @return builder for {@code org.javahelpers.simple.builders.example.SponsorDto}
- */
- public static SponsorDtoBuilder create() {
- return new SponsorDtoBuilder();
- }
-
- /**
- * 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 SponsorDtoBuilder 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 SponsorDtoBuilder conditional(BooleanSupplier condition,
- Consumer yesCondition) {
- return conditional(condition, yesCondition, null);
- }
-
- /**
- * 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)
- .toString();
- }
-
- /**
- * Interface that can be implemented by the DTO to provide fluent modification methods.
- */
- public interface With {
- /**
- * Applies modifications to a builder initialized from this instance and returns the built object.
- *
- * @param b the consumer to apply modifications
- * @return the modified instance
- */
- default SponsorDto with(Consumer b) {
- SponsorDtoBuilder builder;
- try {
- builder = new SponsorDtoBuilder(SponsorDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'SponsorDtoBuilder.With' should only be implemented by classes, which could be casted to 'SponsorDto'", ex);
- }
- b.accept(builder);
- return builder.build();
- }
-
- /**
- * Creates a builder initialized from this instance.
- *
- * @return a builder initialized with this instance's values
- */
- default SponsorDtoBuilder with() {
- try {
- return new SponsorDtoBuilder(SponsorDto.class.cast(this));
- } catch (ClassCastException ex) {
- throw new IllegalArgumentException("The interface 'SponsorDtoBuilder.With' should only be implemented by classes, which could be casted to 'SponsorDto'", ex);
- }
- }
- }
-}
diff --git a/processor/pom.xml b/processor/pom.xml
index 02dc3a64..9146caa5 100644
--- a/processor/pom.xml
+++ b/processor/pom.xml
@@ -192,6 +192,10 @@
${java.version}
${java.version}
${java.version}
+
+
+ com.google.auto.service.processor.AutoServiceProcessor
+
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
index 042db372..a24e03e7 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/BuilderProcessor.java
@@ -73,13 +73,10 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
// Read global configuration from compiler arguments
CompilerArgumentsReader reader = new CompilerArgumentsReader(processingEnv);
BuilderConfiguration globalConfig = reader.readBuilderConfiguration();
+ logger.debug("Loaded global configuration from compiler arguments: %s", globalConfig);
- this.context =
- new ProcessingContext(
- processingEnv.getElementUtils(), processingEnv.getTypeUtils(), logger, globalConfig);
- context.debug("Loaded global configuration from compiler arguments: %s", globalConfig);
- this.codeGenerator =
- new JavaCodeGenerator(processingEnv.getFiler(), processingEnv.getElementUtils(), logger);
+ this.context = new ProcessingContext(logger, globalConfig, processingEnv);
+ this.codeGenerator = new JavaCodeGenerator(processingEnv.getFiler(), logger);
this.jacksonModuleGenerator =
new JacksonModuleGenerator(processingEnv.getElementUtils(), logger);
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
index e64d53a1..bcf6b618 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/BuilderDefinitionDto.java
@@ -24,8 +24,10 @@
package org.javahelpers.simple.builders.processor.dtos;
+import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/** BuilderDefinitionDto holds all information for generating a builder. */
public class BuilderDefinitionDto {
@@ -57,6 +59,31 @@ public class BuilderDefinitionDto {
*/
private final List nestedTypes = new LinkedList<>();
+ /**
+ * Core builder methods (build, create, conditional, toString, etc.) to be generated. These are
+ * added by BuilderEnhancers and have ordering for generation sequence.
+ */
+ private final List coreMethods = new LinkedList<>();
+
+ /**
+ * Class-level annotations to be added to the generated builder class. These are added by
+ * BuilderEnhancers and include annotations like @Generated, @BuilderImplementation, etc.
+ *
+ * Uses a Set to ensure annotation uniqueness and prevent duplicates.
+ */
+ private final Set classAnnotations = new LinkedHashSet<>();
+
+ /**
+ * Interfaces to be implemented by the generated builder class. These are added by
+ * BuilderEnhancers and include interfaces like IBuilderBase.
+ *
+ * Uses a Set to ensure interface uniqueness and prevent duplicates.
+ */
+ private final Set interfaces = new LinkedHashSet<>();
+
+ /** Class-level JavaDoc for the generated builder class. */
+ private String classJavadoc;
+
/** Configuration for builder generation. */
private BuilderConfiguration configuration;
@@ -202,7 +229,61 @@ public void addNestedType(NestedTypeDto nestedType) {
}
/**
- * Returns the builder configuration.
+ * Returns the list of core builder methods.
+ *
+ * @return the list of core methods
+ */
+ public List getCoreMethods() {
+ return coreMethods;
+ }
+
+ /**
+ * Adds a core method to be generated in the builder.
+ *
+ * @param method the core method to add
+ */
+ public void addCoreMethod(MethodDto method) {
+ this.coreMethods.add(method);
+ }
+
+ /**
+ * Returns the set of class-level annotations.
+ *
+ * @return the set of class annotations (unique, no duplicates)
+ */
+ public Set getClassAnnotations() {
+ return classAnnotations;
+ }
+
+ /**
+ * Adds a class-level annotation to be generated in the builder.
+ *
+ * @param annotation the class annotation to add
+ */
+ public void addClassAnnotation(AnnotationDto annotation) {
+ this.classAnnotations.add(annotation);
+ }
+
+ /**
+ * Returns the set of interfaces to be implemented by the builder.
+ *
+ * @return the set of interfaces (unique, no duplicates)
+ */
+ public Set getInterfaces() {
+ return interfaces;
+ }
+
+ /**
+ * Adds an interface to be implemented by the builder.
+ *
+ * @param interfaceType the interface to add
+ */
+ public void addInterface(InterfaceName interfaceType) {
+ this.interfaces.add(interfaceType);
+ }
+
+ /**
+ * Returns the configuration for builder generation.
*
* @return the builder configuration
*/
@@ -218,4 +299,22 @@ public BuilderConfiguration getConfiguration() {
public void setConfiguration(BuilderConfiguration configuration) {
this.configuration = configuration;
}
+
+ /**
+ * Returns the class-level JavaDoc for the generated builder.
+ *
+ * @return the class JavaDoc, or null if not set
+ */
+ public String getClassJavadoc() {
+ return classJavadoc;
+ }
+
+ /**
+ * Sets the class-level JavaDoc for the generated builder.
+ *
+ * @param classJavadoc the class JavaDoc to set
+ */
+ public void setClassJavadoc(String classJavadoc) {
+ this.classJavadoc = classJavadoc;
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java
index 461bb15f..ace2e1d6 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/FieldDto.java
@@ -65,6 +65,12 @@ public class FieldDto {
*/
private boolean nonNullable = false;
+ /**
+ * Parameter-level annotations extracted from the field parameter (constructor or setter param).
+ * These are separate from type-use annotations and should be applied to method parameters.
+ */
+ private final List parameterAnnotations = new ArrayList<>();
+
/**
* Getting name of field.
*
@@ -222,4 +228,42 @@ public boolean isNonNullable() {
public void setNonNullable(boolean nonNullable) {
this.nonNullable = nonNullable;
}
+
+ /**
+ * Returns parameter-level annotations that should be applied to method parameters.
+ *
+ * @return list of parameter annotations
+ */
+ public List getParameterAnnotations() {
+ return parameterAnnotations;
+ }
+
+ /**
+ * Sets parameter-level annotations from the field parameter.
+ *
+ * @param annotations list of annotations to set
+ */
+ public void setParameterAnnotations(List annotations) {
+ this.parameterAnnotations.clear();
+ if (annotations != null) {
+ this.parameterAnnotations.addAll(annotations);
+ }
+ }
+
+ /**
+ * Checks if this field has a parameter annotation with the given fully qualified name.
+ *
+ * @param annotationFqn the fully qualified name of the annotation to check for
+ * @return true if the field has the specified annotation
+ */
+ public boolean hasAnnotation(String annotationFqn) {
+ if (annotationFqn == null || parameterAnnotations.isEmpty()) {
+ return false;
+ }
+ return parameterAnnotations.stream()
+ .anyMatch(
+ annotation ->
+ annotation.getAnnotationType() != null
+ && annotationFqn.equals(annotation.getAnnotationType().getFullQualifiedName()));
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/InterfaceName.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/InterfaceName.java
new file mode 100644
index 00000000..935bf234
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/InterfaceName.java
@@ -0,0 +1,197 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.dtos;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+/**
+ * InterfaceName represents a Java interface type with package and class name information.
+ *
+ * This type is specifically designed for interfaces and contains only interface-relevant
+ * information. Unlike {@link TypeName}, it doesn't include class-specific concepts like builders,
+ * constructors, or inner types.
+ *
+ *
Typical usage includes:
+ *
+ *
+ * IBuilderBase interface for builder contracts
+ * Custom interfaces for builder extensions
+ * Mixin interfaces for Jackson serialization
+ *
+ */
+public class InterfaceName {
+
+ /** Name of package. */
+ private final String packageName;
+
+ /** Name of interface. */
+ private final String simpleName;
+
+ /** Annotations on this interface (TYPE_USE). */
+ private final List annotations = new java.util.ArrayList<>();
+
+ /** Generic type parameters for this interface. */
+ private final List typeParameters = new java.util.ArrayList<>();
+
+ /**
+ * Constructor for InterfaceName.
+ *
+ * @param packageName name of package
+ * @param simpleName name of interface, could not be null
+ */
+ public InterfaceName(String packageName, String simpleName) {
+ requireNonNull(simpleName);
+ this.packageName = packageName;
+ this.simpleName = simpleName;
+ }
+
+ /**
+ * Returns name of package.
+ *
+ * @return package name of type {@code java.lang.String}
+ */
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Returns name of interface.
+ *
+ * @return interface name of type {@code java.lang.String}
+ */
+ public String getSimpleName() {
+ return simpleName;
+ }
+
+ /**
+ * Returns the list of annotations on this interface.
+ *
+ * @return list of annotations
+ */
+ public List getAnnotations() {
+ return annotations;
+ }
+
+ /**
+ * Adds an annotation to this interface.
+ *
+ * @param annotation the annotation to add
+ */
+ public void addAnnotation(AnnotationDto annotation) {
+ this.annotations.add(annotation);
+ }
+
+ /**
+ * Returns the list of generic type parameters for this interface.
+ *
+ * @return list of type parameters
+ */
+ public List getTypeParameters() {
+ return typeParameters;
+ }
+
+ /**
+ * Adds a generic type parameter to this interface.
+ *
+ * @param typeParameter the type parameter to add
+ */
+ public void addTypeParameter(TypeName typeParameter) {
+ this.typeParameters.add(typeParameter);
+ }
+
+ /**
+ * Returns whether this interface has generic type parameters.
+ *
+ * @return true if the interface has type parameters
+ */
+ public boolean hasTypeParameters() {
+ return !typeParameters.isEmpty();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ InterfaceName that = (InterfaceName) o;
+ return new EqualsBuilder()
+ .append(packageName, that.packageName)
+ .append(simpleName, that.simpleName)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37).append(packageName).append(simpleName).toHashCode();
+ }
+
+ /**
+ * Returns a string representation of this interface in Java syntax format. Includes annotations,
+ * package, simple name, and type parameters.
+ *
+ * Examples:
+ *
+ *
+ * {@code com.example.Builder} - simple interface
+ * {@code com.example.Builder} - with type parameter
+ * {@code @Deprecated com.example.Builder} - with annotation and type parameters
+ *
+ *
+ * @return fully qualified name with annotations and type parameters
+ */
+ @Override
+ public String toString() {
+ String annotationsPart =
+ CollectionUtils.isNotEmpty(annotations)
+ ? annotations.stream()
+ .map(AnnotationDto::toString)
+ .collect(java.util.stream.Collectors.joining(" "))
+ + " "
+ : "";
+
+ String qualifiedName = packageName.isEmpty() ? simpleName : packageName + "." + simpleName;
+
+ String typeParamsPart =
+ CollectionUtils.isNotEmpty(typeParameters)
+ ? "<"
+ + typeParameters.stream()
+ .map(TypeName::getClassName)
+ .collect(java.util.stream.Collectors.joining(", "))
+ + ">"
+ : "";
+
+ return annotationsPart + qualifiedName + typeParamsPart;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/MethodDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/MethodDto.java
index 939a7170..9fd34fc2 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/MethodDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/MethodDto.java
@@ -24,6 +24,7 @@
package org.javahelpers.simple.builders.processor.dtos;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -41,9 +42,15 @@ public class MethodDto {
/** Access modifier for method. */
private Optional modifier = Optional.empty();
+ /** Whether the method is static. */
+ private boolean isStatic = false;
+
/** Priority for method conflict resolution. Higher wins. */
private int priority = 0;
+ /** Ordering for method generation. Lower values appear first in generated class. */
+ private int ordering = 1000; // Default high value for field-generated methods
+
/** Name of method. */
private String methodName;
@@ -53,12 +60,34 @@ public class MethodDto {
/** Javadoc comment for the method. */
private String javadoc;
+ /** List of annotations on this method. */
+ private final List annotations = new ArrayList<>();
+
/** List of parameters of Method. */
private final LinkedList parameters = new LinkedList<>();
+ /** List of generic type parameters for the method (e.g., ). */
+ private final List genericParameters = new ArrayList<>();
+
/** Definition of inner implementation for method. */
private final MethodCodeDto methodCodeDto = new MethodCodeDto();
+ /** Default constructor. */
+ public MethodDto() {
+ // Default constructor
+ }
+
+ /**
+ * Constructor with method name and return type.
+ *
+ * @param methodName the name of the method
+ * @param returnType the return type of the method
+ */
+ public MethodDto(String methodName, TypeName returnType) {
+ this.methodName = methodName;
+ this.returnType = returnType;
+ }
+
/**
* Sets the priority for this method. Higher values win when signatures clash. Priority levels:
*
@@ -85,6 +114,33 @@ public int getPriority() {
return priority;
}
+ /**
+ * 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:
+ *
+ *
+ * Methods with fewer parameters come first
+ * Non-generic methods come before generic methods
+ * 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.
@@ -171,6 +227,24 @@ public List getParameters() {
return parameters;
}
+ /**
+ * Adds a generic type parameter to this method.
+ *
+ * @param genericParameter the generic parameter to add
+ */
+ public void addGenericParameter(GenericParameterDto genericParameter) {
+ this.genericParameters.add(genericParameter);
+ }
+
+ /**
+ * Getting a list of generic type parameters of method.
+ *
+ * @return List of generic parameters of type {@code GenericParameterDto}
+ */
+ public List getGenericParameters() {
+ return genericParameters;
+ }
+
/**
* Getting the access modifier for method. Optional for usage in stream-notation.
*
@@ -182,7 +256,7 @@ public Optional getModifier() {
}
/**
- * Setting the access modifier for method.
+ * Sets the access modifier for method.
*
* @param modifier access modifier of type {@code javax.lang.model.element.Modifier}
*/
@@ -190,6 +264,24 @@ public void setModifier(Modifier modifier) {
this.modifier = Optional.ofNullable(modifier);
}
+ /**
+ * Returns whether this method is static.
+ *
+ * @return true if the method is static, false otherwise
+ */
+ public boolean isStatic() {
+ return isStatic;
+ }
+
+ /**
+ * Sets whether this method is static.
+ *
+ * @param isStatic true if the method should be static, false otherwise
+ */
+ public void setStatic(boolean isStatic) {
+ this.isStatic = isStatic;
+ }
+
/**
* Gets the return type of the method.
*
@@ -243,4 +335,155 @@ public String getJavadoc() {
public void setJavadoc(String javadoc) {
this.javadoc = javadoc;
}
+
+ /**
+ * Returns the list of annotations on this method.
+ *
+ * @return list of annotations
+ */
+ public List getAnnotations() {
+ return annotations;
+ }
+
+ /**
+ * Adds an annotation to this method.
+ *
+ * @param annotation the annotation to add
+ */
+ public void addAnnotation(
+ org.javahelpers.simple.builders.processor.dtos.AnnotationDto annotation) {
+ this.annotations.add(annotation);
+ }
+
+ /**
+ * Comparator for sorting MethodDto instances with sophisticated ordering rules.
+ *
+ * Sorting order for methods with same priority and name:
+ *
+ *
+ * Methods with fewer parameters come first
+ * Non-generic methods come before generic methods
+ * 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.
+ *
+ * Examples:
+ *
+ *
+ * {@code public PersonBuilder name(String)}
+ * {@code public PersonBuilder age(int)}
+ * {@code public PersonBuilder tags(Consumer>)}
+ *
+ *
+ * @return method signature as a string
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ // Add modifier if present
+ modifier.ifPresent(m -> sb.append(m.toString().toLowerCase()).append(" "));
+
+ // Add static if applicable
+ if (isStatic) {
+ sb.append("static ");
+ }
+
+ // Add return type (void if not specified)
+ String returnTypeName = returnType != null ? returnType.getClassName() : "void";
+ sb.append(returnTypeName).append(" ");
+
+ // Add method name and parameters
+ String parameterList =
+ parameters.stream()
+ .map(param -> param.getParameterType().getClassName())
+ .collect(java.util.stream.Collectors.joining(", "));
+
+ sb.append(methodName).append("(").append(parameterList).append(")");
+
+ return sb.toString();
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeName.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeName.java
index c42f999d..75d96d05 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeName.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeName.java
@@ -44,6 +44,12 @@ public class TypeName {
/** Annotations on this type (TYPE_USE). */
private final java.util.List annotations = new java.util.ArrayList<>();
+ /** Builder type for this type if it has @SimpleBuilder annotation. */
+ private TypeName builderType;
+
+ /** Whether this type has an empty constructor. */
+ private boolean hasEmptyConstructor = false;
+
/**
* Constructor for TypeName.
*
@@ -74,6 +80,15 @@ public String getClassName() {
return className;
}
+ /**
+ * Returns the full qualified name (package + class name).
+ *
+ * @return full qualified name of type {@code java.lang.String}
+ */
+ public String getFullQualifiedName() {
+ return packageName.isEmpty() ? className : packageName + "." + className;
+ }
+
/**
* Returns the list of annotations on this type.
*
@@ -92,6 +107,42 @@ public void addAnnotation(AnnotationDto annotation) {
this.annotations.add(annotation);
}
+ /**
+ * Returns the builder type for this type if it has @SimpleBuilder annotation.
+ *
+ * @return optional builder type
+ */
+ public Optional getBuilderType() {
+ return Optional.ofNullable(builderType);
+ }
+
+ /**
+ * Sets the builder type for this type.
+ *
+ * @param builderType the builder type
+ */
+ public void setBuilderType(TypeName builderType) {
+ this.builderType = builderType;
+ }
+
+ /**
+ * Returns whether this type has an empty constructor.
+ *
+ * @return true if the type has an empty constructor
+ */
+ public boolean hasEmptyConstructor() {
+ return hasEmptyConstructor;
+ }
+
+ /**
+ * Sets whether this type has an empty constructor.
+ *
+ * @param hasEmptyConstructor true if the type has an empty constructor
+ */
+ public void setHasEmptyConstructor(boolean hasEmptyConstructor) {
+ this.hasEmptyConstructor = hasEmptyConstructor;
+ }
+
/**
* Helper function to hold a specific inner type.Is empty if this is a class without generic parts
* or a class has multiple generics.
@@ -135,4 +186,21 @@ public boolean equals(Object o) {
public int hashCode() {
return new HashCodeBuilder(17, 37).append(packageName).append(className).toHashCode();
}
+
+ /**
+ * Checks if this type has an annotation with the given fully qualified name.
+ *
+ * @param annotationFqn the fully qualified name of the annotation to check for
+ * @return true if the type has the specified annotation
+ */
+ public boolean hasAnnotation(String annotationFqn) {
+ if (annotationFqn == null || annotations.isEmpty()) {
+ return false;
+ }
+ return annotations.stream()
+ .anyMatch(
+ annotation ->
+ annotation.getAnnotationType() != null
+ && annotationFqn.equals(annotation.getAnnotationType().getFullQualifiedName()));
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameArray.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameArray.java
index 6fa34811..f107013d 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameArray.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameArray.java
@@ -71,4 +71,9 @@ public Optional getInnerType() {
public TypeName getTypeOfArray() {
return typeOfArray;
}
+
+ @Override
+ public String getFullQualifiedName() {
+ return typeOfArray.getFullQualifiedName() + "[]";
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameGeneric.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameGeneric.java
index 059b9b27..90c6c8c4 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameGeneric.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameGeneric.java
@@ -42,6 +42,9 @@
public class TypeNameGeneric extends TypeName {
private final List innerTypeArguments;
+ /** Builder type for the element type of this collection (if applicable). */
+ private TypeName elementBuilderType;
+
/**
* Creates a {@code TypeNameGeneric} based on another {@code TypeName} as outer type and a list of
* inner type arguments.
@@ -117,4 +120,38 @@ public Optional getInnerType() {
? Optional.of(innerTypeArguments.get(0))
: Optional.empty();
}
+
+ /**
+ * Returns the builder type for the element type of this collection.
+ *
+ * @return optional element builder type
+ */
+ public Optional getElementBuilderType() {
+ return Optional.ofNullable(elementBuilderType);
+ }
+
+ /**
+ * Sets the builder type for the element type of this collection.
+ *
+ * @param elementBuilderType the element builder type
+ */
+ public void setElementBuilderType(TypeName elementBuilderType) {
+ this.elementBuilderType = elementBuilderType;
+ }
+
+ @Override
+ public String getFullQualifiedName() {
+ String baseName = super.getFullQualifiedName();
+ if (innerTypeArguments.isEmpty()) {
+ return baseName;
+ }
+
+ String typeArgs =
+ innerTypeArguments.stream()
+ .map(TypeName::getFullQualifiedName)
+ .reduce((a, b) -> a + ", " + b)
+ .orElse("");
+
+ return baseName + "<" + typeArgs + ">";
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNamePrimitive.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNamePrimitive.java
index 4cc58add..4abb314a 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNamePrimitive.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNamePrimitive.java
@@ -113,4 +113,9 @@ public enum PrimitiveTypeEnum {
/** The double primitive type. */
DOUBLE;
}
+
+ @Override
+ public String getFullQualifiedName() {
+ return type.name().toLowerCase();
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameVariable.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameVariable.java
index a3dd306e..343a1ee8 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameVariable.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/dtos/TypeNameVariable.java
@@ -34,4 +34,9 @@ public class TypeNameVariable extends TypeName {
public TypeNameVariable(String variableName) {
super("", variableName);
}
+
+ @Override
+ public String getFullQualifiedName() {
+ return getClassName(); // Type variables don't have packages, just the name like "T", "K", "V"
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/enums/CompilerArgumentsEnum.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/enums/CompilerArgumentsEnum.java
index 0957e6d5..14cb8724 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/enums/CompilerArgumentsEnum.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/enums/CompilerArgumentsEnum.java
@@ -123,6 +123,12 @@ public enum CompilerArgumentsEnum {
/** Option for setter method name suffix. */
SETTER_SUFFIX("setterSuffix"),
+ // === Component Filtering ===
+ /**
+ * Option for deactivating specific method generators and builder enhancers by class name pattern.
+ */
+ DEACTIVATE_GENERATION_COMPONENTS("deactivateGenerationComponents"),
+
// === Logging ===
/** Option for verbose logging output. */
VERBOSE("verbose");
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/exceptions/JavapoetMapperException.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/exceptions/JavapoetMapperException.java
new file mode 100644
index 00000000..d6940d2e
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/exceptions/JavapoetMapperException.java
@@ -0,0 +1,59 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.exceptions;
+
+/** Special exception for errors in mapping to Javapoet classes. */
+public class JavapoetMapperException extends RuntimeException {
+
+ /**
+ * Creating an exception with message and parameters.
+ *
+ * @param cause root cause of current exception, containing stacktrace
+ */
+ public JavapoetMapperException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ /**
+ * Creating an exception with message and parameters.
+ *
+ * @param message A specific message, supports String.format arguments
+ * @param args Arguments for Stringlformat on message
+ */
+ public JavapoetMapperException(String message, Object... args) {
+ super(String.format(message, args));
+ }
+
+ /**
+ * Creating an exception with message and parameters.
+ *
+ * @param cause root cause of current exception, containing stacktrace
+ * @param message A specific message, supports String.format arguments
+ * @param args Arguments for Stringlformat on message
+ */
+ public JavapoetMapperException(Throwable cause, String message, Object... args) {
+ super(String.format(message, args), cause);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/AddToCollectionGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/AddToCollectionGenerator.java
new file mode 100644
index 00000000..fca90f7f
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/AddToCollectionGenerator.java
@@ -0,0 +1,185 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates add2FieldName helper methods for List and Set fields.
+ *
+ * This generator creates methods that add single elements to collection fields, supporting both
+ * List and Set types. The generated methods follow the pattern "add2{#FieldName}" and always use
+ * this naming convention regardless of setter suffix configuration.
+ *
+ *
Important behavior: The generated methods preserve immutability by creating a new
+ * collection instance. If the field already has a value, the method creates a copy of the existing
+ * collection, adds the new element, and assigns the new collection. If the field is not yet set, a
+ * new collection is created with the single element.
+ *
+ *
Requirements: Only applies to parameterized collection types ({@code List} or {@code
+ * Set}). Raw types like {@code List} or {@code Set} are not supported.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateAddToCollectionHelpers} to {@code DISABLED}. See the configuration documentation
+ * for details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.List;
+ * import java.util.Set;
+ *
+ * @SimpleBuilder
+ * public record ExampleDto(List tags, Set categories) {}
+ *
+ * // Usage of generated Builder:
+ * var result = ExampleDtoBuilder.builder()
+ * .add2Tags("tag1")
+ * .add2Tags("tag2")
+ * .add2Categories("cat1")
+ * .build();
+ * }
+ */
+public class AddToCollectionGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 30;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateAddToCollectionHelpers()) {
+ return false;
+ }
+
+ TypeName fieldType = field.getFieldType();
+
+ return (fieldType instanceof TypeNameList listType && listType.isParameterized())
+ || (fieldType instanceof TypeNameSet setType && setType.isParameterized());
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ List methods = new ArrayList<>();
+ TypeName fieldType = field.getFieldType();
+
+ if (fieldType instanceof TypeNameList listType && listType.isParameterized()) {
+ MethodDto addMethod =
+ createAddToCollectionMethod(
+ field.getFieldNameEstimated(),
+ field.getFieldName(),
+ listType,
+ listType.getElementType(),
+ builderType,
+ context);
+ methods.add(addMethod);
+ } else if (fieldType instanceof TypeNameSet setType && setType.isParameterized()) {
+ MethodDto addMethod =
+ createAddToCollectionMethod(
+ field.getFieldNameEstimated(),
+ field.getFieldName(),
+ setType,
+ setType.getElementType(),
+ builderType,
+ context);
+ methods.add(addMethod);
+ }
+
+ return methods;
+ }
+
+ private MethodDto createAddToCollectionMethod(
+ String fieldNameEstimated,
+ String fieldName,
+ TypeName fieldType,
+ TypeName elementType,
+ TypeName builderType,
+ ProcessingContext context) {
+ String methodName = "add2" + StringUtils.capitalize(fieldNameEstimated);
+ MethodDto methodDto = new MethodDto(methodName, builderType);
+
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName("element");
+ parameter.setParameterTypeName(elementType);
+ methodDto.addParameter(parameter);
+
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+
+ String collectionImpl;
+ TypeName collectionVarType;
+ if (fieldType instanceof TypeNameList listType) {
+ collectionImpl = listType.isConcreteImplementation() ? listType.getClassName() : "ArrayList";
+ collectionVarType = fieldType;
+ } else if (fieldType instanceof TypeNameSet setType) {
+ collectionImpl = setType.isConcreteImplementation() ? setType.getClassName() : "HashSet";
+ collectionVarType = fieldType;
+ } else {
+ throw new IllegalArgumentException("Unsupported field type: " + fieldType);
+ }
+
+ methodDto.setCode(
+ """
+ $collectionVarType:T newCollection;
+ if (this.$fieldName:N.isSet()) {
+ newCollection = new $collectionImpl:T<>(this.$fieldName:N.value());
+ } else {
+ newCollection = new $collectionImpl:T<>();
+ }
+ newCollection.add(element);
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue(newCollection);
+ return this;
+ """);
+ methodDto.addArgument("collectionVarType", collectionVarType);
+ methodDto.addArgument("collectionImpl", new TypeName("java.util", collectionImpl));
+ methodDto.addArgument("fieldName", fieldName);
+ methodDto.addArgument("elementType", elementType);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setPriority(MethodDto.PRIORITY_MEDIUM);
+
+ methodDto.setJavadoc(
+ """
+ Adds a single element to %s.
+
+ @param element the element to add
+ @return current instance of builder
+ """
+ .formatted(fieldName));
+
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayBuilderConsumerGenerator.java
new file mode 100644
index 00000000..054e0337
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayBuilderConsumerGenerator.java
@@ -0,0 +1,151 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.List;
+import org.javahelpers.simple.builders.core.builders.ArrayListBuilder;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates ArrayListBuilder consumer methods for array fields.
+ *
+ * This generator creates methods that accept a {@code Consumer>} to build
+ * array fields using a fluent builder API. The consumer configures the list builder, which is then
+ * converted to an array and assigned to the field.
+ *
+ * Important behavior: An {@code ArrayListBuilder} is created (preserving existing array
+ * elements if the field is already set), passed to the consumer for configuration, then built and
+ * converted to an array. This allows fluent array construction with the convenience of list
+ * operations.
+ *
+ *
Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}).
+ * Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}.
+ *
+ *
This generator can be deactivated by setting the configuration flag {@code
+ * shouldGenerateBuilderConsumer()} to {@code false}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String[] keywords, String[] tags) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .keywords(k -> k.add("java").add("builder").add("pattern"))
+ * .tags(t -> t.add("programming").add("design"))
+ * .build();
+ * }
+ */
+public class ArrayBuilderConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 25;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
+ return false;
+ }
+
+ return field.getFieldType() instanceof TypeNameArray;
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ TypeName fieldType = field.getFieldType();
+
+ if (!(fieldType instanceof TypeNameArray arrayType)) {
+ return List.of();
+ }
+
+ TypeName elementType = arrayType.getTypeOfArray();
+ TypeName collectionBuilderType = map2TypeName(ArrayListBuilder.class);
+
+ MethodDto method =
+ createFieldConsumerWithArrayBuilder(
+ field, collectionBuilderType, elementType, builderType, context);
+
+ return List.of(method);
+ }
+
+ private MethodDto createFieldConsumerWithArrayBuilder(
+ FieldDto field,
+ TypeName collectionBuilderType,
+ TypeName elementType,
+ TypeName returnBuilderType,
+ ProcessingContext context) {
+ String fieldName = field.getFieldNameEstimated();
+ String fieldNameInBuilder = field.getFieldName();
+ TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(collectionBuilderType, elementType);
+ TypeNameGeneric consumerType = MethodGeneratorUtil.createConsumerType(builderTypeGeneric);
+
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(fieldName + BUILDER_SUFFIX + SUFFIX_CONSUMER);
+ parameter.setParameterTypeName(consumerType);
+
+ MethodDto methodDto =
+ new MethodDto(generateBuilderMethodName(fieldName, context), returnBuilderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+ methodDto.setCode(
+ """
+ $helperType:T builder = this.$fieldName:N.isSet() ? new $helperType:T(java.util.List.of(this.$fieldName:N.value())) : new $helperType:T();
+ $dtoMethodParam:N.accept(builder);
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue(builder.build().toArray(new $elementType:T[0]));
+ return this;
+ """);
+ methodDto.addArgument("fieldName", fieldNameInBuilder);
+ methodDto.addArgument("dtoMethodParam", parameter.getParameterName());
+ methodDto.addArgument("helperType", builderTypeGeneric);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.addArgument("elementType", elementType);
+ methodDto.setPriority(MethodDto.PRIORITY_MEDIUM);
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s using the fluent builder consumer.
+
+ @param %s consumer for %s
+ @return current instance of builder
+ """
+ .formatted(fieldName, parameter.getParameterName(), field.getJavaDoc()));
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayConversionGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayConversionGenerator.java
new file mode 100644
index 00000000..2149a461
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ArrayConversionGenerator.java
@@ -0,0 +1,135 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates array-from-List conversion methods for array fields.
+ *
+ * This generator creates setter methods that accept a {@code List} parameter and convert it
+ * to an array for array fields. This provides a more convenient way to set array values using the
+ * List API instead of manually creating arrays.
+ *
+ * Important behavior: The List is converted to an array using {@code toArray()}, then
+ * assigned to the field. This allows using List operations and utilities before converting to the
+ * required array type.
+ *
+ *
Requirements: Only applies to array fields (e.g., {@code String[]}, {@code Integer[]}).
+ * Does not apply to primitive arrays like {@code int[]} or {@code boolean[]}.
+ *
+ *
This generator cannot be deactivated as it provides essential convenience for array fields.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.List;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String[] keywords, String[] tags) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .keywords(List.of("java", "builder", "pattern"))
+ * .tags(List.of("programming", "design"))
+ * .build();
+ * }
+ */
+public class ArrayConversionGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 35;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ return field.getFieldType() instanceof TypeNameArray;
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ TypeName fieldType = field.getFieldType();
+
+ if (!(fieldType instanceof TypeNameArray arrayType)) {
+ return List.of();
+ }
+
+ TypeName elementType = arrayType.getTypeOfArray();
+ TypeNameGeneric listType = new TypeNameGeneric(map2TypeName(List.class), elementType);
+
+ MethodDto method =
+ createFieldSetterForArrayFromList(field, listType, elementType, builderType, context);
+
+ return List.of(method);
+ }
+
+ private MethodDto createFieldSetterForArrayFromList(
+ FieldDto field,
+ TypeName listType,
+ TypeName elementType,
+ TypeName builderType,
+ ProcessingContext context) {
+ String fieldName = field.getFieldNameEstimated();
+ String fieldNameInBuilder = field.getFieldName();
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(fieldName);
+ parameter.setParameterTypeName(listType);
+
+ MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+ methodDto.setCode(
+ """
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N.toArray(new $elementType:T[0]));
+ return this;
+ """);
+ methodDto.addArgument("fieldName", fieldNameInBuilder);
+ methodDto.addArgument("dtoMethodParams", fieldName);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.addArgument("elementType", elementType);
+ methodDto.setPriority(MethodDto.PRIORITY_HIGH);
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s.
+
+ @param %s %s
+ @return current instance of builder
+ """
+ .formatted(fieldName, parameter.getParameterName(), field.getJavaDoc()));
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BasicSetterGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BasicSetterGenerator.java
new file mode 100644
index 00000000..d7eba179
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BasicSetterGenerator.java
@@ -0,0 +1,93 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.Collections;
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates basic setter methods for all fields of the builder.
+ *
+ * This generator creates the primary setter method for each field, which accepts the field type
+ * directly and stores it in the builder. The method name follows the configured setter
+ * prefix/suffix pattern (default is field name without prefix).
+ *
+ *
Important behavior: The generated setter wraps the value using {@code changedValue()}
+ * to track that the field has been explicitly set. This allows distinguishing between fields that
+ * were never set and fields that were set to {@code null}.
+ *
+ *
Requirements: Always applies to all fields. This is the fundamental setter that every
+ * builder field must have.
+ *
+ *
This generator cannot be deactivated as it provides the core builder functionality. However,
+ * the method naming can be configured using the setter prefix/suffix configuration options.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.List;
+ *
+ * @SimpleBuilder
+ * public record ExampleDto(String title, int pages, List tags) {}
+ *
+ * // Usage of generated Builder:
+ * var result = ExampleDtoBuilder.builder()
+ * .title("My Book")
+ * .pages(250)
+ * .tags(List.of("java", "builder"))
+ * .build();
+ * }
+ */
+public class BasicSetterGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 100;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ return true;
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ MethodDto setterMethod =
+ MethodGeneratorUtil.createBuilderMethodForFieldWithTransform(
+ field, null, field.getFieldType(), builderType, context);
+
+ return Collections.singletonList(setterMethod);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderConsumerGenerator.java
new file mode 100644
index 00000000..2e485976
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderConsumerGenerator.java
@@ -0,0 +1,112 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for fields whose type has a @SimpleBuilder annotation.
+ *
+ * This generator creates methods that accept a {@code Consumer} to configure
+ * nested builder instances. The field's builder is created, passed to the consumer for
+ * configuration, then built and assigned to the field.
+ *
+ * Important behavior: A new builder instance is created for the field type, the consumer
+ * configures it, and then {@code build()} is called automatically. This enables fluent nested
+ * object construction without manually creating and building the nested builder.
+ *
+ *
Requirements: Only applies to fields whose type is annotated with
+ * {@code @SimpleBuilder}. The field type must have a generated builder with a static factory
+ * method.
+ *
+ *
This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateBuilderConsumer} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, AuthorDto author) {}
+ *
+ * @SimpleBuilder
+ * public record AuthorDto(String name, String email) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .title("My Book")
+ * .author(a -> a
+ * .name("John Doe")
+ * .email("john@example.com"))
+ * .build();
+ * }
+ */
+public class BuilderConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 55;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
+ return false;
+ }
+ return field.getFieldType().getBuilderType().isPresent();
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ Optional fieldBuilderOpt = field.getFieldType().getBuilderType();
+ if (fieldBuilderOpt.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ TypeName fieldBuilderType = fieldBuilderOpt.get();
+ MethodDto method =
+ MethodGeneratorUtil.createFieldConsumerWithBuilder(
+ field,
+ fieldBuilderType,
+ "this.$fieldName:N.value()",
+ "",
+ Map.of(),
+ builderType,
+ context);
+ return List.of(method);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderEnhancer.java
new file mode 100644
index 00000000..0ff65421
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderEnhancer.java
@@ -0,0 +1,95 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Interface for enhancing or modifying generated builders beyond the basic field-based methods.
+ *
+ * BuilderEnhancers provide a plugin mechanism for adding custom functionality to generated
+ * builders, such as:
+ *
+ *
+ * Additional utility methods (e.g., validation, transformation)
+ * Interface implementations (e.g., With, Serializable)
+ * Custom annotations or documentation
+ * Builder composition or delegation patterns
+ *
+ *
+ * Enhancers are discovered via ServiceLoader and applied in priority order (highest first). Each
+ * enhancer can decide whether it applies to a specific builder type and then modify the builder
+ * accordingly.
+ *
+ *
Unlike {@link MethodGenerator} which operates on individual fields, BuilderEnhancers operate
+ * on the entire builder after all field methods have been generated.
+ *
+ *
Custom enhancers can be provided by library users through the Java ServiceLoader mechanism by
+ * creating a file {@code
+ * META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator} containing the
+ * fully qualified class names of custom enhancer implementations.
+ *
+ *
This interface is non-sealed to allow custom implementations by library users.
+ */
+public non-sealed interface BuilderEnhancer extends Generator {
+
+ /**
+ * Determines whether this enhancer should be applied to the given builder.
+ *
+ *
This method allows enhancers to selectively apply based on:
+ *
+ *
+ * Builder type characteristics (package, annotations, interfaces)
+ * Configuration settings (enabled features, options)
+ * Field composition (presence of certain field types)
+ *
+ *
+ * @param builderDto the builder being generated
+ * @param dtoType the DTO type the builder is for
+ * @param context the processing context for configuration and utilities
+ * @return true if this enhancer should be applied to this builder
+ */
+ boolean appliesTo(BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context);
+
+ /**
+ * Enhances the builder by adding or modifying its structure.
+ *
+ * This method is called after all field-based methods have been generated, allowing enhancers
+ * to add:
+ *
+ *
+ * Additional methods (utility, validation, transformation)
+ * Interface implementations
+ * Annotations on the builder class or methods
+ * JavaDoc documentation
+ * Inner classes or enums
+ *
+ *
+ * @param builderDto the builder to enhance (modifiable)
+ * @param context the processing context for configuration and utilities
+ */
+ void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context);
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderImplementationAnnotationEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderImplementationAnnotationEnhancer.java
new file mode 100644
index 00000000..8cbee725
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/BuilderImplementationAnnotationEnhancer.java
@@ -0,0 +1,107 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import org.javahelpers.simple.builders.core.annotations.BuilderImplementation;
+import org.javahelpers.simple.builders.processor.dtos.AnnotationDto;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds the @BuilderImplementation annotation to generated builder classes.
+ *
+ * This enhancer adds the {@code @BuilderImplementation} annotation to indicate which DTO class
+ * this builder is designed to build. This helps with documentation and tooling support by clearly
+ * establishing the relationship between the builder and its target class.
+ *
+ *
Important behavior: Adds the {@code @BuilderImplementation} annotation with the target
+ * DTO class as the {@code forClass} parameter. This metadata enables tools to discover which
+ * builder corresponds to which DTO class.
+ *
+ *
Requirements: Applies to all builders by default. This annotation provides essential
+ * metadata about the builder-DTO relationship.
+ *
+ *
This enhancer is enabled by default and can be deactivated by setting the configuration flag
+ * {@code usingBuilderImplementationAnnotation} to {@code DISABLED}. See the configuration
+ * documentation for details.
+ *
+ *
Example of generated annotation
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String author) {}
+ *
+ * // Generated builder with @BuilderImplementation annotation:
+ * // @BuilderImplementation(forClass = BookDto.class)
+ * // public class BookDtoBuilder { ... }
+ * }
+ */
+public class BuilderImplementationAnnotationEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 115;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldUseBuilderImplementationAnnotation();
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ AnnotationDto builderImplementationAnnotation =
+ createBuilderImplementationAnnotation(builderDto);
+ builderDto.addClassAnnotation(builderImplementationAnnotation);
+
+ context.debug(
+ "Added @BuilderImplementation annotation to builder %s",
+ builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Creates the @BuilderImplementation annotation.
+ *
+ * @param builderDto the builder definition
+ * @return the annotation DTO for @BuilderImplementation
+ */
+ private AnnotationDto createBuilderImplementationAnnotation(BuilderDefinitionDto builderDto) {
+ AnnotationDto annotation = new AnnotationDto();
+ annotation.setAnnotationType(JavaLangMapper.map2TypeName(BuilderImplementation.class));
+
+ // Add forClass member with the target DTO class
+ annotation.addMember(
+ "forClass", builderDto.getBuildingTargetTypeName().getClassName() + ".class");
+
+ return annotation;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java
new file mode 100644
index 00000000..76bd40ea
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ClassJavaDocEnhancer.java
@@ -0,0 +1,119 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import com.palantir.javapoet.ClassName;
+import com.palantir.javapoet.CodeBlock;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds class-level JavaDoc to generated builder classes.
+ *
+ * This enhancer adds comprehensive JavaDoc documentation to the generated builder class,
+ * including information about the target DTO class and the builder's purpose. The JavaDoc follows
+ * standard conventions and provides useful information for developers using the builder.
+ *
+ *
Important behavior: Generates class-level JavaDoc that describes the builder's purpose,
+ * references the target DTO class, and provides basic usage information. This documentation appears
+ * in IDE tooltips and generated API documentation.
+ *
+ *
Requirements: Always applies to all builders. Class-level documentation is essential
+ * for API usability.
+ *
+ *
This enhancer cannot be deactivated as it provides essential documentation for generated
+ * builders.
+ *
+ *
Example of generated class JavaDoc
+ *
+ * For a DTO like:
+ *
+ *
{@code
+ * @SimpleBuilder
+ * public record BookDto(String title, String author) {}
+ * }
+ *
+ * The generated builder class will have this JavaDoc:
+ *
+ *
+ *
+ * Builder for {@code BookDto}.
+ *
+ * This builder provides a fluent API for creating instances of BookDto 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.
+ *
+ *
+ */
+public class ClassJavaDocEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 200;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return true; // Class JavaDoc is always needed for builders
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ ClassName dtoClass =
+ ClassName.get(
+ builderDto.getBuildingTargetTypeName().getPackageName(),
+ builderDto.getBuildingTargetTypeName().getClassName());
+
+ CodeBlock javadoc = createClassJavadoc(dtoClass);
+ builderDto.setClassJavadoc(javadoc.toString());
+
+ context.debug(
+ "Added class JavaDoc to builder %s", builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Creates comprehensive JavaDoc for the builder class.
+ *
+ * @param dtoClass the target DTO class
+ * @return CodeBlock containing the JavaDoc content
+ */
+ private CodeBlock createClassJavadoc(ClassName dtoClass) {
+ return CodeBlock.of(
+ """
+ Builder for {@code $1T}.
+
+ This builder provides a fluent API for creating instances of $1T 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.
+ """,
+ dtoClass);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ConditionalEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ConditionalEnhancer.java
new file mode 100644
index 00000000..b254339d
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ConditionalEnhancer.java
@@ -0,0 +1,198 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.function.BooleanSupplier;
+import javax.lang.model.element.Modifier;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds conditional methods to generated builders.
+ *
+ *
This enhancer generates methods for conditional builder modification, allowing builder chains
+ * to apply different configurations based on runtime conditions. Two overloads are provided: one
+ * with true/false branches, and one with only a true branch.
+ *
+ *
Important behavior: The condition is evaluated immediately when the method is called.
+ * Based on the result, either the true action, false action, or no action is applied to the
+ * builder. This enables functional programming patterns in builder chains.
+ *
+ *
Requirements: Applies to all builders by default. These methods enable conditional
+ * logic in fluent builder chains.
+ *
+ *
This enhancer is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateConditionalHelper} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.BooleanSupplier;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String subtitle, int pages) {}
+ *
+ * // Usage of generated Builder:
+ * boolean isExtended = true;
+ * var result = BookDtoBuilder.create()
+ * .title("My Book")
+ * .conditional(() -> isExtended,
+ * b -> b.subtitle("Extended Edition").pages(500),
+ * b -> b.subtitle("Standard Edition").pages(250))
+ * .build();
+ *
+ * // Or with single branch:
+ * var result2 = BookDtoBuilder.create()
+ * .title("My Book")
+ * .conditional(() -> isExtended, b -> b.subtitle("Extended Edition"))
+ * .build();
+ * }
+ */
+public class ConditionalEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 80;
+
+ // Ordering constants for method generation order
+ private static final int ORDERING_CONDITIONAL =
+ 1100; // After builder methods (1000), before toString (2000)
+ private static final int ORDERING_CONDITIONAL_POSITIVE_ONLY =
+ 1100; // After builder methods (1000), before toString (2000)
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldGenerateConditionalLogic();
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add conditional(BooleanSupplier, Consumer, Consumer) method
+ MethodDto conditionalMethod = createConditionalMethod(builderDto);
+ builderDto.addCoreMethod(conditionalMethod);
+
+ // Add conditional(BooleanSupplier, Consumer) method
+ MethodDto conditionalPositiveMethod = createConditionalPositiveOnlyMethod(builderDto);
+ builderDto.addCoreMethod(conditionalPositiveMethod);
+
+ context.debug(
+ "Added conditional methods to builder %s", builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /** Creates the conditional(BooleanSupplier, Consumer, Consumer) method. */
+ private MethodDto createConditionalMethod(BuilderDefinitionDto builderDto) {
+ MethodDto method = new MethodDto("conditional", builderDto.getBuilderTypeName());
+ method.setOrdering(ORDERING_CONDITIONAL);
+ method.setPriority(MethodDto.PRIORITY_HIGHEST);
+ method.setModifier(Modifier.PUBLIC);
+
+ // Add parameters
+ addConditionalPositiveNegativeParameters(method, builderDto.getBuilderTypeName());
+
+ // Create method implementation
+ method.setCode(
+ """
+ if (condition.getAsBoolean()) {
+ trueCase.accept(this);
+ } else if (falseCase != null) {
+ falseCase.accept(this);
+ }
+ return this;
+ """);
+
+ method.setJavadoc(
+ """
+ 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
+ """);
+
+ return method;
+ }
+
+ /** Creates the conditional(BooleanSupplier, Consumer) method. */
+ private MethodDto createConditionalPositiveOnlyMethod(BuilderDefinitionDto builderDto) {
+ MethodDto method = new MethodDto("conditional", builderDto.getBuilderTypeName());
+ method.setOrdering(ORDERING_CONDITIONAL_POSITIVE_ONLY);
+ method.setPriority(MethodDto.PRIORITY_HIGHEST);
+ method.setModifier(Modifier.PUBLIC);
+
+ // Add parameters
+ addConditionalPositiveOnlyParameters(method, builderDto.getBuilderTypeName());
+
+ // Create method implementation
+ method.setCode("return conditional(condition, yesCondition, null);");
+
+ method.setJavadoc(
+ """
+ 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
+ """);
+
+ return method;
+ }
+
+ /** Adds parameters for the conditional(BooleanSupplier, Consumer, Consumer) method. */
+ private void addConditionalPositiveNegativeParameters(MethodDto method, TypeName builderType) {
+ // BooleanSupplier condition parameter
+ addParameter(method, "condition", JavaLangMapper.map2TypeName(BooleanSupplier.class));
+ // Consumer trueCase parameter
+ addParameter(method, "trueCase", MethodGeneratorUtil.createConsumerType(builderType));
+ // Consumer falseCase parameter
+ addParameter(method, "falseCase", MethodGeneratorUtil.createConsumerType(builderType));
+ }
+
+ /** Adds parameters for the conditional(BooleanSupplier, Consumer) method. */
+ private void addConditionalPositiveOnlyParameters(MethodDto method, TypeName builderType) {
+ // BooleanSupplier condition parameter
+ addParameter(method, "condition", JavaLangMapper.map2TypeName(BooleanSupplier.class));
+ // Consumer yesCondition parameter
+ addParameter(method, "yesCondition", MethodGeneratorUtil.createConsumerType(builderType));
+ }
+
+ /** Adds a parameter to the method. */
+ private void addParameter(MethodDto method, String name, TypeName type) {
+ org.javahelpers.simple.builders.processor.dtos.MethodParameterDto parameter =
+ new org.javahelpers.simple.builders.processor.dtos.MethodParameterDto();
+ parameter.setParameterName(name);
+ parameter.setParameterTypeName(type);
+ method.addParameter(parameter);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java
new file mode 100644
index 00000000..5e6a33d4
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/CoreMethodsEnhancer.java
@@ -0,0 +1,308 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.Modifier;
+import org.javahelpers.simple.builders.processor.dtos.AnnotationDto;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.GenericParameterDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds core builder methods (build, create, toString).
+ *
+ * This enhancer generates the essential methods that every builder needs: {@code build()} to
+ * construct the final DTO instance, {@code create()} as a static factory method, and {@code
+ * toString()} for debugging and logging.
+ *
+ *
Important behavior: The {@code build()} method performs null-safety validation for
+ * non-null fields, constructs the DTO instance, and assigns all set field values. The {@code
+ * create()} method provides a convenient static entry point. The {@code toString()} method shows
+ * which fields are set and their values.
+ *
+ *
Requirements: Always applies to all builders. These methods are fundamental to builder
+ * functionality and cannot be disabled.
+ *
+ *
This enhancer cannot be deactivated as it provides the core builder functionality.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String author, int pages) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.create() // Static factory method
+ * .title("My Book")
+ * .author("John Doe")
+ * .pages(250)
+ * .build(); // Constructs the final BookDto
+ *
+ * // toString() for debugging (only shows set fields with unwrapped values):
+ * System.out.println(BookDtoBuilder.create().title("Test"));
+ * // Output: BookDtoBuilder[title=Test]
+ * }
+ */
+public class CoreMethodsEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 100;
+
+ // Ordering constants for method generation order
+ private static final int ORDERING_CREATE = 200; // After constructor, before field methods
+ private static final int ORDERING_BUILD =
+ 1200; // After builder methods and conditional, before toString
+ private static final int ORDERING_TO_STRING = 2000; // Last, after conditional methods
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return true; // Core methods are always needed
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add build() method
+ MethodDto buildMethod = createBuildMethod(builderDto);
+ builderDto.addCoreMethod(buildMethod);
+
+ // Add static create() method
+ MethodDto createMethod = createStaticCreateMethod(builderDto);
+ builderDto.addCoreMethod(createMethod);
+
+ // Add toString() method
+ MethodDto toStringMethod = createToStringMethod(builderDto);
+ builderDto.addCoreMethod(toStringMethod);
+
+ context.debug(
+ "Added core methods to builder %s", builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /** Creates the build() method. */
+ protected MethodDto createBuildMethod(BuilderDefinitionDto builderDto) {
+ TypeName returnType =
+ MethodGeneratorUtil.createGenericTypeName(
+ builderDto.getBuildingTargetTypeName(), builderDto.getGenerics());
+ MethodDto method = new MethodDto("build", returnType);
+ method.setOrdering(ORDERING_BUILD);
+ method.setPriority(MethodDto.PRIORITY_HIGHEST);
+ method.setModifier(Modifier.PUBLIC);
+
+ // Add @Override annotation only if implementing IBuilderBase interface
+ if (builderDto.getConfiguration().shouldImplementBuilderBase()) {
+ org.javahelpers.simple.builders.processor.dtos.AnnotationDto overrideAnnotation =
+ new org.javahelpers.simple.builders.processor.dtos.AnnotationDto();
+ overrideAnnotation.setAnnotationType(JavaLangMapper.map2TypeName(Override.class));
+ method.addAnnotation(overrideAnnotation);
+ }
+
+ // Create method implementation with validation and setter application
+ StringBuilder code = new StringBuilder();
+
+ // Add validation for non-nullable constructor fields
+ for (var field : builderDto.getConstructorFieldsForBuilder()) {
+ if (field.isNonNullable()) {
+ code.append("if (!this.")
+ .append(field.getFieldName())
+ .append(".isSet()) {\n")
+ .append(" throw new IllegalStateException(\"Required field '")
+ .append(field.getFieldName())
+ .append("' must be set before calling build()\");\n")
+ .append("}\n");
+ code.append("if (this.")
+ .append(field.getFieldName())
+ .append(".value() == null) {\n")
+ .append(" throw new IllegalStateException(\"Field '")
+ .append(field.getFieldName())
+ .append("' is marked as non-null but null value was provided\");\n")
+ .append("}\n");
+ }
+ }
+
+ // Add validation for non-nullable setter fields
+ for (var field : builderDto.getSetterFieldsForBuilder()) {
+ if (field.isNonNullable()) {
+ code.append("if (this.")
+ .append(field.getFieldName())
+ .append(".isSet() && this.")
+ .append(field.getFieldName())
+ .append(".value() == null) {\n")
+ .append(" throw new IllegalStateException(\"Field '")
+ .append(field.getFieldName())
+ .append("' is marked as non-null but null value was provided\");\n")
+ .append("}\n");
+ }
+ }
+
+ // Create DTO instance
+ String ctorArgs = createConstructorArgsString(builderDto);
+ if (builderDto.getGenerics().isEmpty()) {
+ code.append("$buildResultType:T result = new $dtoBaseType:T(")
+ .append(ctorArgs)
+ .append(");\n");
+ } else {
+ code.append("$buildResultType:T result = new $dtoBaseType:T<>(")
+ .append(ctorArgs)
+ .append(");\n");
+ }
+
+ // Apply setter-based fields
+ for (var field : builderDto.getSetterFieldsForBuilder()) {
+ code.append("this.")
+ .append(field.getFieldName())
+ .append(".ifSet(result::")
+ .append(field.getSetterName())
+ .append(");\n");
+ }
+
+ code.append("return result;");
+
+ method.setCode(code.toString());
+ method.addArgument("dtoBaseType", builderDto.getBuildingTargetTypeName());
+ method.addArgument("buildResultType", returnType);
+
+ method.setJavadoc("Builds the configured DTO instance.");
+
+ return method;
+ }
+
+ /** Creates the static create() method. */
+ protected MethodDto createStaticCreateMethod(BuilderDefinitionDto builderDto) {
+ TypeName returnType =
+ MethodGeneratorUtil.createGenericTypeName(
+ builderDto.getBuilderTypeName(), builderDto.getGenerics());
+ MethodDto method = new MethodDto("create", returnType);
+ method.setOrdering(ORDERING_CREATE);
+ method.setPriority(MethodDto.PRIORITY_HIGHEST);
+ method.setModifier(Modifier.PUBLIC);
+ method.setStatic(true);
+
+ // Use appropriate code template based on whether we have generics
+ if (builderDto.getGenerics().isEmpty()) {
+ method.setCode("return new $builderType:T();");
+ } else {
+ method.setCode("return new $builderType:T<>();");
+ }
+
+ // Add generic type parameters to method if builder has generics
+ // (because this is a static function, the generic names from class are not available)
+ for (GenericParameterDto genericParam : builderDto.getGenerics()) {
+ method.addGenericParameter(genericParam);
+ }
+ method.addArgument("builderType", builderDto.getBuilderTypeName());
+
+ String targetFullName = builderDto.getBuildingTargetTypeName().getFullQualifiedName();
+
+ method.setJavadoc(
+ """
+ Creating a new builder for {@code %s}.
+
+ @return builder for {@code %s}
+ """
+ .formatted(targetFullName, targetFullName));
+
+ return method;
+ }
+
+ /** Creates the toString() method. */
+ protected MethodDto createToStringMethod(BuilderDefinitionDto builderDto) {
+ MethodDto method =
+ new MethodDto(
+ "toString",
+ new org.javahelpers.simple.builders.processor.dtos.TypeName("java.lang", "String"));
+ method.setOrdering(ORDERING_TO_STRING);
+ method.setPriority(MethodDto.PRIORITY_HIGHEST);
+ method.setModifier(Modifier.PUBLIC);
+ AnnotationDto overrideAnnotation = new AnnotationDto();
+ overrideAnnotation.setAnnotationType(JavaLangMapper.map2TypeName(Override.class));
+ method.addAnnotation(overrideAnnotation);
+
+ // Create method implementation
+ method.setCode(
+ "return new $toStringBuilder:T(this, $toStringStyle:T.INSTANCE)"
+ + createToStringAppendCalls(builderDto)
+ + "\n .toString();");
+
+ // Add template arguments for code generation
+ method.addArgument(
+ "toStringBuilder",
+ new org.javahelpers.simple.builders.processor.dtos.TypeName(
+ "org.apache.commons.lang3.builder", "ToStringBuilder"));
+ method.addArgument(
+ "toStringStyle",
+ new org.javahelpers.simple.builders.processor.dtos.TypeName(
+ "org.javahelpers.simple.builders.core.util", "BuilderToStringStyle"));
+
+ method.setJavadoc(
+ """
+ Returns a string representation of this builder, including only fields that have been set.
+
+ @return string representation of the builder
+ """);
+
+ return method;
+ }
+
+ /** Creates the constructor arguments string for the build() method. */
+ private String createConstructorArgsString(BuilderDefinitionDto builderDto) {
+ return builderDto.getConstructorFieldsForBuilder().stream()
+ .map(field -> "this." + field.getFieldName() + ".value()")
+ .reduce((a, b) -> a + ", " + b)
+ .orElse("");
+ }
+
+ /** Creates the append calls for toString() method. */
+ private String createToStringAppendCalls(BuilderDefinitionDto builderDto) {
+ StringBuilder sb = new StringBuilder();
+
+ // Combine all fields and process them
+ List allFields = new ArrayList<>();
+ allFields.addAll(builderDto.getConstructorFieldsForBuilder());
+ allFields.addAll(builderDto.getSetterFieldsForBuilder());
+
+ for (int i = 0; i < allFields.size(); i++) {
+ FieldDto field = allFields.get(i);
+ sb.append("\n .append(\"")
+ .append(field.getFieldName())
+ .append("\", this.")
+ .append(field.getFieldName())
+ .append(")");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/FieldConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/FieldConsumerGenerator.java
new file mode 100644
index 00000000..b5f70964
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/FieldConsumerGenerator.java
@@ -0,0 +1,133 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+
+import java.util.Collections;
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for fields with concrete classes that have empty constructors.
+ *
+ * This generator creates methods that accept a {@code Consumer} to configure field
+ * instances. The field type is instantiated using its no-arg constructor, then passed to the
+ * consumer for configuration.
+ *
+ * Important behavior: A new instance of the field type is created using its empty
+ * constructor, then the consumer is invoked to configure it. This allows fluent configuration of
+ * complex objects without manually creating them first.
+ *
+ *
Requirements: Only applies to fields whose type has an accessible no-arg constructor.
+ * Does not apply if the field type has a builder (higher priority) or if it's a standard collection
+ * type with a specific consumer generator enabled.
+ *
+ *
This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateFieldConsumer} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, PublisherDto publisher) {}
+ *
+ * // Needs to be a class, otherwise it would not have a NoArg-Constructor
+ * public class PublisherDto {
+ * private String name;
+ * public PublisherDto() {}
+ * public void changeName(String name) { this.name = name; }
+ * }
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .title("My Book")
+ * .publisher(p -> p.changeName("Publisher Inc."))
+ * .build();
+ * }
+ */
+public class FieldConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 54;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateFieldConsumer()) {
+ return false;
+ }
+
+ // Only apply if no builder consumer applies (builder has higher priority)
+ if (field.getFieldType().getBuilderType().isPresent()) {
+ return false;
+ }
+
+ // Don't apply to standard collection types when their specific consumer generators are enabled
+ if (hasSpecificCollectionConsumer(field, context)) {
+ return false;
+ }
+
+ return field.getFieldType().hasEmptyConstructor();
+ }
+
+ /**
+ * Checks if a specific collection consumer generator is enabled for this field type.
+ *
+ * @param field the field to check
+ * @param context the processing context
+ * @return true if a specific consumer (List, Set, or Map) should be generated instead
+ */
+ private boolean hasSpecificCollectionConsumer(FieldDto field, ProcessingContext context) {
+ TypeName fieldType = field.getFieldType();
+
+ return (fieldType instanceof TypeNameList
+ && context.getConfiguration().shouldUseArrayListBuilder())
+ || (fieldType instanceof TypeNameSet
+ && context.getConfiguration().shouldUseHashSetBuilder())
+ || (fieldType instanceof TypeNameMap
+ && context.getConfiguration().shouldUseHashMapBuilder());
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ if (!field.getFieldType().hasEmptyConstructor()) {
+ return Collections.emptyList();
+ }
+
+ MethodDto method = createSimpleFieldConsumer(field, field.getFieldType(), builderType, context);
+ return Collections.singletonList(method);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java
new file mode 100644
index 00000000..7fa470db
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratedAnnotationEnhancer.java
@@ -0,0 +1,105 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import javax.annotation.processing.Generated;
+import org.javahelpers.simple.builders.processor.dtos.AnnotationDto;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds the @Generated annotation to generated builder classes.
+ *
+ * This enhancer adds the standard {@code @Generated} annotation to indicate that the builder
+ * class was generated by the simple-builders annotation processor. This helps tools and developers
+ * identify generated code.
+ *
+ *
Important behavior: Adds the {@code @Generated} annotation with the processor class
+ * name as the value. This annotation is recognized by many tools for code coverage, static
+ * analysis, and IDE features.
+ *
+ *
Requirements: Applies to all builders by default. The {@code @Generated} annotation is
+ * standard practice for generated code.
+ *
+ *
This enhancer is enabled by default and can be deactivated by setting the configuration flag
+ * {@code usingGeneratedAnnotation} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example of generated annotation
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String author) {}
+ *
+ * // Generated builder with @Generated annotation:
+ * // @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
+ * // public class BookDtoBuilder { ... }
+ * }
+ */
+public class GeneratedAnnotationEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 120;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldUseGeneratedAnnotation();
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ AnnotationDto generatedAnnotation = createGeneratedAnnotation();
+ builderDto.addClassAnnotation(generatedAnnotation);
+
+ context.debug(
+ "Added @Generated annotation to builder %s",
+ builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Creates the @Generated annotation.
+ *
+ * @return the annotation DTO for @Generated
+ */
+ private AnnotationDto createGeneratedAnnotation() {
+ AnnotationDto annotation = new AnnotationDto();
+ annotation.setAnnotationType(JavaLangMapper.map2TypeName(Generated.class));
+
+ // Add value member with processor class name (using the original name)
+ annotation.addMember(
+ "value", "\"Generated by org.javahelpers.simple.builders.processor.BuilderProcessor\"");
+
+ return annotation;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/Generator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/Generator.java
new file mode 100644
index 00000000..ca5f0c45
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/Generator.java
@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+/**
+ * Marker interface for all generator components (method generators and builder enhancers).
+ *
+ * This interface serves as a common parent for both {@link MethodGenerator} and {@link
+ * BuilderEnhancer}, allowing them to be registered in a single ServiceLoader service file while
+ * maintaining their distinct interfaces and method signatures.
+ *
+ *
This is a sealed interface that permits only two implementations:
+ *
+ *
+ * {@link MethodGenerator} - for generating methods on individual fields
+ * {@link BuilderEnhancer} - for enhancing the entire builder class
+ *
+ *
+ * Custom generators must implement one of the permitted sub-interfaces, not this interface
+ * directly. Custom generators should be registered in {@code
+ * META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator}
+ *
+ * @see MethodGenerator
+ * @see BuilderEnhancer
+ */
+public sealed interface Generator permits MethodGenerator, BuilderEnhancer {
+
+ /**
+ * Returns the priority of this generator. Generators with higher priority values are executed
+ * first.
+ *
+ *
Recommended priority ranges:
+ *
+ *
+ * 100+ - Core infrastructure (basic setters, core methods, critical annotations)
+ * 70-99 - Standard features (optional helpers, consumer methods)
+ * 40-69 - Convenience features (varargs, suppliers)
+ * 10-39 - Optional enhancements (collection helpers, documentation)
+ *
+ *
+ * @return the priority value (higher values execute first)
+ */
+ int getPriority();
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java
new file mode 100644
index 00000000..eed36cad
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/GeneratorRegistry.java
@@ -0,0 +1,211 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ServiceLoader;
+import javax.annotation.processing.ProcessingEnvironment;
+import org.apache.commons.collections4.CollectionUtils;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ComponentFilter;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Unified registry that manages all generators (both method generators and builder enhancers).
+ *
+ * This class is responsible for:
+ *
+ *
+ * Loading all generators via ServiceLoader (built-in and custom)
+ * Sorting generators by priority (highest first)
+ * Coordinating field-level method generation via {@link MethodGenerator}
+ * Coordinating builder-level enhancement via {@link BuilderEnhancer}
+ *
+ *
+ * The registry loads all {@link Generator} implementations from a single service file and
+ * separates them into method generators and builder enhancers based on their type.
+ */
+public class GeneratorRegistry {
+
+ private final List methodGenerators;
+ private final List builderEnhancers;
+ private final ProcessingContext context;
+ private final ComponentFilter componentFilter;
+
+ /**
+ * Creates a new registry and initializes it with built-in and custom generators.
+ *
+ * @param context the processing context for configuration and utilities
+ * @param processingEnv the processing environment for reading compiler arguments
+ */
+ public GeneratorRegistry(ProcessingContext context, ProcessingEnvironment processingEnv) {
+ this.context = context;
+ this.methodGenerators = new ArrayList<>();
+ this.builderEnhancers = new ArrayList<>();
+ this.componentFilter = new ComponentFilter(processingEnv);
+
+ loadAllGenerators();
+ sortGeneratorsByPriority();
+
+ context.debug(
+ "Initialized GeneratorRegistry with %d method generators and %d builder enhancers",
+ methodGenerators.size(), builderEnhancers.size());
+ }
+
+ /**
+ * Generates all methods for a field using all registered method generators.
+ *
+ * @param field the field to generate methods for, should not be null
+ * @param dtoType the TypeName of the DTO containing the field, should not be null
+ * @param builderType the type of the builder being generated, should not be null
+ * @return list of all generated methods from all applicable generators
+ */
+ public List generateAllMethods(
+ FieldDto field, TypeName dtoType, TypeName builderType) {
+ List allMethods = new ArrayList<>();
+
+ for (MethodGenerator generator : methodGenerators) {
+ if (generator.appliesTo(field, dtoType, context)) {
+ try {
+ context.debug(
+ " -> Applying method generator: %s (priority: %d)",
+ generator.getClass().getSimpleName(), generator.getPriority());
+
+ List generatedMethods = generator.generateMethods(field, builderType, context);
+
+ if (CollectionUtils.isNotEmpty(generatedMethods)) {
+ allMethods.addAll(generatedMethods);
+ }
+ } catch (Exception e) {
+ context.error(
+ "Failed to apply method generator %s to field %s: %s",
+ generator.getClass().getName(), field.getFieldName(), e.getMessage());
+ }
+ }
+ }
+
+ return allMethods;
+ }
+
+ /**
+ * Applies all applicable builder enhancers to the given builder.
+ *
+ * @param builderDto the builder to enhance
+ * @param dtoType the DTO type the builder is for
+ */
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, TypeName dtoType) {
+ for (BuilderEnhancer enhancer : builderEnhancers) {
+ if (enhancer.appliesTo(builderDto, dtoType, context)) {
+ try {
+ context.debug(
+ " -> Applying builder enhancer: %s (priority: %d)",
+ enhancer.getClass().getSimpleName(), enhancer.getPriority());
+
+ enhancer.enhanceBuilder(builderDto, context);
+
+ context.debug(
+ " Enhanced builder %s with %s",
+ builderDto.getBuilderTypeName().getClassName(), enhancer.getClass().getSimpleName());
+ } catch (Exception e) {
+ context.error(
+ "Failed to apply enhancer %s to builder %s: %s",
+ enhancer.getClass().getName(),
+ builderDto.getBuilderTypeName().getClassName(),
+ e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads all generators (built-in and custom) via ServiceLoader.
+ *
+ * Generators are discovered by looking for implementations of {@link Generator} declared in
+ * {@code META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator} files.
+ *
+ *
The loaded generators are separated into method generators and builder enhancers based on
+ * their type (using the sealed interface hierarchy).
+ *
+ *
If loading fails for any generator, a warning is logged but processing continues with the
+ * remaining generators.
+ */
+ private void loadAllGenerators() {
+ int methodGenCount = 0;
+ int enhancerCount = 0;
+
+ try {
+ ServiceLoader serviceLoader =
+ ServiceLoader.load(Generator.class, Generator.class.getClassLoader());
+
+ for (Generator generator : serviceLoader) {
+ String generatorClassName = generator.getClass().getName();
+
+ // Check if this generator should be deactivated
+ if (componentFilter.shouldDeactivateComponent(generatorClassName)) {
+ context.debug("Skipping deactivated generator: %s", generatorClassName);
+ continue;
+ }
+
+ // Separate into method generators and builder enhancers
+ // The sealed interface ensures generator is either MethodGenerator or BuilderEnhancer
+ if (generator instanceof MethodGenerator methodGen) {
+ methodGenerators.add(methodGen);
+ methodGenCount++;
+ context.debug(
+ "Loaded method generator: %s (priority: %d)",
+ generatorClassName, methodGen.getPriority());
+ } else if (generator instanceof BuilderEnhancer enhancer) {
+ builderEnhancers.add(enhancer);
+ enhancerCount++;
+ context.debug(
+ "Loaded builder enhancer: %s (priority: %d)",
+ generatorClassName, enhancer.getPriority());
+ }
+ }
+ } catch (Exception e) {
+ context.error("Failed to load generators: %s", e.getMessage());
+ }
+
+ context.debug(
+ "Loaded %d method generators and %d builder enhancers total",
+ methodGenCount, enhancerCount);
+ }
+
+ /**
+ * Sorts all registered generators by priority in descending order (highest priority first).
+ *
+ * This ensures that generators with higher priority values execute before those with lower
+ * priority values.
+ */
+ private void sortGeneratorsByPriority() {
+ methodGenerators.sort(Comparator.comparingInt(MethodGenerator::getPriority).reversed());
+ builderEnhancers.sort(Comparator.comparingInt(BuilderEnhancer::getPriority).reversed());
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java
new file mode 100644
index 00000000..09dfb12d
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/InterfaceEnhancer.java
@@ -0,0 +1,119 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.InterfaceName;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds the IBuilderBase interface to generated builder classes.
+ *
+ *
This enhancer adds the {@code IBuilderBase} interface to builders when enabled in the
+ * configuration. This interface provides a contract for builder implementations and enables
+ * polymorphic usage of builders.
+ *
+ * Important behavior: Makes the builder implement {@code IBuilderBase} where {@code T}
+ * is the target DTO type. This enables polymorphic builder usage and provides a common interface
+ * for all builders.
+ *
+ * Requirements: Applies to all builders by default. The {@code IBuilderBase} interface
+ * must be available on the classpath.
+ *
+ *
This enhancer is enabled by default and can be deactivated by setting the configuration flag
+ * {@code implementsBuilderBase()} to {@code false}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated interface
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import org.javahelpers.simple.builders.core.IBuilderBase;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String author) {}
+ *
+ * // Generated builder implementing IBuilderBase:
+ * // public class BookDtoBuilder implements IBuilderBase { ... }
+ *
+ * // Usage - configure with concrete type, use polymorphically for build:
+ * BookDtoBuilder builder = BookDtoBuilder.create()
+ * .title("My Book")
+ * .author("John Doe");
+ *
+ * // Can be passed to methods expecting IBuilderBase:
+ * BookDto result = buildFromInterface(builder);
+ *
+ * static T buildFromInterface(IBuilderBase builder) {
+ * return builder.build();
+ * }
+ * }
+ */
+public class InterfaceEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 90;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldImplementBuilderBase();
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ // Add the IBuilderBase interface to the builder
+ InterfaceName builderBaseInterface = createBuilderBaseInterface(builderDto);
+ builderDto.addInterface(builderBaseInterface);
+
+ context.debug(
+ "Added IBuilderBase interface to builder %s",
+ builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Creates the IBuilderBase interface type parameterized with the DTO type.
+ *
+ * @param builderDto the builder definition containing the DTO type and generics
+ * @return the parameterized interface type
+ */
+ private InterfaceName createBuilderBaseInterface(BuilderDefinitionDto builderDto) {
+ InterfaceName interfaceType =
+ new InterfaceName("org.javahelpers.simple.builders.core.interfaces", "IBuilderBase");
+
+ // Use generic DTO type if the builder has generics, otherwise use base type
+ TypeName parameterizedDtoType =
+ MethodGeneratorUtil.createGenericTypeName(
+ builderDto.getBuildingTargetTypeName(), builderDto.getGenerics());
+
+ interfaceType.addTypeParameter(parameterizedDtoType);
+ return interfaceType;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java
new file mode 100644
index 00000000..d3ef7ad2
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/JacksonAnnotationEnhancer.java
@@ -0,0 +1,133 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import org.javahelpers.simple.builders.processor.dtos.AnnotationDto;
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds the @JsonPOJOBuilder annotation to generated builder classes.
+ *
+ * This enhancer adds the Jackson {@code @JsonPOJOBuilder} annotation to enable Jackson
+ * deserialization support for the generated builders. This allows Jackson to properly deserialize
+ * JSON into DTO instances using the builder pattern.
+ *
+ *
Important behavior: The {@code @JsonPOJOBuilder} annotation is added to the builder
+ * class with the {@code withPrefix} parameter matching the configured setter prefix (default is
+ * empty string). This tells Jackson how to map JSON properties to builder methods.
+ *
+ *
Requirements: Only applies when Jackson deserializer annotation support is enabled and
+ * the {@code com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder} class is available on the
+ * classpath.
+ *
+ *
This enhancer is disabled by default and can be activated by setting the configuration flag
+ * {@code usingJacksonDeserializerAnnotation} to {@code ENABLED}. For detailed usage instructions,
+ * see the
+ * Jackson Support section in CONFIGURATION.md .
+ *
+ *
Example to demonstrate the generated annotation
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+ *
+ * @SimpleBuilder
+ * @JsonDeserialize(builder = BookDtoBuilder.class)
+ * public record BookDto(String title, String author) {}
+ *
+ * // Generated builder with Jackson annotation:
+ * @JsonPOJOBuilder(withPrefix = "")
+ * public class BookDtoBuilder {
+ * // ... builder methods
+ * }
+ *
+ * // Usage with Jackson:
+ * ObjectMapper mapper = new ObjectMapper();
+ * String json = "{\"title\":\"My Book\",\"author\":\"John Doe\"}";
+ * BookDto book = mapper.readValue(json, BookDto.class);
+ * }
+ */
+public class JacksonAnnotationEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 110;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldUseJacksonDeserializerAnnotation()
+ && isJacksonAvailable(context);
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ AnnotationDto jacksonAnnotation = createJsonPOJOBuilderAnnotation(builderDto);
+ builderDto.addClassAnnotation(jacksonAnnotation);
+
+ context.debug(
+ "Added @JsonPOJOBuilder annotation to builder %s",
+ builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Checks if Jackson is available on the classpath.
+ *
+ * @param context the processing context
+ * @return true if Jackson is available, false otherwise
+ */
+ private boolean isJacksonAvailable(ProcessingContext context) {
+ return context.getTypeElement("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder")
+ != null;
+ }
+
+ /**
+ * Creates the @JsonPOJOBuilder annotation.
+ *
+ * @param builderDto the builder definition
+ * @return the annotation DTO for @JsonPOJOBuilder
+ */
+ private AnnotationDto createJsonPOJOBuilderAnnotation(BuilderDefinitionDto builderDto) {
+ AnnotationDto annotation = new AnnotationDto();
+ annotation.setAnnotationType(
+ new TypeName("com.fasterxml.jackson.databind.annotation", "JsonPOJOBuilder"));
+
+ // Add withPrefix member with the setter prefix from configuration
+ String setterPrefix = builderDto.getConfiguration().getSetterSuffix();
+ if (setterPrefix != null) {
+ annotation.addMember("withPrefix", "\"" + setterPrefix + "\"");
+ } else {
+ annotation.addMember("withPrefix", "\"\"");
+ }
+
+ return annotation;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ListConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ListConsumerGenerator.java
new file mode 100644
index 00000000..8f18f5cd
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/ListConsumerGenerator.java
@@ -0,0 +1,148 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.javahelpers.simple.builders.core.builders.ArrayListBuilder;
+import org.javahelpers.simple.builders.core.builders.ArrayListBuilderWithElementBuilders;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for List fields with collection builder support.
+ *
+ * This generator creates methods that accept {@code Consumer} or {@code
+ * Consumer} depending on whether the element type has a
+ * builder. The consumer configures the collection builder, which is then built and assigned.
+ *
+ * Important behavior: A collection builder is created, passed to the consumer for
+ * configuration (adding elements, configuring nested builders, etc.), then automatically built. For
+ * element types with builders, {@code ArrayListBuilderWithElementBuilders} provides additional
+ * methods to add elements via their builders.
+ *
+ *
Requirements: Only applies to {@code List} fields. Uses {@code ArrayListBuilder}
+ * for simple element types, or {@code ArrayListBuilderWithElementBuilders} when the
+ * element type has a {@code @SimpleBuilder} annotation.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code usingArrayListBuilder} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.List;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(List tags, List authors) {}
+ *
+ * @SimpleBuilder
+ * public record AuthorDto(String name) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .tags(t -> t.add("java").add("builder"))
+ * .authors(a -> a
+ * .add(b -> b.name("John Doe"))
+ * .add(b -> b.name("Jane Smith")))
+ * .build();
+ * }
+ */
+public class ListConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 53;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
+ return false;
+ }
+ // Only apply to List fields
+ if (!(field.getFieldType() instanceof TypeNameList fieldTypeGeneric
+ && fieldTypeGeneric.isParameterized())) {
+ return false;
+ }
+ // Don't apply if field itself has a builder (higher priority)
+ if (field.getFieldType().getBuilderType().isPresent()) {
+ return false;
+ }
+
+ return context.getConfiguration().shouldUseArrayListBuilder()
+ || context.getConfiguration().shouldUseArrayListBuilderWithElementBuilders();
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ if (!(field.getFieldType() instanceof TypeNameList fieldTypeGeneric
+ && fieldTypeGeneric.isParameterized())) {
+ return Collections.emptyList();
+ }
+
+ TypeName elementType = fieldTypeGeneric.getElementType();
+ Optional elementBuilderType = fieldTypeGeneric.getElementBuilderType();
+
+ if (elementBuilderType.isPresent()
+ && context.getConfiguration().shouldUseArrayListBuilderWithElementBuilders()) {
+ TypeName collectionBuilderType =
+ new TypeNameGeneric(
+ map2TypeName(ArrayListBuilderWithElementBuilders.class),
+ elementType,
+ elementBuilderType.get());
+ MethodDto method =
+ MethodGeneratorUtil.createFieldConsumerWithElementBuilders(
+ field, collectionBuilderType, elementBuilderType.get(), builderType, context);
+ return List.of(method);
+ } else if (context.getConfiguration().shouldUseArrayListBuilder()) {
+ TypeName arrayListBuilderType = map2TypeName(ArrayListBuilder.class);
+ TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(arrayListBuilderType, elementType);
+ MethodDto method =
+ MethodGeneratorUtil.createFieldConsumerWithBuilder(
+ field,
+ builderTypeGeneric,
+ "this.$fieldName:N.value()",
+ "",
+ Map.of(),
+ builderType,
+ context);
+ return List.of(method);
+ }
+
+ return Collections.emptyList();
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MapConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MapConsumerGenerator.java
new file mode 100644
index 00000000..a42531f5
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MapConsumerGenerator.java
@@ -0,0 +1,123 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.javahelpers.simple.builders.core.builders.HashMapBuilder;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for Map fields with map builder support.
+ *
+ * This generator creates methods that accept {@code Consumer>} to build map
+ * instances. The consumer configures the map builder by adding key-value pairs, which is then built
+ * and assigned to the field.
+ *
+ * Important behavior: A {@code HashMapBuilder} is created, passed to the consumer for
+ * configuration (adding entries via {@code put()} method), then automatically built. This provides
+ * a fluent API for constructing maps.
+ *
+ *
Requirements: Only applies to {@code Map} fields. Uses {@code HashMapBuilder} for all map types.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code usingHashMapBuilder} to {@code DISABLED}. See the configuration documentation for details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.Map;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(Map metadata, Map ratings) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .metadata(m -> m.put("author", "John Doe").put("isbn", "123-456"))
+ * .ratings(r -> r.put("quality", 5).put("readability", 4))
+ * .build();
+ * }
+ */
+public class MapConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 51;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ BuilderConfiguration configuration = context.getConfiguration();
+ TypeName fieldType = field.getFieldType();
+ return
+ // Builder consumer generation must be enabled
+ configuration.shouldGenerateBuilderConsumer()
+ // HashMapBuilder usage must be enabled
+ && configuration.shouldUseHashMapBuilder()
+ // Field must be a Map type
+ && fieldType instanceof TypeNameMap fieldTypeGeneric
+ // Map must be parameterized (has key/value types)
+ && fieldTypeGeneric.isParameterized()
+ // Field shouldn't have its own builder (higher priority)
+ && !fieldTypeGeneric.getBuilderType().isPresent()
+ // Field shouldn't have empty constructor (higher priority)
+ && !fieldTypeGeneric.hasEmptyConstructor();
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ if (!(field.getFieldType() instanceof TypeNameMap fieldTypeGeneric
+ && fieldTypeGeneric.isParameterized())) {
+ return Collections.emptyList();
+ }
+
+ TypeNameGeneric builderTargetTypeName =
+ new TypeNameGeneric(
+ map2TypeName(HashMapBuilder.class),
+ fieldTypeGeneric.getKeyType(),
+ fieldTypeGeneric.getValueType());
+ MethodDto mapConsumerWithBuilder =
+ MethodGeneratorUtil.createFieldConsumerWithBuilder(
+ field,
+ builderTargetTypeName,
+ "this.$fieldName:N.value()",
+ "",
+ Map.of(),
+ builderType,
+ context);
+ return List.of(mapConsumerWithBuilder);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java
new file mode 100644
index 00000000..410a756c
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGenerator.java
@@ -0,0 +1,88 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Service Provider Interface (SPI) for generating builder methods for fields.
+ *
+ * Implementations of this interface are responsible for generating specific types of methods
+ * (e.g., setters, consumers, suppliers, helpers) for builder fields. Each generator focuses on a
+ * single feature or method type.
+ *
+ *
Custom generators can be provided by library users through the Java ServiceLoader mechanism by
+ * creating a file {@code
+ * META-INF/services/org.javahelpers.simple.builders.processor.generators.Generator} containing the
+ * fully qualified class names of custom generator implementations.
+ *
+ *
Generators are executed in priority order (highest first). Multiple generators can contribute
+ * methods to the same field.
+ *
+ *
This interface is non-sealed to allow custom implementations by library users.
+ */
+public non-sealed interface MethodGenerator extends Generator {
+
+ /**
+ * Determines whether this generator applies to the given field based on field type,
+ * configuration, and other context.
+ *
+ *
This method is called before {@link #generateMethods} to determine if the generator should
+ * be invoked for a particular field. Generators should check:
+ *
+ *
+ * Configuration flags (e.g., {@code shouldGenerateBuilderConsumer()})
+ * Field type compatibility (e.g., only for collections, strings, etc.)
+ * DTO package or annotations (e.g., only for specific packages or annotated DTOs)
+ *
+ *
+ * @param field the field being processed
+ * @param dtoType the type of the DTO class containing this field
+ * @param context the processing context containing configuration and utilities
+ * @return true if this generator should generate methods for this field, false otherwise
+ */
+ boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context);
+
+ /**
+ * Generates methods for the given field.
+ *
+ * This method is only called if {@link #appliesTo} returns true. Implementations should
+ * generate all relevant methods for their feature and return them as a list.
+ *
+ *
The returned methods will be added to the field's method list and eventually rendered in the
+ * generated builder class.
+ *
+ * @param field the field being processed (contains field name, type, javadoc, builder types,
+ * etc.)
+ * @param builderType the type of the builder being generated (used for return types)
+ * @param context the processing context containing configuration and utilities
+ * @return list of generated methods (may be empty but should not be null)
+ */
+ List generateMethods(FieldDto field, TypeName builderType, ProcessingContext context);
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGeneratorUtil.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGeneratorUtil.java
new file mode 100644
index 00000000..c713b7a8
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/MethodGeneratorUtil.java
@@ -0,0 +1,380 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import javax.lang.model.element.Modifier;
+import org.apache.commons.lang3.StringUtils;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.JavapoetMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Utility class providing common functionality for method generators.
+ *
+ * This class contains shared constants and helper methods used across multiple generator
+ * implementations.
+ */
+public final class MethodGeneratorUtil {
+
+ // Constants for method parameter suffixes
+ public static final String SUFFIX_CONSUMER = "Consumer";
+ public static final String SUFFIX_SUPPLIER = "Supplier";
+ public static final String BUILDER_SUFFIX = "Builder";
+
+ public static final TypeName TRACKED_VALUE_TYPE =
+ TypeName.of(org.javahelpers.simple.builders.core.util.TrackedValue.class);
+
+ private MethodGeneratorUtil() {
+ // Utility class - prevent instantiation
+ }
+
+ /**
+ * Generates the name of builder methods according to configuration and field name.
+ *
+ *
If the prefix is empty, returns the fieldName as-is. If the prefix is set, capitalizes the
+ * first letter of fieldName and prepends the prefix.
+ *
+ *
Examples:
+ *
+ *
+ * fieldName="name", prefix="" → "name"
+ * fieldName="name", prefix="with" → "withName"
+ * fieldName="age", prefix="set" → "setAge"
+ *
+ *
+ * @param fieldName the field name
+ * @param context the processing context containing the configuration with the method name prefix
+ * @return the method name with prefix applied
+ */
+ public static String generateBuilderMethodName(String fieldName, ProcessingContext context) {
+ String suffix = context.getConfiguration().getSetterSuffix();
+ if (suffix == null || suffix.isEmpty()) {
+ return fieldName;
+ }
+ return suffix + StringUtils.capitalize(fieldName);
+ }
+
+ /**
+ * Gets the method access modifier from the builder configuration.
+ *
+ * @param context the processing context
+ * @return the Modifier for method access, or null for package-private
+ */
+ public static Modifier getMethodAccessModifier(ProcessingContext context) {
+ return JavapoetMapper.map2Modifier(context.getConfiguration().getMethodAccess());
+ }
+
+ /**
+ * Sets the access modifier on a MethodDto if the modifier is not null.
+ *
+ * @param method the MethodDto to update
+ * @param modifier the access modifier to set, or null for package-private
+ */
+ public static void setMethodAccessModifier(MethodDto method, Modifier modifier) {
+ if (modifier != null) {
+ method.setModifier(modifier);
+ }
+ }
+
+ /**
+ * Creates a generic TypeName from a base type and generic parameters.
+ *
+ * If the generic parameters list is empty, returns the base type as-is. Otherwise creates a
+ * TypeNameGeneric with the base type and generic type variables.
+ *
+ * @param baseType the base type name
+ * @param genericParameters the list of generic parameter DTOs
+ * @return the generic type name, or the base type if no generics
+ */
+ public static TypeName createGenericTypeName(
+ TypeName baseType, List genericParameters) {
+ if (genericParameters.isEmpty()) {
+ return baseType;
+ }
+
+ List typeVariables =
+ genericParameters.stream()
+ .map(GenericParameterDto::getName)
+ .map(TypeNameVariable::new)
+ .map(TypeName.class::cast)
+ .toList();
+
+ return new TypeNameGeneric(baseType, typeVariables);
+ }
+
+ /**
+ * Creates a field setter method with optional transform and annotations.
+ *
+ * @param field the field DTO containing all field information
+ * @param transform optional transform expression (e.g., "Optional.of(%s)")
+ * @param parameterType the type to use for the method parameter (may differ from
+ * field.getFieldType())
+ * @param builderType the builder type for the return type
+ * @param context processing context
+ * @return the method DTO for the setter
+ */
+ public static MethodDto createBuilderMethodForFieldWithTransform(
+ FieldDto field,
+ String transform,
+ TypeName parameterType,
+ TypeName builderType,
+ ProcessingContext context) {
+
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(field.getFieldNameEstimated());
+ parameter.setParameterTypeName(parameterType);
+
+ if (field.getParameterAnnotations() != null) {
+ field.getParameterAnnotations().forEach(parameter::addAnnotation);
+ }
+
+ MethodDto methodDto =
+ new MethodDto(
+ generateBuilderMethodName(field.getFieldNameEstimated(), context), builderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+
+ String params;
+ if (StringUtils.isBlank(transform)) {
+ params = parameter.getParameterName();
+ } else {
+ params = String.format(transform, parameter.getParameterName());
+ }
+
+ methodDto.setCode(
+ """
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParams:N);
+ return this;
+ """);
+ methodDto.addArgument("fieldName", field.getFieldName());
+ methodDto.addArgument("dtoMethodParams", params);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+
+ methodDto.setPriority(transform == null ? MethodDto.PRIORITY_HIGHEST : MethodDto.PRIORITY_HIGH);
+
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s.
+
+ @param %s %s
+ @return current instance of builder
+ """
+ .formatted(
+ field.getFieldNameEstimated(), parameter.getParameterName(), field.getJavaDoc()));
+
+ return methodDto;
+ }
+
+ /**
+ * Creates a field consumer method that accepts a builder for the field value.
+ *
+ * @param field the field DTO
+ * @param fieldBuilderType the builder type used to construct the field value
+ * @param existingValueConstructorArgs constructor arguments when field already has a value
+ * @param emptyConstructorArgs constructor arguments when field is not yet set
+ * @param additionalTemplateArguments additional code template arguments for method generation
+ * @param parentBuilderType the parent builder type that this method returns
+ * @param context the processing context
+ * @return the method DTO for the consumer
+ */
+ public static MethodDto createFieldConsumerWithBuilder(
+ FieldDto field,
+ TypeName fieldBuilderType,
+ String existingValueConstructorArgs,
+ String emptyConstructorArgs,
+ Map additionalTemplateArguments,
+ TypeName parentBuilderType,
+ ProcessingContext context) {
+ TypeNameGeneric consumerType = createConsumerType(fieldBuilderType);
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(field.getFieldName() + BUILDER_SUFFIX + SUFFIX_CONSUMER);
+ parameter.setParameterTypeName(consumerType);
+ MethodDto methodDto =
+ new MethodDto(generateBuilderMethodName(field.getFieldName(), context), parentBuilderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+
+ String buildExpression = calculateBuildExpression(field.getFieldType());
+
+ methodDto.setCode(
+ """
+ $helperType:T builder = this.$fieldName:N.isSet() ? new $helperType:T(%s) : new $helperType:T(%s);
+ $dtoMethodParam:N.accept(builder);
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($buildExpression:N);
+ return this;
+ """
+ .formatted(existingValueConstructorArgs, emptyConstructorArgs));
+ methodDto.addArgument("fieldName", field.getFieldName());
+ methodDto.addArgument("dtoMethodParam", parameter.getParameterName());
+ methodDto.addArgument("helperType", fieldBuilderType);
+ methodDto.addArgument("buildExpression", buildExpression);
+ additionalTemplateArguments.forEach(methodDto::addArgument);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setPriority(MethodDto.PRIORITY_MEDIUM);
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s using a builder consumer that produces the value.
+
+ @param %s consumer providing an instance of a builder for %s
+ @return current instance of builder
+ """
+ .formatted(field.getFieldName(), parameter.getParameterName(), field.getJavaDoc()));
+ return methodDto;
+ }
+
+ /**
+ * Creates a Consumer type.
+ *
+ * @param builderType the builder type
+ * @return a Consumer type
+ */
+ public static TypeNameGeneric createConsumerType(TypeName builderType) {
+ return new TypeNameGeneric(JavaLangMapper.map2TypeName(Consumer.class), builderType);
+ }
+
+ /**
+ * Calculates the build expression for a field type, wrapping concrete collections if needed.
+ *
+ * @param fieldType the field type
+ * @return the build expression
+ */
+ private static String calculateBuildExpression(TypeName fieldType) {
+ return wrapConcreteCollectionType(fieldType, "builder.build()");
+ }
+
+ /**
+ * Wraps an expression with a concrete collection constructor if needed.
+ *
+ * This utility method checks if the field type is a concrete collection implementation (e.g.,
+ * {@code ArrayList}, {@code HashSet}, {@code HashMap}) and wraps the base expression with the
+ * appropriate constructor call. This is useful for custom generators that need to handle concrete
+ * collection types.
+ *
+ *
Examples:
+ *
+ *
+ * For {@code ArrayList}: wraps {@code List.of(args)} → {@code new
+ * ArrayList<>(List.of(args))}
+ * For {@code HashSet}: wraps {@code Set.of(args)} → {@code new
+ * HashSet<>(Set.of(args))}
+ * For {@code List} (interface): returns {@code List.of(args)} unchanged
+ *
+ *
+ * @param fieldType the field type to check
+ * @param baseExpression the base expression to potentially wrap
+ * @return the wrapped expression for concrete collections, or base expression otherwise
+ */
+ public static String wrapConcreteCollectionType(TypeName fieldType, String baseExpression) {
+ if (fieldType instanceof TypeNameList listType && listType.isConcreteImplementation()) {
+ return "new " + listType.getClassName() + "<>(" + baseExpression + ")";
+ } else if (fieldType instanceof TypeNameSet setType && setType.isConcreteImplementation()) {
+ return "new " + setType.getClassName() + "<>(" + baseExpression + ")";
+ } else if (fieldType instanceof TypeNameMap mapType && mapType.isConcreteImplementation()) {
+ return "new " + mapType.getClassName() + "<>(" + baseExpression + ")";
+ }
+ return baseExpression;
+ }
+
+ /**
+ * Creates a field consumer method that accepts a builder for collections with element builders.
+ *
+ * @param field the field DTO
+ * @param collectionBuilderType the collection builder type
+ * @param elementBuilderType the element builder type
+ * @param returnBuilderType the return builder type
+ * @param context the processing context
+ * @return the method DTO for the consumer
+ */
+ public static MethodDto createFieldConsumerWithElementBuilders(
+ FieldDto field,
+ TypeName collectionBuilderType,
+ TypeName elementBuilderType,
+ TypeName returnBuilderType,
+ ProcessingContext context) {
+ return createFieldConsumerWithBuilder(
+ field,
+ collectionBuilderType,
+ "this.$fieldName:N.value(), $elementBuilderType:T::create",
+ "$elementBuilderType:T::create",
+ Map.of("elementBuilderType", elementBuilderType),
+ returnBuilderType,
+ context);
+ }
+
+ /**
+ * Creates a simple field consumer method that accepts a Consumer for the field value.
+ *
+ * This method creates a consumer that initializes the field value if not set, accepts the
+ * consumer to modify it, and stores the result.
+ *
+ * @param field the field DTO containing field information
+ * @param fieldType the type of the field (used for instantiation)
+ * @param builderType the builder type for the return type
+ * @param context the processing context
+ * @return the method DTO for the simple field consumer
+ */
+ public static MethodDto createSimpleFieldConsumer(
+ FieldDto field, TypeName fieldType, TypeName builderType, ProcessingContext context) {
+ TypeNameGeneric consumerType = createConsumerType(fieldType);
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(field.getFieldNameEstimated() + SUFFIX_CONSUMER);
+ parameter.setParameterTypeName(consumerType);
+
+ MethodDto methodDto =
+ new MethodDto(
+ generateBuilderMethodName(field.getFieldNameEstimated(), context), builderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+
+ methodDto.setCode(
+ """
+ $helperType:T consumer = this.$fieldName:N.isSet() ? this.$fieldName:N.value() : new $helperType:T();
+ $dtoMethodParam:N.accept(consumer);
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue(consumer);
+ return this;
+ """);
+ methodDto.addArgument("fieldName", field.getFieldName());
+ methodDto.addArgument("dtoMethodParam", parameter.getParameterName());
+ methodDto.addArgument("helperType", fieldType);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setPriority(MethodDto.PRIORITY_MEDIUM);
+
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s by executing the provided consumer.
+
+ @param %s consumer providing an instance of %s
+ @return current instance of builder
+ """
+ .formatted(
+ field.getFieldNameEstimated(), parameter.getParameterName(), field.getJavaDoc()));
+
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/OptionalHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/OptionalHelperGenerator.java
new file mode 100644
index 00000000..acd7aa4c
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/OptionalHelperGenerator.java
@@ -0,0 +1,105 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.isParameterizedOptional;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates unboxed Optional helper methods for {@code Optional} fields.
+ *
+ * This generator creates convenience methods that accept the inner type {@code T} directly and
+ * wrap it in {@code Optional.ofNullable()} automatically. This makes it easier to set Optional
+ * values without explicitly wrapping them.
+ *
+ *
Important behavior: The method accepts the unwrapped type {@code T}, wraps it using
+ * {@code Optional.ofNullable()}, and assigns it to the field. This allows passing {@code null}
+ * values which will be converted to {@code Optional.empty()}.
+ *
+ *
Requirements: Only applies to parameterized {@code Optional} fields.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateUnboxedOptional} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.Optional;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, Optional subtitle, Optional rating) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .title("My Book")
+ * .subtitle("A Great Story") // String -> Optional
+ * .rating(5) // Integer -> Optional
+ * .build();
+ * }
+ */
+public class OptionalHelperGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 70;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateUnboxedOptional()) {
+ return false;
+ }
+ return isParameterizedOptional(field.getFieldType());
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ TypeNameGeneric genericType = (TypeNameGeneric) field.getFieldType();
+ List innerTypes = genericType.getInnerTypeArguments();
+
+ if (CollectionUtils.isEmpty(innerTypes)) {
+ return Collections.emptyList();
+ }
+
+ TypeName innerType = innerTypes.get(0);
+ MethodDto method =
+ MethodGeneratorUtil.createBuilderMethodForFieldWithTransform(
+ field, "Optional.ofNullable(%s)", innerType, builderType, context);
+
+ return Collections.singletonList(method);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SetConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SetConsumerGenerator.java
new file mode 100644
index 00000000..777ad79a
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SetConsumerGenerator.java
@@ -0,0 +1,146 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.javahelpers.simple.builders.core.builders.HashSetBuilder;
+import org.javahelpers.simple.builders.core.builders.HashSetBuilderWithElementBuilders;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for Set fields with collection builder support.
+ *
+ * This generator creates methods that accept {@code Consumer} or {@code
+ * Consumer} depending on whether the element type has a builder.
+ * The consumer configures the collection builder, which is then built and assigned.
+ *
+ * Important behavior: A collection builder is created, passed to the consumer for
+ * configuration (adding elements, configuring nested builders, etc.), then automatically built. For
+ * element types with builders, {@code HashSetBuilderWithElementBuilders} provides additional
+ * methods to add elements via their builders.
+ *
+ *
Requirements: Only applies to {@code Set} fields. Uses {@code HashSetBuilder} for
+ * simple element types, or {@code HashSetBuilderWithElementBuilders} when the element
+ * type has a {@code @SimpleBuilder} annotation.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code usingHashSetBuilder} to {@code DISABLED}. See the configuration documentation for details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.Set;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(Set categories, Set authors) {}
+ *
+ * @SimpleBuilder
+ * public record AuthorDto(String name) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .categories(c -> c.add("Fiction").add("Adventure"))
+ * .authors(a -> a
+ * .add(b -> b.name("John Doe"))
+ * .add(b -> b.name("Jane Smith")))
+ * .build();
+ * }
+ */
+public class SetConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 52;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
+ return false;
+ }
+ // Only apply to Set fields
+ if (!(field.getFieldType() instanceof TypeNameSet fieldTypeGeneric
+ && fieldTypeGeneric.isParameterized())) {
+ return false;
+ }
+ // Don't apply if field itself has a builder (higher priority)
+ if (field.getFieldType().getBuilderType().isPresent()) {
+ return false;
+ }
+ return context.getConfiguration().shouldUseHashSetBuilder()
+ || context.getConfiguration().shouldUseHashSetBuilderWithElementBuilders();
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ if (!(field.getFieldType() instanceof TypeNameSet fieldTypeGeneric
+ && fieldTypeGeneric.isParameterized())) {
+ return Collections.emptyList();
+ }
+
+ TypeName elementType = fieldTypeGeneric.getElementType();
+ Optional elementBuilderType = fieldTypeGeneric.getElementBuilderType();
+
+ if (elementBuilderType.isPresent()
+ && context.getConfiguration().shouldUseHashSetBuilderWithElementBuilders()) {
+ TypeName collectionBuilderType =
+ new TypeNameGeneric(
+ map2TypeName(HashSetBuilderWithElementBuilders.class),
+ elementType,
+ elementBuilderType.get());
+ MethodDto method =
+ MethodGeneratorUtil.createFieldConsumerWithElementBuilders(
+ field, collectionBuilderType, elementBuilderType.get(), builderType, context);
+ return List.of(method);
+ } else if (context.getConfiguration().shouldUseHashSetBuilder()) {
+ TypeName hashSetBuilderType = map2TypeName(HashSetBuilder.class);
+ TypeNameGeneric builderTypeGeneric = new TypeNameGeneric(hashSetBuilderType, elementType);
+ MethodDto method =
+ MethodGeneratorUtil.createFieldConsumerWithBuilder(
+ field,
+ builderTypeGeneric,
+ "this.$fieldName:N.value()",
+ "",
+ Map.of(),
+ builderType,
+ context);
+ return List.of(method);
+ }
+
+ return Collections.emptyList();
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringBuilderConsumerGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringBuilderConsumerGenerator.java
new file mode 100644
index 00000000..24d33db0
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringBuilderConsumerGenerator.java
@@ -0,0 +1,154 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*;
+
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Consumer-based methods for String and {@code Optional} fields using
+ * StringBuilder.
+ *
+ * This generator creates methods that accept a {@code Consumer} to build string
+ * values. The consumer configures the StringBuilder, which is then converted to a String and
+ * assigned to the field.
+ *
+ * Important behavior: A new {@code StringBuilder} is created, passed to the consumer for
+ * configuration (appending text, formatting, etc.), then converted to a String. For {@code
+ * Optional} fields, the result is wrapped in {@code Optional.of()}.
+ *
+ * Requirements: Only applies to {@code String} or {@code Optional} fields. Does
+ * not apply to String arrays or if the field type has a builder or empty constructor.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateBuilderConsumer} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.Optional;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, Optional subtitle) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .title(sb -> sb.append("The ").append("Complete").append(" Guide"))
+ * .subtitle(sb -> sb.append("Volume ").append(1))
+ * .build();
+ * }
+ */
+public class StringBuilderConsumerGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 45;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
+ return false;
+ }
+ // Don't apply if field has a builder or empty constructor (higher priority)
+ if (field.getFieldType().getBuilderType().isPresent()
+ || field.getFieldType().hasEmptyConstructor()) {
+ return false;
+ }
+
+ TypeName fieldType = field.getFieldType();
+ // Only apply to String or Optional fields (but not String arrays)
+ return (isString(fieldType) && !(fieldType instanceof TypeNameArray))
+ || isOptionalString(fieldType);
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+ String transform =
+ isOptionalString(field.getFieldType())
+ ? "Optional.of(builder.toString())"
+ : "builder.toString()";
+ MethodDto method =
+ createStringBuilderConsumer(
+ field.getFieldName(),
+ field.getFieldName(),
+ field.getJavaDoc(),
+ transform,
+ builderType,
+ context);
+ return List.of(method);
+ }
+
+ private MethodDto createStringBuilderConsumer(
+ String fieldName,
+ String fieldNameInBuilder,
+ String fieldJavadoc,
+ String transform,
+ TypeName builderType,
+ ProcessingContext context) {
+ TypeName stringBuilderType = map2TypeName(StringBuilder.class);
+ TypeNameGeneric consumerType = createConsumerType(stringBuilderType);
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(fieldName + "StringBuilderConsumer");
+ parameter.setParameterTypeName(consumerType);
+ MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+ methodDto.setCode(
+ """
+ StringBuilder builder = new StringBuilder();
+ $dtoMethodParam:N.accept(builder);
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($transform:N);
+ return this;
+ """);
+ methodDto.addArgument("fieldName", fieldNameInBuilder);
+ methodDto.addArgument("dtoMethodParam", parameter.getParameterName());
+ methodDto.addArgument("transform", transform);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setReturnType(builderType);
+ methodDto.setPriority(MethodDto.PRIORITY_LOW);
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s by executing the provided consumer.
+
+ @param %s consumer providing an instance of %s
+ @return current instance of builder
+ """
+ .formatted(fieldName, parameter.getParameterName(), fieldJavadoc));
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringFormatHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringFormatHelperGenerator.java
new file mode 100644
index 00000000..45aa0e40
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/StringFormatHelperGenerator.java
@@ -0,0 +1,196 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.isParameterizedOptional;
+import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.isString;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates String.format helper methods for String and {@code Optional} fields.
+ *
+ * This generator creates convenience methods that accept a format string and varargs arguments,
+ * internally using {@code String.format()} to produce the final value. This provides a concise way
+ * to build formatted strings directly in the builder chain.
+ *
+ *
Important behavior: The method accepts a format string and optional arguments, applies
+ * {@code String.format()}, and assigns the result to the field. For {@code Optional}
+ * fields, the formatted result is wrapped in {@code Optional.of()}.
+ *
+ * Requirements: Only applies to {@code String} or {@code Optional} fields. Does
+ * not apply to String arrays.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateStringFormatHelpers} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.Optional;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, Optional description) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .title("Book #%d: %s", 1, "Java Patterns")
+ * .description("Published in %d by %s", 2024, "Tech Press")
+ * .build();
+ * }
+ */
+public class StringFormatHelperGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 80;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateStringFormatHelpers()) {
+ return false;
+ }
+ TypeName fieldType = field.getFieldType();
+
+ if (isString(fieldType) && !(fieldType instanceof TypeNameArray)) {
+ return true;
+ }
+
+ if (isParameterizedOptional(fieldType)) {
+ TypeNameGeneric genericType = (TypeNameGeneric) fieldType;
+ List innerTypes = genericType.getInnerTypeArguments();
+ if (CollectionUtils.isNotEmpty(innerTypes) && isString(innerTypes.get(0))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ List methods = new ArrayList<>();
+ TypeName fieldType = field.getFieldType();
+
+ if (isString(fieldType) && !(fieldType instanceof TypeNameArray)) {
+ MethodDto method =
+ createStringFormatMethodWithTransform(
+ field.getFieldNameEstimated(),
+ field.getFieldName(),
+ "String.format(format, args)",
+ field.getParameterAnnotations(),
+ builderType,
+ context);
+ methods.add(method);
+ } else if (isParameterizedOptional(fieldType)) {
+ TypeNameGeneric genericType = (TypeNameGeneric) fieldType;
+ List innerTypes = genericType.getInnerTypeArguments();
+ if (!innerTypes.isEmpty() && isString(innerTypes.get(0))) {
+ MethodDto method =
+ createStringFormatMethodWithTransform(
+ field.getFieldNameEstimated(),
+ field.getFieldName(),
+ "Optional.of(String.format(format, args))",
+ field.getParameterAnnotations(),
+ builderType,
+ context);
+ methods.add(method);
+ }
+ }
+
+ return methods;
+ }
+
+ /**
+ * Creates a String.format helper method with transform.
+ *
+ * @param fieldName the name of the field (estimated)
+ * @param fieldNameInBuilder the builder field name (may be renamed)
+ * @param transform the transform expression (e.g., "String.format(format, args)")
+ * @param annotations annotations to apply to the format parameter
+ * @param builderType the builder type for the return type
+ * @param context processing context
+ * @return the method DTO for the String.format helper
+ */
+ private MethodDto createStringFormatMethodWithTransform(
+ String fieldName,
+ String fieldNameInBuilder,
+ String transform,
+ List annotations,
+ TypeName builderType,
+ ProcessingContext context) {
+ TypeName stringType = map2TypeName(String.class);
+
+ MethodParameterDto formatParam = new MethodParameterDto();
+ formatParam.setParameterName("format");
+ formatParam.setParameterTypeName(stringType);
+ if (annotations != null) {
+ annotations.forEach(formatParam::addAnnotation);
+ }
+
+ MethodParameterDto argsParam = new MethodParameterDto();
+ argsParam.setParameterName("args");
+ argsParam.setParameterTypeName(new TypeNameArray(TypeName.of(Object.class)));
+
+ MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType);
+ methodDto.addParameter(formatParam);
+ methodDto.addParameter(argsParam);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+ methodDto.setCode(
+ """
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($transform:N);
+ return this;
+ """);
+ methodDto.addArgument("fieldName", fieldNameInBuilder);
+ methodDto.addArgument("transform", transform);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setPriority(MethodDto.PRIORITY_HIGH);
+ methodDto.setJavadoc(
+ """
+ Sets the String value for %s by using String.format(format, args).
+ See {@link String#format(String, Object...)} for details.
+
+ @param %s A format string
+ @param %s Arguments referenced by the format specifiers in the format string.
+ @return current instance of builder
+ """
+ .formatted(fieldName, formatParam.getParameterName(), argsParam.getParameterName()));
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SupplierMethodGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SupplierMethodGenerator.java
new file mode 100644
index 00000000..c2061904
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/SupplierMethodGenerator.java
@@ -0,0 +1,156 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import static org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil.*;
+import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+import org.javahelpers.simple.builders.processor.dtos.FieldDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodParameterDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.dtos.TypeNameGeneric;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates Supplier-based methods for builder fields.
+ *
+ * This generator creates methods that accept {@code Supplier} functional interfaces for lazy
+ * initialization of field values. The supplier is invoked immediately when the setter is called,
+ * and the result is stored in the builder.
+ *
+ * Important behavior: The supplier is evaluated eagerly when the method is called, not
+ * lazily when {@code build()} is invoked. This is useful for deferred initialization, dynamic value
+ * generation, or passing method references.
+ *
+ *
Requirements: Applies to all fields except functional interface types (to avoid
+ * ambiguity with the field type itself being a functional interface).
+ *
+ *
This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateFieldSupplier} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.Supplier;
+ *
+ * @SimpleBuilder
+ * public record ExampleDto(String title, int pages) {}
+ *
+ * // Usage of generated Builder:
+ * var result = ExampleDtoBuilder.builder()
+ * .title(() -> "Generated Title")
+ * .pages(() -> calculatePages())
+ * .build();
+ * }
+ */
+public class SupplierMethodGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 60;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldGenerateFieldSupplier();
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ MethodDto supplierMethod =
+ createFieldSupplier(
+ field.getFieldNameEstimated(),
+ field.getFieldName(),
+ field.getJavaDoc(),
+ field.getFieldType(),
+ builderType,
+ context);
+
+ return Collections.singletonList(supplierMethod);
+ }
+
+ /**
+ * Creates a supplier method that accepts a {@code Supplier} and invokes it to get the field
+ * value.
+ *
+ * @param fieldName the estimated field name (used for method name)
+ * @param fieldNameInBuilder the builder field name (may be renamed)
+ * @param fieldJavaDoc the javadoc for the field
+ * @param fieldType the type of the field
+ * @param builderType the builder type for the return type
+ * @param context processing context
+ * @return the method DTO for the supplier
+ */
+ private MethodDto createFieldSupplier(
+ String fieldName,
+ String fieldNameInBuilder,
+ String fieldJavaDoc,
+ TypeName fieldType,
+ TypeName builderType,
+ ProcessingContext context) {
+ TypeNameGeneric supplierType = new TypeNameGeneric(map2TypeName(Supplier.class), fieldType);
+ String parameterName = fieldName + SUFFIX_SUPPLIER;
+
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName(parameterName);
+ parameter.setParameterTypeName(supplierType);
+
+ MethodDto methodDto = new MethodDto(generateBuilderMethodName(fieldName, context), builderType);
+ methodDto.addParameter(parameter);
+ setMethodAccessModifier(methodDto, getMethodAccessModifier(context));
+
+ methodDto.setCode(
+ """
+ this.$fieldName:N = $builderFieldWrapper:T.changedValue($dtoMethodParam:N.get());
+ return this;
+ """);
+ methodDto.addArgument("fieldName", fieldNameInBuilder);
+ methodDto.addArgument("dtoMethodParam", parameterName);
+ methodDto.addArgument("builderFieldWrapper", TRACKED_VALUE_TYPE);
+ methodDto.setPriority(MethodDto.PRIORITY_HIGH);
+
+ methodDto.setJavadoc(
+ """
+ Sets the value for %s by invoking the provided supplier.
+
+ @param %s supplier for %s
+ @return current instance of builder
+ """
+ .formatted(fieldName, parameter.getParameterName(), fieldJavaDoc));
+
+ return methodDto;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/VarArgsHelperGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/VarArgsHelperGenerator.java
new file mode 100644
index 00000000..91b7d3e3
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/VarArgsHelperGenerator.java
@@ -0,0 +1,148 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package org.javahelpers.simple.builders.processor.generators;
+
+import java.util.Collections;
+import java.util.List;
+import org.javahelpers.simple.builders.processor.dtos.*;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Generates varargs helper methods for List, Set, and Map fields.
+ *
+ * This generator creates convenience methods that accept varargs parameters for List, Set, and
+ * Map fields, making it easier to set collection values without explicitly creating collection
+ * instances. The varargs are converted to the appropriate collection type.
+ *
+ *
Important behavior: The varargs are converted using {@code List.of()}, {@code
+ * Set.of()}, or {@code Map.ofEntries()} depending on the field type. For concrete collection types
+ * (e.g., {@code ArrayList}, {@code HashSet}), the result is wrapped in the appropriate constructor.
+ *
+ *
Requirements: Only applies to parameterized {@code List}, {@code Set}, or {@code
+ * Map} fields. For maps, accepts {@code Map.Entry} varargs.
+ *
+ * This generator is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateVarArgsHelpers} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.List;
+ * import java.util.Set;
+ * import java.util.Map;
+ *
+ * @SimpleBuilder
+ * public record BookDto(List tags, Set categories, Map ratings) {}
+ *
+ * // Usage of generated Builder:
+ * var result = BookDtoBuilder.builder()
+ * .tags("java", "builder", "pattern")
+ * .categories("programming", "design")
+ * .ratings(Map.entry("quality", 5), Map.entry("readability", 4))
+ * .build();
+ * }
+ */
+public class VarArgsHelperGenerator implements MethodGenerator {
+
+ private static final int PRIORITY = 40;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(FieldDto field, TypeName dtoType, ProcessingContext context) {
+ if (!context.getConfiguration().shouldGenerateVarArgsHelpers()) {
+ return false;
+ }
+ return (field.getFieldType() instanceof TypeNameList listType && listType.isParameterized())
+ || (field.getFieldType() instanceof TypeNameSet setType && setType.isParameterized())
+ || (field.getFieldType() instanceof TypeNameMap mapType && mapType.isParameterized());
+ }
+
+ @Override
+ public List generateMethods(
+ FieldDto field, TypeName builderType, ProcessingContext context) {
+
+ TypeName fieldType = field.getFieldType();
+ TypeName parameterType = null;
+
+ if (fieldType instanceof TypeNameList listType) {
+ parameterType = new TypeNameArray(listType.getElementType());
+ } else if (fieldType instanceof TypeNameSet setType) {
+ parameterType = new TypeNameArray(setType.getElementType());
+ } else if (fieldType instanceof TypeNameMap mapType) {
+ parameterType =
+ new TypeNameArray(
+ new TypeNameGeneric(
+ "java.util.Map", "Entry", mapType.getKeyType(), mapType.getValueType()));
+ }
+
+ if (parameterType == null) {
+ return Collections.emptyList();
+ }
+
+ MethodDto varArgsMethod =
+ createFieldSetterByVarArgs(field, parameterType, builderType, context);
+ return Collections.singletonList(varArgsMethod);
+ }
+
+ /**
+ * Creates a field setter method for collection varargs with automatic transform calculation. The
+ * transform is calculated based on the original field type to preserve specific collection
+ * implementations (e.g., ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap).
+ *
+ * @param field the field definition containing name, type, and javadoc
+ * @param parameterType the type of the method parameter (varargs array type)
+ * @param builderType the builder type for the return type
+ * @param context processing context
+ * @return the method DTO for the setter
+ */
+ private MethodDto createFieldSetterByVarArgs(
+ FieldDto field, TypeName parameterType, TypeName builderType, ProcessingContext context) {
+ String baseExpression;
+ TypeName fieldType = field.getFieldType();
+
+ if (fieldType instanceof TypeNameList listType) {
+ baseExpression =
+ listType.isConcreteImplementation() ? "java.util.List.of(%s)" : "List.of(%s)";
+ } else if (fieldType instanceof TypeNameSet setType) {
+ baseExpression = setType.isConcreteImplementation() ? "java.util.Set.of(%s)" : "Set.of(%s)";
+ } else if (fieldType instanceof TypeNameMap mapType) {
+ baseExpression =
+ mapType.isConcreteImplementation() ? "java.util.Map.ofEntries(%s)" : "Map.ofEntries(%s)";
+ } else {
+ return null;
+ }
+ String transform = MethodGeneratorUtil.wrapConcreteCollectionType(fieldType, baseExpression);
+
+ return MethodGeneratorUtil.createBuilderMethodForFieldWithTransform(
+ field, transform, parameterType, builderType, context);
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java
new file mode 100644
index 00000000..ce3cedab
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/generators/WithInterfaceEnhancer.java
@@ -0,0 +1,219 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Andreas Igel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package org.javahelpers.simple.builders.processor.generators;
+
+import org.javahelpers.simple.builders.processor.dtos.BuilderDefinitionDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodDto;
+import org.javahelpers.simple.builders.processor.dtos.MethodParameterDto;
+import org.javahelpers.simple.builders.processor.dtos.NestedTypeDto;
+import org.javahelpers.simple.builders.processor.dtos.TypeName;
+import org.javahelpers.simple.builders.processor.dtos.TypeNameGeneric;
+import org.javahelpers.simple.builders.processor.util.JavaLangMapper;
+import org.javahelpers.simple.builders.processor.util.ProcessingContext;
+
+/**
+ * Enhancer that adds the "With" interface to generated builders.
+ *
+ * The "With" interface provides fluent modification methods for DTO instances, allowing users to
+ * create modified copies of existing DTOs using the builder pattern. This is particularly useful
+ * for immutable DTOs and functional programming patterns.
+ *
+ *
Important behavior: The {@code with(Consumer)} method creates a builder initialized
+ * from the current DTO instance, applies the consumer's modifications, and returns a new built DTO.
+ * The {@code with()} method returns a builder initialized from the current instance for further
+ * chaining. Both methods preserve immutability by creating new instances.
+ *
+ *
Requirements: Applies to all builders when enabled. The DTO class must implement the
+ * generated "With" interface.
+ *
+ *
This enhancer is enabled by default and can be deactivated by setting the configuration flag
+ * {@code generateWithInterface} to {@code DISABLED}. See the configuration documentation for
+ * details.
+ *
+ *
Example to demonstrate the generated methods
+ *
+ * {@code
+ * // ExampleDto for demonstration
+ * import org.javahelpers.simple.builders.annotation.SimpleBuilder;
+ * import java.util.function.Consumer;
+ *
+ * @SimpleBuilder
+ * public record BookDto(String title, String author, int pages) implements BookDtoWith {}
+ *
+ * // Usage of generated With interface:
+ * BookDto original = BookDtoBuilder.create()
+ * .title("Original Title")
+ * .author("John Doe")
+ * .pages(250)
+ * .build();
+ *
+ * // Create modified copy:
+ * BookDto modified = original.with(b -> b
+ * .title("Updated Title")
+ * .pages(300));
+ *
+ * // Or get builder for more changes:
+ * BookDto furtherModified = original.with()
+ * .title("Another Title")
+ * .build();
+ * }
+ */
+public class WithInterfaceEnhancer implements BuilderEnhancer {
+
+ private static final int PRIORITY = 95;
+
+ @Override
+ public int getPriority() {
+ return PRIORITY;
+ }
+
+ @Override
+ public boolean appliesTo(
+ BuilderDefinitionDto builderDto, TypeName dtoType, ProcessingContext context) {
+ return context.getConfiguration().shouldGenerateWithInterface();
+ }
+
+ @Override
+ public void enhanceBuilder(BuilderDefinitionDto builderDto, ProcessingContext context) {
+ NestedTypeDto withInterface = createWithInterface(builderDto, context);
+ builderDto.addNestedType(withInterface);
+
+ context.debug(
+ "Added With interface to builder %s", builderDto.getBuilderTypeName().getClassName());
+ }
+
+ /**
+ * Creates the "With" interface for the builder.
+ *
+ * @param builderDto the builder definition
+ * @param context the processing context
+ * @return the nested type DTO for the With interface
+ */
+ private NestedTypeDto createWithInterface(
+ BuilderDefinitionDto builderDto, ProcessingContext context) {
+ context.debug(
+ "Creating With interface for: %s", builderDto.getBuilderTypeName().getClassName());
+
+ NestedTypeDto withInterface = new NestedTypeDto();
+ withInterface.setTypeName("With");
+ withInterface.setKind(NestedTypeDto.NestedTypeKind.INTERFACE);
+ withInterface.setPublic(true);
+ withInterface.setJavadoc(
+ "Interface that can be implemented by the DTO to provide fluent modification methods.");
+
+ // Create the first method: DtoType with(Consumer b)
+ MethodDto withConsumerMethod = createWithConsumerMethod(builderDto);
+ withInterface.addMethod(withConsumerMethod);
+
+ // Create the second method: BuilderType with()
+ MethodDto withBuilderMethod = createWithBuilderMethod(builderDto);
+ withInterface.addMethod(withBuilderMethod);
+
+ return withInterface;
+ }
+
+ /**
+ * Creates the `DtoType with(Consumer b)` method definition.
+ *
+ * @param builderDef the builder definition
+ * @return the method definition
+ */
+ private MethodDto createWithConsumerMethod(BuilderDefinitionDto builderDef) {
+ // Return type is the DTO type
+ TypeName dtoType = builderDef.getBuildingTargetTypeName();
+ MethodDto method = new MethodDto("with", dtoType);
+
+ // Parameter: Consumer b
+ MethodParameterDto parameter = new MethodParameterDto();
+ parameter.setParameterName("b");
+ // For interface methods, we store the full type as a string
+ TypeNameGeneric consumerType =
+ MethodGeneratorUtil.createConsumerType(builderDef.getBuilderTypeName());
+ parameter.setParameterTypeName(consumerType);
+ method.addParameter(parameter);
+
+ // Add implementation with validation to catch wrong implementations
+ method.setCode(
+ """
+ $builderType:T builder;
+ try {
+ builder = new $builderType:T($dtoType:T.class.cast(this));
+ } catch ($classcastexception:T ex) {
+ throw new $illegalargumentexception:T("The interface '$builderType:T.With' should only be implemented by classes, which could be casted to '$dtoType:T'", ex);
+ }
+ b.accept(builder);
+ return builder.build();
+ """);
+ method.addArgument("builderType", builderDef.getBuilderTypeName());
+ method.addArgument("dtoType", builderDef.getBuildingTargetTypeName());
+ method.addArgument("classcastexception", JavaLangMapper.map2TypeName(ClassCastException.class));
+ method.addArgument(
+ "illegalargumentexception", JavaLangMapper.map2TypeName(IllegalArgumentException.class));
+
+ method.setJavadoc(
+ """
+ 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
+ """);
+
+ return method;
+ }
+
+ /**
+ * Creates the `BuilderType with()` method definition.
+ *
+ * @param builderDef the builder definition
+ * @return the method definition
+ */
+ private MethodDto createWithBuilderMethod(BuilderDefinitionDto builderDef) {
+ // Return type is the Builder type
+ MethodDto method = new MethodDto("with", builderDef.getBuilderTypeName());
+
+ // Add implementation with validation to catch wrong implementations
+ method.setCode(
+ """
+ try {
+ return new $builderType:T($dtoType:T.class.cast(this));
+ } catch ($classcastexception:T ex) {
+ throw new $illegalargumentexception:T("The interface '$builderType:T.With' should only be implemented by classes, which could be casted to '$dtoType:T'", ex);
+ }
+ """);
+ method.addArgument("builderType", builderDef.getBuilderTypeName());
+ method.addArgument("dtoType", builderDef.getBuildingTargetTypeName());
+ method.addArgument("classcastexception", JavaLangMapper.map2TypeName(ClassCastException.class));
+ method.addArgument(
+ "illegalargumentexception", JavaLangMapper.map2TypeName(IllegalArgumentException.class));
+
+ method.setJavadoc(
+ """
+ Creates a builder initialized from this instance.
+
+ @return a builder initialized with this instance's values
+ """);
+
+ return method;
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
index a943e912..1f3dc538 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/util/BuilderDefinitionCreator.java
@@ -25,12 +25,9 @@
package org.javahelpers.simple.builders.processor.util;
import static java.util.stream.Collectors.toSet;
-import static javax.lang.model.type.TypeKind.ARRAY;
import static org.javahelpers.simple.builders.processor.util.AnnotationValidator.validateAnnotatedElement;
import static org.javahelpers.simple.builders.processor.util.JavaLangAnalyser.*;
import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2MethodParameter;
-import static org.javahelpers.simple.builders.processor.util.JavaLangMapper.map2TypeName;
-import static org.javahelpers.simple.builders.processor.util.TypeNameAnalyser.*;
import java.util.HashMap;
import java.util.LinkedList;
@@ -38,41 +35,17 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.javahelpers.simple.builders.core.annotations.IgnoreInBuilder;
-import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
-import org.javahelpers.simple.builders.core.builders.ArrayListBuilder;
-import org.javahelpers.simple.builders.core.builders.ArrayListBuilderWithElementBuilders;
-import org.javahelpers.simple.builders.core.builders.HashMapBuilder;
-import org.javahelpers.simple.builders.core.builders.HashSetBuilder;
-import org.javahelpers.simple.builders.core.builders.HashSetBuilderWithElementBuilders;
-import org.javahelpers.simple.builders.core.util.TrackedValue;
import org.javahelpers.simple.builders.processor.dtos.*;
import org.javahelpers.simple.builders.processor.exceptions.BuilderException;
+import org.javahelpers.simple.builders.processor.generators.MethodGeneratorUtil;
/** Class for creating a specific BuilderDefinitionDto for an annotated DTO class. */
public class BuilderDefinitionCreator {
- private static final String BUILDER_SUFFIX = "Builder";
-
- // Template argument keys for code generation
- private static final String ARG_FIELD_NAME = "fieldName";
- private static final String ARG_DTO_METHOD_PARAM = "dtoMethodParam";
- private static final String ARG_DTO_METHOD_PARAMS = "dtoMethodParams";
- private static final String ARG_BUILDER_FIELD_WRAPPER = "builderFieldWrapper";
- private static final String ARG_HELPER_TYPE = "helperType";
- private static final String ARG_ELEMENT_TYPE = "elementType";
-
- // Type constants
- private static final TypeName TRACKED_VALUE_TYPE = TypeName.of(TrackedValue.class);
-
- // Parameter name suffixes
- private static final String SUFFIX_CONSUMER = "Consumer";
- private static final String SUFFIX_SUPPLIER = "Supplier";
private BuilderDefinitionCreator() {
// Private constructor to prevent instantiation
@@ -106,11 +79,8 @@ public static BuilderDefinitionDto extractFromElement(
extractSetterFields(annotatedType, result, context, fieldNameRegistry);
result.addAllFields(setterFields);
- // Create the With interface if enabled in configuration
- if (context.getConfiguration().shouldGenerateWithInterface()) {
- NestedTypeDto withInterface = createWithInterface(result, context);
- result.addNestedType(withInterface);
- }
+ // Apply builder enhancers (including With interface generation)
+ context.getGeneratorRegistry().enhanceBuilder(result, result.getBuildingTargetTypeName());
return result;
}
@@ -156,10 +126,14 @@ private static List extractConstructorFields(
context.debug(
"Analyzing constructor: %s with %d parameter(s)",
ctor.getSimpleName(), ctor.getParameters().size());
+ TypeName builderType =
+ MethodGeneratorUtil.createGenericTypeName(
+ builderDef.getBuilderTypeName(), builderDef.getGenerics());
+
for (VariableElement param : ctor.getParameters()) {
Optional fieldFromCtor =
createFieldFromConstructor(
- annotatedType, param, builderDef.getBuilderTypeName(), context, fieldNameRegistry);
+ annotatedType, param, builderType, context, fieldNameRegistry);
if (fieldFromCtor.isPresent()) {
FieldDto field = fieldFromCtor.get();
logFieldAddition(field, context);
@@ -194,6 +168,10 @@ private static List extractSetterFields(
int addedCount = 0;
int skippedCount = 0;
+ TypeName builderType =
+ MethodGeneratorUtil.createGenericTypeName(
+ result.getBuilderTypeName(), result.getGenerics());
+
for (ExecutableElement mth : methods) {
context.debug(
"Analyzing method: %s with %d parameter(s)",
@@ -214,7 +192,7 @@ private static List extractSetterFields(
}
Optional maybeField =
- createFieldFromSetter(mth, result.getBuilderTypeName(), context, fieldNameRegistry);
+ createFieldFromSetter(mth, builderType, context, fieldNameRegistry);
if (maybeField.isPresent()) {
processedCount++;
FieldDto field = maybeField.get();
@@ -271,396 +249,6 @@ private static boolean isMethodRelevantForBuilder(
return true;
}
- private static void addAdditionalHelperMethodsForField(
- FieldDto field,
- List annotations,
- TypeName builderType,
- ProcessingContext context) {
- String fieldNameInBuilder = field.getFieldName();
- String fieldJavaDoc = field.getJavaDoc();
-
- // Check for String type (not array) and add format method
- if (isString(field.getFieldType())
- && !(field.getFieldType() instanceof TypeNameArray)
- && context.getConfiguration().shouldGenerateStringFormatHelpers()) {
- String fieldName = field.getFieldNameEstimated();
- MethodDto method =
- createStringFormatMethodWithTransform(
- fieldName,
- fieldNameInBuilder,
- fieldJavaDoc,
- "String.format(format, args)",
- annotations,
- builderType,
- context);
- field.addMethod(method);
- }
-
- if ((field.getFieldType() instanceof TypeNameArray arrayType)) {
- TypeName elementType = arrayType.getTypeOfArray();
-
- // Add method accepting List and converting to array
- TypeNameGeneric listType = new TypeNameGeneric(map2TypeName(List.class), elementType);
- String fieldName = field.getFieldNameEstimated();
- MethodDto method1 =
- createFieldSetterForArrayFromList(
- fieldName, fieldNameInBuilder, listType, elementType, builderType, context);
- field.addMethod(method1);
-
- // Add Consumer> method only if builder consumers are enabled
- if (context.getConfiguration().shouldGenerateBuilderConsumer()) {
- TypeName collectionBuilderType = map2TypeName(ArrayListBuilder.class);
- MethodDto method2 =
- createFieldConsumerWithArrayBuilder(
- fieldName,
- fieldNameInBuilder,
- collectionBuilderType,
- elementType,
- builderType,
- context);
- field.addMethod(method2);
- }
- return;
- }
-
- // Only process generic types (List, Set, Map, Optional, etc.)
- if (!(field.getFieldType() instanceof TypeNameGeneric fieldTypeGeneric)) {
- return;
- }
-
- List innerTypes = fieldTypeGeneric.getInnerTypeArguments();
- if (field.getFieldType() instanceof TypeNameList listType && listType.isParameterized()) {
- // Only add varargs helper if enabled in configuration
- if (context.getConfiguration().shouldGenerateVarArgsHelpers()) {
- MethodDto method =
- createFieldSetterByVarArgs(
- field, new TypeNameArray(listType.getElementType()), builderType, context);
- field.addMethod(method);
- }
- // Add add2 helper for List fields to add a single element
- if (context.getConfiguration().shouldGenerateAddToCollectionHelpers()) {
- MethodDto addMethod =
- createAddToCollectionMethod(
- field.getFieldName(), listType, listType.getElementType(), builderType, context);
- field.addMethod(addMethod);
- }
- } else if (field.getFieldType() instanceof TypeNameSet setType && setType.isParameterized()) {
- // Only add varargs helper if enabled in configuration
- if (context.getConfiguration().shouldGenerateVarArgsHelpers()) {
- MethodDto method =
- createFieldSetterByVarArgs(
- field, new TypeNameArray(setType.getElementType()), builderType, context);
- field.addMethod(method);
- }
- // Add add2 helper for Set fields to add a single element
- if (context.getConfiguration().shouldGenerateAddToCollectionHelpers()) {
- MethodDto addMethod =
- createAddToCollectionMethod(
- field.getFieldName(), setType, setType.getElementType(), builderType, context);
- field.addMethod(addMethod);
- }
- } else if (field.getFieldType() instanceof TypeNameMap mapType && mapType.isParameterized()) {
- // Only add varargs helper if enabled in configuration
- if (context.getConfiguration().shouldGenerateVarArgsHelpers()) {
- // Use fully qualified name for Map.Entry to ensure proper import
- TypeName mapEntryType =
- new TypeNameArray(
- new TypeNameGeneric(
- "java.util.Map", "Entry", mapType.getKeyType(), mapType.getValueType()));
- MethodDto method = createFieldSetterByVarArgs(field, mapEntryType, builderType, context);
- field.addMethod(method);
- }
- } else if (isParameterizedOptional(field.getFieldType())) {
- String fieldName = field.getFieldNameEstimated();
-
- // Only generate unboxed optional method if enabled in configuration
- if (context.getConfiguration().shouldGenerateUnboxedOptional()) {
- // Add setter that accepts the inner type T and wraps it in Optional.ofNullable()
- MethodDto method =
- createFieldSetterWithTransform(
- fieldName,
- fieldNameInBuilder,
- fieldJavaDoc,
- "Optional.ofNullable(%s)",
- innerTypes.get(0),
- builderType,
- context);
- field.addMethod(method);
- }
-
- // If Optional, add format method
- TypeName innerType = innerTypes.get(0);
- if (isString(innerType) && context.getConfiguration().shouldGenerateStringFormatHelpers()) {
- MethodDto method =
- createStringFormatMethodWithTransform(
- fieldName,
- fieldNameInBuilder,
- fieldJavaDoc,
- "Optional.of(String.format(format, args))",
- List.of(),
- builderType,
- context);
- field.addMethod(method);
- }
- }
- }
-
- private static void addConsumerMethodsForField(
- FieldDto field,
- VariableElement fieldParameter,
- TypeElement fieldTypeElement,
- TypeName builderType,
- ProcessingContext context) {
- // Do not generate consumer methods for generic type variables (e.g., T)
- if (field.getFieldType() instanceof TypeNameVariable) {
- return;
- }
- // Skip consumer generation for functional interfaces
- if (isFunctionalInterface(fieldTypeElement)) {
- return;
- }
-
- if (!tryAddBuilderConsumer(field, fieldParameter, builderType, context)
- && !tryAddFieldConsumer(field, fieldTypeElement, builderType, context)
- && !tryAddListConsumer(field, fieldParameter, builderType, context)
- && !tryAddMapConsumer(field, builderType, context)
- && !tryAddSetConsumer(field, fieldParameter, builderType, context)) {
- tryAddStringBuilderConsumer(field, builderType, context);
- }
- }
-
- /** Tries to add a direct builder-based consumer when the field type itself has a builder. */
- private static boolean tryAddBuilderConsumer(
- FieldDto field,
- VariableElement fieldParameter,
- TypeName builderType,
- ProcessingContext context) {
- // Builder consumers are controlled by generateBuilderConsumer
- if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
- return false;
- }
- Optional fieldBuilderOpt = resolveBuilderType(fieldParameter, context);
- if (fieldBuilderOpt.isPresent()) {
- TypeName fieldBuilderType = fieldBuilderOpt.get();
- MethodDto method =
- BuilderDefinitionCreator.createFieldConsumerWithBuilder(
- field, fieldBuilderType, builderType, context);
- field.addMethod(method);
- return true;
- }
- return false;
- }
-
- /** Tries to add a field consumer when the field type has an accessible empty constructor. */
- private static boolean tryAddFieldConsumer(
- FieldDto field,
- TypeElement fieldTypeElement,
- TypeName builderType,
- ProcessingContext context) {
- // Check if field consumer generation is enabled in configuration
- if (!context.getConfiguration().shouldGenerateFieldConsumer()) {
- return false;
- }
- if (!isJavaClass(field.getFieldType())
- && fieldTypeElement != null
- && fieldTypeElement.getKind() == javax.lang.model.element.ElementKind.CLASS
- && !fieldTypeElement.getModifiers().contains(Modifier.ABSTRACT)
- && hasEmptyConstructor(fieldTypeElement, context)) {
- // Only generate a Consumer for concrete classes with an accessible empty constructor
- MethodDto method =
- createFieldConsumer(
- field.getFieldName(),
- field.getFieldName(),
- field.getJavaDoc(),
- field.getFieldType(),
- builderType,
- context);
- field.addMethod(method);
- return true;
- }
- return false;
- }
-
- /** Tries to add StringBuilder-based consumer for String and Optional. */
- private static boolean tryAddStringBuilderConsumer(
- FieldDto field, TypeName builderType, ProcessingContext context) {
- // StringBuilder is a builder pattern, controlled by generateBuilderConsumer
- if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
- return false;
- }
- if (shouldGenerateStringBuilderConsumer(field.getFieldType())) {
- String transform =
- isOptionalString(field.getFieldType())
- ? "Optional.of(builder.toString())"
- : "builder.toString()";
- MethodDto method =
- createStringBuilderConsumer(
- field.getFieldName(),
- field.getFieldName(),
- field.getJavaDoc(),
- transform,
- builderType,
- context);
- field.addMethod(method);
- return true;
- }
- return false;
- }
-
- /** Tries to add List-specific consumer methods. Returns true if handled. */
- private static boolean tryAddListConsumer(
- FieldDto field,
- VariableElement fieldParameter,
- TypeName builderType,
- ProcessingContext context) {
- if (!(field.getFieldType() instanceof TypeNameList fieldTypeGeneric
- && fieldTypeGeneric.isParameterized())) {
- return false;
- }
-
- TypeName elementType = fieldTypeGeneric.getElementType();
-
- // Get the TypeMirror of the element type from the parameter's type
- TypeMirror fieldTypeMirror = fieldParameter.asType();
- TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror);
-
- Optional elementBuilderType =
- resolveBuilderType(elementType, elementTypeMirror, context);
-
- // Only generate builder consumer methods if enabled
- if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
- return false;
- }
-
- if (elementBuilderType.isPresent()
- && context.getConfiguration().shouldUseArrayListBuilderWithElementBuilders()) {
- // Element type has a builder - use ArrayListBuilderWithElementBuilders if enabled
- TypeName collectionBuilderType =
- new TypeNameGeneric(
- map2TypeName(ArrayListBuilderWithElementBuilders.class),
- elementType,
- elementBuilderType.get());
- MethodDto method =
- createFieldConsumerWithElementBuilders(
- field, collectionBuilderType, elementBuilderType.get(), builderType, context);
- field.addMethod(method);
- } else if (context.getConfiguration().shouldUseArrayListBuilder()) {
- // Regular ArrayListBuilder if enabled
- TypeName collectionBuilderType = map2TypeName(ArrayListBuilder.class);
- MethodDto method =
- createFieldConsumerWithBuilder(
- field, collectionBuilderType, elementType, builderType, context);
- field.addMethod(method);
- } else {
- return false;
- }
- return true;
- }
-
- /** Tries to add Map-specific consumer methods. Returns true if handled. */
- private static boolean tryAddMapConsumer(
- FieldDto field, TypeName builderType, ProcessingContext context) {
- // Check if builder consumers are enabled
- if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
- return false;
- }
- // Check if HashMapBuilder is enabled
- if (!context.getConfiguration().shouldUseHashMapBuilder()) {
- return false;
- }
- if (!(field.getFieldType() instanceof TypeNameMap fieldTypeGeneric
- && fieldTypeGeneric.isParameterized())) {
- return false;
- }
-
- TypeNameGeneric builderTargetTypeName =
- new TypeNameGeneric(
- map2TypeName(HashMapBuilder.class),
- fieldTypeGeneric.getKeyType(),
- fieldTypeGeneric.getValueType());
- MethodDto mapConsumerWithBuilder =
- createFieldConsumerWithBuilder(field, builderTargetTypeName, builderType, context);
- field.addMethod(mapConsumerWithBuilder);
- return true;
- }
-
- /** Tries to add Set-specific consumer methods. Returns true if handled. */
- private static boolean tryAddSetConsumer(
- FieldDto field,
- VariableElement fieldParameter,
- TypeName builderType,
- ProcessingContext context) {
- if (!(field.getFieldType() instanceof TypeNameSet fieldTypeGeneric
- && fieldTypeGeneric.isParameterized())) {
- return false;
- }
-
- TypeName elementType = fieldTypeGeneric.getElementType();
-
- // Get the TypeMirror of the element type from the parameter's type
- TypeMirror fieldTypeMirror = fieldParameter.asType();
- TypeMirror elementTypeMirror = extractFirstTypeArgument(fieldTypeMirror);
-
- Optional elementBuilderType =
- resolveBuilderType(elementType, elementTypeMirror, context);
-
- // Only generate builder consumer methods if enabled
- if (!context.getConfiguration().shouldGenerateBuilderConsumer()) {
- return false;
- }
-
- if (elementBuilderType.isPresent()
- && context.getConfiguration().shouldUseHashSetBuilderWithElementBuilders()) {
- // Element type has a builder - use HashSetBuilderWithElementBuilders if enabled
- TypeName collectionBuilderType =
- new TypeNameGeneric(
- map2TypeName(HashSetBuilderWithElementBuilders.class),
- elementType,
- elementBuilderType.get());
- MethodDto method =
- createFieldConsumerWithElementBuilders(
- field, collectionBuilderType, elementBuilderType.get(), builderType, context);
- field.addMethod(method);
- } else if (context.getConfiguration().shouldUseHashSetBuilder()) {
- // Regular HashSetBuilder if enabled
- TypeName collectionBuilderType = map2TypeName(HashSetBuilder.class);
- MethodDto method =
- createFieldConsumerWithBuilder(
- field, collectionBuilderType, elementType, builderType, context);
- field.addMethod(method);
- } else {
- return false;
- }
- return true;
- }
-
- private static void addSupplierMethodsForField(
- FieldDto field,
- TypeElement fieldTypeElement,
- TypeName builderType,
- ProcessingContext context) {
- // Check if supplier generation is enabled in configuration
- if (!context.getConfiguration().shouldGenerateFieldSupplier()) {
- return;
- }
- // Skip supplier generation for functional interfaces
- if (isFunctionalInterface(fieldTypeElement)) {
- return;
- }
- // For all fields including Optional, use the real field type for suppliers
- String fieldName = field.getFieldNameEstimated();
- String fieldNameInBuilder = field.getFieldName();
- MethodDto method =
- createFieldSupplier(
- fieldName,
- fieldNameInBuilder,
- field.getJavaDoc(),
- field.getFieldType(),
- builderType,
- context);
- field.addMethod(method);
- }
-
private static Optional createFieldFromSetter(
ExecutableElement mth,
TypeName builderType,
@@ -689,7 +277,8 @@ private static Optional createFieldFromSetter(
}
VariableElement fieldParameter = parameters.get(0);
- TypeElement dtoType = (TypeElement) mth.getEnclosingElement();
+ TypeElement dtoTypeElement = (TypeElement) mth.getEnclosingElement();
+ TypeName dtoType = JavaLangMapper.map2TypeName(dtoTypeElement, context);
// Extract only the @param Javadoc for the single setter parameter (if present)
String fullJavaDoc = context.getDocComment(mth);
@@ -732,13 +321,15 @@ private static Optional createFieldFromConstructor(
javaDoc = fieldName;
}
+ // Convert TypeElement to TypeName once
+ TypeName dtoType = JavaLangMapper.map2TypeName(annotatedType, context);
+
// Check for field name conflicts and rename if necessary
String finalFieldName = resolveFieldNameConflict(fieldName, param, fieldNameRegistry, context);
// Pass both original field name (for methods) and final field name (for builder field)
Optional