diff --git a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java index 488bcb10..0a9d5b16 100644 --- a/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java +++ b/core/src/main/java/org/javahelpers/simple/builders/core/annotations/SimpleBuilder.java @@ -63,7 +63,15 @@ *
Use {@link Template} to create reusable configuration presets. + *
This annotation is itself a built-in {@link Template}: it is meta-annotated with + * {@code @SimpleBuilder.Template(options = @Options())}. When placed on a class or record, the + * processor treats it like any other template annotation. The optional {@link #options()} attribute + * on a concrete {@code @SimpleBuilder} usage overrides the template defaults. + * + *
Use {@link Template} to create reusable configuration presets for project- or layer-specific + * conventions. A custom template annotation is an annotation type that is itself meta-annotated + * with {@code @SimpleBuilder.Template(options = @SimpleBuilder.Options(...))} and then applied to + * classes and records. * *
This annotation is {@link Inherited}: a subclass of an annotated type is treated as if it also
* carried {@code @SimpleBuilder} for the purpose of triggering builder generation, unless it is
@@ -89,6 +97,7 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@Inherited
+@SimpleBuilder.Template
public @interface SimpleBuilder {
/**
@@ -782,10 +791,11 @@
@Inherited
@interface Template {
/**
- * The options to apply when this template is used.
+ * The options to apply when this template is used. Defaults to an empty {@link Options}
+ * instance, so templates inherit the built-in defaults unless options are explicitly set.
*
* @return the builder configuration options
*/
- Options options();
+ Options options() default @Options();
}
}
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index 0579421b..01a0ca35 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -80,7 +80,7 @@ public class PersonDto {
## Template Annotations
-`@SimpleBuilder.Template` is a **meta-annotation**: it is placed on a custom annotation declaration (`@interface`), not directly on a class or record. Use it to create reusable configuration presets that can be applied to many classes with a single custom annotation. For one-off builder generation, use `@SimpleBuilder` directly on the class.
+`@SimpleBuilder.Template` is a **meta-annotation**: it is placed on a custom annotation declaration (`@interface`), not directly on a class or record. Use it to create reusable configuration presets that can be applied to many classes with a single custom annotation. `@SimpleBuilder` itself is the built-in template: it is meta-annotated with `@SimpleBuilder.Template` and can be used directly on a class for one-off builder generation, or you can define your own custom template annotations.
| Need | Use |
|------|-----|
@@ -1154,9 +1154,9 @@ Or in compiler options:
### Template Annotations Not Working
1. **Check @SimpleBuilder.Template**: Ensure template annotation has `@SimpleBuilder.Template`
-2. **Verify options parameter**: Template must specify `options = @SimpleBuilder.Options(...)`
+2. **Verify options parameter**: Template can specify `options = @SimpleBuilder.Options(...)` or rely on the built-in defaults (omitting it is equivalent to an empty `@Options()`)
3. **Retention and Target**: Add `@Retention(RetentionPolicy.CLASS)` and `@Target(ElementType.TYPE)`
-4. **Don't combine**: Don't use `@SimpleBuilder` when using a template annotation
+4. **Combining with @SimpleBuilder**: `@SimpleBuilder` is itself a built-in template. If both `@SimpleBuilder` and a custom template are present on the same class, `@SimpleBuilder` takes precedence in that scope. To use a custom template, place only the custom annotation on the class.
5. **Subclasses not getting a builder**: Add `@Inherited` to the custom template annotation so it propagates to unannotated subclasses (see [Template Annotations](#template-annotations) above). Without `@Inherited`, only the exact type carrying the annotation gets a builder.
6. **Subclass builder has wrong options**: Ensure the custom template annotation is `@Inherited` and that the parent annotation declares the desired `@SimpleBuilder.Options`. Inherited options are applied to subclass builders; a subclass's own annotation overrides the inherited options.
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 5efaf2bd..d89d43e1 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
@@ -44,7 +44,6 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import org.javahelpers.simple.builders.core.annotations.Ignore4BuilderGeneration;
-import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
import org.javahelpers.simple.builders.core.annotations.SimpleBuilder.Template;
import org.javahelpers.simple.builders.processor.analysis.JavaLangAnalyser;
import org.javahelpers.simple.builders.processor.classgen.roaster.RoasterCodeGenerator;
@@ -135,20 +134,14 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
BuilderConfigurationReader reader = context.getConfigurationReader();
- // Find all elements to process:
- // 1. Elements annotated with @SimpleBuilder
- // 2. Elements annotated with custom annotations that have @SimpleBuilder.Template
- // Configuration is resolved per-element to handle priority correctly when both exist
+ // Find all elements to process: any element annotated with an annotation that is
+ // meta-annotated with @SimpleBuilder.Template. This includes @SimpleBuilder itself, which is
+ // a built-in template. Configuration is resolved per-element to handle priority correctly.
Set This class analyzes {@link SimpleBuilder.Options} and custom template annotations (annotations
- * meta-annotated with {@link SimpleBuilder.Template}) on an element.
+ * Both {@code @SimpleBuilder} and custom annotations are treated as template annotations: an
+ * annotation triggers builder generation when it is itself meta-annotated with
+ * {@code @SimpleBuilder.Template}. {@code @SimpleBuilder} is the built-in template; its optional
+ * {@code options()} attribute overrides the defaults declared on its own
+ * {@code @SimpleBuilder.Template} meta-annotation. Custom template annotations carry their
+ * configuration on the {@code @SimpleBuilder.Template} meta-annotation.
*
* Priority order (highest to lowest):
*
* Custom template annotations are annotations that are themselves meta-annotated with
- * {@code @SimpleBuilder.Template}; they are not placed directly on the class. Within each
- * inheritance scope, {@code @SimpleBuilder} options take precedence over template options.
+ * {@code @SimpleBuilder.Template}; they are placed directly on the class or record. Direct
+ * annotations always override inherited annotations.
*/
public class BuilderConfigurationReader {
private static final String SIMPLE_BUILDER_ANNOTATION =
@@ -97,34 +100,124 @@ public BuilderConfiguration getGlobalConfiguration() {
}
/**
- * Reads builder configuration from {@code @SimpleBuilder(options = ...)} inline options.
+ * Resolves the complete builder configuration for an element by chaining all configuration
+ * sources in priority order.
*
- * Directly declared options take precedence over inherited options from superclasses.
+ * Priority chain (highest to lowest):
*
- * Returns null if the element has no {@code @SimpleBuilder} annotation.
+ * When both {@code @SimpleBuilder} and another direct template annotation are present on the
+ * same element, {@code @SimpleBuilder} takes precedence within that scope. A subclass's own
+ * annotation always overrides inherited annotations.
+ *
+ * @param element the annotated element to resolve configuration for
+ * @return the fully resolved configuration with all sources merged
*/
- public BuilderConfiguration readFromInlineOptions(Element element) {
- BuilderConfiguration direct = readFromInlineOptions(element, AnnotationScope.DIRECT);
- if (direct != null) {
- return direct;
+ public BuilderConfiguration resolveConfiguration(Element element) throws BuilderException {
+ String elementName = element.getSimpleName().toString();
+ logger.debugStartOperation("Resolving configuration for element: %s", elementName);
+
+ BuilderConfiguration inheritedConfig = readFromScope(element, AnnotationScope.INHERITED);
+ BuilderConfiguration directConfig = readFromScope(element, AnnotationScope.DIRECT);
+
+ BuilderConfiguration result =
+ BuilderConfiguration.DEFAULT
+ .merge(globalConfiguration)
+ .merge(inheritedConfig)
+ .merge(directConfig);
+
+ // Validate access modifiers and warn about problematic configurations
+ validateAccessModifiers(element, result);
+
+ logger.debugEndOperation("Resulting configuration resolved: %s", result.toString());
+ return result;
+ }
+
+ /**
+ * Reads the highest-priority template configuration for the element in the requested scope.
+ *
+ * If {@code @SimpleBuilder} is present in the scope, its effective configuration (built-in
+ * template defaults overridden by any inline {@code options()}) is returned. Otherwise the first
+ * custom template annotation found in the scope is used.
+ */
+ private BuilderConfiguration readFromScope(Element element, AnnotationScope scope) {
+ List extends AnnotationMirror> mirrors = getAnnotationMirrors(element, scope);
+ BuilderConfiguration simpleBuilderConfig = null;
+ BuilderConfiguration customTemplateConfig = null;
+
+ for (AnnotationMirror mirror : mirrors) {
+ if (isSimpleBuilderAnnotation(mirror)) {
+ simpleBuilderConfig = extractSimpleBuilderConfiguration(mirror);
+ } else if (customTemplateConfig == null) {
+ BuilderConfiguration templateConfig = extractCustomTemplateConfiguration(mirror);
+ if (templateConfig != null) {
+ logger.debug(
+ "Annotation based Configuration for scope %s: %s", scope, templateConfig.toString());
+ customTemplateConfig = templateConfig;
+ }
+ }
}
- return readFromInlineOptions(element, AnnotationScope.INHERITED);
+
+ if (simpleBuilderConfig != null) {
+ logger.debug("Built-in template @SimpleBuilder found in %s scope", scope);
+ return simpleBuilderConfig;
+ }
+ return customTemplateConfig;
+ }
+
+ /**
+ * Extracts the effective configuration for a concrete {@code @SimpleBuilder} usage. The built-in
+ * template defaults defined on the {@code @SimpleBuilder} annotation type are merged with the
+ * inline {@code options()} from the usage, so inline options override the template defaults.
+ */
+ private BuilderConfiguration extractSimpleBuilderConfiguration(
+ AnnotationMirror simpleBuilderMirror) {
+ TypeElement simpleBuilderType =
+ (TypeElement) simpleBuilderMirror.getAnnotationType().asElement();
+ BuilderConfiguration templateDefaults = extractTemplateConfigurationFromType(simpleBuilderType);
+ BuilderConfiguration inlineOptions = extractOptionsFromAnnotationMirror(simpleBuilderMirror);
+ return mergeNullable(templateDefaults, inlineOptions);
+ }
+
+ /**
+ * Extracts the configuration for a custom template annotation usage. The configuration is read
+ * from the {@code @SimpleBuilder.Template} meta-annotation on the custom annotation type.
+ */
+ private BuilderConfiguration extractCustomTemplateConfiguration(
+ AnnotationMirror annotationMirror) {
+ TypeElement annotationType = (TypeElement) annotationMirror.getAnnotationType().asElement();
+ return extractTemplateConfigurationFromType(annotationType);
}
- private BuilderConfiguration readFromInlineOptions(Element element, AnnotationScope scope) {
- AnnotationMirror simpleBuilderMirror =
- extractAnnotationMirror(element, SIMPLE_BUILDER_ANNOTATION, scope);
- return extractOptionsFromAnnotationMirror(simpleBuilderMirror);
+ /**
+ * Extracts the template configuration declared on an annotation type by reading the {@code
+ * options} attribute of its {@code @SimpleBuilder.Template} meta-annotation.
+ *
+ * @return the configuration, or {@code null} if the type is not a template annotation
+ */
+ private BuilderConfiguration extractTemplateConfigurationFromType(TypeElement annotationType) {
+ AnnotationMirror templateMetaMirror = findTemplateMetaMirror(annotationType);
+ if (templateMetaMirror == null) {
+ return null;
+ }
+ return extractOptionsFromAnnotationMirror(templateMetaMirror);
}
- private AnnotationMirror extractAnnotationMirror(
- Element element, String annotationName, AnnotationScope scope) {
- for (AnnotationMirror mirror : getAnnotationMirrors(element, scope)) {
- if (mirror.getAnnotationType().toString().equals(annotationName)) {
- return mirror;
+ /**
+ * Finds the {@code @SimpleBuilder.Template} meta-annotation on an annotation type.
+ *
+ * @return the template meta-annotation mirror, or {@code null} if not present
+ */
+ private AnnotationMirror findTemplateMetaMirror(TypeElement annotationType) {
+ for (AnnotationMirror metaMirror : annotationType.getAnnotationMirrors()) {
+ if (isTemplateAnnotation(metaMirror)) {
+ return metaMirror;
}
}
return null;
@@ -132,10 +225,11 @@ private AnnotationMirror extractAnnotationMirror(
/**
* Extracts configuration from the 'options' attribute of an annotation mirror. Used for
- * inline @SimpleBuilder(options = ...) where reflection doesn't work.
+ * {@code @SimpleBuilder(options = ...)} and for {@code @SimpleBuilder.Template(options = ...)}.
*
* @param annotationMirror the annotation mirror (either @SimpleBuilder or @Template)
- * @return the configuration extracted from the options attribute
+ * @return the configuration extracted from the options attribute, or {@code null} if no options
+ * are set
*/
private BuilderConfiguration extractOptionsFromAnnotationMirror(
AnnotationMirror annotationMirror) {
@@ -143,10 +237,11 @@ private BuilderConfiguration extractOptionsFromAnnotationMirror(
return null;
}
- // Find the 'options' attribute
+ // Find the 'options' attribute, including default values. This is important for @SimpleBuilder,
+ // whose options() attribute defaults to @Options() even when it is not explicitly specified.
AnnotationMirror optionsMirror = null;
Map extends ExecutableElement, ? extends AnnotationValue> elementValues =
- annotationMirror.getElementValues();
+ elementUtils.getElementValuesWithDefaults(annotationMirror);
for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry :
elementValues.entrySet()) {
@@ -168,6 +263,18 @@ private BuilderConfiguration extractOptionsFromAnnotationMirror(
return parseOptionsFromMirror(optionsMirror);
}
+ /** Merges two nullable configurations, preferring the override when both are present. */
+ private BuilderConfiguration mergeNullable(
+ BuilderConfiguration base, BuilderConfiguration override) {
+ if (base == null) {
+ return override;
+ }
+ if (override == null) {
+ return base;
+ }
+ return base.merge(override);
+ }
+
/**
* Parses SimpleBuilder.Options from AnnotationMirror. Only contains explicitly set values (not
* defaults).
@@ -243,48 +350,6 @@ private String extractEnumName(Object value) {
: enumString;
}
- /**
- * Reads builder configuration from a custom template annotation on the element.
- *
- * Directly declared template annotations take precedence over inherited template annotations.
- * If a {@code @SimpleBuilder} annotation is present in the same scope, template annotations in
- * that scope are ignored.
- *
- * Returns null if no template annotation is found or if {@code @SimpleBuilder} is present.
- *
- * @param element the annotated element to analyze
- * @return configuration from the template annotation, or null if not present
- */
- public BuilderConfiguration readFromTemplate(Element element) {
- BuilderConfiguration direct = readFromTemplate(element, AnnotationScope.DIRECT);
- if (direct != null) {
- return direct;
- }
- return readFromTemplate(element, AnnotationScope.INHERITED);
- }
-
- private BuilderConfiguration readFromTemplate(Element element, AnnotationScope scope) {
- List extends AnnotationMirror> mirrors = getAnnotationMirrors(element, scope);
-
- // If @SimpleBuilder is present in this scope, ignore template annotations in the same scope
- if (containsSimpleBuilder(mirrors)) {
- logger.debug(
- "Template annotations ignored because @SimpleBuilder is present in %s scope", scope);
- return null;
- }
-
- // Check all annotations in this scope to find one annotated with @SimpleBuilder.Template
- for (AnnotationMirror mirror : mirrors) {
- BuilderConfiguration templateConfig = checkForTemplateAnnotation(mirror, element);
- if (templateConfig != null) {
- logger.debug("Annotation based Configuration: %s", templateConfig.toString());
- return templateConfig;
- }
- }
-
- return null;
- }
-
private enum AnnotationScope {
DIRECT,
INHERITED
@@ -310,17 +375,8 @@ private List extends AnnotationMirror> getAnnotationMirrors(
return inheritedMirrors;
}
- private boolean containsSimpleBuilder(List extends AnnotationMirror> mirrors) {
- for (AnnotationMirror mirror : mirrors) {
- if (isSimpleBuilderAnnotation(mirror)) {
- return true;
- }
- }
- return false;
- }
-
/**
- * Checks if an annotation mirror represents @SimpleBuilder.
+ * Checks whether an annotation mirror represents @SimpleBuilder.
*
* @param mirror the annotation mirror to check
* @return true if this is @SimpleBuilder
@@ -330,29 +386,6 @@ private boolean isSimpleBuilderAnnotation(AnnotationMirror mirror) {
return typeName.equals(SIMPLE_BUILDER_ANNOTATION);
}
- /**
- * Checks if an annotation is a template annotation and extracts its configuration.
- *
- * @param mirror the annotation mirror to check
- * @param element the element being processed (for logging)
- * @return the configuration if this is a template annotation, null otherwise
- */
- private BuilderConfiguration checkForTemplateAnnotation(
- AnnotationMirror mirror, Element element) {
- Element annotationElement = mirror.getAnnotationType().asElement();
-
- // Check using AnnotationMirror for template annotations
- for (AnnotationMirror metaMirror : annotationElement.getAnnotationMirrors()) {
- if (isTemplateAnnotation(metaMirror)) {
- logger.debug(
- "Found template annotation '%s' on '%s'",
- annotationElement.getSimpleName(), element.getSimpleName());
- return extractOptionsFromTemplateMirror(metaMirror);
- }
- }
- return null;
- }
-
/**
* Checks if an annotation mirror represents @SimpleBuilder.Template.
*
@@ -366,77 +399,6 @@ private boolean isTemplateAnnotation(AnnotationMirror metaMirror) {
|| metaAnnotationName.equals(SIMPLE_BUILDER_TEMPLATE_ANNOTATION_ALT);
}
- /**
- * Extracts configuration from @SimpleBuilder.Template(options = ...) using AnnotationMirror.
- * Fallback for same-round compiled templates where reflection doesn't work.
- */
- private BuilderConfiguration extractOptionsFromTemplateMirror(AnnotationMirror templateMirror) {
- Map extends ExecutableElement, ? extends AnnotationValue> templateValues =
- elementUtils.getElementValuesWithDefaults(templateMirror);
-
- for (Map.Entry extends ExecutableElement, ? extends AnnotationValue> entry :
- templateValues.entrySet()) {
- if (entry.getKey().getSimpleName().toString().equals("options")) {
- Object value = entry.getValue().getValue();
- if (value instanceof AnnotationMirror optionsMirror) {
- return parseOptionsFromMirror(optionsMirror);
- }
- }
- }
- return null;
- }
-
- /**
- * Resolves the complete builder configuration for an element by chaining all configuration
- * sources in priority order.
- *
- * Priority chain (highest to lowest):
- *
- * Custom template annotations are annotations that are themselves meta-annotated with
- * {@code @SimpleBuilder.Template} and are placed directly on the class; the
- * {@code @SimpleBuilder.Template} meta-annotation is not placed on the class itself. Within each
- * inheritance scope, {@code @SimpleBuilder} options take precedence over template options. Direct
- * annotations always override inherited annotations.
- *
- * @param element the annotated element to resolve configuration for
- * @return the fully resolved configuration with all sources merged
- */
- public BuilderConfiguration resolveConfiguration(Element element) throws BuilderException {
- String elementName = element.getSimpleName().toString();
- logger.debugStartOperation("Resolving configuration for element: %s", elementName);
-
- BuilderConfiguration inheritedTemplateConfig =
- readFromTemplate(element, AnnotationScope.INHERITED);
- BuilderConfiguration inheritedInlineConfig =
- readFromInlineOptions(element, AnnotationScope.INHERITED);
- BuilderConfiguration directTemplateConfig = readFromTemplate(element, AnnotationScope.DIRECT);
- BuilderConfiguration directInlineConfig =
- readFromInlineOptions(element, AnnotationScope.DIRECT);
-
- BuilderConfiguration result =
- BuilderConfiguration.DEFAULT
- .merge(globalConfiguration)
- .merge(inheritedTemplateConfig)
- .merge(inheritedInlineConfig)
- .merge(directTemplateConfig)
- .merge(directInlineConfig);
-
- // Validate access modifiers and warn about problematic configurations
- validateAccessModifiers(element, result);
-
- logger.debugEndOperation("Resulting configuration resolved: %s", result.toString());
- return result;
- }
-
/**
* Validates access modifier configurations and throws exception for invalid settings.
*
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderAnnotationInheritanceTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderAnnotationInheritanceTest.java
index 33e4ef44..5210b161 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderAnnotationInheritanceTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderAnnotationInheritanceTest.java
@@ -592,4 +592,82 @@ public class ChildDto extends ParentDto {
assertNotContaining(childBuilder, "Supplier<");
assertNotContaining(childBuilder, "Consumer<");
}
+
+ /**
+ * A direct {@code @SimpleBuilder} on a subclass must override an inherited custom template
+ * annotation. This proves that {@code @SimpleBuilder} is handled through the same template
+ * discovery path as custom templates and wins within the same scope by precedence.
+ */
+ @Test
+ void directSimpleBuilderOverridesInheritedCustomTemplateOptions() {
+ JavaFileObject parentTemplate =
+ ProcessorTestUtils.forSource(
+ """
+ package test;
+
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Inherited;
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.RetentionPolicy;
+ import java.lang.annotation.Target;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+ import org.javahelpers.simple.builders.core.enums.OptionState;
+
+ @SimpleBuilder.Template(options = @SimpleBuilder.Options(
+ generateFieldSupplier = OptionState.DISABLED,
+ builderSuffix = "ParentBuilder"
+ ))
+ @Inherited
+ @Retention(RetentionPolicy.CLASS)
+ @Target(ElementType.TYPE)
+ public @interface ParentTemplate {}
+ """);
+
+ JavaFileObject parentSource =
+ ProcessorTestUtils.forSource(
+ """
+ package test;
+
+ @ParentTemplate
+ public class ParentDto {
+ private String name;
+
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+ }
+ """);
+
+ JavaFileObject childSource =
+ ProcessorTestUtils.forSource(
+ """
+ package test;
+
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder(options = @SimpleBuilder.Options(
+ builderSuffix = "ChildBuilder"
+ ))
+ public class ChildDto extends ParentDto {
+ private int age;
+
+ public int getAge() { return age; }
+ public void setAge(int age) { this.age = age; }
+ }
+ """);
+
+ Compilation compilation = compile(parentTemplate, parentSource, childSource);
+
+ assertThat(compilation).succeededWithoutWarnings();
+
+ String parentBuilder = loadGeneratedSource(compilation, "ParentDtoParentBuilder");
+ assertContaining(parentBuilder, "class ParentDtoParentBuilder");
+ assertContaining(parentBuilder, "public ParentDtoParentBuilder name(String name)");
+ assertNotContaining(parentBuilder, "Supplier<");
+
+ String childBuilder = loadGeneratedSource(compilation, "ChildDtoChildBuilder");
+ assertContaining(childBuilder, "class ChildDtoChildBuilder");
+ assertContaining(childBuilder, "public ChildDtoChildBuilder name(String name)");
+ assertContaining(childBuilder, "public ChildDtoChildBuilder age(int age)");
+ assertNotContaining(childBuilder, "Supplier<");
+ }
}
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java
index 80ddcc7c..18993690 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderConfigurationReaderTest.java
@@ -34,13 +34,16 @@
/**
* Integration tests for {@link
- * org.javahelpers.simple.builders.processor.util.BuilderConfigurationReader}.
+ * org.javahelpers.simple.builders.processor.processing.BuilderConfigurationReader}.
*
- * Verifies configuration reading from various sources through end-to-end compilation:
+ * Verifies configuration reading from various sources through end-to-end compilation. Both
+ * {@code @SimpleBuilder} (which is itself a built-in {@code @SimpleBuilder.Template}) and custom
+ * template annotations are discovered and resolved through the same path.
*
* Verifies BuilderConfigurationReader.readFromOptions() correctly reads and applies all
- * options.
+ * Verifies the built-in {@code @SimpleBuilder} template is resolved and its inline {@code
+ * options()} override the template defaults.
*/
@Test
void readFromOptions_WithOptionsAnnotation_AppliesAllOptions() {
@@ -134,10 +137,10 @@ public class PersonDto {
}
/**
- * Test: Builder respects configuration from template annotation.
+ * Test: Builder respects configuration from a custom template annotation.
*
- * Verifies BuilderConfigurationReader.readFromTemplate() correctly detects and applies
- * template configuration.
+ * Verifies a custom annotation meta-annotated with {@code @SimpleBuilder.Template} is resolved
+ * through the same path as the built-in {@code @SimpleBuilder}.
*/
@Test
void readFromTemplate_WithTemplateAnnotation_AppliesTemplateConfiguration() {
@@ -244,10 +247,11 @@ class PersonDto {
}
/**
- * Test: Options annotation overrides template annotation (proper priority).
+ * Test: Inline @SimpleBuilder options override a custom template annotation.
*
- * Verifies BuilderConfigurationReader.resolveConfiguration() applies correct priority: Options
- * > Template > Compiler args > Defaults
+ * Verifies resolveConfiguration() applies correct priority when both the built-in
+ * {@code @SimpleBuilder} and a custom template are present: direct {@code @SimpleBuilder} options
+ * win over the custom template, which in turn wins over compiler arguments and defaults.
*/
@Test
void resolveConfiguration_OptionsOverridesTemplate_AppliesPriorityCorrectly() {
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
index 4a2daf22..568301ab 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/BuilderProcessorTest.java
@@ -98,7 +98,7 @@ void shouldLogDebugMessagesWhenVerboseModeEnabled() {
"[DEBUG] simple-builders: Processing round started. Found 1 annotated elements.",
"[DEBUG] Processing element: VerboseTest",
"[DEBUG] ├─ Resolving configuration for element: VerboseTest",
- "[DEBUG] │ ├─ Template annotations ignored because @SimpleBuilder is present in DIRECT scope",
+ "[DEBUG] │ ├─ Built-in template @SimpleBuilder found in DIRECT scope",
"[DEBUG] │ └─ Resulting configuration resolved: BuilderConfiguration[",
"[DEBUG] ├─ Extracting builder definition from: test.VerboseTest",
"[DEBUG] │ ├─ Builder will be generated as: test.VerboseTestBuilder",
- *
*
*
+ *
*
- * @param element the annotated element to analyze
- * @return configuration from the inline options, or null if not present
+ *
- *
- *
- *
*
@@ -53,8 +56,8 @@ class BuilderConfigurationReaderTest {
/**
* Test: Builder respects configuration from @SimpleBuilder.Options annotation.
*
- *