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
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@
* <li><b>Integration:</b> generateWithInterface (default: true)
* </ul>
*
* <p>Use {@link Template} to create reusable configuration presets.
* <p>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.
*
* <p>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.
*
* <p>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
Expand All @@ -89,6 +97,7 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@Inherited
@SimpleBuilder.Template
public @interface SimpleBuilder {

/**
Expand Down Expand Up @@ -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();
}
}
6 changes: 3 additions & 3 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|------|-----|
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Element> elementsToProcess = new HashSet<>();

// Find all @SimpleBuilder annotations
TypeElement simpleBuilderAnnotation =
context.getTypeElement(SimpleBuilder.class.getCanonicalName());
if (simpleBuilderAnnotation != null) {
elementsToProcess.addAll(roundEnv.getElementsAnnotatedWith(simpleBuilderAnnotation));
}

// Find all Annotations with @SimpleBuilder.Template
// Find all annotations meta-annotated with @SimpleBuilder.Template (this includes
// @SimpleBuilder itself, which is now a built-in template). Each such annotation triggers
// builder generation for the elements it is applied to.
List<TypeElement> annotationsWithTemplate = extractingAnnotationsWithTemplate(annotations);
for (TypeElement annotation : annotationsWithTemplate) {
elementsToProcess.addAll(roundEnv.getElementsAnnotatedWith(annotation));
Expand Down Expand Up @@ -279,15 +272,8 @@ private static List<TypeElement> extractingAnnotationsWithTemplate(
}

private static boolean shouldSkipAnnotation(TypeElement annotation) {
// Only process real annotation specifications
if (annotation.getKind() != javax.lang.model.element.ElementKind.ANNOTATION_TYPE) {
return true;
}
// Skip @SimpleBuilder annotation because we only want to find annotations with
// @SimpleBuilder.Template
return annotation
.getQualifiedName()
.toString()
.equals(org.javahelpers.simple.builders.core.annotations.SimpleBuilder.class.getName());
// Only process real annotation specifications. @SimpleBuilder is now a built-in template, so
// it is processed through the same path as custom template annotations.
return annotation.getKind() != javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
}
}
Loading