From 2b9d8684e94b616cf3fe855f80f568d16dc76f8d Mon Sep 17 00:00:00 2001 From: Jordan Zimmerman Date: Sat, 20 Jun 2026 07:55:26 +0100 Subject: [PATCH] New option `inheritRecordAnnotations` Augments `inheritComponentAnnotations` by supplying a list of record-level annotations that should be copied. Nominally this is for `SuppressWarnings` but it can be used for any set of annotations. --- options.md | 28 +++++++++---------- .../recordbuilder/core/RecordBuilder.java | 8 ++++++ .../InternalRecordBuilderProcessor.java | 14 ++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/options.md b/options.md index 9bb7689d..235d5f41 100644 --- a/options.md +++ b/options.md @@ -56,20 +56,20 @@ The names used for generated methods, classes, etc. can be changed via the follo ## Miscellaneous -| option | details | -|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `@RecordBuilder.Options(inheritComponentAnnotations = true/false)` | If true, any annotations (if applicable) on record components are copied to the builder methods. The default is `true`. | -| `@RecordBuilder.Options(publicBuilderConstructors = true/false)` | Makes the generated builder's constructors public. The default is `false`. | -| `@RecordBuilder.Options(builderClassModifiers = {}})` | Any additional `javax.lang.model.element.Modifier` you wish to apply to the builder. | -| `@RecordBuilder.Options(beanClassName = "Foo")` | If set, the Builder will contain an internal interface with this name. | -| `@RecordBuilder.Options(addClassRetainedGenerated = true/false)` | If true, generated classes are annotated with `RecordBuilderGenerated`. The default is `false`. | -| `@RecordBuilder.Options(addStaticBuilder = true/false)` | If true, a functional-style builder is added so that record instances can be instantiated without `new()`. The default is `true`. | -| `@RecordBuilder.Options(inheritComponentAnnotations = true/false)` | If true, any annotations (if applicable) on record components are copied to the builder methods. The default is `true`. | -| `@RecordBuilder.Options(addConcreteSettersForOptional = )` | Add non-optional setter methods for optional record components. The default is `ConcreteSettersForOptionalMode.DISABLED`. | -| `@RecordBuilder.Options(nullableAnnotationClass = "com.foo.Nullable")` | Nullability annotation to use when RecordBuilder needs to add one. | -| `@RecordBuilder.Options(useValidationApi = true/false)` | Pass built records through the Java Validation API if it's available in the classpath. The default is `false`. | -| `@RecordBuilder.Options(builderMode = BuilderMode.XXX)` | Whether to add standard builder, staged builder or both. The default is `BuilderMode.STANDARD`. | -| `@RecordBuilder.Options(onceOnlyAssignment = true/false)` | If true, attributes can be set/assigned only 1 time. Attempts to reassign/reset attributes will throw `java.lang.IllegalStateException`. The default is `false`. | +| option | details | +|------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `@RecordBuilder.Options(inheritComponentAnnotations = true/false)` | If true, any annotations (if applicable) on record components are copied to the builder methods. The default is `true`. | +| `@RecordBuilder.Options(publicBuilderConstructors = true/false)` | Makes the generated builder's constructors public. The default is `false`. | +| `@RecordBuilder.Options(builderClassModifiers = {}})` | Any additional `javax.lang.model.element.Modifier` you wish to apply to the builder. | +| `@RecordBuilder.Options(beanClassName = "Foo")` | If set, the Builder will contain an internal interface with this name. | +| `@RecordBuilder.Options(addClassRetainedGenerated = true/false)` | If true, generated classes are annotated with `RecordBuilderGenerated`. The default is `false`. | +| `@RecordBuilder.Options(addStaticBuilder = true/false)` | If true, a functional-style builder is added so that record instances can be instantiated without `new()`. The default is `true`. | +| `@RecordBuilder.Options(addConcreteSettersForOptional = )` | Add non-optional setter methods for optional record components. The default is `ConcreteSettersForOptionalMode.DISABLED`. | +| `@RecordBuilder.Options(nullableAnnotationClass = "com.foo.Nullable")` | Nullability annotation to use when RecordBuilder needs to add one. | +| `@RecordBuilder.Options(useValidationApi = true/false)` | Pass built records through the Java Validation API if it's available in the classpath. The default is `false`. | +| `@RecordBuilder.Options(builderMode = BuilderMode.XXX)` | Whether to add standard builder, staged builder or both. The default is `BuilderMode.STANDARD`. | +| `@RecordBuilder.Options(onceOnlyAssignment = true/false)` | If true, attributes can be set/assigned only 1 time. Attempts to reassign/reset attributes will throw `java.lang.IllegalStateException`. The default is `false`. | +| `@RecordBuilder.Options(inheritRecordAnnotations = {})` | Set of annotations that are copied from the record to the generated builder when `inheritComponentAnnotations()` is `true`. | ### Staged Builders diff --git a/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java b/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java index 158ac369..f94288bb 100644 --- a/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java +++ b/record-builder-core/src/main/java/io/soabase/recordbuilder/core/RecordBuilder.java @@ -359,6 +359,14 @@ * @see #nullablePattern */ boolean defaultNotNull() default false; + + /** + * Set of annotations that are copied from the record to the generated builder if + * {@link #inheritComponentAnnotations()} is true + * + * @return annotations + */ + String[] inheritRecordAnnotations() default { "java.lang.SuppressWarnings" }; } @Retention(RetentionPolicy.CLASS) diff --git a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java index 98ffdb6d..6f65cb62 100644 --- a/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java +++ b/record-builder-processor/src/main/java/io/soabase/recordbuilder/processor/InternalRecordBuilderProcessor.java @@ -23,6 +23,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; import java.lang.annotation.ElementType; import java.util.*; import java.util.function.BiConsumer; @@ -94,6 +95,8 @@ class InternalRecordBuilderProcessor { return; } + addInheritedRecordAnnotations(builder, recordFacade, metaData); + addVisibility(recordFacade.builderIsInRecordPackage(), recordFacade.modifiers()); if (metaData.enableWither()) { addWithNestedClass(); @@ -186,6 +189,17 @@ private boolean validateMethodNameConflicts(ProcessingEnvironment processingEnv, }); } + private void addInheritedRecordAnnotations(TypeSpec.Builder builder, RecordFacade recordFacade, + RecordBuilder.Options metaData) { + if (metaData.inheritComponentAnnotations()) { + Set inheritRecordAnnotationsInclusions = Set.of(metaData.inheritRecordAnnotations()); + recordFacade.element().getAnnotationMirrors().stream() + .filter(annotation -> inheritRecordAnnotationsInclusions.contains( + ((TypeElement) annotation.getAnnotationType().asElement()).getQualifiedName().toString())) + .map(AnnotationSpec::get).forEach(builder::addAnnotation); + } + } + private void addVisibility(boolean builderIsInRecordPackage, Set modifiers) { if (builderIsInRecordPackage) { if (modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PRIVATE)