diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java index f97a2929877..0d129775722 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/AnnotatedClassFinder.java @@ -22,6 +22,8 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; @@ -63,7 +65,7 @@ public final class AnnotatedClassFinder { * @return the first {@link Class} annotated with the target annotation within the * hierarchy defined by the given {@code source} or {@code null} if none is found. */ - public Class findFromClass(Class source) { + public @Nullable Class findFromClass(Class source) { Assert.notNull(source, "'source' must not be null"); return findFromPackage(ClassUtils.getPackageName(source)); } @@ -75,7 +77,7 @@ public final class AnnotatedClassFinder { * @return the first {@link Class} annotated with the target annotation within the * hierarchy defined by the given {@code source} or {@code null} if none is found. */ - public Class findFromPackage(String source) { + public @Nullable Class findFromPackage(String source) { Assert.notNull(source, "'source' must not be null"); Class configuration = cache.get(source); if (configuration == null) { @@ -85,13 +87,15 @@ public final class AnnotatedClassFinder { return configuration; } - private Class scanPackage(String source) { + private @Nullable Class scanPackage(String source) { while (!source.isEmpty()) { Set components = this.scanner.findCandidateComponents(source); if (!components.isEmpty()) { Assert.state(components.size() == 1, () -> "Found multiple @" + this.annotationType.getSimpleName() + " annotated classes " + components); - return ClassUtils.resolveClassName(components.iterator().next().getBeanClassName(), null); + String beanClassName = components.iterator().next().getBeanClassName(); + Assert.state(beanClassName != null, "'beanClassName' must not be null"); + return ClassUtils.resolveClassName(beanClassName, null); } source = getParentPackage(source); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java index 085f58ac1e9..69386bfbbae 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/FilteredClassLoader.java @@ -27,6 +27,8 @@ import java.util.Collections; import java.util.Enumeration; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.core.SmartClassLoader; import org.springframework.core.io.ClassPathResource; @@ -117,7 +119,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa } @Override - public URL getResource(String name) { + public @Nullable URL getResource(String name) { for (Predicate filter : this.resourcesFilters) { if (filter.test(name)) { return null; @@ -137,7 +139,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa } @Override - public InputStream getResourceAsStream(String name) { + public @Nullable InputStream getResourceAsStream(String name) { for (Predicate filter : this.resourcesFilters) { if (filter.test(name)) { return null; @@ -147,7 +149,7 @@ public class FilteredClassLoader extends URLClassLoader implements SmartClassLoa } @Override - public Class publicDefineClass(String name, byte[] b, ProtectionDomain protectionDomain) { + public Class publicDefineClass(String name, byte[] b, @Nullable ProtectionDomain protectionDomain) { for (Predicate filter : this.classesFilters) { if (filter.test(name)) { throw new IllegalArgumentException(String.format("Defining class with name %s is not supported", name)); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java index ccba2ca316a..4237f9f3710 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizer.java @@ -24,6 +24,8 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -156,6 +158,7 @@ class ImportsContextCustomizer implements ContextCustomizer { private static final String[] NO_IMPORTS = {}; + @SuppressWarnings("NullAway.Init") private ConfigurableListableBeanFactory beanFactory; @Override @@ -254,7 +257,7 @@ class ImportsContextCustomizer implements ContextCustomizer { return ANNOTATION_FILTERS.stream().anyMatch((filter) -> filter.matches(typeName)); } - private Set determineImports(MergedAnnotations annotations, Class testClass) { + private @Nullable Set determineImports(MergedAnnotations annotations, Class testClass) { Set determinedImports = new LinkedHashSet<>(); AnnotationMetadata metadata = AnnotationMetadata.introspect(testClass); for (MergedAnnotation annotation : annotations.stream(Import.class).toList()) { @@ -269,7 +272,7 @@ class ImportsContextCustomizer implements ContextCustomizer { return determinedImports; } - private Set determineImports(Class source, AnnotationMetadata metadata) { + private @Nullable Set determineImports(Class source, AnnotationMetadata metadata) { if (DeterminableImports.class.isAssignableFrom(source)) { // We can determine the imports return ((DeterminableImports) instantiate(source)).determineImports(metadata); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java index 9e336aaf1e1..cec74766e3b 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java @@ -19,6 +19,8 @@ package org.springframework.boot.test.context; import java.lang.reflect.Method; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.AotDetector; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; @@ -41,7 +43,7 @@ import org.springframework.util.ReflectionUtils; class ImportsContextCustomizerFactory implements ContextCustomizerFactory { @Override - public ContextCustomizer createContextCustomizer(Class testClass, + public @Nullable ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { if (AotDetector.useGeneratedArtifacts()) { return null; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java index cf2d1abc62d..65fb88a6f91 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.beans.BeanUtils; @@ -105,6 +107,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao private static final Consumer ALREADY_CONFIGURED = (springApplication) -> { }; + private static final Object NONE = new Object(); + @Override public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { return loadContext(mergedConfig, Mode.STANDARD, null, null); @@ -123,8 +127,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao } private ApplicationContext loadContext(MergedContextConfiguration mergedConfig, Mode mode, - ApplicationContextInitializer initializer, RuntimeHints runtimeHints) - throws Exception { + @Nullable ApplicationContextInitializer initializer, + @Nullable RuntimeHints runtimeHints) throws Exception { assertHasClassesOrLocations(mergedConfig); SpringBootTestAnnotation annotation = SpringBootTestAnnotation.get(mergedConfig); String[] args = annotation.getArgs(); @@ -153,7 +157,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao + SpringVersion.getVersion() + ")."); } - private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) { + private @Nullable Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) { if (useMainMethod == UseMainMethod.NEVER) { return null; } @@ -167,14 +171,16 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao "Cannot use main method as no @SpringBootConfiguration-annotated class is available"); Method mainMethod = findMainMethod(springBootConfiguration); Assert.state(mainMethod != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE, - () -> "Main method not found on '%s'".formatted(springBootConfiguration.getName())); + () -> "Main method not found on '%s'" + .formatted((springBootConfiguration != null) ? springBootConfiguration.getName() : null)); return mainMethod; } - private static Method findMainMethod(Class type) { + private static @Nullable Method findMainMethod(@Nullable Class type) { Method mainMethod = (type != null) ? ReflectionUtils.findMethod(type, "main", String[].class) : null; if (mainMethod == null && KotlinDetector.isKotlinPresent()) { try { + Assert.state(type != null, "'type' must not be null"); Class kotlinClass = ClassUtils.forName(type.getName() + "Kt", type.getClassLoader()); mainMethod = ReflectionUtils.findMethod(kotlinClass, "main", String[].class); } @@ -285,7 +291,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao * method if you need a custom environment. * @return a {@link ConfigurableEnvironment} instance */ - protected ConfigurableEnvironment getEnvironment() { + protected @Nullable ConfigurableEnvironment getEnvironment() { return null; } @@ -462,9 +468,9 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao private static class ParentContextApplicationContextInitializer implements ApplicationContextInitializer { - private final ApplicationContext parent; + private final @Nullable ApplicationContext parent; - ParentContextApplicationContextInitializer(ApplicationContext parent) { + ParentContextApplicationContextInitializer(@Nullable ApplicationContext parent) { this.parent = parent; } @@ -507,7 +513,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao private final Mode mode; - private final ApplicationContextInitializer initializer; + private final @Nullable ApplicationContextInitializer initializer; private final Consumer configurer; @@ -515,7 +521,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao private final List failedContexts = Collections.synchronizedList(new ArrayList<>()); - ContextLoaderHook(Mode mode, ApplicationContextInitializer initializer, + ContextLoaderHook(Mode mode, + @Nullable ApplicationContextInitializer initializer, Consumer configurer) { this.mode = mode; this.initializer = initializer; @@ -530,6 +537,7 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao public void starting(ConfigurableBootstrapContext bootstrapContext) { ContextLoaderHook.this.configurer.accept(application); if (ContextLoaderHook.this.mode == Mode.AOT_RUNTIME) { + Assert.state(ContextLoaderHook.this.initializer != null, "'initializer' must not be null"); application.addInitializers( (AotApplicationContextInitializer) ContextLoaderHook.this.initializer::initialize); } @@ -544,24 +552,24 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao } @Override - public void failed(ConfigurableApplicationContext context, Throwable exception) { + public void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) { ContextLoaderHook.this.failedContexts.add(context); } }; } - private ApplicationContext runMain(Runnable action) throws Exception { + private ApplicationContext runMain(Runnable action) throws Exception { return run(() -> { action.run(); - return null; + return NONE; }); } - private ApplicationContext run(ThrowingSupplier action) throws Exception { + private ApplicationContext run(ThrowingSupplier action) throws Exception { try { - ConfigurableApplicationContext context = SpringApplication.withHook(this, action); - if (context != null) { + Object result = SpringApplication.withHook(this, action); + if (result instanceof ApplicationContext context) { return context; } } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java index 2e27af3bd62..99b6f1f4363 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestAnnotation.java @@ -19,6 +19,8 @@ package org.springframework.boot.test.context; import java.util.Arrays; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.test.context.SpringBootTest.UseMainMethod; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.context.ConfigurableApplicationContext; @@ -52,7 +54,7 @@ class SpringBootTestAnnotation implements ContextCustomizer { this(TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class)); } - private SpringBootTestAnnotation(SpringBootTest annotation) { + private SpringBootTestAnnotation(@Nullable SpringBootTest annotation) { this.args = (annotation != null) ? annotation.args() : NO_ARGS; this.webEnvironment = (annotation != null) ? annotation.webEnvironment() : WebEnvironment.NONE; this.useMainMethod = (annotation != null) ? annotation.useMainMethod() : UseMainMethod.NEVER; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index 766bc51a55a..f3627ecfa8c 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -25,6 +25,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.WebApplicationType; @@ -287,7 +288,7 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr * the bootstrapper class as a property. * @return the differentiator or {@code null} */ - protected String getDifferentiatorPropertySourceProperty() { + protected @Nullable String getDifferentiatorPropertySourceProperty() { return getClass().getName() + "=true"; } @@ -320,22 +321,22 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr * @param testClass the source test class * @return the {@link WebEnvironment} or {@code null} */ - protected WebEnvironment getWebEnvironment(Class testClass) { + protected @Nullable WebEnvironment getWebEnvironment(Class testClass) { SpringBootTest annotation = getAnnotation(testClass); return (annotation != null) ? annotation.webEnvironment() : null; } - protected Class[] getClasses(Class testClass) { + protected Class @Nullable [] getClasses(Class testClass) { SpringBootTest annotation = getAnnotation(testClass); return (annotation != null) ? annotation.classes() : null; } - protected String[] getProperties(Class testClass) { + protected String @Nullable [] getProperties(Class testClass) { SpringBootTest annotation = getAnnotation(testClass); return (annotation != null) ? annotation.properties() : null; } - protected SpringBootTest getAnnotation(Class testClass) { + protected @Nullable SpringBootTest getAnnotation(Class testClass) { return TestContextAnnotationUtils.findMergedAnnotation(testClass, SpringBootTest.class); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java index d1229b02273..6f5be5b15ae 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssert.java @@ -29,6 +29,7 @@ import org.assertj.core.api.AbstractThrowableAssert; import org.assertj.core.api.Assertions; import org.assertj.core.api.MapAssert; import org.assertj.core.error.BasicErrorMessageFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -54,14 +55,14 @@ import static org.assertj.core.api.Assertions.assertThat; public class ApplicationContextAssert extends AbstractAssert, C> { - private final Throwable startupFailure; + private final @Nullable Throwable startupFailure; /** * Create a new {@link ApplicationContextAssert} instance. * @param applicationContext the source application context * @param startupFailure the startup failure or {@code null} */ - ApplicationContextAssert(C applicationContext, Throwable startupFailure) { + ApplicationContextAssert(C applicationContext, @Nullable Throwable startupFailure) { super(applicationContext, ApplicationContextAssert.class); Assert.notNull(applicationContext, "'applicationContext' must not be null"); this.startupFailure = startupFailure; @@ -80,7 +81,8 @@ public class ApplicationContextAssert */ public ApplicationContextAssert hasBean(String name) { if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to have bean named:%n <%s>", name)); + throwAssertionError( + contextFailedToStartWhenExpecting(this.startupFailure, "to have bean named:%n <%s>", name)); } if (findBean(name) == null) { throwAssertionError(new BasicErrorMessageFactory( @@ -123,7 +125,8 @@ public class ApplicationContextAssert public ApplicationContextAssert hasSingleBean(Class type, Scope scope) { Assert.notNull(scope, "'scope' must not be null"); if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to have a single bean of type:%n <%s>", type)); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, + "to have a single bean of type:%n <%s>", type)); } String[] names = scope.getBeanNamesForType(getApplicationContext(), type); if (names.length == 0) { @@ -170,7 +173,8 @@ public class ApplicationContextAssert public ApplicationContextAssert doesNotHaveBean(Class type, Scope scope) { Assert.notNull(scope, "'scope' must not be null"); if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of type:%n <%s>", type)); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, + "not to have any beans of type:%n <%s>", type)); } String[] names = scope.getBeanNamesForType(getApplicationContext(), type); if (names.length > 0) { @@ -194,7 +198,8 @@ public class ApplicationContextAssert */ public ApplicationContextAssert doesNotHaveBean(String name) { if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("not to have any beans of name:%n <%s>", name)); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, + "not to have any beans of name:%n <%s>", name)); } try { Object bean = getApplicationContext().getBean(name); @@ -221,7 +226,8 @@ public class ApplicationContextAssert */ public AbstractObjectArrayAssert getBeanNames(Class type) { if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to get beans names with type:%n <%s>", type)); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, + "to get beans names with type:%n <%s>", type)); } return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type)) .as("Bean names of type <%s> from <%s>", type, getApplicationContext()); @@ -267,7 +273,8 @@ public class ApplicationContextAssert public AbstractObjectAssert getBean(Class type, Scope scope) { Assert.notNull(scope, "'scope' must not be null"); if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to contain bean of type:%n <%s>", type)); + throwAssertionError( + contextFailedToStartWhenExpecting(this.startupFailure, "to contain bean of type:%n <%s>", type)); } String[] names = scope.getBeanNamesForType(getApplicationContext(), type); String name = (names.length > 0) ? getPrimary(names, scope) : null; @@ -280,7 +287,7 @@ public class ApplicationContextAssert return Assertions.assertThat(bean).as("Bean of type <%s> from <%s>", type, getApplicationContext()); } - private String getPrimary(String[] names, Scope scope) { + private @Nullable String getPrimary(String[] names, Scope scope) { if (names.length == 1) { return names[0]; } @@ -325,7 +332,8 @@ public class ApplicationContextAssert */ public AbstractObjectAssert getBean(String name) { if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s>", name)); + throwAssertionError( + contextFailedToStartWhenExpecting(this.startupFailure, "to contain a bean of name:%n <%s>", name)); } Object bean = findBean(name); return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name, getApplicationContext()); @@ -351,8 +359,8 @@ public class ApplicationContextAssert @SuppressWarnings("unchecked") public AbstractObjectAssert getBean(String name, Class type) { if (this.startupFailure != null) { - throwAssertionError( - contextFailedToStartWhenExpecting("to contain a bean of name:%n <%s> (%s)", name, type)); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, + "to contain a bean of name:%n <%s> (%s)", name, type)); } Object bean = findBean(name); if (bean != null && type != null && !type.isInstance(bean)) { @@ -364,7 +372,7 @@ public class ApplicationContextAssert .as("Bean of name <%s> and type <%s> from <%s>", name, type, getApplicationContext()); } - private Object findBean(String name) { + private @Nullable Object findBean(String name) { try { return getApplicationContext().getBean(name); } @@ -409,7 +417,8 @@ public class ApplicationContextAssert public MapAssert getBeans(Class type, Scope scope) { Assert.notNull(scope, "'scope' must not be null"); if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to get beans of type:%n <%s>", type)); + throwAssertionError( + contextFailedToStartWhenExpecting(this.startupFailure, "to get beans of type:%n <%s>", type)); } return Assertions.assertThat(scope.getBeansOfType(getApplicationContext(), type)) .as("Beans of type <%s> from <%s>", type, getApplicationContext()); @@ -456,7 +465,7 @@ public class ApplicationContextAssert */ public ApplicationContextAssert hasNotFailed() { if (this.startupFailure != null) { - throwAssertionError(contextFailedToStartWhenExpecting("to have not failed")); + throwAssertionError(contextFailedToStartWhenExpecting(this.startupFailure, "to have not failed")); } return this; } @@ -465,12 +474,13 @@ public class ApplicationContextAssert return this.actual; } - protected final Throwable getStartupFailure() { + protected final @Nullable Throwable getStartupFailure() { return this.startupFailure; } - private ContextFailedToStart contextFailedToStartWhenExpecting(String expectationFormat, Object... arguments) { - return new ContextFailedToStart<>(getApplicationContext(), this.startupFailure, expectationFormat, arguments); + private ContextFailedToStart contextFailedToStartWhenExpecting(Throwable startupFailure, + String expectationFormat, Object... arguments) { + return new ContextFailedToStart<>(getApplicationContext(), startupFailure, expectationFormat, arguments); } /** diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java index 1d4910a7710..93e855a8af0 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/ApplicationContextAssertProvider.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.function.Supplier; import org.assertj.core.api.AssertProvider; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; @@ -86,7 +87,7 @@ public interface ApplicationContextAssertProvider * context started without issue. * @return the startup failure or {@code null} */ - Throwable getStartupFailure(); + @Nullable Throwable getStartupFailure(); @Override void close(); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java index 84e7a5d4166..f7b66ecf16d 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/AssertProviderApplicationContextInvocationHandler.java @@ -24,6 +24,8 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; @@ -39,9 +41,9 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan private final Class applicationContextType; - private final ApplicationContext applicationContext; + private final @Nullable ApplicationContext applicationContext; - private final RuntimeException startupFailure; + private final @Nullable RuntimeException startupFailure; AssertProviderApplicationContextInvocationHandler(Class applicationContextType, Supplier contextSupplier) { this.applicationContextType = applicationContextType; @@ -66,7 +68,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (isToString(method)) { return toString(); } @@ -95,6 +97,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan return "Unstarted application context " + this.applicationContextType.getName() + "[startupFailure=" + this.startupFailure.getClass().getName() + "]"; } + Assert.state(this.applicationContext != null, "'applicationContext' must not be null"); ToStringCreator builder = new ToStringCreator(this.applicationContext) .append("id", this.applicationContext.getId()) .append("applicationName", this.applicationContext.getApplicationName()) @@ -119,7 +122,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan return ("getStartupFailure".equals(method.getName()) && method.getParameterCount() == 0); } - private Object getStartupFailure() { + private @Nullable Object getStartupFailure() { return this.startupFailure; } @@ -135,7 +138,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan return ("close".equals(method.getName()) && method.getParameterCount() == 0); } - private Object invokeClose() throws IOException { + private @Nullable Object invokeClose() throws IOException { if (this.applicationContext instanceof Closeable closeable) { closeable.close(); } @@ -155,6 +158,7 @@ class AssertProviderApplicationContextInvocationHandler implements InvocationHan if (this.startupFailure != null) { throw new IllegalStateException(this + " failed to start", this.startupFailure); } + Assert.state(this.applicationContext != null, "'applicationContext' must not be null"); return this.applicationContext; } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java index 12a8d30c5e4..8edfcd8bb73 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/assertj/package-info.java @@ -17,4 +17,7 @@ /** * AssertJ support for ApplicationContexts. */ +@NullMarked package org.springframework.boot.test.context.assertj; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java index 697cb317030..c73e96701ee 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/TestTypeExcludeFilter.java @@ -18,6 +18,8 @@ package org.springframework.boot.test.context.filter; import java.io.IOException; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.boot.test.context.TestComponent; @@ -68,7 +70,7 @@ class TestTypeExcludeFilter extends TypeExcludeFilter { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { return (obj != null) && (getClass() == obj.getClass()); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java index 25ec6ad1f0d..792a221375e 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/filter/package-info.java @@ -17,4 +17,7 @@ /** * Test support for {@link org.springframework.boot.context.TypeExcludeFilter}. */ +@NullMarked package org.springframework.boot.test.context.filter; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java index 9b5885910f2..cd1fee7aaf9 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/package-info.java @@ -18,4 +18,7 @@ * Classes and annotations related to configuring Spring's {@code ApplicationContext} for * tests. */ +@NullMarked package org.springframework.boot.test.context; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java index aa1d621c253..407cc064d4f 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/AbstractApplicationContextRunner.java @@ -24,6 +24,8 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -213,7 +215,7 @@ public abstract class AbstractApplicationContextRunner the type of the bean * @return a new instance with the updated bean */ - public SELF withBean(String name, Class type, Object... constructorArgs) { + public SELF withBean(@Nullable String name, Class type, Object... constructorArgs) { return newInstance(this.runnerConfiguration.withBean(name, type, constructorArgs)); } @@ -295,7 +297,7 @@ public abstract class AbstractApplicationContextRunner the type of the bean * @return a new instance with the updated bean */ - public SELF withBean(String name, Class type, Supplier supplier, + public SELF withBean(@Nullable String name, Class type, Supplier supplier, BeanDefinitionCustomizer... customizers) { return newInstance(this.runnerConfiguration.withBean(name, type, supplier, customizers)); } @@ -370,7 +372,7 @@ public abstract class AbstractApplicationContextRunner assertType = (Class) resolvableType.resolveGeneric(1); Class contextType = (Class) resolvableType.resolveGeneric(2); + Assert.state(assertType != null, "'assertType' must not be null"); + Assert.state(contextType != null, "'contextType' must not be null"); return ApplicationContextAssertProvider.get(assertType, contextType, () -> createAndLoadContext(refresh), this.runnerConfiguration.additionalContextInterfaces); } @@ -471,11 +475,11 @@ public abstract class AbstractApplicationContextRunner registrar; - public BeanRegistration(String name, Class type, Object... constructorArgs) { + public BeanRegistration(@Nullable String name, Class type, Object... constructorArgs) { this.registrar = (context) -> context.registerBean(name, type, constructorArgs); } - public BeanRegistration(String name, Class type, Supplier supplier, + public BeanRegistration(@Nullable String name, Class type, Supplier supplier, BeanDefinitionCustomizer... customizers) { this.registrar = (context) -> context.registerBean(name, type, supplier, customizers); } @@ -503,9 +507,9 @@ public abstract class AbstractApplicationContextRunner> beanRegistrations = Collections.emptyList(); @@ -561,7 +565,7 @@ public abstract class AbstractApplicationContextRunner withClassLoader(ClassLoader classLoader) { + private RunnerConfiguration withClassLoader(@Nullable ClassLoader classLoader) { RunnerConfiguration config = new RunnerConfiguration<>(this); config.classLoader = classLoader; return config; @@ -573,14 +577,14 @@ public abstract class AbstractApplicationContextRunner RunnerConfiguration withBean(String name, Class type, Object... constructorArgs) { + private RunnerConfiguration withBean(@Nullable String name, Class type, Object... constructorArgs) { RunnerConfiguration config = new RunnerConfiguration<>(this); config.beanRegistrations = add(config.beanRegistrations, new BeanRegistration<>(name, type, constructorArgs)); return config; } - private RunnerConfiguration withBean(String name, Class type, Supplier supplier, + private RunnerConfiguration withBean(@Nullable String name, Class type, Supplier supplier, BeanDefinitionCustomizer... customizers) { RunnerConfiguration config = new RunnerConfiguration<>(this); config.beanRegistrations = add(config.beanRegistrations, diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java index 79cf0ebf4a1..1abbb3363e9 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/WebApplicationContextRunner.java @@ -18,8 +18,11 @@ package org.springframework.boot.test.context.runner; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext; +import org.springframework.lang.Contract; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; @@ -81,8 +84,9 @@ public final class WebApplicationContextRunner extends * @param contextFactory the context factory to decorate * @return an updated supplier that will set the {@link MockServletContext} */ - public static Supplier withMockServletContext( - Supplier contextFactory) { + @Contract("!null -> !null") + public static @Nullable Supplier withMockServletContext( + @Nullable Supplier contextFactory) { return (contextFactory != null) ? () -> { ConfigurableWebApplicationContext context = contextFactory.get(); context.setServletContext(new MockServletContext()); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java index c2588c2311d..4bec1e2a2e5 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/context/runner/package-info.java @@ -17,4 +17,7 @@ /** * Test utilities to run application contexts for testing. */ +@NullMarked package org.springframework.boot.test.context.runner; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java index 505ba8890f8..73055475b9d 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/AbstractJsonMarshalTester.java @@ -27,6 +27,7 @@ import java.io.StringReader; import java.lang.reflect.Field; import org.assertj.core.api.Assertions; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; @@ -68,9 +69,9 @@ import org.springframework.util.ReflectionUtils; */ public abstract class AbstractJsonMarshalTester { - private Class resourceLoadClass; + private @Nullable Class resourceLoadClass; - private ResolvableType type; + private @Nullable ResolvableType type; /** * Create a new uninitialized {@link AbstractJsonMarshalTester} instance. @@ -107,18 +108,30 @@ public abstract class AbstractJsonMarshalTester { * Return the type under test. * @return the type under test */ - protected final ResolvableType getType() { + protected final @Nullable ResolvableType getType() { return this.type; } + private ResolvableType getTypeNotNull() { + ResolvableType type = getType(); + Assert.state(type != null, "Instance has not been initialized"); + return type; + } + /** * Return class used to load relative resources. * @return the resource load class */ - protected final Class getResourceLoadClass() { + protected final @Nullable Class getResourceLoadClass() { return this.resourceLoadClass; } + private Class getResourceLoadClassNotNull() { + Class resourceLoadClass = getResourceLoadClass(); + Assert.state(resourceLoadClass != null, "Instance has not been initialized"); + return resourceLoadClass; + } + /** * Return {@link JsonContent} from writing the specific value. * @param value the value to write @@ -128,7 +141,7 @@ public abstract class AbstractJsonMarshalTester { public JsonContent write(T value) throws IOException { verify(); Assert.notNull(value, "'value' must not be null"); - String json = writeObject(value, this.type); + String json = writeObject(value, getTypeNotNull()); return getJsonContent(json); } @@ -140,7 +153,7 @@ public abstract class AbstractJsonMarshalTester { * @since 2.1.5 */ protected JsonContent getJsonContent(String json) { - return new JsonContent<>(getResourceLoadClass(), getType(), json); + return new JsonContent<>(getResourceLoadClassNotNull(), getType(), json); } /** @@ -281,7 +294,7 @@ public abstract class AbstractJsonMarshalTester { verify(); Assert.notNull(resource, "'resource' must not be null"); InputStream inputStream = resource.getInputStream(); - T object = readObject(inputStream, this.type); + T object = readObject(inputStream, getTypeNotNull()); closeQuietly(inputStream); return new ObjectContent<>(this.type, object); } @@ -306,7 +319,7 @@ public abstract class AbstractJsonMarshalTester { public ObjectContent read(Reader reader) throws IOException { verify(); Assert.notNull(reader, "'reader' must not be null"); - T object = readObject(reader, this.type); + T object = readObject(reader, getTypeNotNull()); closeQuietly(reader); return new ObjectContent<>(this.type, object); } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java index dc62c6f7852..f23de0f8cfb 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/BasicJsonTester.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.InputStream; import java.nio.charset.Charset; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -47,7 +49,7 @@ import org.springframework.util.Assert; */ public class BasicJsonTester { - private JsonLoader loader; + private @Nullable JsonLoader loader; /** * Create a new uninitialized {@link BasicJsonTester} instance. @@ -69,7 +71,7 @@ public class BasicJsonTester { * @param charset the charset used to load resources * @since 1.4.1 */ - public BasicJsonTester(Class resourceLoadClass, Charset charset) { + public BasicJsonTester(Class resourceLoadClass, @Nullable Charset charset) { Assert.notNull(resourceLoadClass, "'resourceLoadClass' must not be null"); this.loader = new JsonLoader(resourceLoadClass, charset); } @@ -91,7 +93,7 @@ public class BasicJsonTester { * @param charset the charset used when loading relative classpath resources * @since 1.4.1 */ - protected final void initialize(Class resourceLoadClass, Charset charset) { + protected final void initialize(Class resourceLoadClass, @Nullable Charset charset) { if (this.loader == null) { this.loader = new JsonLoader(resourceLoadClass, charset); } @@ -105,8 +107,8 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(CharSequence source) { - verify(); - return getJsonContent(this.loader.getJson(source)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(source)); } /** @@ -116,8 +118,8 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(String path, Class resourceLoadClass) { - verify(); - return getJsonContent(this.loader.getJson(path, resourceLoadClass)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(path, resourceLoadClass)); } /** @@ -126,8 +128,8 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(byte[] source) { - verify(); - return getJsonContent(this.loader.getJson(source)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(source)); } /** @@ -136,8 +138,8 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(File source) { - verify(); - return getJsonContent(this.loader.getJson(source)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(source)); } /** @@ -146,8 +148,8 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(InputStream source) { - verify(); - return getJsonContent(this.loader.getJson(source)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(source)); } /** @@ -156,16 +158,17 @@ public class BasicJsonTester { * @return the JSON content */ public JsonContent from(Resource source) { - verify(); - return getJsonContent(this.loader.getJson(source)); + JsonLoader loader = verify(); + return getJsonContent(loader, loader.getJson(source)); } - private void verify() { + private JsonLoader verify() { Assert.state(this.loader != null, "Uninitialized BasicJsonTester"); + return this.loader; } - private JsonContent getJsonContent(String json) { - return new JsonContent<>(this.loader.getResourceLoadClass(), null, json); + private JsonContent getJsonContent(JsonLoader loader, String json) { + return new JsonContent<>(loader.getResourceLoadClass(), null, json); } } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java index 53d775f134a..def26ddc435 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.ObjectWriter; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; @@ -66,7 +67,7 @@ public class JacksonTester extends AbstractJsonMarshalTester { private final ObjectMapper objectMapper; - private Class view; + private @Nullable Class view; /** * Create a new {@link JacksonTester} instance. @@ -87,7 +88,8 @@ public class JacksonTester extends AbstractJsonMarshalTester { this(resourceLoadClass, type, objectMapper, null); } - public JacksonTester(Class resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, Class view) { + public JacksonTester(Class resourceLoadClass, ResolvableType type, ObjectMapper objectMapper, + @Nullable Class view) { super(resourceLoadClass, type); Assert.notNull(objectMapper, "'objectMapper' must not be null"); this.objectMapper = objectMapper; @@ -100,7 +102,9 @@ public class JacksonTester extends AbstractJsonMarshalTester { .jsonProvider(new JacksonJsonProvider(this.objectMapper)) .mappingProvider(new JacksonMappingProvider(this.objectMapper)) .build(); - return new JsonContent<>(getResourceLoadClass(), getType(), json, configuration); + Class resourceLoadClass = getResourceLoadClass(); + Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null"); + return new JsonContent<>(resourceLoadClass, getType(), json, configuration); } @Override @@ -167,7 +171,11 @@ public class JacksonTester extends AbstractJsonMarshalTester { * @return the new instance */ public JacksonTester forView(Class view) { - return new JacksonTester<>(getResourceLoadClass(), getType(), this.objectMapper, view); + Class resourceLoadClass = getResourceLoadClass(); + ResolvableType type = getType(); + Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null"); + Assert.state(type != null, "'type' must not be null"); + return new JacksonTester<>(resourceLoadClass, type, this.objectMapper, view); } /** diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java index d1b5b346024..b963c4c894f 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContent.java @@ -18,6 +18,7 @@ package org.springframework.boot.test.json; import com.jayway.jsonpath.Configuration; import org.assertj.core.api.AssertProvider; +import org.jspecify.annotations.Nullable; import org.springframework.core.ResolvableType; import org.springframework.util.Assert; @@ -36,7 +37,7 @@ public final class JsonContent implements AssertProvider { private final Class resourceLoadClass; - private final ResolvableType type; + private final @Nullable ResolvableType type; private final String json; @@ -48,7 +49,7 @@ public final class JsonContent implements AssertProvider { * @param type the type under test (or {@code null} if not known) * @param json the actual JSON content */ - public JsonContent(Class resourceLoadClass, ResolvableType type, String json) { + public JsonContent(Class resourceLoadClass, @Nullable ResolvableType type, String json) { this(resourceLoadClass, type, json, Configuration.defaultConfiguration()); } @@ -59,7 +60,7 @@ public final class JsonContent implements AssertProvider { * @param json the actual JSON content * @param configuration the JsonPath configuration */ - JsonContent(Class resourceLoadClass, ResolvableType type, String json, Configuration configuration) { + JsonContent(Class resourceLoadClass, @Nullable ResolvableType type, String json, Configuration configuration) { Assert.notNull(resourceLoadClass, "'resourceLoadClass' must not be null"); Assert.notNull(json, "'json' must not be null"); Assert.notNull(configuration, "'configuration' must not be null"); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java index 9ae8fa99f43..8fe013a19e0 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JsonContentAssert.java @@ -33,6 +33,7 @@ import org.assertj.core.api.Assert; import org.assertj.core.api.Assertions; import org.assertj.core.api.ListAssert; import org.assertj.core.api.MapAssert; +import org.jspecify.annotations.Nullable; import org.skyscreamer.jsonassert.JSONCompare; import org.skyscreamer.jsonassert.JSONCompareMode; import org.skyscreamer.jsonassert.JSONCompareResult; @@ -74,7 +75,7 @@ public class JsonContentAssert extends AbstractAssert resourceLoadClass, Charset charset, CharSequence json) { + public JsonContentAssert(Class resourceLoadClass, @Nullable Charset charset, CharSequence json) { this(resourceLoadClass, charset, json, Configuration.defaultConfiguration()); } @@ -86,7 +87,8 @@ public class JsonContentAssert extends AbstractAssert resourceLoadClass, Charset charset, CharSequence json, Configuration configuration) { + JsonContentAssert(Class resourceLoadClass, @Nullable Charset charset, CharSequence json, + Configuration configuration) { super(json, JsonContentAssert.class); this.configuration = configuration; this.loader = new JsonLoader(resourceLoadClass, charset); @@ -98,7 +100,7 @@ public class JsonContentAssert extends AbstractAssert T extractingJsonPathValue(CharSequence expression, Object[] args, Class type, + private @Nullable T extractingJsonPathValue(CharSequence expression, Object[] args, Class type, String expectedDescription) { JsonPathValue value = new JsonPathValue(expression, args); if (value.getValue(false) != null) { @@ -994,7 +996,7 @@ public class JsonContentAssert extends AbstractAssert type, String expectedDescription) { + void assertHasValue(@Nullable Class type, String expectedDescription) { Object value = getValue(true); if (value == null || isIndefiniteAndEmpty()) { failWithNoValueMessage(); @@ -1128,7 +1130,7 @@ public class JsonContentAssert extends AbstractAssert resourceLoadClass, Charset charset) { + JsonLoader(Class resourceLoadClass, @Nullable Charset charset) { this.resourceLoadClass = resourceLoadClass; this.charset = (charset != null) ? charset : StandardCharsets.UTF_8; } @@ -50,7 +53,8 @@ class JsonLoader { return this.resourceLoadClass; } - String getJson(CharSequence source) { + @Contract("!null -> !null") + @Nullable String getJson(@Nullable CharSequence source) { if (source == null) { return null; } diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java index 01c375f81ff..61a1f2ab1eb 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/ObjectContent.java @@ -17,6 +17,7 @@ package org.springframework.boot.test.json; import org.assertj.core.api.AssertProvider; +import org.jspecify.annotations.Nullable; import org.springframework.core.ResolvableType; import org.springframework.util.Assert; @@ -32,7 +33,7 @@ import org.springframework.util.Assert; */ public final class ObjectContent implements AssertProvider> { - private final ResolvableType type; + private final @Nullable ResolvableType type; private final T object; @@ -41,7 +42,7 @@ public final class ObjectContent implements AssertProvider systemCaptures = new ArrayDeque<>(); - private AnsiOutputState ansiOutputState; + private @Nullable AnsiOutputState ansiOutputState; private final AtomicReference out = new AtomicReference<>(null); @@ -329,7 +331,7 @@ class OutputCapture implements CapturedOutput { AnsiOutput.setEnabled(this.saved); } - static AnsiOutputState saveAndDisable() { + static @Nullable AnsiOutputState saveAndDisable() { if (!ClassUtils.isPresent("org.springframework.boot.ansi.AnsiOutput", OutputCapture.class.getClassLoader())) { return null; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java index 4b5549fca20..81183bd2f17 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/system/package-info.java @@ -17,4 +17,7 @@ /** * Classes for {@link java.lang.System System}-related testing. */ +@NullMarked package org.springframework.boot.test.system; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java index 0d402b769a9..a20e5ed02f4 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/ApplicationContextTestUtils.java @@ -16,6 +16,8 @@ package org.springframework.boot.test.util; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -31,7 +33,7 @@ public abstract class ApplicationContextTestUtils { * Closes this {@link ApplicationContext} and its parent hierarchy if any. * @param context the context to close (can be {@code null}) */ - public static void closeAll(ApplicationContext context) { + public static void closeAll(@Nullable ApplicationContext context) { if (context != null) { if (context instanceof ConfigurableApplicationContext configurableContext) { configurableContext.close(); diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java index e0ce1267288..d2ff23717d3 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/TestPropertyValues.java @@ -27,6 +27,8 @@ import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -37,6 +39,7 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.SystemEnvironmentPropertySource; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -76,7 +79,7 @@ public final class TestPropertyValues { * @return a new {@link TestPropertyValues} instance * @since 2.4.0 */ - public TestPropertyValues and(Iterable pairs) { + public TestPropertyValues and(@Nullable Iterable pairs) { return (pairs != null) ? and(StreamSupport.stream(pairs.spliterator(), false)) : this; } @@ -87,7 +90,7 @@ public final class TestPropertyValues { * @return a new {@link TestPropertyValues} instance * @since 2.4.0 */ - public TestPropertyValues and(Stream pairs) { + public TestPropertyValues and(@Nullable Stream pairs) { return (pairs != null) ? and(pairs, Pair::parse) : this; } @@ -97,7 +100,7 @@ public final class TestPropertyValues { * @return a new {@link TestPropertyValues} instance * @since 2.4.0 */ - public TestPropertyValues and(Map map) { + public TestPropertyValues and(@Nullable Map map) { return (map != null) ? and(map.entrySet().stream(), Pair::fromMapEntry) : this; } @@ -110,7 +113,7 @@ public final class TestPropertyValues { * @return a new {@link TestPropertyValues} instance * @since 2.4.0 */ - public TestPropertyValues and(Stream stream, Function mapper) { + public TestPropertyValues and(@Nullable Stream stream, Function mapper) { if (stream == null) { return this; } @@ -200,8 +203,8 @@ public final class TestPropertyValues { @SuppressWarnings("unchecked") private void addToSources(MutablePropertySources sources, Type type, String name) { - if (sources.contains(name)) { - PropertySource propertySource = sources.get(name); + PropertySource propertySource = sources.get(name); + if (propertySource != null) { if (propertySource.getClass() == type.getSourceClass()) { ((Map) propertySource.getSource()).putAll(this.properties); return; @@ -232,7 +235,7 @@ public final class TestPropertyValues { * environment * @return the new instance */ - public static TestPropertyValues of(Iterable pairs) { + public static TestPropertyValues of(@Nullable Iterable pairs) { return (pairs != null) ? of(StreamSupport.stream(pairs.spliterator(), false)) : empty(); } @@ -244,7 +247,7 @@ public final class TestPropertyValues { * environment * @return the new instance */ - public static TestPropertyValues of(Stream pairs) { + public static TestPropertyValues of(@Nullable Stream pairs) { return (pairs != null) ? of(pairs, Pair::parse) : empty(); } @@ -254,7 +257,7 @@ public final class TestPropertyValues { * @param map the map of properties that need to be added to the environment * @return the new instance */ - public static TestPropertyValues of(Map map) { + public static TestPropertyValues of(@Nullable Map map) { return (map != null) ? of(map.entrySet().stream(), Pair::fromMapEntry) : empty(); } @@ -267,7 +270,7 @@ public final class TestPropertyValues { * {@link Pair} * @return the new instance */ - public static TestPropertyValues of(Stream stream, Function mapper) { + public static TestPropertyValues of(@Nullable Stream stream, Function mapper) { return (stream != null) ? empty().and(stream, mapper) : empty(); } @@ -297,9 +300,9 @@ public final class TestPropertyValues { private final Class sourceClass; - private final String suffix; + private final @Nullable String suffix; - Type(Class sourceClass, String suffix) { + Type(Class sourceClass, @Nullable String suffix) { this.sourceClass = sourceClass; this.suffix = suffix; } @@ -321,9 +324,9 @@ public final class TestPropertyValues { private final String name; - private final String value; + private final @Nullable String value; - private Pair(String name, String value) { + private Pair(String name, @Nullable String value) { Assert.hasLength(name, "'name' must not be empty"); this.name = name; this.value = value; @@ -333,7 +336,7 @@ public final class TestPropertyValues { properties.put(this.name, this.value); } - public static Pair parse(String pair) { + public static @Nullable Pair parse(String pair) { int index = getSeparatorIndex(pair); String name = (index > 0) ? pair.substring(0, index) : pair; String value = (index > 0) ? pair.substring(index + 1) : ""; @@ -358,7 +361,8 @@ public final class TestPropertyValues { * @return the {@link Pair} instance or {@code null} * @since 2.4.0 */ - public static Pair fromMapEntry(Map.Entry entry) { + @Contract("!null -> !null") + public static @Nullable Pair fromMapEntry(Map.@Nullable Entry entry) { return (entry != null) ? of(entry.getKey(), entry.getValue()) : null; } @@ -369,8 +373,8 @@ public final class TestPropertyValues { * @return the {@link Pair} instance or {@code null} * @since 2.4.0 */ - public static Pair of(String name, String value) { - if (StringUtils.hasLength(name) || StringUtils.hasLength(value)) { + public static @Nullable Pair of(@Nullable String name, @Nullable String value) { + if (StringUtils.hasLength(name)) { return new Pair(name, value); } return null; diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java index 5e245486c5e..a78bb13cee4 100644 --- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/util/package-info.java @@ -17,4 +17,7 @@ /** * General purpose test utilities. */ +@NullMarked package org.springframework.boot.test.util; + +import org.jspecify.annotations.NullMarked;