diff --git a/options.md b/options.md index d64cdaf2..e4de80bd 100644 --- a/options.md +++ b/options.md @@ -114,3 +114,8 @@ Special handling for collections. See the project test classes for usage. | `@RecordBuilder.Options(useUnmodifiableCollections = true/false)` | Adds special handling for collection record components. The default is `false`. | | `@RecordBuilder.Options(allowNullableCollections = true/false)` | Adds special null handling for record collectioncomponents. The default is `false`. | | `@RecordBuilder.Options(addSingleItemCollectionBuilders = true/false)` | Adds special handling for record collectioncomponents. The default is `false`. | + +## Required Components + +You can annotate record components with `@RecordBuilder.Required` to generate a builder with required components in the builder parameters. +See [RequiredComponent.java](record-builder-test/src/main/java/io/soabase/recordbuilder/test/RequiredComponents.java) for an example. 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 3f2d57e0..61e7ff5c 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 @@ -386,4 +386,13 @@ enum BuilderMode { */ Class source() default Object.class; } + + /** + * Apply to record components to specify required components for the generated builder. + */ + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.FIELD) + @Inherited + @interface Required { + } } 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 0a9d8c7e..358f366e 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 @@ -107,7 +107,12 @@ class InternalRecordBuilderProcessor { } if ((metaData.builderMode() != BuilderMode.STAGED) && (metaData.builderMode() != BuilderMode.STAGED_REQUIRED_ONLY)) { - addStaticDefaultBuilderMethod(); + if (record.getEnclosedElements().stream() + .anyMatch(element -> element.getAnnotation(RecordBuilder.Required.class) != null)) { + addStaticRequiredComponentsBuilderMethod(record); + } else { + addStaticDefaultBuilderMethod(); + } } addStaticCopyBuilderMethod(); if (metaData.enableWither()) { @@ -793,6 +798,29 @@ private void addStaticDefaultBuilderMethod() { builder.addMethod(methodSpec); } + private void addStaticRequiredComponentsBuilderMethod(TypeElement record) { + /* + * Adds the builder method with all required components similar to: + * + * public static MyRecordBuilder builder(int p1, int p2) { return new MyRecordBuilder().pi(pi).p2(p2); } + */ + var requiredParameters = record.getEnclosedElements().stream() + .filter(element -> element.getAnnotation(RecordBuilder.Required.class) != null) + .map(element -> ParameterSpec + .builder(TypeName.get(element.asType()), element.getSimpleName().toString()).build()) + .toList(); + var codeBuilder = CodeBlock.builder().add("return new $T()", builderClassType.typeName()); + IntStream.range(0, requiredParameters.size()).forEach(index -> { + codeBuilder.add(".$L($L)", requiredParameters.get(index).name, requiredParameters.get(index).name); + }); + var methodSpec = MethodSpec.methodBuilder(metaData.builderMethodName()).addParameters(requiredParameters) + .addJavadoc("Return a new builder with all fields set to the values taken from the given parameters\n") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC).addAnnotation(generatedRecordBuilderAnnotation) + .addTypeVariables(typeVariables).returns(builderClassType.typeName()).addStatement(codeBuilder.build()) + .build(); + builder.addMethod(methodSpec); + } + private void addStaticStagedBuilderMethod(String builderMethodName) { /* * Adds the staged builder method similar to: diff --git a/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RequiredComponents.java b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RequiredComponents.java new file mode 100644 index 00000000..f29cc741 --- /dev/null +++ b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RequiredComponents.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.soabase.recordbuilder.test; + +import io.soabase.recordbuilder.core.RecordBuilder; + +import java.util.Optional; + +@RecordBuilder +public record RequiredComponents(@RecordBuilder.Required int a, @RecordBuilder.Required String b, float c, + Optional d) { +} diff --git a/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestRequiredComponents.java b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestRequiredComponents.java new file mode 100644 index 00000000..49280558 --- /dev/null +++ b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestRequiredComponents.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 The original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.soabase.recordbuilder.test; + +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestRequiredComponents { + @Test + void testRequiredComponents() { + var obj = RequiredComponentsBuilder.builder(100, "hello").build(); + assertEquals(new RequiredComponents(100, "hello", 0.0f, Optional.empty()), obj); + } +}