Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ A runnable example demonstrating package-scoped builder generation and usage:

### MapStruct Example

A runnable example of the MapStruct `BuilderProvider` integration - no configuration needed beyond putting mapstruct-processor on the same annotation processor path. A bundled `AccessorNamingStrategy` marks the generated helper methods (`add2*`, `*Update`, `Supplier`/`Consumer`/`format` overloads, `conditional(...)`) as non-setters, honoring per-bean naming configuration like `setterSuffix`, so only direct property setters participate in bean mapping - no phantom "unmapped target property" warnings.
A runnable example of the MapStruct `BuilderProvider` integration - no configuration needed beyond putting mapstruct-processor on the same annotation processor path. A bundled `AccessorNamingStrategy` marks the generated helper methods (`add2*`, `*Update`, `Supplier`/`Consumer`/`format` overloads, `conditional(...)`) as non-setters, honoring per-bean naming configuration like `setterPrefix`, so only direct property setters participate in bean mapping - no phantom "unmapped target property" warnings.

- **Mapper**: [`PersonDtoMapper.java`](example/src/main/java/org/javahelpers/simple/builders/example/PersonDtoMapper.java) - Plain `@Mapper` interface; MapStruct resolves `PersonDtoBuilder` automatically
- **Generated implementation**: [`PersonDtoMapperImpl.java`](example/generated-example-builder/org/javahelpers/simple/builders/example/PersonDtoMapperImpl.java) - Uses `PersonDtoBuilder.create()`, the plain setter overloads, and `build()`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@
/**
* Add Jackson annotations to the generated builder class. <br>
* Adds {@code @JsonPOJOBuilder(withPrefix = "...")} to the builder class. The prefix matches
* the configured {@link #setterSuffix()}.
* the configured {@link #setterPrefix()}.
*
* <p>Example:
*
Expand Down Expand Up @@ -858,15 +858,15 @@
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>
* When a suffix is set, the field name is capitalized after the suffix.
* Prefix to prepend to setter method names in the generated builder. <br>
* For example, with prefix "with", a field named "name" will generate "withName()". <br>
* When a prefix is set, the field name is capitalized after the prefix.
*
* <p>Example:
*
* <pre>{@code
* @SimpleBuilder(options = @SimpleBuilder.Options(
* setterSuffix = "with"
* setterPrefix = "with"
* ))
* public class PersonDto {
* // Generates: withName(String) instead of name(String)
Expand All @@ -878,12 +878,12 @@
* .build();
* }</pre>
*
* Default: "" (empty - no suffix) <br>
* Compiler option: -Asimplebuilder.setterSuffix
* Default: "" (empty - no prefix) <br>
* Compiler option: -Asimplebuilder.setterPrefix
*
* @return the suffix for setter method names
* @return the prefix for setter method names
*/
String setterSuffix() default "";
String setterPrefix() default "";

