feat: add @SimpleBuilderFor for generating builders of external types (#293) - #296
Open
igel-devin-ai wants to merge 18 commits into
Open
igel-devin-ai wants to merge 18 commits into
igel-devin-ai wants to merge 18 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AndreasIgel
reviewed
Sep 22, 2026
AndreasIgel
reviewed
Sep 24, 2026
AndreasIgel
reviewed
Sep 24, 2026
AndreasIgel
reviewed
Sep 24, 2026
AndreasIgel
reviewed
Sep 25, 2026
…java-helpers#293) Introduce @SimpleBuilderFor on a holder class to generate builders for types that cannot carry @SimpleBuilder themselves (e.g. third-party library classes). The builders are generated in the holder's package, configured via the annotation's options attribute and compiler options. - New @SimpleBuilderFor annotation in core with Class<?>[] value and optional SimpleBuilder.Options options - BuilderProcessor expands holders into generation targets; builder name collisions and @Ignore4BuilderGeneration targets are skipped with warnings on the holder - Explicitly declared types always get a builder, even outside builderGenerationPackages (contradictory config warns) - Builders generated in the same round are now trusted and exempt from builderUsagePackages - a self-generated builder is always used - Member selection (constructors, setters, getters) now respects member accessibility from the generated builder's package, so generated code only calls members it may legally call - Clear compile-time diagnostics when no accessible constructor or no accessible target type exists - Jackson module defaults to the generated builder's package - Example module: holder + external type + test; also enable surefire so the module's tests actually execute (they silently ran 0 before) - Tests: SimpleBuilderForTest with 12 cases; updated scope tests for the trusted-builder semantics and the new round-start log line Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
…r package from reporting element, tighten docs Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
…ts, concrete map types Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Generated-in-round builders must honor the usage scope like any other candidate: restoring the original resolve() ordering — usage-scope check first, then the registered-builder lookup. The Map-based registration (registerGeneratedBuilders) stays, since @SimpleBuilderFor targets need their builder names resolved in non-default packages. Reverts the scoping example and test expectations to upstream semantics. Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
…n wins, API cleanup - Example + docs now declare @SimpleBuilderFor on package-info.java (holder class renamed to ExternalBuildersProvider in javadoc examples) - @Ignore4BuilderGeneration on an explicitly listed target no longer suppresses generation: the external type's own annotations are not consulted; docs and test updated accordingly - Round-start logging counts both element kinds identically; the 'nothing found' message only appears when both are empty - resolveExternalConfiguration(Element) locates the annotation mirror itself; the unreachable orElseThrow guard is dropped - BuilderScopeResolver offers a single registerGeneratedBuilders(Map<TypeName,TypeName>) entry point - builderPackageOf always returns a concrete package (the reporting element's package), so the builder-package slot in ProcessingContext never carries a null sentinel; getBuilderPackageName/ isMemberAccessibleFromBuilderPackage simplified Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- builderTypeName now returns TypeName instead of a qualified-name String; plannedBuilderNames is Set<TypeName> and the direct-target call site passes the type's own package (no null sentinel) - drop 'No elements to process.' — the unconditional 'Found N ...' counts already report empty rounds Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
Introduce ProcessingTarget (configuration + builderPackage) as the documented holder for per-target processing state. ProcessingContext now carries a single field instead of two loosely related ones, set once via initProcessingTarget before extraction. The record's javadoc explains why the builder package is captured explicitly (not derivable for @SimpleBuilderFor targets). Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
… holder The target->builder-name mapping now lives in a dedicated GeneratedBuilders registry (analysis package) with add/find/clear helpers and javadoc documenting why the builder TypeName is stored explicitly (holder-package builders for @SimpleBuilderFor). BuilderScopeResolver owns it and exposes it via generatedBuilders(); mutations clear the resolution cache through an onChange callback. Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
…y API - renames: planGenerationOfAnnotatedElement, planGenerationOfTypeByHolder, alreadyPlannedBuilders (with comment explaining its conflict-detection role), results, resolveSimpleBuilderForConfiguration - attribute reading moved to JavaLangAnalyser.findAnnotationAttribute; also tolerates a non-list 'value' attribute value - GeneratedBuilders is now a pure holder keyed by TypeName (add/findBuilder/clear, add returns whether it was a new registration); BuilderScopeResolver gained registerGeneratedBuilder and resetGeneratedBuilders which also drop the resolution cache - replaces the onChange callback Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- Objects.requireNonNull on holder/element at entry points - Sonar's null analysis flagged getSimpleName() derefs after the defensive null-check in findAnnotation propagated 'may be null' upstream - lambdas replaced by context::isMemberAccessibleFromBuilderPackage method references - dropped unused annotatedType parameter from isMethodRelevantForBuilder Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- rename targets to results in extractExternalTargetTypes - drop the non-list 'value' tolerance - javac always delivers Class<?>[] attributes as a list, single-value declarations included - rename to resolveHolderConfiguration - 'For' in the name was misleading Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- warn when the 'value' attribute of @SimpleBuilderFor cannot be read, instead of returning silently - javadoc on options(): spell out the UNSET default resolution (compiler argument, then built-in default per Options member) Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- SimpleBuilderFor: link 'Generating Builders for External Types' and 'Compiler Options' sections - JacksonAnnotationEnhancer: fix stale CONFIGURATION.md link (file moved to docs/, repo moved to java-helpers) Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
- branch predated java-helpers#297 and its pom revert dropped the merged surefire / junit-jupiter-engine setup; pom restored to upstream so the example tests keep running - javadoc: state explicitly that @SimpleBuilderFor is not @inherited Co-Authored-By: Andreas Igel <andreas.igel@computacenter.com>
devin-ai-integration
Bot
force-pushed
the
devin/simple-builder-for-external-types
branch
from
September 25, 2026 15:52
0d361b2 to
834969f
Compare
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Implements #293: a new
@SimpleBuilderForannotation that generates builders for types that cannot carry@SimpleBuilderthemselves — external/library classes or any type you don't want to annotate.Placement:
valueaccepts one or moreClass<?>;optionsreusesSimpleBuilder.Optionsand falls back to compiler defaults. Generated builders are written to the holder class's package (the target-package selection stays a separate concern, #294). Naming follows the existing<SimpleName><builderSuffix>convention.Processor wiring (
BuilderProcessor): holders are collected viagetElementsAnnotatedWith(SimpleBuilderFor.class), expanded intoElementToGenerate(element, config, builderPackage, reportingElement)wherebuilderPackageis the holder's package andreportingElementroutes diagnostics to the holder. Config resolves via a newBuilderConfigurationReader.resolveExternalConfiguration(defaults → compiler args →optionsattribute only — the foreign type's own annotations are ignored). Duplicate builder names (two holders, or direct@SimpleBuilder+@SimpleBuilderFor) are skipped with a warning, as are@Ignore4BuilderGenerationtargets.Scoping semantics: an explicit declaration always generates —
builderGenerationPackagesdoes not filter@SimpleBuilderFortargets; a conflicting scope emits a warning instead.BuilderScopeResolvernow registers the actual builderTypeNameper target (registerGeneratedBuilders(Map)); thebuilderUsagePackagesfilter keeps applying to all references, including generated-in-round builders — its position ahead of the generated-builder lookup is unchanged.Accessibility: generation fails with a clear diagnostic (warning; error under
-Asimplebuilder.strict=true) when the target type isn't visible from the builder package or has no accessible constructor —ProcessingContext.isMemberAccessibleFromBuilderPackagegates constructor, setter, and getter selection, so generated code only calls members it may legally call (also fixes latent private-constructor/member selection on the@SimpleBuilderpath).Jackson:
JacksonModuleGeneratornow groups modules by the generated builder's package instead of the target's.Example:
example/ExternalTypeBuilders+external/ExternalAddressproduceexample/ExternalAddressBuilder(committed undergenerated-example-builder/), exercised byExternalAddressBuilderTest. The surefire/junit-engine harness fix for the example module is split out: #297Tests: new
SimpleBuilderForTest(12 cases: multi-value, options, records, inaccessible members, no-accessible-ctor, strict mode, collisions, scope override warning, consumer wiring). UpdatedBuilderScopeResolverTestfor theMap-based registration and the verbose log assertions for the new log lines.mvn clean verify: BUILD SUCCESS (462 processor tests + 30 example tests).