diff --git a/options.md b/options.md index f2f7ef1c..8092edbc 100644 --- a/options.md +++ b/options.md @@ -94,12 +94,13 @@ for an example. ## Null Handling -| option | details | -|-----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------| -| `@RecordBuilder.Options(interpretNotNulls = true/false)` | Add not-null checks for record components annotated with any null-pattern annotation. The default is `false`. | -| `@RecordBuilder.Options(interpretNotNullsPattern = "regex")` | The regex pattern used to determine if an annotation name means non-null. | -| `@RecordBuilder.Options(allowNullableCollections = true/false)` | Adds special null handling for record collectioncomponents. The default is `false`. | -| `@RecordBuilder.Options(nullablePattern = "regex")` | Regex pattern to use for `BuilderMode.STAGED_REQUIRED_ONLY` and `BuilderMode.STANDARD_AND_STAGED_REQUIRED_ONLY`. | +| option | details | +|-----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| `@RecordBuilder.Options(interpretNotNulls = true/false)` | Add not-null checks for record components annotated with any null-pattern annotation. The default is `false`. | +| `@RecordBuilder.Options(interpretNotNullsPattern = "regex")` | The regex pattern used to determine if an annotation name means non-null. | +| `@RecordBuilder.Options(allowNullableCollections = true/false)` | Adds special null handling for record collectioncomponents. The default is `false`. | +| `@RecordBuilder.Options(nullablePattern = "regex")` | Regex pattern to use for `BuilderMode.STAGED_REQUIRED_ONLY` and `BuilderMode.STANDARD_AND_STAGED_REQUIRED_ONLY`. | +| `@RecordBuilder.Options(defaultNotNull = true/false)` | Add not-null checks for all record components unless annotated with any non-null-pattern annotation. The default is `false`. | ## Collections 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 70c78dcf..75dad20e 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 @@ -339,6 +339,13 @@ * array. */ String onceOnlyAssignmentName() default "_onceOnlyCheck"; + + /** + * Assumes that all fields are non-null, unless explicitly marked as null. + * + * @see #nullablePattern + */ + boolean defaultNotNull() default false; } @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 9f003cb1..896213be 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 @@ -491,7 +491,7 @@ private void addStaticBuilder() { } private void addNullCheckCodeBlock(CodeBlock.Builder builder) { - if (metaData.interpretNotNulls()) { + if (metaData.interpretNotNulls() || metaData.defaultNotNull()) { for (int i = 0; i < recordComponents.size(); ++i) { addNullCheckCodeBlock(builder, i); } @@ -499,10 +499,12 @@ private void addNullCheckCodeBlock(CodeBlock.Builder builder) { } private void addNullCheckCodeBlock(CodeBlock.Builder builder, int index) { - if (metaData.interpretNotNulls()) { + if (metaData.interpretNotNulls() || metaData.defaultNotNull()) { var component = recordComponents.get(index); if (!collectionBuilderUtils.isImmutableCollection(component)) { - if (!component.typeName().isPrimitive() && isNotNullAnnotated(component)) { + if (!component.typeName().isPrimitive() + && (metaData.interpretNotNulls() && isNotNullAnnotated(component) + || metaData.defaultNotNull() && !isNullableAnnotated(component))) { builder.addStatement("$T.requireNonNull($L, $S)", Objects.class, component.name(), component.name() + " is required"); } diff --git a/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RecordWithDefaultNotNull.java b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RecordWithDefaultNotNull.java new file mode 100644 index 00000000..bbc91d31 --- /dev/null +++ b/record-builder-test/src/main/java/io/soabase/recordbuilder/test/RecordWithDefaultNotNull.java @@ -0,0 +1,24 @@ +/* + * 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 javax.validation.constraints.Null; + +@RecordBuilder.Options(defaultNotNull = true) +@RecordBuilder +public record RecordWithDefaultNotNull(Integer notNullInteger, String notNullString, @Null String nullString) { +} diff --git a/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestDefaultNotNull.java b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestDefaultNotNull.java new file mode 100644 index 00000000..08eea5d9 --- /dev/null +++ b/record-builder-test/src/test/java/io/soabase/recordbuilder/test/TestDefaultNotNull.java @@ -0,0 +1,35 @@ +/* + * 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.Assertions; +import org.junit.jupiter.api.Test; + +public class TestDefaultNotNull { + @Test + void testDefaultNotNullFieldSet() { + final var validRecord = RecordWithDefaultNotNullBuilder.RecordWithDefaultNotNull(123, "wibble", null); + Assertions.assertEquals(123, validRecord.notNullInteger()); + Assertions.assertEquals("wibble", validRecord.notNullString()); + Assertions.assertNull(validRecord.nullString()); + } + + @Test + void testDefaultNotNullThrowsExceptionForNullFields() { + Assertions.assertThrows(NullPointerException.class, + () -> RecordWithDefaultNotNullBuilder.RecordWithDefaultNotNull(null, null, null)); + } +}