/**
* Formatting mode for the generated source code. <br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
usingExistingBuilders = OptionState.DISABLED,
usingFieldFunctionPrefill = OptionState.DISABLED,
builderSuffix = "Builder",
setterSuffix = "",
setterPrefix = "",
formattingMode = "lightweight"))
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
Expand Down
10 changes: 5 additions & 5 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1175,15 +1175,15 @@ public class ExternalBuilders { }

---

#### `setterSuffix`
#### `setterPrefix`

**Default**: `""` (empty) | **Compiler Option**: `-Asimplebuilder.setterSuffix=customPrefix`
**Default**: `""` (empty) | **Compiler Option**: `-Asimplebuilder.setterPrefix=customPrefix`

Adds a prefix to all setter method names.

**Example**:
```java
@SimpleBuilder.Options(setterSuffix = "with")
@SimpleBuilder.Options(setterPrefix = "with")
public class PersonDto {
private String name;
}
Expand Down Expand Up @@ -1720,7 +1720,7 @@ methodAccess = AccessModifier.PRIVATE

# Naming
-Asimplebuilder.builderSuffix=CustomSuffix
-Asimplebuilder.setterSuffix=customPrefix
-Asimplebuilder.setterPrefix=customPrefix

# Reliability
-Asimplebuilder.strict=ENABLED|DISABLED
Expand Down Expand Up @@ -1784,7 +1784,7 @@ methodAccess = AccessModifier.PRIVATE

// Naming
builderSuffix = "Builder",
setterSuffix = ""
setterPrefix = ""
)
public class ExampleDto {
private String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ private void registerGeneratedTypes(List<ElementToGenerate> elementsToGenerate)
.ifPresent(
resolvedBuilder ->
SimpleBuildersSpiIntegration.registerBuilder(
beanType, resolvedBuilder, elementToGenerate.config().getSetterSuffix()));
beanType, resolvedBuilder, elementToGenerate.config().getSetterPrefix()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* The builders {@link BuilderProcessor} generates, published to SPI adapters of other frameworks
* sharing the annotation processor path (and therefore the classloader). Each entry carries the
* resolved builder's type, creation and build method names plus the bean's configured {@code
* setterSuffix}, so adapters do not scan candidates or read annotation configuration themselves.
* setterPrefix}, so adapters do not scan candidates or read annotation configuration themselves.
*
* <p>The lifecycle reports where {@link BuilderProcessor}'s builder generation stands and is
* transitioned by that processor alone — SPI adapters only ever read this holder, they never
Expand Down Expand Up @@ -74,15 +74,15 @@ public enum State {

/**
* One bean-to-builder pair resolved at registration time: the type this processor emits for
* {@code beanType} plus its creation/build method names and the bean's {@code setterSuffix}
* {@code beanType} plus its creation/build method names and the bean's {@code setterPrefix}
* naming option — flattened to names so SPI adapters never touch the resolution model.
*/
public record PublishedBuilder(
TypeName beanType,
TypeName builderType,
String creationMethodName,
String buildMethodName,
String setterSuffix) {}
String setterPrefix) {}

private static volatile State state = State.INIT;

