diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java index d671304708d..ed1cb29a69e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 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. @@ -22,7 +22,10 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; import java.util.stream.Collectors; import com.tngtech.archunit.base.DescribedPredicate; @@ -47,6 +50,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTree; import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; import org.gradle.api.tasks.IgnoreEmptyDirectories; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; @@ -63,6 +67,7 @@ import org.gradle.api.tasks.TaskAction; * * @author Andy Wilkinson * @author Yanming Zhou + * @author Ivan Malutin */ public abstract class ArchitectureCheck extends DefaultTask { @@ -70,12 +75,15 @@ public abstract class ArchitectureCheck extends DefaultTask { public ArchitectureCheck() { getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + getProhibitObjectsRequireNonNull().convention(true); getRules().addAll(allPackagesShouldBeFreeOfTangles(), allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(), allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters(), noClassesShouldCallStepVerifierStepVerifyComplete(), noClassesShouldConfigureDefaultStepVerifierTimeout(), noClassesShouldCallCollectorsToList(), noClassesShouldCallURLEncoderWithStringEncoding(), noClassesShouldCallURLDecoderWithStringEncoding()); + getRules().addAll(getProhibitObjectsRequireNonNull() + .map((prohibit) -> prohibit ? noClassesShouldCallObjectsRequireNonNull() : Collections.emptyList())); getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList())); } @@ -208,6 +216,18 @@ public abstract class ArchitectureCheck extends DefaultTask { .because("java.net.URLDecoder.decode(String s, Charset charset) should be used instead"); } + private List noClassesShouldCallObjectsRequireNonNull() { + return List.of( + ArchRuleDefinition.noClasses() + .should() + .callMethod(Objects.class, "requireNonNull", Object.class, String.class) + .because("org.springframework.utils.Assert.notNull(Object, String) should be used instead"), + ArchRuleDefinition.noClasses() + .should() + .callMethod(Objects.class, "requireNonNull", Object.class, Supplier.class) + .because("org.springframework.utils.Assert.notNull(Object, Supplier) should be used instead")); + } + public void setClasses(FileCollection classes) { this.classes = classes; } @@ -236,8 +256,12 @@ public abstract class ArchitectureCheck extends DefaultTask { @Internal public abstract ListProperty getRules(); + @Internal + public abstract Property getProhibitObjectsRequireNonNull(); + @Input - // The rules themselves can't be an input as they aren't serializable so we use their + // The rules themselves can't be an input as they aren't serializable so we use + // their // descriptions instead abstract ListProperty getRuleDescriptions(); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java index 1294d619297..598d8d2fbe2 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * Tests for {@link ArchitectureCheck}. * * @author Andy Wilkinson + * @author Ivan Malutin */ class ArchitectureCheckTests { @@ -121,6 +122,30 @@ class ArchitectureCheckTests { }); } + @Test + void whenClassDoesNotCallObjectsRequireNonNullTaskSucceedsAndWritesAnEmptyReport() throws Exception { + prepareTask("objects/noRequireNonNull", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck)).isEmpty(); + }); + } + + @Test + void whenClassCallsObjectsRequireNonNullWithMessageTaskFailsAndWritesReport() throws Exception { + prepareTask("objects/requireNonNullWithString", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(architectureCheck::checkArchitecture); + assertThat(failureReport(architectureCheck)).isNotEmpty(); + }); + } + + @Test + void whenClassCallsObjectsRequireNonNullWithSupplierTaskFailsAndWritesReport() throws Exception { + prepareTask("objects/requireNonNullWithSupplier", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(architectureCheck::checkArchitecture); + assertThat(failureReport(architectureCheck)).isNotEmpty(); + }); + } + private void prepareTask(String classes, Callback callback) throws Exception { File projectDir = new File(this.temp, "project"); projectDir.mkdirs(); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/noRequireNonNull/NoRequireNonNull.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/noRequireNonNull/NoRequireNonNull.java new file mode 100644 index 00000000000..96542f00908 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/noRequireNonNull/NoRequireNonNull.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2024 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 + * + * https://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 org.springframework.boot.build.architecture.objects.noRequireNonNull; + +import java.util.Collections; + +import org.springframework.util.Assert; + +class NoRequireNonNull { + + void exampleMethod() { + Assert.notNull(new Object(), "Object must not be null"); + // Compilation of a method reference generates code that uses + // Objects.requireNonNull(Object). Check that it doesn't cause a failure. + Collections.emptyList().forEach(System.out::println); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithString/RequireNonNullWithString.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithString/RequireNonNullWithString.java new file mode 100644 index 00000000000..583cf65cbd7 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithString/RequireNonNullWithString.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 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 + * + * https://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 org.springframework.boot.build.architecture.objects.requireNonNullWithString; + +import java.util.Objects; + +class RequireNonNullWithString { + + void exampleMethod() { + Objects.requireNonNull(new Object(), "Object cannot be null"); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithSupplier/RequireNonNullWithSupplier.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithSupplier/RequireNonNullWithSupplier.java new file mode 100644 index 00000000000..bc5989d31af --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/objects/requireNonNullWithSupplier/RequireNonNullWithSupplier.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-2024 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 + * + * https://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 org.springframework.boot.build.architecture.objects.requireNonNullWithSupplier; + +import java.util.Objects; + +class RequireNonNullWithSupplier { + + void exampleMethod() { + Objects.requireNonNull(new Object(), () -> "Object cannot be null"); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-classic/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-loader-classic/build.gradle index e04b7b35ceb..d45e33698c5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-classic/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-classic/build.gradle @@ -21,3 +21,7 @@ dependencies { testRuntimeOnly("org.bouncycastle:bcprov-jdk18on:1.78.1") testRuntimeOnly("org.springframework:spring-webmvc") } + +tasks.named("checkArchitectureMain").configure { + prohibitObjectsRequireNonNull = false +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-loader/build.gradle index 2bdc365e07d..0784082e601 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/build.gradle @@ -21,3 +21,7 @@ dependencies { testRuntimeOnly("org.bouncycastle:bcprov-jdk18on:1.78.1") testRuntimeOnly("org.springframework:spring-webmvc") } + +tasks.named("checkArchitectureMain").configure { + prohibitObjectsRequireNonNull = false +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index 3abd1067907..4b843c7d62c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import com.jayway.jsonpath.JsonPath; @@ -616,8 +615,8 @@ class ValueObjectBinderTests { private final Object value; ExampleFailingConstructorBean(String name, String value) { - Objects.requireNonNull(name, "'name' must be not null."); - Objects.requireNonNull(value, "'value' must be not null."); + Assert.notNull(name, "'name' must be not null."); + Assert.notNull(value, "'value' must be not null."); this.name = name; this.value = value; }