Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
fff0ff1
Make @SimpleBuilder @Inherited to match documentation (#244)
AndreasIgel Aug 15, 2026
13a8fc8
Rename to BuilderAnnotationInheritanceTest and cover Template inherit…
AndreasIgel Aug 15, 2026
105310a
Refactoring code to move assertNoBuilderGenerated to common asserts
AndreasIgel Aug 15, 2026
2c83b78
fixing codeformat
AndreasIgel Aug 15, 2026
aca9ef6
Document @Inherited behavior of @SimpleBuilder.Template
AndreasIgel Aug 15, 2026
bd3bf93
Document options-inheritance limitation for inherited subclass builders
AndreasIgel Aug 15, 2026
e8e0352
Fix issue reference: #245 -> #248
AndreasIgel Aug 15, 2026
9776343
Clarify @SimpleBuilder vs @SimpleBuilder.Template usage in docs
AndreasIgel Aug 15, 2026
4b7723a
Link issue #248 from @SimpleBuilder.Template inheritance Javadoc
AndreasIgel Aug 15, 2026
218ea79
Remove redundant troubleshooting item about @SimpleBuilder.Template t…
AndreasIgel Aug 15, 2026
bd6020d
Merge remote-tracking branch 'upstream/main'
devin-ai-integration[bot] Aug 16, 2026
62d1269
Merge remote-tracking branch 'upstream/main'
devin-ai-integration[bot] Aug 16, 2026
ec99605
Merge upstream main
devin-ai-integration[bot] Aug 22, 2026
640bc82
Merge branch 'java-helpers:main' into main
AndreasIgel Sep 2, 2026
893769b
WIP: builder generation/usage package scoping and BuilderScopeResolver
devin-ai-integration[bot] Aug 24, 2026
23dc622
test: cover builder package scoping
devin-ai-integration[bot] Sep 2, 2026
aabccb0
docs: demonstrate builder package scoping in example module
devin-ai-integration[bot] Sep 2, 2026
b3c8490
refactor: cache builder scope resolution
devin-ai-integration[bot] Sep 2, 2026
9b8f4f7
refactor: address review on builder scope resolver (#114)
devin-ai-integration[bot] Sep 6, 2026
f921d46
refactor: match builder scope packages case-insensitively (#114)
devin-ai-integration[bot] Sep 12, 2026
dc92641
fix: trust same-round builders inside the usage scope (#114)
devin-ai-integration[bot] Sep 12, 2026
4403401
fix: keep configuration-resolution phase in performance tracking (#114)
devin-ai-integration[bot] Sep 12, 2026
97e1fb4
Merge upstream/main: resolve ConfigurationProcessingTest conflict
AndreasIgel Sep 12, 2026
78269ed
refactor: use Strings.CI for case-insensitive package scope matching
AndreasIgel Sep 12, 2026
f39ff07
Merge remote-tracking branch 'upstream/main' into devin/builder-scope
devin-ai-integration[bot] Sep 13, 2026
5fae665
refactor: single option registry on CompilerArgumentsEnum; split Buil…
devin-ai-integration[bot] Sep 13, 2026
d58af17
test+chore: drop dead mapper/analyser helpers; cover jsonString escap…
devin-ai-integration[bot] Sep 13, 2026
470dac7
docs: clarify builder scope interaction between generation and usage
AndreasIgel Sep 13, 2026
968fda8
refactor: extract ignore-annotation check into helper
AndreasIgel Sep 13, 2026
233c64b
refactor: drop redundant empty-scope guards in BuilderScopeResolver
AndreasIgel Sep 13, 2026
487956c
refactor: make annotation helpers in BuilderScopeResolver static
AndreasIgel Sep 13, 2026
4777346
revert: restore JavaLangAnalyser.isFunctionalInterface
AndreasIgel Sep 13, 2026
27b1149
refactor: extract package scope handling into PackageScopes value type
AndreasIgel Sep 13, 2026
c770838
refactor: extract builder-option guard into isNotABuilderOption
AndreasIgel Sep 13, 2026
2b35863
refactor: rename unqualify back to extractEnumName
AndreasIgel Sep 13, 2026
7105dd0
refactor: drop isBuilderOption guard in readBuilderConfiguration
AndreasIgel Sep 13, 2026
e732361
refactor: move option value parsers out of CompilerArgumentsEnum
AndreasIgel Sep 13, 2026
0df8c28
refactor: use streams for option iteration in readers
AndreasIgel Sep 13, 2026
e521a40
feat: track element collection as first performance phase
AndreasIgel Sep 13, 2026
08f963a
refactor: wrap generated-types registration in private helper
AndreasIgel Sep 13, 2026
3ba0bac
chore: debug-log element count surviving the generation scope
AndreasIgel Sep 13, 2026
382b838
refactor: move generation-scope check into BuilderScopeResolver
AndreasIgel Sep 13, 2026
d070007
test: rename scope-test sources to referenced-dto vocabulary
AndreasIgel Sep 13, 2026
2b3dcba
test: split scope-test responsibilities between processing and resolv…
AndreasIgel Sep 13, 2026
c0679c1
refactor: drop unused fromCompilerArgument lookup and its tests
AndreasIgel Sep 13, 2026
a1e2c77
docs: contrast per-type opt-out with package generation scope
AndreasIgel Sep 13, 2026
9bd5425
test: assert complete ordered verbose log output and update docs
AndreasIgel Sep 13, 2026
920564c
fix: use %n instead of \n in assertNotesInOrder error messages
AndreasIgel Sep 13, 2026
07d7f34
refactor: rename setBuilderTypeIfUsable to setBuilderTypeIfScopeMatches
AndreasIgel Sep 13, 2026
d5631f2
test: add PHASE_ELEMENT_COLLECTION assertions to ActivePerformanceTra…
AndreasIgel Sep 13, 2026
415e07d
docs: document config-source difference in BuilderScopeResolver javadocs
AndreasIgel Sep 13, 2026
12bafe8
refactor: address PR #288 review comments
AndreasIgel Sep 13, 2026
a320e96
refactor: rename isBuilderOption to hasValueApplier with explanatory …
AndreasIgel Sep 13, 2026
cc97de5
Changing the handling of usageScope
AndreasIgel Sep 13, 2026
1dee3d0
Code and test improvements
AndreasIgel Sep 13, 2026
9fb17d5
refactor: centralize package scope behavior
AndreasIgel Sep 19, 2026
84edecf
test: reuse shared processing logger capture
AndreasIgel Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ A zero-reflection Java annotation processor that generates fluent, type-safe bui
- [Elementary Builder Example](#elementary-builder-example)
- [Full-Featured Examples](#full-featured-examples)
- [Advanced Features](#advanced-features)
- [Builder Scoping Example](#builder-scoping-example)
- [Performance Measurement](#performance-measurement)
- [Contributing](#contributing)
- [License](#license)
Expand Down Expand Up @@ -465,6 +466,16 @@ Examples demonstrating special annotations and nested object relationships:
- **Mannschaft DTO**: [`MannschaftDto.java`](example/src/main/java/org/javahelpers/simple/builders/example/MannschaftDto.java) and [`MannschaftDtoBuilder.java`](example/generated-example-builder/org/javahelpers/simple/builders/example/MannschaftDtoBuilder.java) - Demonstrates `@IgnoreInBuilder` annotation to exclude specific setter methods from the generated builder, plus Set collections with nested objects
- **Default Values**: [`ProductWithDefaults.java`](example/src/main/java/org/javahelpers/simple/builders/example/ProductWithDefaults.java) (record) and [`OrderWithDefaults.java`](example/src/main/java/org/javahelpers/simple/builders/example/OrderWithDefaults.java) (class) - Demonstrate `@Default` annotation for unset builder fields

### Builder Scoping Example

A runnable example demonstrating package-scoped builder generation and usage:

- **Source DTO**: [`ScopedOwnerDto.java`](example/src/main/java/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDto.java) - Configures both package scopes inline and demonstrates the generation-scope, usage-scope, and out-of-scope field cases
- **Trusted helper**: [`TrustedHelperDto.java`](example/src/main/java/org/javahelpers/simple/builders/example/scoping/TrustedHelperDto.java) - In-generation-scope helper whose builder is referenced as a builder consumer
- **Library helper**: [`library/LibraryHelperDto.java`](example/src/main/java/org/javahelpers/simple/builders/example/library/LibraryHelperDto.java) - Annotated but outside the generation scope, so no builder exists and the owner falls back to a plain setter
- **Generated Builder**: [`ScopedOwnerDtoBuilder.java`](example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java) - Shows the consumer overload for `trusted` and plain setters for `library` and `sponsor`
- **Tests**: [`ScopedOwnerDtoBuilderTest.java`](example/src/test/java/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilderTest.java) - Asserts the generated API shape

These examples serve as both documentation and integration tests for the annotation processor.

## Performance Measurement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
* generation for the exact type it is placed on and does not cascade to further subclasses, which
* may therefore still get a builder from an inherited {@code @SimpleBuilder} or template
* annotation.
*
* <p>This is the per-type opt-out. To restrict generation to whole packages instead (including
* types whose {@code @SimpleBuilder} is inherited or applied via a template), use {@code
* builderGenerationPackages} in {@link SimpleBuilder.Options} or the {@code
* simplebuilder.builderGenerationPackages} compiler option.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
* <li><b>Collection Helpers:</b> generateVarArgsHelpers, usingArrayListBuilder,
* usingArrayListBuilderWithElementBuilders, usingHashSetBuilder,
* usingHashSetBuilderWithElementBuilders, usingHashMapBuilder (all default: true)
* <li><b>Builder Scoping:</b> builderGenerationPackages, builderUsagePackages, builderUsageSuffix
* (default: empty)
* <li><b>Integration:</b> generateWithInterface (default: true)
* <li><b>Documentation:</b> generateJavaDoc (default: true)
* </ul>
Expand Down Expand Up @@ -679,6 +681,56 @@
*/
OptionState generateJavaDoc() default OptionState.UNSET;

// === Builder Scoping ===
/**
* Comma-separated list of packages for which builders are generated. <br>
* Subpackages are included automatically and matching ignores case. When non-empty, builder
* generation is restricted to DTOs whose package equals or is a subpackage of a listed package.
* Types in this scope are trusted to have their builder generated in the same compilation and
* may be referenced without a type-existence search; they are automatically usable as helpers
* and do not need to be listed in {@link #builderUsagePackages()}.
*
* <p>On a single class this is mainly useful to <i>opt back in</i>: annotation options take
* precedence over the compiler option, so a type can include its own package even when the
* global scope excludes it. Set on a {@link Template} annotation or a base class ({@code
* SimpleBuilder} is {@code @Inherited}), it instead filters which inheriting types get a
* builder. To exclude a single type, prefer {@link Ignore4BuilderGeneration}.
*
* <p>Example: {@code "com.example.dto, com.example.shared"}
*
* <p>Default: "" (empty - builders are generated for all {@code @SimpleBuilder} annotated DTOs,
* no type search) <br>
* Compiler option: -Asimplebuilder.builderGenerationPackages
*
* @return the packages for which builders are generated
*/
String builderGenerationPackages() default "";

/**
* Comma-separated list of packages whose builders may be used as helper methods for other DTOs.
* <br>
* Subpackages are included automatically and matching ignores case. The processor constructs
* the candidate builder name using {@link #builderUsageSuffix()} (or {@link #builderSuffix()}
* if not configured) and verifies that a class with that name exists on the classpath or was
* generated in the current processing round. This allows referencing builders generated with
* custom template annotations or different suffixes. If the builder type cannot be resolved,
* the field falls back to a plain setter.
*
* <p>Packages in {@link #builderGenerationPackages()} are automatically part of the usage scope
* and never need to be repeated here. When this option is empty, builders from any package may
* be referenced; once set, only packages listed here (and generation-scope types) may provide
* builder helpers.
*
* <p>Example: {@code "com.example.library, com.example.external"}
*
* <p>Default: "" (empty - all types may be referenced as builders; the builder type is not
* verified to exist on the classpath, preserving backward-compatible behavior) <br>
* Compiler option: -Asimplebuilder.builderUsagePackages
*
* @return the packages whose builders may be used as helpers
*/
String builderUsagePackages() default "";

// === Naming ===
/**
* Suffix to append to the DTO name to generate the builder class name. <br>
Expand All @@ -702,6 +754,35 @@
*/
String builderSuffix() default "Builder";

/**
* Suffix to append to the DTO name when looking up a builder from the usage scope. <br>
* The processor constructs the candidate builder name using this suffix and verifies that a
* class with that name exists on the classpath. If empty, the value of {@link #builderSuffix()}
* is used. This allows referencing builders that were generated with a different suffix (e.g.
* by another module using "Factory" as suffix) without changing the suffix used for own builder
* generation.
*
* <p>Example:
*
* <pre>{@code
* @SimpleBuilder(options = @SimpleBuilder.Options(
* builderSuffix = "Builder",
* builderUsageSuffix = "Factory"
* ))
* public class OwnerDto {
* // Own builder: OwnerDtoBuilder
* // Referenced builders from usage scope: looked up as *Factory
* }
* }</pre>
*
* Default: "" (empty - falls back to {@link #builderSuffix()}) <br>
* Compiler option: -Asimplebuilder.builderUsageSuffix
*
* @return the suffix for usage-scope builder class names, or empty to use {@link
* #builderSuffix()}
*/
String builderUsageSuffix() default "";

/**
* Suffix to append to setter method names in the generated builder. <br>
* For example, with suffix "with", a field named "name" will generate "withName()". <br>
Expand Down
93 changes: 92 additions & 1 deletion docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Simple-builders supports fine-grained configuration through the `@SimpleBuilder.
- [Conditional Logic](#conditional-logic)
- [Access Control](#access-control)
- [Collection Helpers](#collection-helpers)
- [Builder Scoping](#builder-scoping)
- [Component Filtering](#component-filtering)
- [Integration](#integration)
- [Documentation](#documentation)
Expand Down Expand Up @@ -665,6 +666,55 @@ Generates methods using `HashMapBuilder` for fluent Map construction.

---

### Builder Scoping

#### `builderGenerationPackages`

**Default**: `""` (empty, unchanged behavior) | **Compiler Option**:
`-Asimplebuilder.builderGenerationPackages=package1,package2`

Restricts builder generation to annotated DTOs in the listed packages. Packages are
comma-separated, each listed package includes all of its subpackages, and matching ignores
case. Types in the generation scope are trusted to have their builders generated in the
current compilation, so references to their builders do not require a type search.

This option is an allowlist for whole packages: once set, builders are generated only inside
the listed packages and every other package is excluded implicitly. Excluding a single
package while generating everywhere else is not expressible; to exclude a single class,
use `@Ignore4BuilderGeneration` instead. The scope also applies to types that carry
`@SimpleBuilder` only through inheritance or a template annotation.

#### `builderUsagePackages`

**Default**: `""` (empty, unchanged behavior) | **Compiler Option**:
`-Asimplebuilder.builderUsagePackages=package1,package2`

Controls which packages may provide builders as nested builder helpers. Packages are
comma-separated, each listed package includes all of its subpackages, and matching ignores
case.

The processor constructs the candidate builder name using `builderUsageSuffix`
(or `builderSuffix` if not configured) and verifies the builder contract: a
constructor accepting the referenced type and a no-arg `build()` method returning
it. Any class with the expected name and a matching contract qualifies, allowing
references to builders generated with custom template annotations, external tools,
or different suffixes. If the candidate builder cannot be found, the field falls
back to a plain setter.

Packages listed in `builderGenerationPackages` are automatically included in the usage
scope — their builders are generated in the same compilation and don't need to be listed
here.

When `builderUsagePackages` is empty, builders from any package may be referenced (the
behavior before scoping existed). When set, only packages listed in `builderUsagePackages`
(and generation-scope types) may provide builder helpers — types outside both scopes fall
back to a plain setter.

The `example` module contains a runnable demo in package
`org.javahelpers.simple.builders.example.scoping` ([`ScopedOwnerDto.java`](../example/src/main/java/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDto.java)).
It demonstrates a generation-scope builder consumer, a usage-scope missing-builder fallback, and
an out-of-scope plain setter in [`ScopedOwnerDtoBuilder.java`](../example/generated-example-builder/org/javahelpers/simple/builders/example/scoping/ScopedOwnerDtoBuilder.java).

### Component Filtering

#### `deactivateGenerationComponents`
Expand Down Expand Up @@ -912,6 +962,39 @@ public class PersonDto { }

---

#### `builderUsageSuffix`

**Default**: `""` (empty — falls back to `builderSuffix`) | **Compiler Option**:
`-Asimplebuilder.builderUsageSuffix=CustomSuffix`

Customizes the suffix used when looking up builders from the usage scope. The processor
constructs the candidate builder name as `referencedType.getSimpleName() + builderUsageSuffix`.
If empty, `builderSuffix` is used instead. This allows referencing builders that were generated
with a different suffix (e.g. by another module using `"Factory"` as suffix) without changing
the suffix used for own builder generation.

The candidate class must provide a constructor accepting the referenced type and a no-arg
`build()` method returning it. The contract check is annotation-agnostic, so builders
generated with custom template annotations or external tools are supported. If the
candidate class does not exist or does not satisfy this contract, the field falls back
to a plain setter.

**Example**:
```java
@SimpleBuilder.Options(
builderSuffix = "Builder", // own builders: *Builder
builderUsageSuffix = "Factory" // usage-scope builders: *Factory
)
public class OwnerDto {
private ExternalDto external;
}

// Own builder: OwnerDtoBuilder
// External builder looked up as: ExternalDtoFactory (not ExternalDtoBuilder)
```

---

#### `setterSuffix`

**Default**: `""` (empty) | **Compiler Option**: `-Asimplebuilder.setterSuffix=customPrefix`
Expand Down Expand Up @@ -952,7 +1035,7 @@ processing. A summary report is logged to the compiler output at the end of proc

**When enabled**: A hierarchical performance report is printed to the compiler log, including:
- Total processing time and average time per class
- Phase breakdown (Configuration Resolution, Builder Definition Extraction, DTO Mapping, Code Generation)
- Phase breakdown (Element Collection, Configuration Resolution, Builder Definition Extraction, DTO Mapping, Code Generation)
- Top 20 slowest classes with field and collection counts
- Top 5 slowest MethodGenerators and BuilderEnhancers

Expand Down Expand Up @@ -1418,6 +1501,10 @@ methodAccess = AccessModifier.PRIVATE
# Component Filtering
-Asimplebuilder.deactivateGenerationComponents=pattern1,pattern2,...

# Builder Scoping
-Asimplebuilder.builderGenerationPackages=package1,package2
-Asimplebuilder.builderUsagePackages=package1,package2

# Integration & Annotations
-Asimplebuilder.generateWithInterface=ENABLED|DISABLED
-Asimplebuilder.implementsBuilderBase=ENABLED|DISABLED
Expand Down Expand Up @@ -1477,6 +1564,10 @@ methodAccess = AccessModifier.PRIVATE
usingHashSetBuilder = OptionState.ENABLED,
usingHashSetBuilderWithElementBuilders = OptionState.ENABLED,
usingHashMapBuilder = OptionState.ENABLED,

// Builder Scoping
builderGenerationPackages = "com.example.dto",
builderUsagePackages = "com.example.library",

// Integration & Annotations
generateWithInterface = OptionState.ENABLED,
Expand Down
1 change: 1 addition & 0 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ For complete documentation, see [DEBUG_LOGGING.md](DEBUG_LOGGING.md).
========== Compilation Diagnostics ==========
--- NOTES ---
[DEBUG] simple-builders: Processing round started. Found 1 annotated elements.
[DEBUG] simple-builders: 1 of 1 annotated element(s) are inside the builderGenerationPackages scope.
[DEBUG] Processing element: Project
[DEBUG] ├─ Extracting builder definition from: test.Project
[DEBUG] │ ├─ Builder will be generated as: test.ProjectBuilder
Expand Down
4 changes: 4 additions & 0 deletions docs/CUSTOMIZING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Simple-builders is designed to be extensible through custom generators and enhan

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)

Builder usage decisions are centralized in `BuilderScopeResolver`. Custom generators and enhancers
do not need any changes: they continue to rely on `TypeName.getBuilderType().isPresent()` when
checking whether a builder is available.

## Generator Interface

Simple-builders uses a sealed `Generator` interface hierarchy that supports two types of functionality:
Expand Down
4 changes: 4 additions & 0 deletions docs/DEBUG_LOGGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ When debug logging is enabled, you'll see detailed output with visual separators
```
[INFO] simple-builders: PROCESSING ROUND START
[INFO] [DEBUG] simple-builders: Processing round started. Found 3 annotated elements.
[INFO] [DEBUG] simple-builders: 3 of 3 annotated element(s) are inside the builderGenerationPackages scope.
[INFO] [DEBUG] Processing element: PersonDto
[INFO] [DEBUG] ├─ Extracting builder definition from: org.example.PersonDto
[INFO] [DEBUG] │ ├─ Builder will be generated as: org.example.PersonDtoBuilder
Expand Down Expand Up @@ -123,6 +124,9 @@ When debug logging is enabled, you'll see detailed output with visual separators
[INFO] [DEBUG] ├─ Code generation for builder: CustomerDtoBuilder
[INFO] [DEBUG] │ └─ Successfully generated builder: CustomerDtoBuilder
[INFO] simple-builders: Successfully generated 3 builder(s) in this processing round
[INFO] simple-builders: PROCESSING ROUND START
[INFO] [DEBUG] simple-builders: Processing round started. Found 0 annotated elements.
[INFO] [DEBUG] simple-builders: 0 of 0 annotated element(s) are inside the builderGenerationPackages scope.
```

**Note**: Debug messages are prefixed with `[DEBUG]` and use `Diagnostic.Kind.OTHER` which appears as `[INFO]` in Maven output.
Expand Down
Loading
Loading