Expand Down Expand Up @@ -151,7 +151,7 @@ public static boolean isSimpleBuildersFinishedForIntegration(Elements observed)
* Publishes the builder {@link BuilderProcessor} resolved for {@code beanType} in this
* compilation — generated builders always create via a static factory.
*/
static void registerBuilder(TypeName beanType, ResolvedBuilder builder, String setterSuffix) {
static void registerBuilder(TypeName beanType, ResolvedBuilder builder, String setterPrefix) {
if (!(builder.funcForEmptyBuilder() instanceof StaticFactoryCall factory)) {
return;
}
Expand All @@ -161,7 +161,7 @@ static void registerBuilder(TypeName beanType, ResolvedBuilder builder, String s
builder.typeName(),
factory.methodName(),
builder.buildMethodName(),
setterSuffix);
setterPrefix);
BY_BEAN.put(publishedBuilder.beanType().getFullQualifiedName(), publishedBuilder);
BY_BUILDER.put(publishedBuilder.builderType().getFullQualifiedName(), publishedBuilder);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private AnnotationDto createJsonPOJOBuilderAnnotation(BuilderDefinitionDto build
new TypeName("com.fasterxml.jackson.databind.annotation", "JsonPOJOBuilder"));

// Add withPrefix member with the setter prefix from configuration
String setterPrefix = builderDto.getConfiguration().getSetterSuffix();
String setterPrefix = builderDto.getConfiguration().getSetterPrefix();
if (setterPrefix != null) {
annotation.addMember("withPrefix", "\"" + setterPrefix + "\"");
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
*
* <p>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.
* this naming convention regardless of setter prefix configuration.
*
* <p><b>Important behavior:</b> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
* Generates basic setter methods for all fields of the builder.
*
* <p>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).
* directly and stores it in the builder. The method name follows the configured setter prefix
* pattern (default is field name without prefix).
*
* <p><b>Important behavior:</b> The generated setter wraps the value using {@code changedValue()}
* to track that the field has been explicitly set. This allows distinguishing between fields that
Expand All @@ -48,7 +48,7 @@
* builder field must have.
*
* <p>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.
* the method naming can be configured using the setter prefix configuration option.
*
* <h3>Example to demonstrate the generated methods</h3>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ private MethodGeneratorUtil() {
* @return the method name with prefix applied
*/
public static String generateBuilderMethodName(String fieldName, ProcessingContext context) {
return generateBuilderMethodName(fieldName, context.getConfiguration().getSetterSuffix());
return generateBuilderMethodName(fieldName, context.getConfiguration().getSetterPrefix());
}

/**
* Generates a builder method name by prepending the given setter prefix to the capitalized field
* name — the same name derivation the context-based overload resolves from {@code setterSuffix},
* name — the same name derivation the context-based overload resolves from {@code setterPrefix},
* for callers without a {@link ProcessingContext} (e.g. SPI adapters).
*
* @param fieldName the field name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@
* ({@code <field>(Supplier)}, {@code <field>(String, Object...)}, {@code <field>(Consumer)})
* compete with the direct setter. For methods declared on a builder published by {@code
* BuilderProcessor} this strategy returns {@link MethodType#OTHER} for everything that is not the
* direct property setter ({@code <setterSuffix><Field>} — the option is applied as a prefix —
* direct property setter ({@code <setterPrefix><Field>} — the option is applied as a prefix —
* taking a single argument of the field type). The property set is read off the builder's own
* tracked fields and the setter suffix comes from the published {@link PublishedBuilder}
* tracked fields and the setter prefix comes from the published {@link PublishedBuilder}
* descriptors, so builders produced by earlier compilations and foreign types keep the {@link
* DefaultAccessorNamingStrategy} behaviour.
*/
Expand Down Expand Up @@ -136,12 +136,12 @@ private Map<String, TypeMirror> directSettersOf(TypeElement builderType) {
return Map.of();
}
Map<String, TypeMirror> directSetters = new HashMap<>();
String setterSuffix = published.get().setterSuffix();
String setterPrefix = published.get().setterPrefix();
for (VariableElement field : ElementFilter.fieldsIn(builderType.getEnclosedElements())) {
if (!field.getModifiers().contains(Modifier.STATIC)) {
directSetters.put(
MethodGeneratorUtil.generateBuilderMethodName(
field.getSimpleName().toString(), setterSuffix),
field.getSimpleName().toString(), setterPrefix),
trackedPropertyType(field.asType()));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
* itself as builder-creation candidates. simple-builders keeps the factory on the generated builder
* class ({@code PersonDtoBuilder.create()}), so beans are paired with builders exclusively through
* the registry {@code BuilderProcessor} publishes — a constant-time lookup of already-resolved
* {@link PublishedBuilder} descriptors (builder type, creation and build method, setter suffix),
* {@link PublishedBuilder} descriptors (builder type, creation and build method, setter prefix),
* covering custom packages and {@code @SimpleBuilderFor} targets. Builders produced by earlier
* compilations are not discovered: only the beans the processor plans in the current run get
* builder mapping.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
* @param builderSuffix Suffix for builder class name
* @param builderUsageSuffix Suffix for builder class name when referencing builders from the usage
* scope; null means fall back to {@code builderSuffix}
* @param setterSuffix Suffix for setter method names
* @param setterPrefix Prefix for setter method names
* @param formattingMode Formatting mode for generated source code (null = inherit from compiler
* arg)
* @param strict Strict/fail-fast generation mode
Expand Down Expand Up @@ -118,7 +118,7 @@ public record BuilderConfiguration(
OptionState usingFieldFunctionPrefill,
String builderSuffix,
String builderUsageSuffix,
String setterSuffix,
String setterPrefix,
String formattingMode,
OptionState strict) {

Expand Down Expand Up @@ -162,7 +162,7 @@ public record BuilderConfiguration(
.usingExistingBuilders(ENABLED)
.usingFieldFunctionPrefill(ENABLED)
.builderSuffix("Builder")
.setterSuffix("")
.setterPrefix("")
.formattingMode(FormattingMode.JDT.getOptionValue())
.strict(DISABLED)
.build();
Expand Down Expand Up @@ -322,8 +322,8 @@ public String getBuilderUsageSuffix() {
return StringUtils.defaultIfBlank(builderUsageSuffix, builderSuffix);
}

public String getSetterSuffix() {
return setterSuffix;
public String getSetterPrefix() {
return setterPrefix;
}

public boolean isStrictModeEnabled() {
Expand Down Expand Up @@ -411,7 +411,7 @@ public BuilderConfiguration merge(BuilderConfiguration other) {
mergeOptionState(other.usingFieldFunctionPrefill, this.usingFieldFunctionPrefill))
.builderSuffix(mergeString(other.builderSuffix, this.builderSuffix))
.builderUsageSuffix(mergeString(other.builderUsageSuffix, this.builderUsageSuffix))
.setterSuffix(mergeString(other.setterSuffix, this.setterSuffix))
.setterPrefix(mergeString(other.setterPrefix, this.setterPrefix))
.formattingMode(mergeString(other.formattingMode, this.formattingMode))
.strict(mergeOptionState(other.strict, this.strict))
.build();
Expand Down Expand Up @@ -502,7 +502,7 @@ public String toString() {
.appendValueIfSet("usingFieldFunctionPrefill", usingFieldFunctionPrefill)
.appendIfNotEmpty("builderSuffix", builderSuffix)
.appendIfNotEmpty("builderUsageSuffix", builderUsageSuffix)
.appendIfNotEmpty("setterSuffix", setterSuffix)
.appendIfNotEmpty("setterPrefix", setterPrefix)
.appendIfNotEmpty("formattingMode", formattingMode)
.appendValueIfSet("strict", strict)
.toString();
Expand Down Expand Up @@ -587,7 +587,7 @@ public static class Builder {
// === Naming ===
private String builderSuffix = null;
private String builderUsageSuffix = null;
private String setterSuffix = null;
private String setterPrefix = null;

// === Formatting ===
private String formattingMode = null;
Expand Down Expand Up @@ -912,8 +912,8 @@ public Builder builderUsageSuffix(String value) {
return this;
}

public Builder setterSuffix(String value) {
this.setterSuffix = value == null ? null : value.trim();
public Builder setterPrefix(String value) {
this.setterPrefix = value == null ? null : value.trim();
return this;
}

Expand Down Expand Up @@ -967,7 +967,7 @@ public BuilderConfiguration build() {
usingFieldFunctionPrefill,
builderSuffix,
builderUsageSuffix,
setterSuffix,
setterPrefix,
formattingMode,
strict);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ public enum CompilerArgumentsEnum {
/** Option for builder class name suffix when referencing usage-scope builders. */
BUILDER_USAGE_SUFFIX("builderUsageSuffix", string(Builder::builderUsageSuffix)),

/** Option for setter method name suffix. */
SETTER_SUFFIX("setterSuffix", string(Builder::setterSuffix)),
/** Option for setter method name prefix. */
SETTER_PREFIX("setterPrefix", string(Builder::setterPrefix)),

// === Component Filtering ===
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,15 @@ public java.util.List<TaskDto> getTasks() {
}

@Test
void add2Method_ignoresSetterSuffix() {
void add2Method_ignoresSetterPrefix() {
JavaFileObject dto =
com.google.testing.compile.JavaFileObjects.forSourceLines(
"test.PersonDto",
"package test;",
"import java.util.List;",
"@org.javahelpers.simple.builders.core.annotations.SimpleBuilder(",
" options = @org.javahelpers.simple.builders.core.annotations.SimpleBuilder.Options(",
" setterSuffix = \"with\"",
" setterPrefix = \"with\"",
" )",
")",
"public class PersonDto {",
Expand All @@ -195,13 +195,13 @@ void add2Method_ignoresSetterSuffix() {
String generatedCode = loadGeneratedSource(compilation, "PersonDtoBuilder");
assertGenerationSucceeded(compilation, "PersonDtoBuilder", generatedCode);

// Verify regular setter respects setterSuffix
// Verify regular setter respects setterPrefix
ProcessorAsserts.assertContaining(
generatedCode, "public PersonDtoBuilder withName(String name)");
ProcessorAsserts.assertContaining(
generatedCode, "public PersonDtoBuilder withNicknames(List<String> nicknames)");

// Verify add2 method does NOT respect setterSuffix - always uses add2FieldName pattern
// Verify add2 method does NOT respect setterPrefix - always uses add2FieldName pattern
ProcessorAsserts.assertContaining(
generatedCode, "public PersonDtoBuilder add2Nicknames(String element)");

Expand Down
Loading
Loading