Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.javahelpers.simple.builders.core.util.BuilderToStringStyle;
import org.javahelpers.simple.builders.core.util.TrackedValue;
import org.javahelpers.simple.builders.example.SponsorDto;
import org.javahelpers.simple.builders.example.SponsorDtoBuilder;
import org.javahelpers.simple.builders.example.library.LibraryHelperDto;

/**
Expand All @@ -32,6 +33,7 @@
* .library(LibraryHelperDto::new)
* .sponsor(new SponsorDto())
* .sponsor(SponsorDto::new)
* .sponsor(sponsorDtoBuilder -> sponsorDtoBuilder)
* .trusted(new TrustedHelperDto())
* .trusted(TrustedHelperDto::new)
* .trusted(trustedHelperDtoBuilder -> trustedHelperDtoBuilder)
Expand Down Expand Up @@ -180,17 +182,25 @@ public ScopedOwnerDtoBuilder sponsor(SponsorDto sponsor) {
}

/**
* Sets the value for <code>sponsor</code> by executing the provided consumer.
* Sets the value for <code>sponsor</code> using a builder consumer that produces the value.
* <p>
* Generated from setter {@link ScopedOwnerDto#setSponsor(SponsorDto) setSponsor(SponsorDto sponsor)}
*
* @param sponsorConsumer consumer providing an instance of sponsor
* <h4>Example:</h4>
*
* <pre>{@code
* builder.sponsor(sponsorDtoBuilder -> sponsorDtoBuilder);
* }</pre>
*
* @param sponsorBuilderConsumer consumer providing an instance of a builder for sponsor
* @return current instance of builder
*/
public ScopedOwnerDtoBuilder sponsor(Consumer<SponsorDto> sponsorConsumer) {
SponsorDto consumer = this.sponsor.isSet() ? this.sponsor.value() : new SponsorDto();
sponsorConsumer.accept(consumer);
this.sponsor = changedValue(consumer);
public ScopedOwnerDtoBuilder sponsor(Consumer<SponsorDtoBuilder> sponsorBuilderConsumer) {
SponsorDtoBuilder builder = this.sponsor.isSet()
? new SponsorDtoBuilder(this.sponsor.value())
: new SponsorDtoBuilder();
sponsorBuilderConsumer.accept(builder);
this.sponsor = changedValue(builder.build());
return this;
}

Expand All @@ -217,7 +227,8 @@ public ScopedOwnerDtoBuilder sponsor(Supplier<SponsorDto> sponsorSupplier) {
* Updates the current value of <code>sponsor</code> in place by applying the given operator, instead of reading it
* out, changing it and setting it again. Useful for adjustments relative to the current value, e.g. trimming,
* upper-casing, clamping or incrementing, and in combination with the <code>With</code> copy-and-modify flow. The
* value must have been set before (directly or via an existing instance).
* value must have been set before (directly or via an existing instance). For changing multiple values of a nested
* DTO, prefer the builder-consumer helper {@link #sponsor(Consumer)}.
* <p>
* Generated from setter {@link ScopedOwnerDto#setSponsor(SponsorDto) setSponsor(SponsorDto sponsor)}
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ void exposesScopedBuilderConsumerOverloads() {
assertFalse(
hasBuilderConsumerMethod(
"library", "org.javahelpers.simple.builders.example.library.LibraryHelperDtoBuilder"));
assertFalse(hasBuilderConsumerMethod("sponsor", SponsorDtoBuilder.class.getName()));
// SponsorDto's builder is generated in the same compilation, so it is trusted and used
// regardless of the usage scope
assertTrue(hasBuilderConsumerMethod("sponsor", SponsorDtoBuilder.class.getName()));

assertTrue(hasMethod("trusted", TrustedHelperDto.class));
assertTrue(hasMethod("library", LibraryHelperDto.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ public BuilderScopeResolver(ProcessingContext context) {
* <p>The decision follows these rules:
*
* <ol>
* <li>If the referenced type's builder is generated in the current processing round (registered
* via {@link #registerGeneratedTypes}), the candidate builder (using {@code builderSuffix})
* is returned immediately — trusted without a classpath lookup or contract check, and
* exempt from the usage scope: a builder this processor generates is always used.
* <li>If the usage scope is set and the referenced type's package is not in it, no builder may
* be referenced. The usage scope includes generation-scope packages automatically. When the
* scope is empty, any package is allowed (backward compatibility).
* <li>If the referenced type's builder is generated in the current processing round (registered
* via {@link #registerGeneratedTypes}), the candidate builder (using {@code builderSuffix})
* is returned immediately — trusted without a classpath lookup or contract check.
* <li>Otherwise, the candidate builder name is constructed using {@code builderUsageSuffix}
* (falling back to {@code builderSuffix} if not configured). The candidate is looked up on
* the classpath and returned if it satisfies the builder contract: a constructor accepting
Expand Down Expand Up @@ -162,24 +163,25 @@ private Optional<TypeName> resolve(TypeElement referencedType) {
String referencedTypeFqn = referencedType.getQualifiedName().toString();
String packageName = context.getPackageName(referencedType);

// The usage scope determines whether a type is eligible to be referenced as a builder
// helper. When empty, any package is allowed (backward compatibility). When set, only
// packages in the scope qualify. The scope already includes generation-scope packages.
if (!usagePackages.isEmpty() && !usagePackages.includes(packageName)) {
return Optional.empty();
}

// Types whose builders are generated in the current processing round are trusted
// immediately — our own generators always produce the builder contract, so no
// classpath lookup or contract check is needed. The candidate uses builderSuffix
// because that is what our own generators produce.
// classpath lookup or contract check is needed. Being generated by this processor also
// makes them exempt from the usage scope: an explicitly generated builder is always used.
// The candidate uses builderSuffix because that is what our own generators produce.
if (generatedTypeNames.contains(referencedTypeFqn)) {
TypeName candidate =
JavaLangMapper.createBuilderTypeName(
referencedType, context, context.getConfiguration().getBuilderSuffix());
return Optional.of(candidate);
}

// The usage scope determines whether a type is eligible to be referenced as a builder
// helper. When empty, any package is allowed (backward compatibility). When set, only
// packages in the scope qualify. The scope already includes generation-scope packages.
if (!usagePackages.isEmpty() && !usagePackages.includes(packageName)) {
return Optional.empty();
}

// For types not generated in this round, look up the candidate on the classpath using
// builderUsageSuffix (which falls back to builderSuffix if not configured) and verify
// the builder contract.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,11 @@ public class LibHelper { public LibHelper() {} }
assertThat(compilation).succeeded();
// With usage scope "other" (not "lib") and no registration → empty
assertEquals(Optional.empty(), ResolverProbeProcessor.beforeRegistration);
// Registration alone is not enough — the type must also be in the usage scope
assertEquals(Optional.empty(), ResolverProbeProcessor.afterRegistration);
// Registered types are trusted and exempt from the usage scope — a builder this
// processor generates is always used
assertEquals(
"lib.LibHelperBuilder",
ResolverProbeProcessor.afterRegistration.get().getFullQualifiedName());
// With usage scope "lib" but no registration → empty (builder not on classpath)
assertEquals(Optional.empty(), ResolverProbeProcessor.usageBeforeRegistration);
// With usage scope "lib" AND registration → builder resolved
Expand Down
Loading