diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml index 9dab7d74a1b..04064bd8a80 100644 --- a/config/checkstyle/checkstyle-suppressions.xml +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -86,4 +86,8 @@ + + + + diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationArguments.java b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationArguments.java index e18c7f04afa..821a5b4cc48 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationArguments.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationArguments.java @@ -19,6 +19,8 @@ package org.springframework.boot; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + /** * Provides access to the arguments that were used to run a {@link SpringApplication}. * @@ -63,7 +65,7 @@ public interface ApplicationArguments { * @param name the name of the option * @return a list of option values for the given name */ - List getOptionValues(String name); + @Nullable List getOptionValues(String name); /** * Return the collection of non-option arguments parsed. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java index 8b5afca8755..f18a7a6022d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationContextFactory.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeanUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; @@ -46,11 +48,12 @@ public interface ApplicationContextFactory { * Return the {@link Environment} type expected to be set on the * {@link #create(WebApplicationType) created} application context. The result of this * method can be used to convert an existing environment instance to the correct type. - * @param webApplicationType the web application type + * @param webApplicationType the web application type or {@code null} * @return the expected application context type or {@code null} to use the default * @since 2.6.14 */ - default Class getEnvironmentType(WebApplicationType webApplicationType) { + default @Nullable Class getEnvironmentType( + @Nullable WebApplicationType webApplicationType) { return null; } @@ -59,11 +62,11 @@ public interface ApplicationContextFactory { * {@link #create(WebApplicationType) created} application context. The result of this * method must match the type returned by * {@link #getEnvironmentType(WebApplicationType)}. - * @param webApplicationType the web application type + * @param webApplicationType the web application type or {@code null} * @return an environment instance or {@code null} to use the default * @since 2.6.14 */ - default ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + default @Nullable ConfigurableEnvironment createEnvironment(@Nullable WebApplicationType webApplicationType) { return null; } @@ -73,7 +76,7 @@ public interface ApplicationContextFactory { * @param webApplicationType the web application type * @return the newly created application context */ - ConfigurableApplicationContext create(WebApplicationType webApplicationType); + ConfigurableApplicationContext create(@Nullable WebApplicationType webApplicationType); /** * Creates an {@code ApplicationContextFactory} that will create contexts by diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationEnvironment.java b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationEnvironment.java index 39ef60fd1e0..79bc85f2a25 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationEnvironment.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationEnvironment.java @@ -16,6 +16,8 @@ package org.springframework.boot; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.MutablePropertySources; @@ -29,12 +31,12 @@ import org.springframework.core.env.StandardEnvironment; class ApplicationEnvironment extends StandardEnvironment { @Override - protected String doGetActiveProfilesProperty() { + protected @Nullable String doGetActiveProfilesProperty() { return null; } @Override - protected String doGetDefaultProfilesProperty() { + protected @Nullable String doGetDefaultProfilesProperty() { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java index 3e39d416ad9..95c0d797546 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationInfoPropertySource.java @@ -19,6 +19,8 @@ package org.springframework.boot; import java.util.HashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.system.ApplicationPid; @@ -38,7 +40,7 @@ class ApplicationInfoPropertySource extends MapPropertySource implements OriginL static final String NAME = "applicationInfo"; - ApplicationInfoPropertySource(Class mainClass) { + ApplicationInfoPropertySource(@Nullable Class mainClass) { super(NAME, getProperties(readVersion(mainClass))); } @@ -47,7 +49,7 @@ class ApplicationInfoPropertySource extends MapPropertySource implements OriginL } @Override - public Origin getOrigin(String key) { + public @Nullable Origin getOrigin(String key) { return null; } @@ -56,7 +58,7 @@ class ApplicationInfoPropertySource extends MapPropertySource implements OriginL return true; } - private static Map getProperties(String applicationVersion) { + private static Map getProperties(@Nullable String applicationVersion) { Map result = new HashMap<>(); if (StringUtils.hasText(applicationVersion)) { result.put("spring.application.version", applicationVersion); @@ -68,7 +70,7 @@ class ApplicationInfoPropertySource extends MapPropertySource implements OriginL return result; } - private static String readVersion(Class applicationClass) { + private static @Nullable String readVersion(@Nullable Class applicationClass) { Package sourcePackage = (applicationClass != null) ? applicationClass.getPackage() : null; return (sourcePackage != null) ? sourcePackage.getImplementationVersion() : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationProperties.java index 1811e67424e..48058b7d6bb 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ApplicationProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ApplicationProperties.java @@ -19,12 +19,15 @@ package org.springframework.boot; import java.util.LinkedHashSet; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.Banner.Mode; import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar; import org.springframework.boot.logging.LoggingSystemProperty; import org.springframework.core.env.Environment; +import org.springframework.util.Assert; /** * Spring application properties. @@ -48,7 +51,7 @@ class ApplicationProperties { /** * Mode used to display the banner when the application runs. */ - private Banner.Mode bannerMode; + private Banner.@Nullable Mode bannerMode; /** * Whether to keep the application alive even if there are no more non-daemon threads. @@ -80,7 +83,7 @@ class ApplicationProperties { * Flag to explicitly request a specific type of web application. If not set, * auto-detected based on the classpath. */ - private WebApplicationType webApplicationType; + private @Nullable WebApplicationType webApplicationType; boolean isAllowBeanDefinitionOverriding() { return this.allowBeanDefinitionOverriding; @@ -102,12 +105,13 @@ class ApplicationProperties { if (this.bannerMode != null) { return this.bannerMode; } - boolean structuredLoggingEnabled = environment - .containsProperty(LoggingSystemProperty.CONSOLE_STRUCTURED_FORMAT.getApplicationPropertyName()); + String applicationPropertyName = LoggingSystemProperty.CONSOLE_STRUCTURED_FORMAT.getApplicationPropertyName(); + Assert.state(applicationPropertyName != null, "applicationPropertyName must not be null"); + boolean structuredLoggingEnabled = environment.containsProperty(applicationPropertyName); return (structuredLoggingEnabled) ? Mode.OFF : Banner.Mode.CONSOLE; } - void setBannerMode(Mode bannerMode) { + void setBannerMode(@Nullable Mode bannerMode) { this.bannerMode = bannerMode; } @@ -151,18 +155,18 @@ class ApplicationProperties { this.sources = new LinkedHashSet<>(sources); } - WebApplicationType getWebApplicationType() { + @Nullable WebApplicationType getWebApplicationType() { return this.webApplicationType; } - void setWebApplicationType(WebApplicationType webApplicationType) { + void setWebApplicationType(@Nullable WebApplicationType webApplicationType) { this.webApplicationType = webApplicationType; } static class ApplicationPropertiesRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { BindableRuntimeHintsRegistrar.forTypes(ApplicationProperties.class).registerHints(hints, classLoader); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/Banner.java b/core/spring-boot/src/main/java/org/springframework/boot/Banner.java index 2cc278a9c3e..4aa6b5ec069 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/Banner.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/Banner.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.io.PrintStream; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.Environment; /** @@ -34,10 +36,10 @@ public interface Banner { /** * Print the banner to the specified print stream. * @param environment the spring environment - * @param sourceClass the source class for the application + * @param sourceClass the source class for the application or {@code null} * @param out the output print stream */ - void printBanner(Environment environment, Class sourceClass, PrintStream out); + void printBanner(Environment environment, @Nullable Class sourceClass, PrintStream out); /** * An enumeration of possible values for configuring the Banner. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java b/core/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java index 2f04ad91fe5..e0519799dff 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/BeanDefinitionLoader.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.regex.Pattern; import groovy.lang.Closure; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanDefinitionStoreException; @@ -68,11 +69,11 @@ class BeanDefinitionLoader { private final AbstractBeanDefinitionReader xmlReader; - private final BeanDefinitionReader groovyReader; + private final @Nullable BeanDefinitionReader groovyReader; private final ClassPathBeanDefinitionScanner scanner; - private ResourceLoader resourceLoader; + private @Nullable ResourceLoader resourceLoader; /** * Create a new {@link BeanDefinitionLoader} that will load beans into the specified @@ -86,7 +87,7 @@ class BeanDefinitionLoader { this.sources = sources; this.annotatedReader = new AnnotatedBeanDefinitionReader(registry); this.xmlReader = new XmlBeanDefinitionReader(registry); - this.groovyReader = (isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null); + this.groovyReader = isGroovyPresent() ? new GroovyBeanDefinitionReader(registry) : null; this.scanner = new ClassPathBeanDefinitionScanner(registry); this.scanner.addExcludeFilter(new ClassExcludeFilter(sources)); } @@ -152,7 +153,7 @@ class BeanDefinitionLoader { } private void load(Class source) { - if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { + if (this.groovyReader != null && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) { // Any GroovyLoaders added in beans{} DSL can contribute beans here GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class); ((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans()); @@ -163,7 +164,9 @@ class BeanDefinitionLoader { } private void load(Resource source) { - if (source.getFilename().endsWith(".groovy")) { + String filename = source.getFilename(); + Assert.state(filename != null, "Source has no filename"); + if (filename.endsWith(".groovy")) { if (this.groovyReader == null) { throw new BeanDefinitionStoreException("Cannot load Groovy beans without Groovy on classpath"); } @@ -231,7 +234,7 @@ class BeanDefinitionLoader { } } - private boolean isLoadCandidate(Resource resource) { + private boolean isLoadCandidate(@Nullable Resource resource) { if (resource == null || !resource.exists()) { return false; } @@ -253,7 +256,7 @@ class BeanDefinitionLoader { return true; } - private Package findPackage(CharSequence source) { + private @Nullable Package findPackage(CharSequence source) { Package pkg = getClass().getClassLoader().getDefinedPackage(source.toString()); if (pkg != null) { return pkg; @@ -264,7 +267,9 @@ class BeanDefinitionLoader { Resource[] resources = resolver .getResources(ClassUtils.convertClassNameToResourcePath(source.toString()) + "/*.class"); for (Resource resource : resources) { - String className = StringUtils.stripFilenameExtension(resource.getFilename()); + String filename = resource.getFilename(); + Assert.state(filename != null, "No filename available"); + String className = StringUtils.stripFilenameExtension(filename); load(Class.forName(source + "." + className)); break; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java b/core/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java index 3abc682e543..bd3678106e5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/BootstrapContext.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; @@ -41,7 +43,7 @@ public interface BootstrapContext { * @return the instance managed by the context * @throws IllegalStateException if the type has not been registered */ - T get(Class type) throws IllegalStateException; + @Nullable T get(Class type) throws IllegalStateException; /** * Return an instance from the context if the type has been registered. The instance @@ -51,7 +53,7 @@ public interface BootstrapContext { * @param other the instance to use if the type has not been registered * @return the instance */ - T getOrElse(Class type, T other); + @Nullable T getOrElse(Class type, @Nullable T other); /** * Return an instance from the context if the type has been registered. The instance @@ -61,7 +63,7 @@ public interface BootstrapContext { * @param other a supplier for the instance to use if the type has not been registered * @return the instance */ - T getOrElseSupply(Class type, Supplier other); + @Nullable T getOrElseSupply(Class type, Supplier<@Nullable T> other); /** * Return an instance from the context if the type has been registered. The instance @@ -74,7 +76,8 @@ public interface BootstrapContext { * @throws X if the type has not been registered * @throws IllegalStateException if the type has not been registered */ - T getOrElseThrow(Class type, Supplier exceptionSupplier) throws X; + @Nullable T getOrElseThrow(Class type, Supplier exceptionSupplier) + throws X; /** * Return if a registration exists for the given type. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java b/core/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java index 35f49ad3f0d..30c5c5f8f46 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/BootstrapRegistry.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.core.env.Environment; @@ -78,7 +80,7 @@ public interface BootstrapRegistry { * @param type the instance type * @return the registered {@link InstanceSupplier} or {@code null} */ - InstanceSupplier getRegisteredInstanceSupplier(Class type); + @Nullable InstanceSupplier getRegisteredInstanceSupplier(Class type); /** * Add an {@link ApplicationListener} that will be called with a @@ -101,9 +103,9 @@ public interface BootstrapRegistry { * Factory method used to create the instance when needed. * @param context the {@link BootstrapContext} which may be used to obtain other * bootstrap instances. - * @return the instance + * @return the instance or {@code null} */ - T get(BootstrapContext context); + @Nullable T get(BootstrapContext context); /** * Return the scope of the supplied instance. @@ -126,7 +128,7 @@ public interface BootstrapRegistry { return new InstanceSupplier<>() { @Override - public T get(BootstrapContext context) { + public @Nullable T get(BootstrapContext context) { return parent.get(context); } @@ -145,7 +147,7 @@ public interface BootstrapRegistry { * @param instance the instance * @return a new {@link InstanceSupplier} */ - static InstanceSupplier of(T instance) { + static InstanceSupplier of(@Nullable T instance) { return (registry) -> instance; } @@ -156,7 +158,7 @@ public interface BootstrapRegistry { * @param supplier the supplier that will provide the instance * @return a new {@link InstanceSupplier} */ - static InstanceSupplier from(Supplier supplier) { + static InstanceSupplier from(@Nullable Supplier supplier) { return (registry) -> (supplier != null) ? supplier.get() : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ClearCachesApplicationListener.java b/core/spring-boot/src/main/java/org/springframework/boot/ClearCachesApplicationListener.java index 5740b043256..9ef9e06073d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ClearCachesApplicationListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ClearCachesApplicationListener.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.lang.reflect.Method; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.util.ReflectionUtils; @@ -35,7 +37,7 @@ class ClearCachesApplicationListener implements ApplicationListener getOptionValues(String name) { + public @Nullable List getOptionValues(String name) { List values = this.source.getOptionValues(name); return (values != null) ? Collections.unmodifiableList(values) : null; } @@ -82,7 +84,7 @@ public class DefaultApplicationArguments implements ApplicationArguments { } @Override - public List getOptionValues(String name) { + public @Nullable List getOptionValues(String name) { return super.getOptionValues(name); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java index 5bfc15362fc..4e9f24f5ab4 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/DefaultApplicationContextFactory.java @@ -19,12 +19,15 @@ package org.springframework.boot; import java.util.function.BiFunction; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.AotDetector; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.lang.Contract; /** * Default {@link ApplicationContextFactory} implementation that will create an @@ -34,18 +37,25 @@ import org.springframework.core.io.support.SpringFactoriesLoader; */ class DefaultApplicationContextFactory implements ApplicationContextFactory { + // Method reference is not detected with correct nullability + @SuppressWarnings("NullAway") @Override - public Class getEnvironmentType(WebApplicationType webApplicationType) { + public @Nullable Class getEnvironmentType( + @Nullable WebApplicationType webApplicationType) { return getFromSpringFactories(webApplicationType, ApplicationContextFactory::getEnvironmentType, null); } + // Method reference is not detected with correct nullability + @SuppressWarnings("NullAway") @Override - public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) { + public @Nullable ConfigurableEnvironment createEnvironment(@Nullable WebApplicationType webApplicationType) { return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null); } + // Method reference is not detected with correct nullability + @SuppressWarnings("NullAway") @Override - public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { + public ConfigurableApplicationContext create(@Nullable WebApplicationType webApplicationType) { try { return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, this::createDefaultApplicationContext); @@ -63,8 +73,10 @@ class DefaultApplicationContextFactory implements ApplicationContextFactory { return new GenericApplicationContext(); } - private T getFromSpringFactories(WebApplicationType webApplicationType, - BiFunction action, Supplier defaultResult) { + @Contract("_, _, !null -> !null") + private @Nullable T getFromSpringFactories(@Nullable WebApplicationType webApplicationType, + BiFunction action, + @Nullable Supplier defaultResult) { for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) { T result = action.apply(candidate, webApplicationType); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java b/core/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java index 05faf209a40..063aff688b4 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/DefaultBootstrapContext.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; @@ -72,7 +74,7 @@ public class DefaultBootstrapContext implements ConfigurableBootstrapContext { @Override @SuppressWarnings("unchecked") - public InstanceSupplier getRegisteredInstanceSupplier(Class type) { + public @Nullable InstanceSupplier getRegisteredInstanceSupplier(Class type) { synchronized (this.instanceSuppliers) { return (InstanceSupplier) this.instanceSuppliers.get(type); } @@ -84,17 +86,18 @@ public class DefaultBootstrapContext implements ConfigurableBootstrapContext { } @Override - public T get(Class type) throws IllegalStateException { + public @Nullable T get(Class type) throws IllegalStateException { return getOrElseThrow(type, () -> new IllegalStateException(type.getName() + " has not been registered")); } @Override - public T getOrElse(Class type, T other) { + @SuppressWarnings("NullAway") // Doesn't detect lambda with correct nullability + public @Nullable T getOrElse(Class type, @Nullable T other) { return getOrElseSupply(type, () -> other); } @Override - public T getOrElseSupply(Class type, Supplier other) { + public @Nullable T getOrElseSupply(Class type, Supplier<@Nullable T> other) { synchronized (this.instanceSuppliers) { InstanceSupplier instanceSupplier = this.instanceSuppliers.get(type); return (instanceSupplier != null) ? getInstance(type, instanceSupplier) : other.get(); @@ -102,7 +105,8 @@ public class DefaultBootstrapContext implements ConfigurableBootstrapContext { } @Override - public T getOrElseThrow(Class type, Supplier exceptionSupplier) throws X { + public @Nullable T getOrElseThrow(Class type, Supplier exceptionSupplier) + throws X { synchronized (this.instanceSuppliers) { InstanceSupplier instanceSupplier = this.instanceSuppliers.get(type); if (instanceSupplier == null) { @@ -113,10 +117,13 @@ public class DefaultBootstrapContext implements ConfigurableBootstrapContext { } @SuppressWarnings("unchecked") - private T getInstance(Class type, InstanceSupplier instanceSupplier) { + private @Nullable T getInstance(Class type, InstanceSupplier instanceSupplier) { T instance = (T) this.instances.get(type); if (instance == null) { instance = (T) instanceSupplier.get(this); + if (instance == null) { + return null; + } if (instanceSupplier.getScope() == Scope.SINGLETON) { this.instances.put(type, instance); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/DefaultPropertiesPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/DefaultPropertiesPropertySource.java index 7e1eef02fea..ee19df5bfa5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/DefaultPropertiesPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/DefaultPropertiesPropertySource.java @@ -20,6 +20,8 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; @@ -56,7 +58,7 @@ public class DefaultPropertiesPropertySource extends MapPropertySource { * @param propertySource the property source to check * @return {@code true} if the name matches */ - public static boolean hasMatchingName(PropertySource propertySource) { + public static boolean hasMatchingName(@Nullable PropertySource propertySource) { return (propertySource != null) && propertySource.getName().equals(NAME); } @@ -67,7 +69,8 @@ public class DefaultPropertiesPropertySource extends MapPropertySource { * @param action the action used to consume the * {@link DefaultPropertiesPropertySource} */ - public static void ifNotEmpty(Map source, Consumer action) { + public static void ifNotEmpty(Map source, + @Nullable Consumer action) { if (!CollectionUtils.isEmpty(source) && action != null) { action.accept(new DefaultPropertiesPropertySource(source)); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java index a982c5fb579..6e6df421d1e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java @@ -19,6 +19,8 @@ package org.springframework.boot; import java.util.ArrayList; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -84,7 +86,7 @@ public final class LazyInitializationBeanFactoryPostProcessor implements BeanFac } } - private Class getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) { + private @Nullable Class getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) { try { return beanFactory.getType(beanName, false); } @@ -94,7 +96,7 @@ public final class LazyInitializationBeanFactoryPostProcessor implements BeanFac } private boolean isExcluded(Collection filters, String beanName, - AbstractBeanDefinition beanDefinition, Class beanType) { + AbstractBeanDefinition beanDefinition, @Nullable Class beanType) { if (beanType != null) { for (LazyInitializationExcludeFilter filter : filters) { if (filter.isExcluded(beanName, beanDefinition, beanType)) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java b/core/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java index 8e6f1a5ee35..f5ae815b4ee 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ResourceBanner.java @@ -28,6 +28,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.ansi.AnsiPropertySource; import org.springframework.core.env.ConfigurableEnvironment; @@ -64,7 +65,7 @@ public class ResourceBanner implements Banner { } @Override - public void printBanner(Environment environment, Class sourceClass, PrintStream out) { + public void printBanner(Environment environment, @Nullable Class sourceClass, PrintStream out) { try (InputStream input = this.resource.getInputStream()) { String banner = StreamUtils.copyToString(input, environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8)); @@ -86,32 +87,32 @@ public class ResourceBanner implements Banner { * @param sourceClass the source class * @return a mutable list of property resolvers */ - protected List getPropertyResolvers(Environment environment, Class sourceClass) { + protected List getPropertyResolvers(Environment environment, @Nullable Class sourceClass) { List resolvers = new ArrayList<>(); resolvers.add(new PropertySourcesPropertyResolver(createNullDefaultSources(environment, sourceClass))); resolvers.add(new PropertySourcesPropertyResolver(createEmptyDefaultSources(environment, sourceClass))); return resolvers; } - private MutablePropertySources createNullDefaultSources(Environment environment, Class sourceClass) { + private MutablePropertySources createNullDefaultSources(Environment environment, @Nullable Class sourceClass) { MutablePropertySources nullDefaultSources = new MutablePropertySources(); if (environment instanceof ConfigurableEnvironment configurableEnvironment) { configurableEnvironment.getPropertySources().forEach(nullDefaultSources::addLast); } nullDefaultSources.addLast(getTitleSource(sourceClass, null)); nullDefaultSources.addLast(getAnsiSource()); - nullDefaultSources.addLast(getVersionSource(sourceClass, environment, null)); + nullDefaultSources.addLast(getVersionSource(environment, null)); return nullDefaultSources; } - private MutablePropertySources createEmptyDefaultSources(Environment environment, Class sourceClass) { + private MutablePropertySources createEmptyDefaultSources(Environment environment, @Nullable Class sourceClass) { MutablePropertySources emptyDefaultSources = new MutablePropertySources(); emptyDefaultSources.addLast(getTitleSource(sourceClass, "")); - emptyDefaultSources.addLast(getVersionSource(sourceClass, environment, "")); + emptyDefaultSources.addLast(getVersionSource(environment, "")); return emptyDefaultSources; } - private MapPropertySource getTitleSource(Class sourceClass, String defaultValue) { + private MapPropertySource getTitleSource(@Nullable Class sourceClass, @Nullable String defaultValue) { String applicationTitle = getApplicationTitle(sourceClass); Map titleMap = Collections.singletonMap("application.title", (applicationTitle != null) ? applicationTitle : defaultValue); @@ -124,7 +125,7 @@ public class ResourceBanner implements Banner { * @param sourceClass the source class * @return the application title */ - protected String getApplicationTitle(Class sourceClass) { + protected @Nullable String getApplicationTitle(@Nullable Class sourceClass) { Package sourcePackage = (sourceClass != null) ? sourceClass.getPackage() : null; return (sourcePackage != null) ? sourcePackage.getImplementationTitle() : null; } @@ -133,11 +134,11 @@ public class ResourceBanner implements Banner { return new AnsiPropertySource("ansi", true); } - private MapPropertySource getVersionSource(Class sourceClass, Environment environment, String defaultValue) { - return new MapPropertySource("version", getVersionsMap(sourceClass, environment, defaultValue)); + private MapPropertySource getVersionSource(Environment environment, @Nullable String defaultValue) { + return new MapPropertySource("version", getVersionsMap(environment, defaultValue)); } - private Map getVersionsMap(Class sourceClass, Environment environment, String defaultValue) { + private Map getVersionsMap(Environment environment, @Nullable String defaultValue) { String appVersion = getApplicationVersion(environment); String bootVersion = getBootVersion(); Map versions = new HashMap<>(); @@ -148,7 +149,7 @@ public class ResourceBanner implements Banner { return versions; } - private String getApplicationVersion(Environment environment) { + private @Nullable String getApplicationVersion(Environment environment) { return environment.getProperty("spring.application.version"); } @@ -156,7 +157,7 @@ public class ResourceBanner implements Banner { return SpringBootVersion.getVersion(); } - private String getVersionString(String version, boolean format, String fallback) { + private @Nullable String getVersionString(@Nullable String version, boolean format, @Nullable String fallback) { if (version == null) { return fallback; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 51ece877060..0e3078fe32f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -41,6 +41,7 @@ import java.util.stream.Stream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.crac.management.CRaCMXBean; +import org.jspecify.annotations.Nullable; import org.springframework.aot.AotDetector; import org.springframework.beans.BeansException; @@ -204,27 +205,27 @@ public class SpringApplication { private final Set> primarySources; - private Class mainApplicationClass; + private @Nullable Class mainApplicationClass; private boolean addCommandLineProperties = true; private boolean addConversionService = true; - private Banner banner; + private @Nullable Banner banner; - private ResourceLoader resourceLoader; + private @Nullable ResourceLoader resourceLoader; - private BeanNameGenerator beanNameGenerator; + private @Nullable BeanNameGenerator beanNameGenerator; - private ConfigurableEnvironment environment; + private @Nullable ConfigurableEnvironment environment; private boolean headless = true; - private List> initializers; + private List> initializers = new ArrayList<>(); - private List> listeners; + private List> listeners = new ArrayList<>(); - private Map defaultProperties; + private @Nullable Map defaultProperties; private final List bootstrapRegistryInitializers; @@ -232,7 +233,7 @@ public class SpringApplication { private boolean isCustomEnvironment; - private String environmentPrefix; + private @Nullable String environmentPrefix; private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT; @@ -265,7 +266,7 @@ public class SpringApplication { * @see #setSources(Set) */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { + public SpringApplication(@Nullable ResourceLoader resourceLoader, Class... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "'primarySources' must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); @@ -277,7 +278,7 @@ public class SpringApplication { this.mainApplicationClass = deduceMainApplicationClass(); } - private Class deduceMainApplicationClass() { + private @Nullable Class deduceMainApplicationClass() { return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) .walk(this::findMainClass) .orElse(null); @@ -314,11 +315,11 @@ public class SpringApplication { prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); - startup.started(); + Duration timeTakenToStarted = startup.started(); if (this.properties.isLogStartupInfo()) { new StartupInfoLogger(this.mainApplicationClass, environment).logStarted(getApplicationLog(), startup); } - listeners.started(context, startup.timeTakenToStarted()); + listeners.started(context, timeTakenToStarted); callRunners(context, applicationArguments); } catch (Throwable ex) { @@ -373,7 +374,7 @@ public class SpringApplication { private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, - ApplicationArguments applicationArguments, Banner printedBanner) { + ApplicationArguments applicationArguments, @Nullable Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); addAotGeneratedInitializerIfNecessary(this.initializers); @@ -417,6 +418,7 @@ public class SpringApplication { List> aotInitializers = new ArrayList<>( initializers.stream().filter(AotApplicationContextInitializer.class::isInstance).toList()); if (aotInitializers.isEmpty()) { + Assert.state(this.mainApplicationClass != null, "No application main class found"); String initializerClassName = this.mainApplicationClass.getName() + "__ApplicationContextInitializer"; if (!ClassUtils.isPresent(initializerClassName, getClassLoader())) { throw new AotInitializerNotFoundException(this.mainApplicationClass, initializerClassName); @@ -458,7 +460,7 @@ public class SpringApplication { return getSpringFactoriesInstances(type, null); } - private List getSpringFactoriesInstances(Class type, ArgumentResolver argumentResolver) { + private List getSpringFactoriesInstances(Class type, @Nullable ArgumentResolver argumentResolver) { return SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver); } @@ -507,8 +509,8 @@ public class SpringApplication { } if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; - if (sources.contains(name)) { - PropertySource source = sources.get(name); + PropertySource source = sources.get(name); + if (source != null) { CompositePropertySource composite = new CompositePropertySource(name); composite .addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); @@ -546,7 +548,7 @@ public class SpringApplication { } } - private Banner printBanner(ConfigurableEnvironment environment) { + private @Nullable Banner printBanner(ConfigurableEnvironment environment) { if (this.properties.getBannerMode(environment) == Banner.Mode.OFF) { return null; } @@ -604,6 +606,8 @@ public class SpringApplication { for (ApplicationContextInitializer initializer : getInitializers()) { Class requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); + Assert.state(requiredType != null, + () -> "No generic type found for initializr of type " + initializer.getClass()); Assert.state(requiredType.isInstance(context), "Unable to call initializer"); initializer.initialize(context); } @@ -685,9 +689,9 @@ public class SpringApplication { /** * The ResourceLoader that will be used in the ApplicationContext. * @return the resourceLoader the resource loader that will be used in the - * ApplicationContext (or null if the default) + * ApplicationContext (or {@code null} if the default) */ - public ResourceLoader getResourceLoader() { + public @Nullable ResourceLoader getResourceLoader() { return this.resourceLoader; } @@ -699,9 +703,13 @@ public class SpringApplication { */ public ClassLoader getClassLoader() { if (this.resourceLoader != null) { - return this.resourceLoader.getClassLoader(); + ClassLoader classLoader = this.resourceLoader.getClassLoader(); + Assert.state(classLoader != null, "No classloader found"); + return classLoader; } - return ClassUtils.getDefaultClassLoader(); + ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + Assert.state(classLoader != null, "No classloader found"); + return classLoader; } /** @@ -781,8 +789,8 @@ public class SpringApplication { .accept((R) runner); } - private RuntimeException handleRunFailure(ConfigurableApplicationContext context, Throwable exception, - SpringApplicationRunListeners listeners) { + private RuntimeException handleRunFailure(@Nullable ConfigurableApplicationContext context, Throwable exception, + @Nullable SpringApplicationRunListeners listeners) { if (exception instanceof AbandonedRunException abandonedRunException) { return abandonedRunException; } @@ -808,9 +816,11 @@ public class SpringApplication { : new IllegalStateException(exception); } - private Collection getExceptionReporters(ConfigurableApplicationContext context) { + private Collection getExceptionReporters( + @Nullable ConfigurableApplicationContext context) { try { - ArgumentResolver argumentResolver = ArgumentResolver.of(ConfigurableApplicationContext.class, context); + ArgumentResolver argumentResolver = (context != null) + ? ArgumentResolver.of(ConfigurableApplicationContext.class, context) : ArgumentResolver.none(); return getSpringFactoriesInstances(SpringBootExceptionReporter.class, argumentResolver); } catch (Throwable ex) { @@ -857,7 +867,7 @@ public class SpringApplication { } } - private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) { + private void handleExitCode(@Nullable ConfigurableApplicationContext context, Throwable exception) { int exitCode = getExitCodeFromException(context, exception); if (exitCode != 0) { if (context != null) { @@ -870,7 +880,7 @@ public class SpringApplication { } } - private int getExitCodeFromException(ConfigurableApplicationContext context, Throwable exception) { + private int getExitCodeFromException(@Nullable ConfigurableApplicationContext context, Throwable exception) { int exitCode = getExitCodeFromMappedException(context, exception); if (exitCode == 0) { exitCode = getExitCodeFromExitCodeGeneratorException(exception); @@ -878,7 +888,7 @@ public class SpringApplication { return exitCode; } - private int getExitCodeFromMappedException(ConfigurableApplicationContext context, Throwable exception) { + private int getExitCodeFromMappedException(@Nullable ConfigurableApplicationContext context, Throwable exception) { if (context == null || !context.isActive()) { return 0; } @@ -888,7 +898,7 @@ public class SpringApplication { return generators.getExitCode(); } - private int getExitCodeFromExitCodeGeneratorException(Throwable exception) { + private int getExitCodeFromExitCodeGeneratorException(@Nullable Throwable exception) { if (exception == null) { return 0; } @@ -898,7 +908,7 @@ public class SpringApplication { return getExitCodeFromExitCodeGeneratorException(exception.getCause()); } - SpringBootExceptionHandler getSpringBootExceptionHandler() { + @Nullable SpringBootExceptionHandler getSpringBootExceptionHandler() { if (isMainThread(Thread.currentThread())) { return SpringBootExceptionHandler.forCurrentThread(); } @@ -914,7 +924,7 @@ public class SpringApplication { * Returns the main application class that has been deduced or explicitly configured. * @return the main application class or {@code null} */ - public Class getMainApplicationClass() { + public @Nullable Class getMainApplicationClass() { return this.mainApplicationClass; } @@ -924,7 +934,7 @@ public class SpringApplication { * Can be set to {@code null} if there is no explicit application class. * @param mainApplicationClass the mainApplicationClass to set or {@code null} */ - public void setMainApplicationClass(Class mainApplicationClass) { + public void setMainApplicationClass(@Nullable Class mainApplicationClass) { this.mainApplicationClass = mainApplicationClass; } @@ -933,7 +943,7 @@ public class SpringApplication { * @return the type of web application * @since 2.0.0 */ - public WebApplicationType getWebApplicationType() { + public @Nullable WebApplicationType getWebApplicationType() { return this.properties.getWebApplicationType(); } @@ -1107,7 +1117,7 @@ public class SpringApplication { * context. * @param environment the environment */ - public void setEnvironment(ConfigurableEnvironment environment) { + public void setEnvironment(@Nullable ConfigurableEnvironment environment) { this.isCustomEnvironment = true; this.environment = environment; } @@ -1191,7 +1201,7 @@ public class SpringApplication { * @return the environment property prefix * @since 2.5.0 */ - public String getEnvironmentPrefix() { + public @Nullable String getEnvironmentPrefix() { return this.environmentPrefix; } @@ -1213,7 +1223,7 @@ public class SpringApplication { * @param applicationContextFactory the factory for the context * @since 2.4.0 */ - public void setApplicationContextFactory(ApplicationContextFactory applicationContextFactory) { + public void setApplicationContextFactory(@Nullable ApplicationContextFactory applicationContextFactory) { this.applicationContextFactory = (applicationContextFactory != null) ? applicationContextFactory : ApplicationContextFactory.DEFAULT; } @@ -1424,7 +1434,7 @@ public class SpringApplication { public static void withHook(SpringApplicationHook hook, Runnable action) { withHook(hook, () -> { action.run(); - return null; + return Void.class; }); } @@ -1598,7 +1608,7 @@ public class SpringApplication { */ public static class AbandonedRunException extends RuntimeException { - private final ConfigurableApplicationContext applicationContext; + private final @Nullable ConfigurableApplicationContext applicationContext; /** * Create a new {@link AbandonedRunException} instance. @@ -1613,7 +1623,7 @@ public class SpringApplication { * @param applicationContext the application context that was available when the * run was abandoned */ - public AbandonedRunException(ConfigurableApplicationContext applicationContext) { + public AbandonedRunException(@Nullable ConfigurableApplicationContext applicationContext) { this.applicationContext = applicationContext; } @@ -1622,7 +1632,7 @@ public class SpringApplication { * {@code null} if no context was available. * @return the application context */ - public ConfigurableApplicationContext getApplicationContext() { + public @Nullable ConfigurableApplicationContext getApplicationContext() { return this.applicationContext; } @@ -1642,7 +1652,7 @@ public class SpringApplication { } @Override - public SpringApplicationRunListener getRunListener(SpringApplication springApplication) { + public @Nullable SpringApplicationRunListener getRunListener(SpringApplication springApplication) { return this.used.compareAndSet(false, true) ? this.delegate.getRunListener(springApplication) : null; } @@ -1699,11 +1709,11 @@ public class SpringApplication { */ abstract static class Startup { - private Duration timeTakenToStarted; + private @Nullable Duration timeTakenToStarted; protected abstract long startTime(); - protected abstract Long processUptime(); + protected abstract @Nullable Long processUptime(); protected abstract String action(); @@ -1714,6 +1724,8 @@ public class SpringApplication { } Duration timeTakenToStarted() { + Assert.state(this.timeTakenToStarted != null, + "timeTakenToStarted is not set. Make sure to call started() before this method"); return this.timeTakenToStarted; } @@ -1744,7 +1756,7 @@ public class SpringApplication { } @Override - protected Long processUptime() { + protected @Nullable Long processUptime() { try { return ManagementFactory.getRuntimeMXBean().getUptime(); } @@ -1794,7 +1806,7 @@ public class SpringApplication { * {@link OrderSourceProvider} used to obtain factory method and target type order * sources. Based on internal {@link DefaultListableBeanFactory} code. */ - private class FactoryAwareOrderSourceProvider implements OrderSourceProvider { + private static class FactoryAwareOrderSourceProvider implements OrderSourceProvider { private final ConfigurableBeanFactory beanFactory; @@ -1806,12 +1818,12 @@ public class SpringApplication { } @Override - public Object getOrderSource(Object obj) { + public @Nullable Object getOrderSource(Object obj) { String beanName = this.instancesToBeanNames.get(obj); return (beanName != null) ? getOrderSource(beanName, obj.getClass()) : null; } - private Object getOrderSource(String beanName, Class instanceType) { + private @Nullable Object getOrderSource(String beanName, Class instanceType) { try { RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory .getMergedBeanDefinition(beanName); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java index d5b61138daf..13bdaf91e8e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java @@ -59,7 +59,8 @@ public class SpringApplicationAotProcessor extends ContextAotProcessor { protected GenericApplicationContext prepareApplicationContext(Class application) { return new AotProcessorHook(application).run(() -> { Method mainMethod = application.getMethod("main", String[].class); - return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs }); + ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs }); + return Void.class; }); } @@ -112,7 +113,7 @@ public class SpringApplicationAotProcessor extends ContextAotProcessor { ApplicationContext context = ex.getApplicationContext(); Assert.state(context instanceof GenericApplicationContext, () -> "AOT processing requires a GenericApplicationContext but got a " - + context.getClass().getName()); + + ((context != null) ? context.getClass().getName() : "null")); return (GenericApplicationContext) context; } throw new IllegalStateException( diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java index 95955234c39..4dd57a91576 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationBannerPrinter.java @@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -45,14 +46,14 @@ class SpringApplicationBannerPrinter { private final ResourceLoader resourceLoader; - private final Banner fallbackBanner; + private final @Nullable Banner fallbackBanner; - SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) { + SpringApplicationBannerPrinter(ResourceLoader resourceLoader, @Nullable Banner fallbackBanner) { this.resourceLoader = resourceLoader; this.fallbackBanner = fallbackBanner; } - Banner print(Environment environment, Class sourceClass, Log logger) { + Banner print(Environment environment, @Nullable Class sourceClass, Log logger) { Banner banner = getBanner(environment); try { logger.info(createStringFromBanner(banner, environment, sourceClass)); @@ -63,7 +64,7 @@ class SpringApplicationBannerPrinter { return new PrintedBanner(banner, sourceClass); } - Banner print(Environment environment, Class sourceClass, PrintStream out) { + Banner print(Environment environment, @Nullable Class sourceClass, PrintStream out) { Banner banner = getBanner(environment); banner.printBanner(environment, sourceClass, out); return new PrintedBanner(banner, sourceClass); @@ -80,7 +81,7 @@ class SpringApplicationBannerPrinter { return DEFAULT_BANNER; } - private Banner getTextBanner(Environment environment) { + private @Nullable Banner getTextBanner(Environment environment) { String location = environment.getProperty(BANNER_LOCATION_PROPERTY, DEFAULT_BANNER_LOCATION); Resource resource = this.resourceLoader.getResource(location); try { @@ -94,8 +95,8 @@ class SpringApplicationBannerPrinter { return null; } - private String createStringFromBanner(Banner banner, Environment environment, Class mainApplicationClass) - throws UnsupportedEncodingException { + private String createStringFromBanner(Banner banner, Environment environment, + @Nullable Class mainApplicationClass) throws UnsupportedEncodingException { String charset = environment.getProperty("spring.banner.charset", StandardCharsets.UTF_8.name()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); try (PrintStream out = new PrintStream(byteArrayOutputStream, false, charset)) { @@ -112,15 +113,15 @@ class SpringApplicationBannerPrinter { private final Banner banner; - private final Class sourceClass; + private final @Nullable Class sourceClass; - PrintedBanner(Banner banner, Class sourceClass) { + PrintedBanner(Banner banner, @Nullable Class sourceClass) { this.banner = banner; this.sourceClass = sourceClass; } @Override - public void printBanner(Environment environment, Class sourceClass, PrintStream out) { + public void printBanner(Environment environment, @Nullable Class sourceClass, PrintStream out) { sourceClass = (sourceClass != null) ? sourceClass : this.sourceClass; this.banner.printBanner(environment, sourceClass, out); } @@ -130,7 +131,7 @@ class SpringApplicationBannerPrinter { static class SpringApplicationBannerPrinterRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources().registerPattern(DEFAULT_BANNER_LOCATION); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationHook.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationHook.java index 33729b8ad33..32dd1c07c40 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationHook.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationHook.java @@ -16,6 +16,8 @@ package org.springframework.boot; +import org.jspecify.annotations.Nullable; + /** * Low-level hook that can be used to attach a {@link SpringApplicationRunListener} to a * {@link SpringApplication} in order to observe or modify its behavior. Hooks are managed @@ -34,8 +36,8 @@ public interface SpringApplicationHook { * Return the {@link SpringApplicationRunListener} that should be hooked into the * given {@link SpringApplication}. * @param springApplication the source {@link SpringApplication} instance - * @return the {@link SpringApplicationRunListener} to attach + * @return the {@link SpringApplicationRunListener} to attach or {@code null} */ - SpringApplicationRunListener getRunListener(SpringApplication springApplication); + @Nullable SpringApplicationRunListener getRunListener(SpringApplication springApplication); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java index d34872043a1..e1bb3047f29 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.time.Duration; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; @@ -80,7 +82,7 @@ public interface SpringApplicationRunListener { * @param timeTaken the time taken to start the application or {@code null} if unknown * @since 2.6.0 */ - default void started(ConfigurableApplicationContext context, Duration timeTaken) { + default void started(ConfigurableApplicationContext context, @Nullable Duration timeTaken) { } /** @@ -92,7 +94,7 @@ public interface SpringApplicationRunListener { * unknown * @since 2.6.0 */ - default void ready(ConfigurableApplicationContext context, Duration timeTaken) { + default void ready(ConfigurableApplicationContext context, @Nullable Duration timeTaken) { } /** @@ -102,7 +104,7 @@ public interface SpringApplicationRunListener { * @param exception the failure * @since 2.0.0 */ - default void failed(ConfigurableApplicationContext context, Throwable exception) { + default void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) { } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java index 92d4cbee548..0af7cb6b1c9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListeners.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.function.Consumer; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; @@ -50,7 +51,7 @@ class SpringApplicationRunListeners { this.applicationStartup = applicationStartup; } - void starting(ConfigurableBootstrapContext bootstrapContext, Class mainApplicationClass) { + void starting(ConfigurableBootstrapContext bootstrapContext, @Nullable Class mainApplicationClass) { doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext), (step) -> { if (mainApplicationClass != null) { @@ -80,16 +81,19 @@ class SpringApplicationRunListeners { doWithListeners("spring.boot.application.ready", (listener) -> listener.ready(context, timeTaken)); } - void failed(ConfigurableApplicationContext context, Throwable exception) { + void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) { doWithListeners("spring.boot.application.failed", (listener) -> callFailedListener(listener, context, exception), (step) -> { step.tag("exception", exception.getClass().toString()); - step.tag("message", exception.getMessage()); + String message = exception.getMessage(); + if (message != null) { + step.tag("message", message); + } }); } - private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context, - Throwable exception) { + private void callFailedListener(SpringApplicationRunListener listener, + @Nullable ConfigurableApplicationContext context, Throwable exception) { try { listener.failed(context, exception); } @@ -113,7 +117,7 @@ class SpringApplicationRunListeners { } private void doWithListeners(String stepName, Consumer listenerAction, - Consumer stepAction) { + @Nullable Consumer stepAction) { StartupStep step = this.applicationStartup.start(stepName); this.listeners.forEach(listenerAction); if (stepAction != null) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringBootBanner.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringBootBanner.java index 0627f9cf975..5835eadbdba 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringBootBanner.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringBootBanner.java @@ -18,6 +18,8 @@ package org.springframework.boot; import java.io.PrintStream; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.ansi.AnsiColor; import org.springframework.boot.ansi.AnsiOutput; import org.springframework.boot.ansi.AnsiStyle; @@ -44,7 +46,7 @@ class SpringBootBanner implements Banner { private static final int STRAP_LINE_SIZE = 42; @Override - public void printBanner(Environment environment, Class sourceClass, PrintStream printStream) { + public void printBanner(Environment environment, @Nullable Class sourceClass, PrintStream printStream) { printStream.println(); printStream.println(BANNER); String version = String.format(" (v%s)", SpringBootVersion.getVersion()); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/SpringBootExceptionHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/SpringBootExceptionHandler.java index 2e870065b53..06e579ad269 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/SpringBootExceptionHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/SpringBootExceptionHandler.java @@ -24,6 +24,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + /** * {@link UncaughtExceptionHandler} to suppress handling already logged exceptions and * dealing with system exit. @@ -42,13 +44,13 @@ class SpringBootExceptionHandler implements UncaughtExceptionHandler { private static final LoggedExceptionHandlerThreadLocal handler = new LoggedExceptionHandlerThreadLocal(); - private final UncaughtExceptionHandler parent; + private final @Nullable UncaughtExceptionHandler parent; private final List loggedExceptions = new ArrayList<>(); private int exitCode = 0; - SpringBootExceptionHandler(UncaughtExceptionHandler parent) { + SpringBootExceptionHandler(@Nullable UncaughtExceptionHandler parent) { this.parent = parent; } @@ -85,7 +87,10 @@ class SpringBootExceptionHandler implements UncaughtExceptionHandler { * @param ex the source exception * @return {@code true} if the exception contains a log configuration message */ - private boolean isLogConfigurationMessage(Throwable ex) { + private boolean isLogConfigurationMessage(@Nullable Throwable ex) { + if (ex == null) { + return false; + } if (ex instanceof InvocationTargetException) { return isLogConfigurationMessage(ex.getCause()); } @@ -100,7 +105,10 @@ class SpringBootExceptionHandler implements UncaughtExceptionHandler { return false; } - private boolean isRegistered(Throwable ex) { + private boolean isRegistered(@Nullable Throwable ex) { + if (ex == null) { + return false; + } if (this.loggedExceptions.contains(ex)) { return true; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/StartupInfoLogger.java b/core/spring-boot/src/main/java/org/springframework/boot/StartupInfoLogger.java index 18b87c361cb..13bcf556eb2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/StartupInfoLogger.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/StartupInfoLogger.java @@ -19,6 +19,7 @@ package org.springframework.boot; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.aot.AotDetector; import org.springframework.boot.SpringApplication.Startup; @@ -39,11 +40,11 @@ import org.springframework.util.StringUtils; */ class StartupInfoLogger { - private final Class sourceClass; + private final @Nullable Class sourceClass; private final Environment environment; - StartupInfoLogger(Class sourceClass, Environment environment) { + StartupInfoLogger(@Nullable Class sourceClass, Environment environment) { this.sourceClass = sourceClass; this.environment = environment; } @@ -153,7 +154,7 @@ class StartupInfoLogger { } } - private Object callIfPossible(Callable call) { + private @Nullable Object callIfPossible(Callable call) { try { return call.call(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java b/core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java index d814d683036..22854e9c1a1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/WebApplicationType.java @@ -16,6 +16,8 @@ package org.springframework.boot; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; @@ -73,7 +75,7 @@ public enum WebApplicationType { static class WebApplicationTypeRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { for (String servletIndicatorClass : SERVLET_INDICATOR_CLASSES) { registerTypeIfPresent(servletIndicatorClass, classLoader, hints); } @@ -82,7 +84,7 @@ public enum WebApplicationType { registerTypeIfPresent(WEBMVC_INDICATOR_CLASS, classLoader, hints); } - private void registerTypeIfPresent(String typeName, ClassLoader classLoader, RuntimeHints hints) { + private void registerTypeIfPresent(String typeName, @Nullable ClassLoader classLoader, RuntimeHints hints) { if (ClassUtils.isPresent(typeName, classLoader)) { hints.reflection().registerType(TypeReference.of(typeName)); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java b/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java index 0fa8f1174ff..9b2d5a5706e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBean.java @@ -16,6 +16,8 @@ package org.springframework.boot.admin; +import org.jspecify.annotations.Nullable; + /** * An MBean contract to control and monitor a running {@code SpringApplication} over JMX. * Intended for internal use only. @@ -47,7 +49,7 @@ public interface SpringApplicationAdminMXBean { * @param key the property key * @return the property value or {@code null} if it does not exist */ - String getProperty(String key); + @Nullable String getProperty(String key); /** * Shutdown the application. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java b/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java index 4ddb68238d9..a2d0d112fc7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/admin/SpringApplicationAdminMXBeanRegistrar.java @@ -24,6 +24,7 @@ import javax.management.ObjectName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; @@ -55,6 +56,7 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext private static final Log logger = LogFactory.getLog(SpringApplicationAdmin.class); + @SuppressWarnings("NullAway.Init") private ConfigurableApplicationContext applicationContext; private Environment environment = new StandardEnvironment(); @@ -89,7 +91,7 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext } @Override - public boolean supportsSourceType(Class sourceType) { + public boolean supportsSourceType(@Nullable Class sourceType) { return true; } @@ -139,7 +141,7 @@ public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContext } @Override - public String getProperty(String key) { + public @Nullable String getProperty(String key) { return SpringApplicationAdminMXBeanRegistrar.this.environment.getProperty(key); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/admin/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/admin/package-info.java index 43272fdad70..23bc7ee75de 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/admin/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/admin/package-info.java @@ -17,4 +17,7 @@ /** * Administration support for Spring Boot applications. */ +@NullMarked package org.springframework.boot.admin; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiOutput.java b/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiOutput.java index d49e5c73e7b..22f7afffd10 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiOutput.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiOutput.java @@ -20,6 +20,8 @@ import java.io.Console; import java.lang.reflect.Method; import java.util.Locale; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -37,9 +39,9 @@ public abstract class AnsiOutput { private static Enabled enabled = Enabled.DETECT; - private static Boolean consoleAvailable; + private static @Nullable Boolean consoleAvailable; - private static Boolean ansiCapable; + private static @Nullable Boolean ansiCapable; private static final String OPERATING_SYSTEM_NAME = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); @@ -71,7 +73,7 @@ public abstract class AnsiOutput { * @param consoleAvailable if the console is known to be available or {@code null} to * use standard detection logic. */ - public static void setConsoleAvailable(Boolean consoleAvailable) { + public static void setConsoleAvailable(@Nullable Boolean consoleAvailable) { AnsiOutput.consoleAvailable = consoleAvailable; } @@ -133,7 +135,7 @@ public abstract class AnsiOutput { } } - private static void buildDisabled(StringBuilder sb, Object[] elements) { + private static void buildDisabled(StringBuilder sb, @Nullable Object[] elements) { for (Object element : elements) { if (!(element instanceof AnsiElement) && element != null) { sb.append(element); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiPropertySource.java index 7a59ef84530..63cc3367740 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ansi/AnsiPropertySource.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Set; import java.util.function.IntFunction; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.util.StringUtils; @@ -73,7 +75,7 @@ public class AnsiPropertySource extends PropertySource { } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { if (StringUtils.hasLength(name)) { for (Mapping mapping : MAPPINGS) { String prefix = mapping.getPrefix(); @@ -104,7 +106,7 @@ public class AnsiPropertySource extends PropertySource { return this.prefix; } - abstract AnsiElement getElement(String postfix); + abstract @Nullable AnsiElement getElement(String postfix); } @@ -121,7 +123,7 @@ public class AnsiPropertySource extends PropertySource { } @Override - AnsiElement getElement(String postfix) { + @Nullable AnsiElement getElement(String postfix) { for (Enum candidate : this.enums) { if (candidate.name().equals(postfix)) { return (AnsiElement) candidate; @@ -145,7 +147,7 @@ public class AnsiPropertySource extends PropertySource { } @Override - AnsiElement getElement(String postfix) { + @Nullable AnsiElement getElement(String postfix) { if (containsOnlyDigits(postfix)) { try { return this.factory.apply(Integer.parseInt(postfix)); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/ansi/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/ansi/package-info.java index c5c130f603f..018e5d04860 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/ansi/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/ansi/package-info.java @@ -19,4 +19,7 @@ * * @see org.springframework.boot.ansi.AnsiOutput */ +@NullMarked package org.springframework.boot.ansi; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailability.java b/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailability.java index e9b882466ca..2bba5fe0c34 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailability.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailability.java @@ -16,6 +16,8 @@ package org.springframework.boot.availability; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; /** @@ -67,7 +69,7 @@ public interface ApplicationAvailability { * published yet * @see #getState(Class, AvailabilityState) */ - S getState(Class stateType); + @Nullable S getState(Class stateType); /** * Return the last {@link AvailabilityChangeEvent} received for a given state type. @@ -76,6 +78,6 @@ public interface ApplicationAvailability { * @return the readiness state or {@code null} if no event of the given type has been * published yet */ - AvailabilityChangeEvent getLastChangeEvent(Class stateType); + @Nullable AvailabilityChangeEvent getLastChangeEvent(Class stateType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java b/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java index 8b687e2b568..e9c93f32af8 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/availability/ApplicationAvailabilityBean.java @@ -21,6 +21,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; @@ -59,14 +60,14 @@ public class ApplicationAvailabilityBean } @Override - public S getState(Class stateType) { + public @Nullable S getState(Class stateType) { AvailabilityChangeEvent event = getLastChangeEvent(stateType); return (event != null) ? event.getState() : null; } @Override @SuppressWarnings("unchecked") - public AvailabilityChangeEvent getLastChangeEvent(Class stateType) { + public @Nullable AvailabilityChangeEvent getLastChangeEvent(Class stateType) { return (AvailabilityChangeEvent) this.events.get(stateType); } @@ -89,7 +90,7 @@ public class ApplicationAvailabilityBean return message; } - private String getSourceDescription(Object source) { + private String getSourceDescription(@Nullable Object source) { if (source == null || source instanceof ApplicationEventPublisher) { return ""; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/availability/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/availability/package-info.java index b6a258cbf63..47bd95e5659 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/availability/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/availability/package-info.java @@ -17,4 +17,7 @@ /** * Support for describing the availability of Spring Boot applications. */ +@NullMarked package org.springframework.boot.availability; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/builder/ParentContextCloserApplicationListener.java b/core/spring-boot/src/main/java/org/springframework/boot/builder/ParentContextCloserApplicationListener.java index 67e26b6d71c..7efdb9b65aa 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/builder/ParentContextCloserApplicationListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/builder/ParentContextCloserApplicationListener.java @@ -40,8 +40,9 @@ import org.springframework.util.ObjectUtils; public class ParentContextCloserApplicationListener implements ApplicationListener, ApplicationContextAware, Ordered { - private final int order = Ordered.LOWEST_PRECEDENCE - 10; + private static final int order = Ordered.LOWEST_PRECEDENCE - 10; + @SuppressWarnings("NullAway.Init") private ApplicationContext context; @Override diff --git a/core/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java b/core/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java index fce39241e77..722364dbbb9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java @@ -27,6 +27,8 @@ import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.boot.ApplicationContextFactory; @@ -44,6 +46,7 @@ import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -76,9 +79,9 @@ public class SpringApplicationBuilder { private final SpringApplication application; - private volatile ConfigurableApplicationContext context; + private volatile @Nullable ConfigurableApplicationContext context; - private SpringApplicationBuilder parent; + private @Nullable SpringApplicationBuilder parent; private final AtomicBoolean running = new AtomicBoolean(); @@ -86,7 +89,7 @@ public class SpringApplicationBuilder { private final Map defaultProperties = new LinkedHashMap<>(); - private ConfigurableEnvironment environment; + private @Nullable ConfigurableEnvironment environment; private Set additionalProfiles = new LinkedHashSet<>(); @@ -98,7 +101,7 @@ public class SpringApplicationBuilder { this(null, sources); } - public SpringApplicationBuilder(ResourceLoader resourceLoader, Class... sources) { + public SpringApplicationBuilder(@Nullable ResourceLoader resourceLoader, Class... sources) { this.application = createSpringApplication(resourceLoader, sources); } @@ -111,15 +114,15 @@ public class SpringApplicationBuilder { * @return the {@link SpringApplication} instance * @since 2.6.0 */ - protected SpringApplication createSpringApplication(ResourceLoader resourceLoader, Class... sources) { + protected SpringApplication createSpringApplication(@Nullable ResourceLoader resourceLoader, Class... sources) { return new SpringApplication(resourceLoader, sources); } /** * Accessor for the current application context. - * @return the current application context (or null if not yet running) + * @return the current application context (or {@code null} if not yet running) */ - public ConfigurableApplicationContext context() { + public @Nullable ConfigurableApplicationContext context() { return this.context; } @@ -140,15 +143,19 @@ public class SpringApplicationBuilder { */ public ConfigurableApplicationContext run(String... args) { if (this.running.get()) { + ConfigurableApplicationContext context = this.context; + Assert.state(context != null, "No context set"); // If already created we just return the existing context - return this.context; + return context; } configureAsChildIfNecessary(args); if (this.running.compareAndSet(false, true)) { // If not already running copy the sources over and then run. this.context = build().run(args); } - return this.context; + ConfigurableApplicationContext context = this.context; + Assert.state(context != null, "No context set"); + return context; } private void configureAsChildIfNecessary(String... args) { @@ -519,10 +526,10 @@ public class SpringApplicationBuilder { /** * Environment for the application context. - * @param environment the environment to set. + * @param environment the environment to set * @return the current builder */ - public SpringApplicationBuilder environment(ConfigurableEnvironment environment) { + public SpringApplicationBuilder environment(@Nullable ConfigurableEnvironment environment) { this.application.setEnvironment(environment); this.environment = environment; return this; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/builder/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/builder/package-info.java index 201e86a8322..753f249d07a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/builder/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/builder/package-info.java @@ -20,4 +20,7 @@ * * @see org.springframework.boot.builder.SpringApplicationBuilder */ +@NullMarked package org.springframework.boot.builder; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java index 449c5a086bf..c89a449cb82 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; @@ -165,13 +166,13 @@ public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPost return properties; } - private void extractPropertiesFromApplication(Properties properties, Map map) { + private void extractPropertiesFromApplication(Properties properties, @Nullable Map map) { if (map != null) { flatten(properties, map, ""); } } - private void extractPropertiesFromServices(Properties properties, Map map) { + private void extractPropertiesFromServices(Properties properties, @Nullable Map map) { if (map != null) { for (Object services : map.values()) { @SuppressWarnings("unchecked") @@ -190,7 +191,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPost } @SuppressWarnings("unchecked") - private void flatten(Properties properties, Map input, String path) { + private void flatten(Properties properties, Map input, @Nullable String path) { input.forEach((key, value) -> { String name = getPropertyName(path, key); if (value instanceof Map) { @@ -218,7 +219,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPost }); } - private String getPropertyName(String path, String key) { + private String getPropertyName(@Nullable String path, String key) { if (!StringUtils.hasText(path)) { return key; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudPlatform.java b/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudPlatform.java index eec32739851..00f50904635 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudPlatform.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/cloud/CloudPlatform.java @@ -19,6 +19,8 @@ package org.springframework.boot.cloud; import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; @@ -198,7 +200,7 @@ public enum CloudPlatform { return isEnforced(binder.bind(PROPERTY_NAME, String.class).orElse(null)); } - private boolean isEnforced(String platform) { + private boolean isEnforced(@Nullable String platform) { return name().equalsIgnoreCase(platform); } @@ -225,7 +227,7 @@ public enum CloudPlatform { * @param environment the environment * @return the {@link CloudPlatform} or {@code null} */ - public static CloudPlatform getActive(Environment environment) { + public static @Nullable CloudPlatform getActive(@Nullable Environment environment) { if (environment != null) { for (CloudPlatform cloudPlatform : values()) { if (cloudPlatform.isActive(environment)) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/cloud/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/cloud/package-info.java index bf1cb5a7d55..4d2276aaf30 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/cloud/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/cloud/package-info.java @@ -17,4 +17,7 @@ /** * Low level support for Cloud deployments. */ +@NullMarked package org.springframework.boot.cloud; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/ApplicationPidFileWriter.java b/core/spring-boot/src/main/java/org/springframework/boot/context/ApplicationPidFileWriter.java index 6c8b2dee263..56cefb27fcb 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/ApplicationPidFileWriter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/ApplicationPidFileWriter.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; @@ -163,7 +164,7 @@ public class ApplicationPidFileWriter implements ApplicationListener candidates) { + private @Nullable String getProperty(SpringApplicationEvent event, List candidates) { for (Property candidate : candidates) { String value = candidate.getValue(event); if (value != null) { @@ -194,7 +195,7 @@ public class ApplicationPidFileWriter implements ApplicationListener scannedPackages = getComponentScanningPackages(registry); List problematicPackages = getProblematicPackages(scannedPackages); if (problematicPackages.isEmpty()) { @@ -172,13 +173,13 @@ public class ConfigurationWarningsApplicationContextInitializer } } - private void addPackages(Set packages, String[] values) { + private void addPackages(Set packages, String @Nullable [] values) { if (values != null) { Collections.addAll(packages, values); } } - private void addClasses(Set packages, String[] values) { + private void addClasses(Set packages, String @Nullable [] values) { if (values != null) { for (String value : values) { packages.add(ClassUtils.getPackageName(value)); @@ -196,14 +197,14 @@ public class ConfigurationWarningsApplicationContextInitializer return problematicPackages; } - private boolean isProblematicPackage(String scannedPackage) { + private boolean isProblematicPackage(@Nullable String scannedPackage) { if (scannedPackage == null || scannedPackage.isEmpty()) { return true; } return PROBLEM_PACKAGES.contains(scannedPackage); } - private String getDisplayName(String scannedPackage) { + private String getDisplayName(@Nullable String scannedPackage) { if (scannedPackage == null || scannedPackage.isEmpty()) { return "the default package"; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java b/core/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java index d978d6783a4..b9c20554fd7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/TypeExcludeFilter.java @@ -19,6 +19,8 @@ package org.springframework.boot.context; import java.io.IOException; import java.util.Collection; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -49,9 +51,10 @@ import org.springframework.core.type.filter.TypeFilter; */ public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { + @SuppressWarnings("NullAway.Init") private BeanFactory beanFactory; - private Collection delegates; + private @Nullable Collection delegates; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { @@ -81,7 +84,7 @@ public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { throw new IllegalStateException("TypeExcludeFilter " + getClass() + " has not implemented equals"); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/Configurations.java b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/Configurations.java index 6fdebddfd5b..b1a642f25a4 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/Configurations.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/Configurations.java @@ -30,6 +30,8 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportSelector; @@ -61,11 +63,11 @@ public abstract class Configurations { private static final Comparator COMPARATOR = OrderComparator.INSTANCE .thenComparing((other) -> other.getClass().getName()); - private final UnaryOperator>> sorter; + private final @Nullable UnaryOperator>> sorter; private final Set> classes; - private final Function, String> beanNameGenerator; + private final @Nullable Function, String> beanNameGenerator; /** * Create a new {@link Configurations} instance. @@ -85,7 +87,7 @@ public abstract class Configurations { * @param beanNameGenerator an optional function used to generate the bean name * @since 3.4.0 */ - protected Configurations(UnaryOperator>> sorter, Collection> classes, + protected Configurations(@Nullable UnaryOperator>> sorter, Collection> classes, Function, String> beanNameGenerator) { Assert.notNull(classes, "'classes' must not be null"); this.sorter = (sorter != null) ? sorter : UnaryOperator.identity(); @@ -127,7 +129,7 @@ public abstract class Configurations { * @return the bean name * @since 3.4.0 */ - public String getBeanName(Class beanClass) { + public @Nullable String getBeanName(Class beanClass) { return (this.beanNameGenerator != null) ? this.beanNameGenerator.apply(beanClass) : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java index 490aea54799..7919ebff432 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java @@ -27,6 +27,8 @@ import java.util.Enumeration; import java.util.Iterator; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.UrlResource; import org.springframework.util.Assert; @@ -76,7 +78,7 @@ public final class ImportCandidates implements Iterable { * @param classLoader class loader to use for loading * @return list of names of annotated classes */ - public static ImportCandidates load(Class annotation, ClassLoader classLoader) { + public static ImportCandidates load(Class annotation, @Nullable ClassLoader classLoader) { Assert.notNull(annotation, "'annotation' must not be null"); ClassLoader classLoaderToUse = decideClassloader(classLoader); String location = String.format(LOCATION, annotation.getName()); @@ -89,7 +91,7 @@ public final class ImportCandidates implements Iterable { return new ImportCandidates(importCandidates); } - private static ClassLoader decideClassloader(ClassLoader classLoader) { + private static ClassLoader decideClassloader(@Nullable ClassLoader classLoader) { if (classLoader == null) { return ImportCandidates.class.getClassLoader(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/package-info.java index 464c676fb86..7d56a88d56d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/annotation/package-info.java @@ -18,4 +18,7 @@ * Classes related to Spring's {@link org.springframework.context.ApplicationContext} * annotations. */ +@NullMarked package org.springframework.boot.context.annotation; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java index 9681df7d8c9..3ebaf5bfb8d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.util.Assert; @@ -120,7 +122,7 @@ public final class ConfigData { * @param propertySource the property source * @return the options to apply */ - Options get(PropertySource propertySource); + @Nullable Options get(PropertySource propertySource); /** * Create a new {@link PropertySourceOptions} instance that always returns the diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataActivationContext.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataActivationContext.java index 668eb8e2007..22c15a748e5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataActivationContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataActivationContext.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.core.env.Environment; @@ -29,9 +31,9 @@ import org.springframework.core.style.ToStringCreator; */ class ConfigDataActivationContext { - private final CloudPlatform cloudPlatform; + private final @Nullable CloudPlatform cloudPlatform; - private final Profiles profiles; + private final @Nullable Profiles profiles; /** * Create a new {@link ConfigDataActivationContext} instance before any profiles have @@ -50,12 +52,12 @@ class ConfigDataActivationContext { * @param cloudPlatform the cloud platform * @param profiles the profiles */ - ConfigDataActivationContext(CloudPlatform cloudPlatform, Profiles profiles) { + ConfigDataActivationContext(@Nullable CloudPlatform cloudPlatform, Profiles profiles) { this.cloudPlatform = cloudPlatform; this.profiles = profiles; } - private CloudPlatform deduceCloudPlatform(Environment environment, Binder binder) { + private @Nullable CloudPlatform deduceCloudPlatform(Environment environment, Binder binder) { for (CloudPlatform candidate : CloudPlatform.values()) { if (candidate.isEnforced(binder)) { return candidate; @@ -77,7 +79,7 @@ class ConfigDataActivationContext { * Return the active {@link CloudPlatform} or {@code null}. * @return the active cloud platform */ - CloudPlatform getCloudPlatform() { + @Nullable CloudPlatform getCloudPlatform() { return this.cloudPlatform; } @@ -85,7 +87,7 @@ class ConfigDataActivationContext { * Return profile information if it is available. * @return profile information or {@code null} */ - Profiles getProfiles() { + @Nullable Profiles getProfiles() { return this.profiles; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 77abf8d2d9d..f8277380a27 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Set; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.BootstrapRegistry.InstanceSupplier; import org.springframework.boot.BootstrapRegistry.Scope; @@ -43,6 +44,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.log.LogMessage; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -137,7 +139,7 @@ class ConfigDataEnvironment { */ ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles, - ConfigDataEnvironmentUpdateListener environmentUpdateListener) { + @Nullable ConfigDataEnvironmentUpdateListener environmentUpdateListener) { Binder binder = Binder.get(environment); this.logFactory = logFactory; this.logger = logFactory.getLog(getClass()); @@ -321,7 +323,7 @@ class ConfigDataEnvironment { } private void registerBootstrapBinder(ConfigDataEnvironmentContributors contributors, - ConfigDataActivationContext activationContext, BinderOption... binderOptions) { + @Nullable ConfigDataActivationContext activationContext, BinderOption... binderOptions) { this.bootstrapContext.register(Binder.class, InstanceSupplier.from(() -> contributors.getBinder(activationContext, binderOptions)) .withScope(Scope.PROTOTYPE)); @@ -336,6 +338,7 @@ class ConfigDataEnvironment { applyContributor(contributors, activationContext, propertySources); DefaultPropertiesPropertySource.moveToEnd(propertySources); Profiles profiles = activationContext.getProfiles(); + Assert.state(profiles != null, "'profiles' must not be null"); this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault())); this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault())); this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive())); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index 3c85a5b9ed9..bc98a547560 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -26,6 +26,8 @@ import java.util.NoSuchElementException; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -33,6 +35,7 @@ import org.springframework.boot.origin.OriginLookup; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; +import org.springframework.lang.Contract; import org.springframework.util.CollectionUtils; /** @@ -58,17 +61,17 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource; + private final @Nullable PropertySource propertySource; - private final ConfigurationPropertySource configurationPropertySource; + private final @Nullable ConfigurationPropertySource configurationPropertySource; - private final ConfigDataProperties properties; + private final @Nullable ConfigDataProperties properties; private final ConfigData.Options configDataOptions; @@ -93,10 +96,12 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource, - ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, - ConfigData.Options configDataOptions, Map> children, + ConfigDataEnvironmentContributor(Kind kind, @Nullable ConfigDataLocation location, + @Nullable ConfigDataResource resource, boolean fromProfileSpecificImport, + @Nullable PropertySource propertySource, + @Nullable ConfigurationPropertySource configurationPropertySource, + @Nullable ConfigDataProperties properties, ConfigData.@Nullable Options configDataOptions, + @Nullable Map> children, ConversionService conversionService) { this.kind = kind; this.location = location; @@ -118,7 +123,7 @@ class ConfigDataEnvironmentContributor implements Iterable getPropertySource() { + @Nullable PropertySource getPropertySource() { return this.propertySource; } @@ -162,7 +167,7 @@ class ConfigDataEnvironmentContributor implements Iterable contributors, - ConfigDataActivationContext activationContext) { + @Nullable ConfigDataActivationContext activationContext) { Iterable sources = Collections.singleton(getConfigurationPropertySource()); PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver( contributors, activationContext, this, true, this.conversionService); @@ -305,7 +310,8 @@ class ConfigDataEnvironmentContributor implements Iterable contributors) { + @Contract("null -> false") + private boolean hasAnyProfileSpecificChildren(@Nullable List contributors) { if (CollectionUtils.isEmpty(contributors)) { return false; } @@ -441,7 +447,8 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource) { + private static @Nullable ConfigurationPropertySource asConfigurationPropertySource( + PropertySource propertySource) { ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); if (configurationPropertySource != null && propertySource instanceof OriginLookup originLookup) { configurationPropertySource = configurationPropertySource.withPrefix(originLookup.getPrefix()); @@ -521,7 +528,7 @@ class ConfigDataEnvironmentContributor implements Iterable { - private ImportPhase phase; + private @Nullable ImportPhase phase; private Iterator children; private Iterator current; - private ConfigDataEnvironmentContributor next; + private @Nullable ConfigDataEnvironmentContributor next; private ContributorIterator() { this.phase = ImportPhase.AFTER_PROFILE_ACTIVATION; @@ -564,7 +571,7 @@ class ConfigDataEnvironmentContributor implements Iterable contributors; - private final ConfigDataActivationContext activationContext; + private final @Nullable ConfigDataActivationContext activationContext; private final boolean failOnResolveFromInactiveContributor; private final PropertyPlaceholderHelper helper; - private final ConfigDataEnvironmentContributor activeContributor; + private final @Nullable ConfigDataEnvironmentContributor activeContributor; private final ConversionService conversionService; ConfigDataEnvironmentContributorPlaceholdersResolver(Iterable contributors, - ConfigDataActivationContext activationContext, ConfigDataEnvironmentContributor activeContributor, - boolean failOnResolveFromInactiveContributor, ConversionService conversionService) { + @Nullable ConfigDataActivationContext activationContext, + @Nullable ConfigDataEnvironmentContributor activeContributor, boolean failOnResolveFromInactiveContributor, + ConversionService conversionService) { this.contributors = contributors; this.activationContext = activationContext; this.activeContributor = activeContributor; @@ -61,14 +65,14 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde } @Override - public Object resolvePlaceholders(Object value) { + public @Nullable Object resolvePlaceholders(@Nullable Object value) { if (value instanceof String string) { return this.helper.replacePlaceholders(string, this::resolvePlaceholder); } return value; } - private String resolvePlaceholder(String placeholder) { + private @Nullable String resolvePlaceholder(String placeholder) { Object result = null; for (ConfigDataEnvironmentContributor contributor : this.contributors) { PropertySource propertySource = contributor.getPropertySource(); @@ -76,6 +80,7 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde if (value != null && !isActive(contributor)) { if (this.failOnResolveFromInactiveContributor) { ConfigDataResource resource = contributor.getResource(); + Assert.state(propertySource != null, "'propertySource' can't be null here"); Origin origin = OriginLookup.getOrigin(propertySource, placeholder); throw new InactiveConfigDataAccessException(propertySource, resource, placeholder, origin); } @@ -97,7 +102,7 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde .isActive(this.activationContext); } - private String convertValueIfNecessary(Object value) { + private @Nullable String convertValueIfNecessary(Object value) { return (value instanceof String string) ? string : this.conversionService.convert(value, String.class); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java index 5428d3dac1a..0f98e59953f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.function.Predicate; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; @@ -102,7 +103,7 @@ class ConfigDataEnvironmentContributors implements Iterable filter, - BinderOption... options) { + Binder getBinder(@Nullable ConfigDataActivationContext activationContext, + Predicate filter, BinderOption... options) { return getBinder(activationContext, filter, asBinderOptionsSet(options)); } @@ -224,7 +225,7 @@ class ConfigDataEnvironmentContributors implements Iterable filter, Set options) { boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); Iterable sources = () -> getBinderSources( @@ -279,12 +280,12 @@ class ConfigDataEnvironmentContributors implements Iterable additionalProfiles) { this.logger.trace("Post-processing environment to add config data"); resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); @@ -123,7 +124,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * @param additionalProfiles any additional profiles that should be applied */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - ConfigurableBootstrapContext bootstrapContext, String... additionalProfiles) { + @Nullable ConfigurableBootstrapContext bootstrapContext, String... additionalProfiles) { applyTo(environment, resourceLoader, bootstrapContext, Arrays.asList(additionalProfiles)); } @@ -137,8 +138,8 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * throw-away context * @param additionalProfiles any additional profiles that should be applied */ - public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - ConfigurableBootstrapContext bootstrapContext, Collection additionalProfiles) { + public static void applyTo(ConfigurableEnvironment environment, @Nullable ResourceLoader resourceLoader, + @Nullable ConfigurableBootstrapContext bootstrapContext, Collection additionalProfiles) { DeferredLogFactory logFactory = Supplier::get; bootstrapContext = (bootstrapContext != null) ? bootstrapContext : new DefaultBootstrapContext(); ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory, @@ -160,7 +161,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * {@link Environment} updates. */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - ConfigurableBootstrapContext bootstrapContext, Collection additionalProfiles, + @Nullable ConfigurableBootstrapContext bootstrapContext, Collection additionalProfiles, ConfigDataEnvironmentUpdateListener environmentUpdateListener) { DeferredLogFactory logFactory = Supplier::get; bootstrapContext = (bootstrapContext != null) ? bootstrapContext : new DefaultBootstrapContext(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentUpdateListener.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentUpdateListener.java index 0e7aca30e8c..5c4fcf532cd 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentUpdateListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentUpdateListener.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.config; import java.util.EventListener; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.config.ConfigData.Options; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; @@ -43,8 +45,8 @@ public interface ConfigDataEnvironmentUpdateListener extends EventListener { * @param location the original {@link ConfigDataLocation} of the source. * @param resource the {@link ConfigDataResource} of the source. */ - default void onPropertySourceAdded(PropertySource propertySource, ConfigDataLocation location, - ConfigDataResource resource) { + default void onPropertySourceAdded(PropertySource propertySource, @Nullable ConfigDataLocation location, + @Nullable ConfigDataResource resource) { } /** diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataException.java index f60c385cf56..6d0fa8ecfba 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + /** * Abstract base class for configuration data exceptions. * @@ -30,7 +32,7 @@ public abstract class ConfigDataException extends RuntimeException { * @param message the exception message * @param cause the exception cause */ - protected ConfigDataException(String message, Throwable cause) { + protected ConfigDataException(String message, @Nullable Throwable cause) { super(message, cause); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java index 001726dbaad..0cf5b4ff23b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.log.LogMessage; @@ -78,7 +79,7 @@ class ConfigDataImporter { * @param locations the locations to resolve * @return a map of the loaded locations and data */ - Map resolveAndLoad(ConfigDataActivationContext activationContext, + Map resolveAndLoad(@Nullable ConfigDataActivationContext activationContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, List locations) { try { @@ -92,7 +93,7 @@ class ConfigDataImporter { } private List resolve(ConfigDataLocationResolverContext locationResolverContext, - Profiles profiles, List locations) { + @Nullable Profiles profiles, List locations) { List resolved = new ArrayList<>(locations.size()); for (ConfigDataLocation location : locations) { resolved.addAll(resolve(locationResolverContext, profiles, location)); @@ -101,7 +102,7 @@ class ConfigDataImporter { } private List resolve(ConfigDataLocationResolverContext locationResolverContext, - Profiles profiles, ConfigDataLocation location) { + @Nullable Profiles profiles, ConfigDataLocation location) { try { return this.resolvers.resolve(locationResolverContext, location, profiles); } @@ -145,14 +146,16 @@ class ConfigDataImporter { return Collections.unmodifiableMap(result); } - private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location, ConfigDataResource resource) { + private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location, + @Nullable ConfigDataResource resource) { if (ex instanceof ConfigDataResourceNotFoundException notFoundException) { ex = notFoundException.withLocation(location); } getNotFoundAction(location, resource).handle(this.logger, ex); } - private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location, ConfigDataResource resource) { + private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location, + @Nullable ConfigDataResource resource) { if (location.isOptional() || (resource != null && resource.isOptional())) { return ConfigDataNotFoundAction.IGNORE; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java index 416bf2e399b..303b8fd51d0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.config; import java.io.IOException; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.BootstrapContext; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; @@ -61,7 +63,7 @@ public interface ConfigDataLoader { * @throws IOException on IO error * @throws ConfigDataResourceNotFoundException if the resource cannot be found */ - ConfigData load(ConfigDataLoaderContext context, R resource) + @Nullable ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException, ConfigDataResourceNotFoundException; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java index 7a89ac8a247..41caf40acd2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.BootstrapContext; import org.springframework.boot.BootstrapRegistry; @@ -78,7 +79,7 @@ class ConfigDataLoaders { return Collections.unmodifiableList(resourceTypes); } - private Class getResourceType(ConfigDataLoader loader) { + private @Nullable Class getResourceType(ConfigDataLoader loader) { return ResolvableType.forClass(loader.getClass()).as(ConfigDataLoader.class).resolveGeneric(); } @@ -90,7 +91,8 @@ class ConfigDataLoaders { * @return the loaded {@link ConfigData} * @throws IOException on IO error */ - ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException { + @Nullable ConfigData load(ConfigDataLoaderContext context, R resource) + throws IOException { ConfigDataLoader loader = getLoader(context, resource); this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName())); return loader.load(context, resource); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java index fd8e02f4149..ea7dd9f2c68 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java @@ -16,8 +16,11 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -44,9 +47,9 @@ public final class ConfigDataLocation implements OriginProvider { private final String value; - private final Origin origin; + private final @Nullable Origin origin; - private ConfigDataLocation(boolean optional, String value, Origin origin) { + private ConfigDataLocation(boolean optional, String value, @Nullable Origin origin) { this.value = value; this.optional = optional; this.origin = origin; @@ -93,7 +96,7 @@ public final class ConfigDataLocation implements OriginProvider { } @Override - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return this.origin; } @@ -118,7 +121,10 @@ public final class ConfigDataLocation implements OriginProvider { String[] values = StringUtils.delimitedListToStringArray(toString(), delimiter); ConfigDataLocation[] result = new ConfigDataLocation[values.length]; for (int i = 0; i < values.length; i++) { - result[i] = of(values[i]).withOrigin(getOrigin()); + int index = i; + ConfigDataLocation configDataLocation = of(values[index]); + Assert.state(configDataLocation != null, () -> "Unable to parse '%s'".formatted(values[index])); + result[i] = configDataLocation.withOrigin(getOrigin()); } return result; } @@ -150,7 +156,7 @@ public final class ConfigDataLocation implements OriginProvider { * @param origin the origin to set * @return a new {@link ConfigDataLocation} instance. */ - ConfigDataLocation withOrigin(Origin origin) { + ConfigDataLocation withOrigin(@Nullable Origin origin) { return new ConfigDataLocation(this.optional, this.value, origin); } @@ -160,9 +166,16 @@ public final class ConfigDataLocation implements OriginProvider { * @return a {@link ConfigDataLocation} instance or {@code null} if no location was * provided */ - public static ConfigDataLocation of(String location) { + public static @Nullable ConfigDataLocation of(@Nullable String location) { boolean optional = location != null && location.startsWith(OPTIONAL_PREFIX); - String value = (!optional) ? location : location.substring(OPTIONAL_PREFIX.length()); + String value; + if (optional) { + Assert.state(location != null, "'location' can't be null here"); + value = location.substring(OPTIONAL_PREFIX.length()); + } + else { + value = location; + } if (!StringUtils.hasText(value)) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java index fbab332ddcc..0f4921504e6 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.util.Assert; @@ -43,7 +45,7 @@ public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundExcep * @param location the location that could not be found * @param cause the exception cause */ - public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause) { + public ConfigDataLocationNotFoundException(ConfigDataLocation location, @Nullable Throwable cause) { this(location, getMessage(location), cause); } @@ -54,7 +56,7 @@ public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundExcep * @param cause the exception cause * @since 2.4.7 */ - public ConfigDataLocationNotFoundException(ConfigDataLocation location, String message, Throwable cause) { + public ConfigDataLocationNotFoundException(ConfigDataLocation location, String message, @Nullable Throwable cause) { super(message, cause); Assert.notNull(location, "'location' must not be null"); this.location = location; @@ -69,7 +71,7 @@ public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundExcep } @Override - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return Origin.from(this.location); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java index e9b90699ea1..b24b498e609 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.EnvironmentPostProcessor; @@ -41,7 +43,7 @@ public interface ConfigDataLocationResolverContext { * or {@code null} if there is no available parent. * @return the parent location */ - ConfigDataResource getParent(); + @Nullable ConfigDataResource getParent(); /** * Provides access to the {@link ConfigurableBootstrapContext} shared across all diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java index fdc70d3daec..cb028c509a2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.function.Supplier; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.BootstrapContext; import org.springframework.boot.BootstrapRegistry; @@ -84,8 +85,8 @@ class ConfigDataLocationResolvers { return Collections.unmodifiableList(reordered); } - List resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location, - Profiles profiles) { + List resolve(ConfigDataLocationResolverContext context, + @Nullable ConfigDataLocation location, @Nullable Profiles profiles) { if (location == null) { return Collections.emptyList(); } @@ -98,7 +99,7 @@ class ConfigDataLocationResolvers { } private List resolve(ConfigDataLocationResolver resolver, - ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) { + ConfigDataLocationResolverContext context, ConfigDataLocation location, @Nullable Profiles profiles) { List resolved = resolve(location, false, () -> resolver.resolve(context, location)); if (profiles == null) { return resolved; @@ -119,7 +120,7 @@ class ConfigDataLocationResolvers { } @SuppressWarnings("unchecked") - private List nonNullList(List list) { + private List nonNullList(@Nullable List list) { return (list != null) ? (List) list : Collections.emptyList(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationRuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationRuntimeHints.java index f68226b3062..6123032f917 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationRuntimeHints.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationRuntimeHints.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -41,7 +42,7 @@ class ConfigDataLocationRuntimeHints implements RuntimeHintsRegistrar { private static final Log logger = LogFactory.getLog(ConfigDataLocationRuntimeHints.class); @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { List fileNames = getFileNames(classLoader); List locations = getLocations(classLoader); List extensions = getExtensions(classLoader); @@ -60,7 +61,7 @@ class ConfigDataLocationRuntimeHints implements RuntimeHintsRegistrar { * @param classLoader the classloader to use * @return the configuration file names */ - protected List getFileNames(ClassLoader classLoader) { + protected List getFileNames(@Nullable ClassLoader classLoader) { return Arrays.asList(StandardConfigDataLocationResolver.DEFAULT_CONFIG_NAMES); } @@ -70,7 +71,7 @@ class ConfigDataLocationRuntimeHints implements RuntimeHintsRegistrar { * @param classLoader the classloader to use * @return the configuration file locations */ - protected List getLocations(ClassLoader classLoader) { + protected List getLocations(@Nullable ClassLoader classLoader) { List classpathLocations = new ArrayList<>(); for (ConfigDataLocation candidate : ConfigDataEnvironment.DEFAULT_SEARCH_LOCATIONS) { for (ConfigDataLocation configDataLocation : candidate.split()) { @@ -89,7 +90,7 @@ class ConfigDataLocationRuntimeHints implements RuntimeHintsRegistrar { * @param classLoader the classloader to use * @return the configuration file extensions */ - protected List getExtensions(ClassLoader classLoader) { + protected List getExtensions(@Nullable ClassLoader classLoader) { List extensions = new ArrayList<>(); List propertySourceLoaders = getSpringFactoriesLoader(classLoader) .load(PropertySourceLoader.class); @@ -104,7 +105,7 @@ class ConfigDataLocationRuntimeHints implements RuntimeHintsRegistrar { return extensions; } - protected SpringFactoriesLoader getSpringFactoriesLoader(ClassLoader classLoader) { + protected SpringFactoriesLoader getSpringFactoriesLoader(@Nullable ClassLoader classLoader) { return SpringFactoriesLoader.forDefaultResourceLocation(classLoader); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java index af174fbeb12..30ca32a7ff6 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.OriginProvider; /** @@ -31,7 +33,7 @@ public abstract class ConfigDataNotFoundException extends ConfigDataException im * @param message the exception message * @param cause the exception cause */ - ConfigDataNotFoundException(String message, Throwable cause) { + ConfigDataNotFoundException(String message, @Nullable Throwable cause) { super(message, cause); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundFailureAnalyzer.java index ea174c16ee0..6ab330435ca 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundFailureAnalyzer.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.origin.Origin; @@ -48,7 +50,7 @@ class ConfigDataNotFoundFailureAnalyzer extends AbstractFailureAnalyzer imports; - private final Activate activate; + private final @Nullable Activate activate; /** * Create a new {@link ConfigDataProperties} instance. * @param imports the imports requested * @param activate the activate properties */ - ConfigDataProperties(@Name("import") List imports, Activate activate) { + ConfigDataProperties(@Nullable @Name("import") List imports, @Nullable Activate activate) { this.imports = (imports != null) ? imports : Collections.emptyList(); this.activate = activate; } @@ -68,7 +70,7 @@ class ConfigDataProperties { * @param activationContext the activation context * @return {@code true} if the config data property source is active */ - boolean isActive(ConfigDataActivationContext activationContext) { + boolean isActive(@Nullable ConfigDataActivationContext activationContext) { return this.activate == null || this.activate.isActive(activationContext); } @@ -86,7 +88,7 @@ class ConfigDataProperties { * @param binder the binder used to bind the properties * @return a {@link ConfigDataProperties} instance or {@code null} */ - static ConfigDataProperties get(Binder binder) { + static @Nullable ConfigDataProperties get(Binder binder) { return binder.bind(NAME, BINDABLE_PROPERTIES, new ConfigDataLocationBindHandler()).orElse(null); } @@ -95,7 +97,7 @@ class ConfigDataProperties { */ static class Activate { - private final CloudPlatform onCloudPlatform; + private final @Nullable CloudPlatform onCloudPlatform; private final String[] onProfile; @@ -104,7 +106,7 @@ class ConfigDataProperties { * @param onCloudPlatform the cloud platform required for activation * @param onProfile the profile expression required for activation */ - Activate(CloudPlatform onCloudPlatform, String[] onProfile) { + Activate(@Nullable CloudPlatform onCloudPlatform, String[] onProfile) { this.onProfile = onProfile; this.onCloudPlatform = onCloudPlatform; } @@ -115,7 +117,7 @@ class ConfigDataProperties { * @param activationContext the activation context * @return {@code true} if the config data property source is active */ - boolean isActive(ConfigDataActivationContext activationContext) { + boolean isActive(@Nullable ConfigDataActivationContext activationContext) { if (activationContext == null) { return false; } @@ -129,7 +131,7 @@ class ConfigDataProperties { return this.onCloudPlatform == null || this.onCloudPlatform == cloudPlatform; } - private boolean isActive(Profiles profiles) { + private boolean isActive(@Nullable Profiles profiles) { return ObjectUtils.isEmpty(this.onProfile) || (profiles != null && matchesActiveProfiles(profiles::isAccepted)); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java index 2343c28b6da..bbc556f5b94 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataPropertiesRuntimeHints.java @@ -16,10 +16,15 @@ package org.springframework.boot.context.config; +import java.lang.reflect.Method; + +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar; +import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; /** @@ -30,11 +35,11 @@ import org.springframework.util.ReflectionUtils; class ConfigDataPropertiesRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { BindableRuntimeHintsRegistrar.forTypes(ConfigDataProperties.class).registerHints(hints); - hints.reflection() - .registerMethod(ReflectionUtils.findMethod(ConfigDataLocation.class, "of", String.class), - ExecutableMode.INVOKE); + Method method = ReflectionUtils.findMethod(ConfigDataLocation.class, "of", String.class); + Assert.state(method != null, "'method' must not be null"); + hints.reflection().registerMethod(method, ExecutableMode.INVOKE); } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java index 06bb1bb9741..76eb94ee411 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java @@ -20,6 +20,8 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -35,7 +37,7 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep private final ConfigDataResource resource; - private final ConfigDataLocation location; + private final @Nullable ConfigDataLocation location; /** * Create a new {@link ConfigDataResourceNotFoundException} instance. @@ -50,12 +52,12 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep * @param resource the resource that could not be found * @param cause the exception cause */ - public ConfigDataResourceNotFoundException(ConfigDataResource resource, Throwable cause) { + public ConfigDataResourceNotFoundException(ConfigDataResource resource, @Nullable Throwable cause) { this(resource, null, cause); } - private ConfigDataResourceNotFoundException(ConfigDataResource resource, ConfigDataLocation location, - Throwable cause) { + private ConfigDataResourceNotFoundException(ConfigDataResource resource, @Nullable ConfigDataLocation location, + @Nullable Throwable cause) { super(getMessage(resource, location), cause); Assert.notNull(resource, "'resource' must not be null"); this.resource = resource; @@ -74,12 +76,12 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep * Return the original location that was resolved to determine the resource. * @return the location or {@code null} if no location is available */ - public ConfigDataLocation getLocation() { + public @Nullable ConfigDataLocation getLocation() { return this.location; } @Override - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return Origin.from(this.location); } @@ -97,11 +99,11 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep return new ConfigDataResourceNotFoundException(this.resource, location, getCause()); } - private static String getMessage(ConfigDataResource resource, ConfigDataLocation location) { + private static String getMessage(ConfigDataResource resource, @Nullable ConfigDataLocation location) { return String.format("Config data %s cannot be found", getReferenceDescription(resource, location)); } - private static String getReferenceDescription(ConfigDataResource resource, ConfigDataLocation location) { + private static String getReferenceDescription(ConfigDataResource resource, @Nullable ConfigDataLocation location) { String description = String.format("resource '%s'", resource); if (location != null) { description += String.format(" via location '%s'", location); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java index b38a477b9c4..891975bed64 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.config; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + /** * User-provided hint for an otherwise missing file extension. * @@ -30,9 +32,9 @@ final class FileExtensionHint { private static final FileExtensionHint NONE = new FileExtensionHint(null); - private final Matcher matcher; + private final @Nullable Matcher matcher; - private FileExtensionHint(Matcher matcher) { + private FileExtensionHint(@Nullable Matcher matcher) { this.matcher = matcher; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java index 585ca39a4c2..9c7ae9d07cb 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java @@ -16,11 +16,14 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.origin.Origin; import org.springframework.core.env.PropertySource; +import org.springframework.util.Assert; /** * Exception thrown when an attempt is made to resolve a property against an inactive @@ -35,11 +38,11 @@ public class InactiveConfigDataAccessException extends ConfigDataException { private final PropertySource propertySource; - private final ConfigDataResource location; + private final @Nullable ConfigDataResource location; private final String propertyName; - private final Origin origin; + private final @Nullable Origin origin; /** * Create a new {@link InactiveConfigDataAccessException} instance. @@ -49,8 +52,8 @@ public class InactiveConfigDataAccessException extends ConfigDataException { * @param propertyName the name of the property * @param origin the origin or the property or {@code null} */ - InactiveConfigDataAccessException(PropertySource propertySource, ConfigDataResource location, - String propertyName, Origin origin) { + InactiveConfigDataAccessException(PropertySource propertySource, @Nullable ConfigDataResource location, + String propertyName, @Nullable Origin origin) { super(getMessage(propertySource, location, propertyName, origin), null); this.propertySource = propertySource; this.location = location; @@ -58,8 +61,8 @@ public class InactiveConfigDataAccessException extends ConfigDataException { this.origin = origin; } - private static String getMessage(PropertySource propertySource, ConfigDataResource location, String propertyName, - Origin origin) { + private static String getMessage(PropertySource propertySource, @Nullable ConfigDataResource location, + String propertyName, @Nullable Origin origin) { StringBuilder message = new StringBuilder("Inactive property source '"); message.append(propertySource.getName()); if (location != null) { @@ -90,7 +93,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { * source was not loaded from {@link ConfigData}. * @return the config data location or {@code null} */ - public ConfigDataResource getLocation() { + public @Nullable ConfigDataResource getLocation() { return this.location; } @@ -106,7 +109,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { * Return the origin or the property or {@code null}. * @return the property origin */ - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return this.origin; } @@ -122,6 +125,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { if (property != null) { PropertySource propertySource = contributor.getPropertySource(); ConfigDataResource location = contributor.getResource(); + Assert.state(propertySource != null, "'propertySource' must not be null"); throw new InactiveConfigDataAccessException(propertySource, location, name.toString(), property.getOrigin()); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java index afc3d4b4587..41efeedec74 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java @@ -22,6 +22,8 @@ import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -60,12 +62,12 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { private final ConfigurationProperty property; - private final ConfigurationPropertyName replacement; + private final @Nullable ConfigurationPropertyName replacement; - private final ConfigDataResource location; + private final @Nullable ConfigDataResource location; InvalidConfigDataPropertyException(ConfigurationProperty property, boolean profileSpecific, - ConfigurationPropertyName replacement, ConfigDataResource location) { + @Nullable ConfigurationPropertyName replacement, @Nullable ConfigDataResource location) { super(getMessage(property, profileSpecific, replacement, location), null); this.property = property; this.replacement = replacement; @@ -85,7 +87,7 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { * the source was not loaded from {@link ConfigData}. * @return the config data location or {@code null} */ - public ConfigDataResource getLocation() { + public @Nullable ConfigDataResource getLocation() { return this.location; } @@ -94,7 +96,7 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { * replacement is available. * @return the replacement property name */ - public ConfigurationPropertyName getReplacement() { + public @Nullable ConfigurationPropertyName getReplacement() { return this.replacement; } @@ -126,7 +128,7 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { } private static String getMessage(ConfigurationProperty property, boolean profileSpecific, - ConfigurationPropertyName replacement, ConfigDataResource location) { + @Nullable ConfigurationPropertyName replacement, @Nullable ConfigDataResource location) { StringBuilder message = new StringBuilder("Property '"); message.append(property.getName()); if (location != null) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java index eb8dd9f31b9..825d914fde1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.BindResult; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; @@ -77,7 +79,7 @@ public class Profiles implements Iterable { * @param binder the binder for profile properties * @param additionalProfiles any additional active profiles */ - Profiles(Environment environment, Binder binder, Collection additionalProfiles) { + Profiles(Environment environment, Binder binder, @Nullable Collection additionalProfiles) { ProfilesValidator validator = ProfilesValidator.get(binder); if (additionalProfiles != null) { validator.validate(additionalProfiles, () -> "Invalid profile property value found in additional profiles"); @@ -89,7 +91,7 @@ public class Profiles implements Iterable { } private List getActivatedProfiles(Environment environment, Binder binder, ProfilesValidator validator, - Collection additionalProfiles) { + @Nullable Collection additionalProfiles) { return asUniqueItemList(getProfiles(environment, binder, validator, Type.ACTIVE), additionalProfiles); } @@ -117,7 +119,7 @@ public class Profiles implements Iterable { return boundProfiles.orElse(type.getDefaultValue()); } - private boolean hasProgrammaticallySetProfiles(Type type, String environmentPropertyValue, + private boolean hasProgrammaticallySetProfiles(Type type, @Nullable String environmentPropertyValue, Set environmentPropertyProfiles, Set environmentProfiles) { if (!StringUtils.hasLength(environmentPropertyValue)) { return !type.getDefaultValue().equals(environmentProfiles); @@ -134,7 +136,7 @@ public class Profiles implements Iterable { return result; } - private List expandProfiles(List profiles) { + private List expandProfiles(@Nullable List profiles) { Deque stack = new ArrayDeque<>(); asReversedList(profiles).forEach(stack::push); Set expandedProfiles = new LinkedHashSet<>(); @@ -147,7 +149,7 @@ public class Profiles implements Iterable { return asUniqueItemList(expandedProfiles); } - private List asReversedList(List list) { + private List asReversedList(@Nullable List list) { if (CollectionUtils.isEmpty(list)) { return Collections.emptyList(); } @@ -160,7 +162,7 @@ public class Profiles implements Iterable { return asUniqueItemList(profiles, null); } - private List asUniqueItemList(Collection profiles, Collection additional) { + private List asUniqueItemList(Collection profiles, @Nullable Collection additional) { LinkedHashSet uniqueItems = new LinkedHashSet<>(); if (!CollectionUtils.isEmpty(additional)) { uniqueItems.addAll(additional); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ProfilesValidator.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ProfilesValidator.java index d68715c5ed9..f80c829a542 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ProfilesValidator.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ProfilesValidator.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.Bindable; @@ -61,7 +63,7 @@ final class ProfilesValidator implements BindHandler { } } - private void validate(Object value) { + private void validate(@Nullable Object value) { if (!this.validate) { return; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java index 92f82a05dda..fe49fc84c7b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java @@ -30,6 +30,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.context.config.LocationResourceLoader.ResourceType; import org.springframework.boot.context.properties.bind.Binder; @@ -67,7 +68,7 @@ public class StandardConfigDataLocationResolver private static final Pattern URL_PREFIX = Pattern.compile("^([a-zA-Z][a-zA-Z0-9*]*?:)(.*$)"); - private static final String NO_PROFILE = null; + private static final @Nullable String NO_PROFILE = null; private final Log logger; @@ -186,7 +187,7 @@ public class StandardConfigDataLocationResolver } private Set getReferencesForDirectory(ConfigDataLocation configDataLocation, - String directory, String profile) { + String directory, @Nullable String profile) { Set references = new LinkedHashSet<>(); for (String name : this.configNames) { Deque referencesForName = getReferencesForConfigName(name, configDataLocation, @@ -197,7 +198,7 @@ public class StandardConfigDataLocationResolver } private Deque getReferencesForConfigName(String name, - ConfigDataLocation configDataLocation, String directory, String profile) { + ConfigDataLocation configDataLocation, String directory, @Nullable String profile) { Deque references = new ArrayDeque<>(); for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) { for (String extension : propertySourceLoader.getFileExtensions()) { @@ -212,7 +213,7 @@ public class StandardConfigDataLocationResolver } private Set getReferencesForFile(ConfigDataLocation configDataLocation, String file, - String profile) { + @Nullable String profile) { FileExtensionHint fileExtensionHint = FileExtensionHint.from(file); if (fileExtensionHint.isPresent()) { file = FileExtensionHint.removeFrom(file) + fileExtensionHint; @@ -242,7 +243,7 @@ public class StandardConfigDataLocationResolver + "check the location prefix if a different resolver is expected"); } - private String getLoadableFileExtension(PropertySourceLoader loader, String file) { + private @Nullable String getLoadableFileExtension(PropertySourceLoader loader, String file) { for (String fileExtension : loader.getFileExtensions()) { if (StringUtils.endsWithIgnoreCase(file, fileExtension)) { return fileExtension; @@ -285,13 +286,17 @@ public class StandardConfigDataLocationResolver } private Set resolveNonPatternEmptyDirectories(StandardConfigDataReference reference) { - Resource resource = this.resourceLoader.getResource(reference.getDirectory()); + String directory = reference.getDirectory(); + Assert.state(directory != null, "'directory' must not be null"); + Resource resource = this.resourceLoader.getResource(directory); return (resource instanceof ClassPathResource || !resource.exists()) ? Collections.emptySet() : Collections.singleton(new StandardConfigDataResource(reference, resource, true)); } private Set resolvePatternEmptyDirectories(StandardConfigDataReference reference) { - Resource[] subdirectories = this.resourceLoader.getResources(reference.getDirectory(), ResourceType.DIRECTORY); + String directory = reference.getDirectory(); + Assert.state(directory != null, "'directory' must not be null"); + Resource[] subdirectories = this.resourceLoader.getResources(directory, ResourceType.DIRECTORY); ConfigDataLocation location = reference.getConfigDataLocation(); if (!location.isOptional() && ObjectUtils.isEmpty(subdirectories)) { String message = String.format("Config data location '%s' contains no subdirectories", location); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java index 767d2f5a189..f2b1000871c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.config; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.env.PropertySourceLoader; import org.springframework.util.StringUtils; @@ -31,9 +33,9 @@ class StandardConfigDataReference { private final String resourceLocation; - private final String directory; + private final @Nullable String directory; - private final String profile; + private final @Nullable String profile; private final PropertySourceLoader propertySourceLoader; @@ -48,8 +50,8 @@ class StandardConfigDataReference { * @param propertySourceLoader the property source loader that should be used for this * reference */ - StandardConfigDataReference(ConfigDataLocation configDataLocation, String directory, String root, String profile, - String extension, PropertySourceLoader propertySourceLoader) { + StandardConfigDataReference(ConfigDataLocation configDataLocation, @Nullable String directory, String root, + @Nullable String profile, @Nullable String extension, PropertySourceLoader propertySourceLoader) { this.configDataLocation = configDataLocation; String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : ""; this.resourceLocation = root + profileSuffix + ((extension != null) ? "." + extension : ""); @@ -70,11 +72,11 @@ class StandardConfigDataReference { return !this.configDataLocation.isOptional() && this.directory != null; } - String getDirectory() { + @Nullable String getDirectory() { return this.directory; } - String getProfile() { + @Nullable String getProfile() { return this.profile; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java index ec7e5f9b5f5..58ac64291b1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.config; import java.io.File; import java.io.IOException; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileUrlResource; @@ -81,7 +83,7 @@ public class StandardConfigDataResource extends ConfigDataResource { * @return the profile or {@code null} * @since 2.4.6 */ - public String getProfile() { + public @Nullable String getProfile() { return this.reference.getProfile(); } @@ -105,7 +107,7 @@ public class StandardConfigDataResource extends ConfigDataResource { return ours.equals(other) || isSameFile(getUnderlyingFile(ours), getUnderlyingFile(other)); } - private boolean isSameFile(File ours, File other) { + private boolean isSameFile(@Nullable File ours, @Nullable File other) { return (ours != null) && ours.equals(other); } @@ -128,7 +130,7 @@ public class StandardConfigDataResource extends ConfigDataResource { return this.resource.toString(); } - private File getUnderlyingFile(Resource resource) { + private @Nullable File getUnderlyingFile(Resource resource) { try { if (resource instanceof ClassPathResource || resource instanceof FileSystemResource || resource instanceof FileUrlResource) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java index 38149b6f2ad..18849fb7312 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.List; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -38,7 +40,7 @@ class SystemEnvironmentConfigDataLocationResolver private final List loaders; - private final Function environment; + private final Function environment; SystemEnvironmentConfigDataLocationResolver() { this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); @@ -46,7 +48,7 @@ class SystemEnvironmentConfigDataLocationResolver } SystemEnvironmentConfigDataLocationResolver(List loaders, - Function environment) { + Function environment) { this.loaders = loaders; this.environment = environment; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResource.java index 240ef1be846..53a62c2e979 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResource.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Objects; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ByteArrayResource; @@ -39,10 +41,10 @@ class SystemEnvironmentConfigDataResource extends ConfigDataResource { private final PropertySourceLoader loader; - private final Function environment; + private final Function environment; SystemEnvironmentConfigDataResource(String variableName, PropertySourceLoader loader, - Function environment) { + Function environment) { this.variableName = variableName; this.loader = loader; this.environment = environment; @@ -56,7 +58,7 @@ class SystemEnvironmentConfigDataResource extends ConfigDataResource { return this.loader; } - List> load() throws IOException { + @Nullable List> load() throws IOException { String content = this.environment.apply(this.variableName); return (content != null) ? this.loader.load(StringUtils.capitalize(toString()), asResource(content)) : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java index 4498679889a..a97f74de7b9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/package-info.java @@ -20,4 +20,7 @@ * * @see org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor */ +@NullMarked package org.springframework.boot.context.config; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationFailedEvent.java b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationFailedEvent.java index e6c67c593cd..03a78207879 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationFailedEvent.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationFailedEvent.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.event; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; @@ -29,7 +31,7 @@ import org.springframework.context.ConfigurableApplicationContext; @SuppressWarnings("serial") public class ApplicationFailedEvent extends SpringApplicationEvent { - private final ConfigurableApplicationContext context; + private final @Nullable ConfigurableApplicationContext context; private final Throwable exception; @@ -40,8 +42,8 @@ public class ApplicationFailedEvent extends SpringApplicationEvent { * @param context the context that was being created (maybe null) * @param exception the exception that caused the error */ - public ApplicationFailedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context, - Throwable exception) { + public ApplicationFailedEvent(SpringApplication application, String[] args, + @Nullable ConfigurableApplicationContext context, Throwable exception) { super(application, args); this.context = context; this.exception = exception; @@ -49,9 +51,9 @@ public class ApplicationFailedEvent extends SpringApplicationEvent { /** * Return the application context. - * @return the context + * @return the context or {@code null} */ - public ConfigurableApplicationContext getApplicationContext() { + public @Nullable ConfigurableApplicationContext getApplicationContext() { return this.context; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationReadyEvent.java b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationReadyEvent.java index 084605cb5ce..ede47f5b534 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationReadyEvent.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationReadyEvent.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.event; import java.time.Duration; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; @@ -37,7 +39,7 @@ public class ApplicationReadyEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; - private final Duration timeTaken; + private final @Nullable Duration timeTaken; /** * Create a new {@link ApplicationReadyEvent} instance. @@ -48,7 +50,7 @@ public class ApplicationReadyEvent extends SpringApplicationEvent { * @since 2.6.0 */ public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context, - Duration timeTaken) { + @Nullable Duration timeTaken) { super(application, args); this.context = context; this.timeTaken = timeTaken; @@ -68,7 +70,7 @@ public class ApplicationReadyEvent extends SpringApplicationEvent { * @return the time taken to be ready to service requests * @since 2.6.0 */ - public Duration getTimeTaken() { + public @Nullable Duration getTimeTaken() { return this.timeTaken; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartedEvent.java b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartedEvent.java index a3d6f2db7ca..c16344f8f7c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartedEvent.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/event/ApplicationStartedEvent.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.event; import java.time.Duration; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.ApplicationRunner; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -36,7 +38,7 @@ public class ApplicationStartedEvent extends SpringApplicationEvent { private final ConfigurableApplicationContext context; - private final Duration timeTaken; + private final @Nullable Duration timeTaken; /** * Create a new {@link ApplicationStartedEvent} instance. @@ -47,7 +49,7 @@ public class ApplicationStartedEvent extends SpringApplicationEvent { * @since 2.6.0 */ public ApplicationStartedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context, - Duration timeTaken) { + @Nullable Duration timeTaken) { super(application, args); this.context = context; this.timeTaken = timeTaken; @@ -66,7 +68,7 @@ public class ApplicationStartedEvent extends SpringApplicationEvent { * @return the startup time * @since 2.6.0 */ - public Duration getTimeTaken() { + public @Nullable Duration getTimeTaken() { return this.timeTaken; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java b/core/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java index 9768eb9f884..85b0431c50c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/event/EventPublishingRunListener.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.SpringApplication; @@ -99,19 +100,19 @@ class EventPublishingRunListener implements SpringApplicationRunListener, Ordere } @Override - public void started(ConfigurableApplicationContext context, Duration timeTaken) { + public void started(ConfigurableApplicationContext context, @Nullable Duration timeTaken) { context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken)); AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); } @Override - public void ready(ConfigurableApplicationContext context, Duration timeTaken) { + public void ready(ConfigurableApplicationContext context, @Nullable Duration timeTaken) { context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken)); AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC); } @Override - public void failed(ConfigurableApplicationContext context, Throwable exception) { + public void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) { ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception); if (context != null && context.isActive()) { // Listeners have been registered to the application context so we should diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/event/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/event/package-info.java index 1d23650b5ca..8e374232c67 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/event/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/event/package-info.java @@ -18,4 +18,7 @@ * {@link org.springframework.context.ApplicationEvent ApplicationEvents} triggered by * Spring Boot. */ +@NullMarked package org.springframework.boot.context.event; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java index 7035318dd79..00e3faec91d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java @@ -25,6 +25,7 @@ import java.util.function.BiConsumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; @@ -54,6 +55,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.log.LogMessage; +import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; @@ -181,17 +183,17 @@ public class LoggingApplicationListener implements GenericApplicationListener { private final Log logger = LogFactory.getLog(getClass()); - private LoggingSystem loggingSystem; + private @Nullable LoggingSystem loggingSystem; - private LogFile logFile; + private @Nullable LogFile logFile; - private LoggerGroups loggerGroups; + private @Nullable LoggerGroups loggerGroups; private int order = DEFAULT_ORDER; private boolean parseArgs = true; - private LogLevel springBootLogging = null; + private @Nullable LogLevel springBootLogging = null; @Override public boolean supportsEventType(ResolvableType resolvableType) { @@ -199,11 +201,11 @@ public class LoggingApplicationListener implements GenericApplicationListener { } @Override - public boolean supportsSourceType(Class sourceType) { + public boolean supportsSourceType(@Nullable Class sourceType) { return isAssignableFrom(sourceType, SOURCE_TYPES); } - private boolean isAssignableFrom(Class type, Class... supportedTypes) { + private boolean isAssignableFrom(@Nullable Class type, Class... supportedTypes) { if (type != null) { for (Class supportedType : supportedTypes) { if (supportedType.isAssignableFrom(type)) { @@ -250,6 +252,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { ConfigurableApplicationContext applicationContext = event.getApplicationContext(); ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory(); if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) { + Assert.state(this.loggingSystem != null, "loggingSystem is not set"); beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem); } if (this.logFile != null && !beanFactory.containsBean(LOG_FILE_BEAN_NAME)) { @@ -295,6 +298,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { } this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS); initializeEarlyLoggingLevel(environment); + Assert.state(this.loggingSystem != null, "loggingSystem is not set"); initializeSystem(environment, this.loggingSystem, this.logFile); initializeFinalLoggingLevels(environment, this.loggingSystem); registerShutdownHookIfNecessary(environment, this.loggingSystem); @@ -321,7 +325,8 @@ public class LoggingApplicationListener implements GenericApplicationListener { return (value != null && !value.equals("false")); } - private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) { + private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, + @Nullable LogFile logFile) { String logConfig = environment.getProperty(CONFIG_PROPERTY); if (StringUtils.hasLength(logConfig)) { logConfig = logConfig.strip(); @@ -348,7 +353,7 @@ public class LoggingApplicationListener implements GenericApplicationListener { } } - private boolean ignoreLogConfig(String logConfig) { + private boolean ignoreLogConfig(@Nullable String logConfig) { return !StringUtils.hasLength(logConfig) || logConfig.startsWith("-D"); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/package-info.java index 8595059b68c..9d5256fe2e0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/package-info.java @@ -17,4 +17,7 @@ /** * Logging integration with Spring's Application Context. */ +@NullMarked package org.springframework.boot.context.logging; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferedStartupStep.java b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferedStartupStep.java index 36748a24d7d..723a778bcfb 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferedStartupStep.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferedStartupStep.java @@ -24,6 +24,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.core.metrics.StartupStep; import org.springframework.util.Assert; @@ -41,7 +43,7 @@ class BufferedStartupStep implements StartupStep { private final long id; - private final BufferedStartupStep parent; + private final @Nullable BufferedStartupStep parent; private final List tags = new ArrayList<>(); @@ -51,7 +53,7 @@ class BufferedStartupStep implements StartupStep { private final AtomicBoolean ended = new AtomicBoolean(); - BufferedStartupStep(BufferedStartupStep parent, String name, long id, Instant startTime, + BufferedStartupStep(@Nullable BufferedStartupStep parent, String name, long id, Instant startTime, Consumer recorder) { this.parent = parent; this.name = name; @@ -60,7 +62,7 @@ class BufferedStartupStep implements StartupStep { this.recorder = recorder; } - BufferedStartupStep getParent() { + @Nullable BufferedStartupStep getParent() { return this.parent; } @@ -79,7 +81,7 @@ class BufferedStartupStep implements StartupStep { } @Override - public Long getParentId() { + public @Nullable Long getParentId() { return (this.parent != null) ? this.parent.getId() : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferingApplicationStartup.java b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferingApplicationStartup.java index cc8cccca696..25c5c35f192 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferingApplicationStartup.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/BufferingApplicationStartup.java @@ -26,6 +26,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.metrics.buffering.StartupTimeline.TimelineEvent; import org.springframework.core.metrics.ApplicationStartup; import org.springframework.core.metrics.StartupStep; @@ -133,7 +135,7 @@ public class BufferingApplicationStartup implements ApplicationStartup { } } - private BufferedStartupStep getLatestActive(BufferedStartupStep step) { + private @Nullable BufferedStartupStep getLatestActive(@Nullable BufferedStartupStep step) { while (step != null && step.isEnded()) { step = step.getParent(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/package-info.java index 6c48fca34bb..1ce75006281 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/metrics/buffering/package-info.java @@ -18,4 +18,7 @@ * {@link org.springframework.core.metrics.ApplicationStartup} implementation for * buffering steps and measuring their processing time. */ +@NullMarked package org.springframework.boot.context.metrics.buffering; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/package-info.java index ab037696236..f86f064cbf9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/package-info.java @@ -17,4 +17,7 @@ /** * Classes related to Spring's {@link org.springframework.context.ApplicationContext}. */ +@NullMarked package org.springframework.boot.context; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java index 6c6893bab54..efdfce12d71 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BindMethodAttribute.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.context.properties.bind.BindMethod; @@ -36,21 +38,21 @@ final class BindMethodAttribute { private BindMethodAttribute() { } - static BindMethod get(ApplicationContext applicationContext, String beanName) { + static @Nullable BindMethod get(ApplicationContext applicationContext, String beanName) { return (applicationContext instanceof ConfigurableApplicationContext configurableApplicationContext) ? get(configurableApplicationContext.getBeanFactory(), beanName) : null; } - static BindMethod get(ConfigurableListableBeanFactory beanFactory, String beanName) { + static @Nullable BindMethod get(ConfigurableListableBeanFactory beanFactory, String beanName) { return (!beanFactory.containsBeanDefinition(beanName)) ? null : get(beanFactory.getBeanDefinition(beanName)); } - static BindMethod get(BeanDefinitionRegistry beanDefinitionRegistry, String beanName) { + static @Nullable BindMethod get(BeanDefinitionRegistry beanDefinitionRegistry, String beanName) { return (!beanDefinitionRegistry.containsBeanDefinition(beanName)) ? null : get(beanDefinitionRegistry.getBeanDefinition(beanName)); } - static BindMethod get(AttributeAccessor attributes) { + static @Nullable BindMethod get(AttributeAccessor attributes) { return (BindMethod) attributes.getAttribute(NAME); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java index a0403dc9b55..868d619b41d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/BoundConfigurationProperties.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -53,7 +55,7 @@ public class BoundConfigurationProperties { * @param name the property name * @return the bound property or {@code null} */ - public ConfigurationProperty get(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty get(ConfigurationPropertyName name) { return this.properties.get(name); } @@ -71,7 +73,7 @@ public class BoundConfigurationProperties { * @param context the context to search * @return a {@link BoundConfigurationProperties} or {@code null} */ - public static BoundConfigurationProperties get(ApplicationContext context) { + public static @Nullable BoundConfigurationProperties get(ApplicationContext context) { return (!context.containsBeanDefinition(BEAN_NAME)) ? null : context.getBean(BEAN_NAME, BoundConfigurationProperties.class); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java index a823011a9be..dd89c19e7a1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBean.java @@ -24,6 +24,8 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.aop.support.AopUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; @@ -40,6 +42,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.validation.annotation.Validated; @@ -66,11 +69,11 @@ public final class ConfigurationPropertiesBean { private final String name; - private final Object instance; + private final @Nullable Object instance; private final Bindable bindTarget; - private ConfigurationPropertiesBean(String name, Object instance, Bindable bindTarget) { + private ConfigurationPropertiesBean(String name, @Nullable Object instance, Bindable bindTarget) { this.name = name; this.instance = instance; this.bindTarget = bindTarget; @@ -88,7 +91,7 @@ public final class ConfigurationPropertiesBean { * Return the actual Spring bean instance. * @return the bean instance */ - public Object getInstance() { + public @Nullable Object getInstance() { return this.instance; } @@ -97,7 +100,9 @@ public final class ConfigurationPropertiesBean { * @return the bean type */ Class getType() { - return this.bindTarget.getType().resolve(); + Class resolved = this.bindTarget.getType().resolve(); + Assert.state(resolved != null, "'resolved' must not be null"); + return resolved; } /** @@ -107,7 +112,9 @@ public final class ConfigurationPropertiesBean { * @return the configuration properties annotation */ public ConfigurationProperties getAnnotation() { - return this.bindTarget.getAnnotation(ConfigurationProperties.class); + ConfigurationProperties annotation = this.bindTarget.getAnnotation(ConfigurationProperties.class); + Assert.state(annotation != null, "'annotation' must not be null"); + return annotation; } /** @@ -193,7 +200,8 @@ public final class ConfigurationPropertiesBean { * factory method are annotated with * {@link ConfigurationProperties @ConfigurationProperties} */ - public static ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, String beanName) { + public static @Nullable ConfigurationPropertiesBean get(ApplicationContext applicationContext, Object bean, + String beanName) { Method factoryMethod = findFactoryMethod(applicationContext, beanName); Bindable bindTarget = createBindTarget(bean, bean.getClass(), factoryMethod); if (bindTarget == null) { @@ -209,18 +217,19 @@ public final class ConfigurationPropertiesBean { return create(beanName, bean, bindTarget); } - private static Method findFactoryMethod(ApplicationContext applicationContext, String beanName) { + private static @Nullable Method findFactoryMethod(ApplicationContext applicationContext, String beanName) { if (applicationContext instanceof ConfigurableApplicationContext configurableContext) { return findFactoryMethod(configurableContext, beanName); } return null; } - private static Method findFactoryMethod(ConfigurableApplicationContext applicationContext, String beanName) { + private static @Nullable Method findFactoryMethod(ConfigurableApplicationContext applicationContext, + String beanName) { return findFactoryMethod(applicationContext.getBeanFactory(), beanName); } - private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, String beanName) { + private static @Nullable Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, String beanName) { if (beanFactory.containsBeanDefinition(beanName)) { BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName); if (beanDefinition instanceof RootBeanDefinition rootBeanDefinition) { @@ -237,14 +246,16 @@ public final class ConfigurationPropertiesBean { return create(beanName, null, bindTarget.withBindMethod(VALUE_OBJECT_BIND_METHOD)); } - private static Bindable createBindTarget(Object bean, Class beanType, Method factoryMethod) { + private static @Nullable Bindable createBindTarget(@Nullable Object bean, Class beanType, + @Nullable Method factoryMethod) { ResolvableType type = (factoryMethod != null) ? ResolvableType.forMethodReturnType(factoryMethod) : ResolvableType.forClass(beanType); Annotation[] annotations = findAnnotations(bean, beanType, factoryMethod); return (annotations != null) ? Bindable.of(type).withAnnotations(annotations) : null; } - private static Annotation[] findAnnotations(Object instance, Class type, Method factory) { + private static Annotation @Nullable [] findAnnotations(@Nullable Object instance, Class type, + @Nullable Method factory) { ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class); if (annotation == null) { return null; @@ -253,8 +264,8 @@ public final class ConfigurationPropertiesBean { return (validated != null) ? new Annotation[] { annotation, validated } : new Annotation[] { annotation }; } - private static A findAnnotation(Object instance, Class type, Method factory, - Class annotationType) { + private static @Nullable A findAnnotation(@Nullable Object instance, Class type, + @Nullable Method factory, Class annotationType) { MergedAnnotation annotation = MergedAnnotation.missing(); if (factory != null) { annotation = findMergedAnnotation(factory, annotationType); @@ -269,13 +280,15 @@ public final class ConfigurationPropertiesBean { return annotation.isPresent() ? annotation.synthesize() : null; } - private static MergedAnnotation findMergedAnnotation(AnnotatedElement element, + private static MergedAnnotation findMergedAnnotation(@Nullable AnnotatedElement element, Class annotationType) { return (element != null) ? MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY).get(annotationType) : MergedAnnotation.missing(); } - private static ConfigurationPropertiesBean create(String name, Object instance, Bindable bindTarget) { + @Contract("_, _, !null -> !null") + private static @Nullable ConfigurationPropertiesBean create(String name, @Nullable Object instance, + @Nullable Bindable bindTarget) { return (bindTarget != null) ? new ConfigurationPropertiesBean(name, instance, bindTarget) : null; } @@ -298,7 +311,7 @@ public final class ConfigurationPropertiesBean { } private static org.springframework.boot.context.properties.bind.BindMethod deduceBindMethod( - Constructor bindConstructor) { + @Nullable Constructor bindConstructor) { return (bindConstructor != null) ? VALUE_OBJECT_BIND_METHOD : JAVA_BEAN_BIND_METHOD; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java index efb7189b397..4ffd41bcc02 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.properties; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GenerationContext; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; @@ -41,7 +43,7 @@ import org.springframework.util.ClassUtils; class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { @Override - public ConfigurationPropertiesReflectionHintsContribution processAheadOfTime( + public @Nullable ConfigurationPropertiesReflectionHintsContribution processAheadOfTime( ConfigurableListableBeanFactory beanFactory) { String[] beanNames = beanFactory.getBeanNamesForAnnotation(ConfigurationProperties.class); List> bindables = new ArrayList<>(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java index cce492477db..fbc57d2fe1e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanRegistrationAotProcessor.java @@ -20,6 +20,8 @@ import java.util.function.Predicate; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.generate.GeneratedMethod; import org.springframework.aot.generate.GenerationContext; import org.springframework.beans.factory.BeanFactory; @@ -45,7 +47,7 @@ import org.springframework.javapoet.CodeBlock; class ConfigurationPropertiesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { @Override - public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { + public @Nullable BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { if (!isImmutableConfigurationPropertiesBeanDefinition(registeredBean.getMergedBeanDefinition())) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java index 48fd4a3e9b1..f3aa625f9f8 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBinder.java @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanFactory; @@ -72,13 +74,13 @@ class ConfigurationPropertiesBinder { private final PropertySources propertySources; - private final Validator configurationPropertiesValidator; + private final @Nullable Validator configurationPropertiesValidator; private final boolean jsr303Present; - private volatile List bindHandlerAdvisors; + private volatile @Nullable List bindHandlerAdvisors; - private volatile Binder binder; + private volatile @Nullable Binder binder; ConfigurationPropertiesBinder(ApplicationContext applicationContext) { this.applicationContext = applicationContext; @@ -101,7 +103,7 @@ class ConfigurationPropertiesBinder { return getBinder().bindOrCreate(annotation.prefix(), target, bindHandler); } - private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) { + private @Nullable Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) { if (applicationContext.containsBean(VALIDATOR_BEAN_NAME)) { return applicationContext.getBean(VALIDATOR_BEAN_NAME, Validator.class); } @@ -153,7 +155,9 @@ class ConfigurationPropertiesBinder { validators.add(this.configurationPropertiesValidator); } if (this.jsr303Present && target.getAnnotation(Validated.class) != null) { - validators.add(getJsr303Validator(target.getType().resolve())); + Class resolved = target.getType().resolve(); + Assert.state(resolved != null, "'resolved' must not be null"); + validators.add(getJsr303Validator(resolved)); } Validator selfValidator = getSelfValidator(target); if (selfValidator != null) { @@ -162,7 +166,7 @@ class ConfigurationPropertiesBinder { return validators; } - private Validator getSelfValidator(Bindable target) { + private @Nullable Validator getSelfValidator(Bindable target) { if (target.getValue() != null) { Object value = target.getValue().get(); return (value instanceof Validator validator) ? validator : null; @@ -194,11 +198,11 @@ class ConfigurationPropertiesBinder { return new PropertySourcesPlaceholdersResolver(this.propertySources); } - private List getConversionServices() { + private @Nullable List getConversionServices() { return new ConversionServiceDeducer(this.applicationContext).getConversionServices(); } - private Consumer getPropertyEditorInitializer() { + private @Nullable Consumer getPropertyEditorInitializer() { if (this.applicationContext instanceof ConfigurableApplicationContext configurableContext) { return configurableContext.getBeanFactory()::copyRegisteredEditorsTo; } @@ -235,7 +239,7 @@ class ConfigurationPropertiesBinder { ? target.withBindRestrictions(BindRestriction.NO_DIRECT_PROPERTY) : target; } - private boolean isConfigurationProperties(Class target) { + private boolean isConfigurationProperties(@Nullable Class target) { return target != null && MergedAnnotations.from(target).isPresent(ConfigurationProperties.class); } @@ -247,7 +251,7 @@ class ConfigurationPropertiesBinder { static class ConfigurationPropertiesBinderFactory implements FactoryBean, ApplicationContextAware { - private ConfigurationPropertiesBinder binder; + private @Nullable ConfigurationPropertiesBinder binder; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index b4966023a20..fcd11a93992 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; @@ -49,10 +51,13 @@ public class ConfigurationPropertiesBindingPostProcessor */ public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class.getName(); + @SuppressWarnings("NullAway.Init") private ApplicationContext applicationContext; + @SuppressWarnings("NullAway.Init") private BeanDefinitionRegistry registry; + @SuppressWarnings("NullAway.Init") private ConfigurationPropertiesBinder binder; @Override @@ -85,7 +90,7 @@ public class ConfigurationPropertiesBindingPostProcessor return BindMethod.VALUE_OBJECT.equals(BindMethodAttribute.get(this.registry, beanName)); } - private void bind(ConfigurationPropertiesBean bean) { + private void bind(@Nullable ConfigurationPropertiesBean bean) { if (bean == null) { return; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesCharSequenceToObjectConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesCharSequenceToObjectConverter.java index 63a517c1fcb..927f46f10d0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesCharSequenceToObjectConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesCharSequenceToObjectConverter.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.properties; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; @@ -96,7 +98,10 @@ final class ConfigurationPropertiesCharSequenceToObjectConverter implements Cond } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + if (source == null) { + return null; + } return this.conversionService.convert(source.toString(), STRING, targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java index 1ac17b41a4d..99233bdd898 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.java @@ -34,6 +34,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -65,6 +66,7 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist private Set getPackagesToScan(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(ConfigurationPropertiesScan.class.getName())); + Assert.state(attributes != null, "'attributes' must not be null"); String[] basePackages = attributes.getStringArray("basePackages"); Class[] basePackageClasses = attributes.getClassArray("basePackageClasses"); Set packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages)); @@ -83,7 +85,9 @@ class ConfigurationPropertiesScanRegistrar implements ImportBeanDefinitionRegist ClassPathScanningCandidateComponentProvider scanner = getScanner(registry); for (String basePackage : packages) { for (BeanDefinition candidate : scanner.findCandidateComponents(basePackage)) { - register(registrar, candidate.getBeanClassName()); + String beanClassName = candidate.getBeanClassName(); + Assert.state(beanClassName != null, "'beanClassName' must not be null"); + register(registrar, beanClassName); } } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java index f62b4ac5e80..a1acfbec095 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/ConversionServiceDeducer.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; @@ -42,7 +44,7 @@ class ConversionServiceDeducer { this.applicationContext = applicationContext; } - List getConversionServices() { + @Nullable List getConversionServices() { if (hasUserDefinedConfigurationServiceBean()) { return Collections.singletonList(this.applicationContext .getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java index 9dce5384485..a20b61fa3c0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/NotConstructorBoundInjectionFailureAnalyzer.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties; import java.lang.reflect.Constructor; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.UnsatisfiedDependencyException; @@ -28,6 +30,7 @@ import org.springframework.boot.diagnostics.analyzer.AbstractInjectionFailureAna import org.springframework.core.Ordered; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; +import org.springframework.lang.Contract; /** * An {@link AbstractInjectionFailureAnalyzer} for @@ -45,7 +48,8 @@ class NotConstructorBoundInjectionFailureAnalyzer } @Override - protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause, String description) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause, + @Nullable String description) { InjectionPoint injectionPoint = findInjectionPoint(rootFailure); if (isConstructorBindingConfigurationProperties(injectionPoint)) { String simpleName = injectionPoint.getMember().getDeclaringClass().getSimpleName(); @@ -60,7 +64,8 @@ class NotConstructorBoundInjectionFailureAnalyzer return null; } - private boolean isConstructorBindingConfigurationProperties(InjectionPoint injectionPoint) { + @Contract("null -> false") + private boolean isConstructorBindingConfigurationProperties(@Nullable InjectionPoint injectionPoint) { return injectionPoint != null && injectionPoint.getMember() instanceof Constructor constructor && isConstructorBindingConfigurationProperties(constructor); } @@ -72,7 +77,7 @@ class NotConstructorBoundInjectionFailureAnalyzer .isPresent(ConfigurationProperties.class) && bindMethod == BindMethod.VALUE_OBJECT; } - private InjectionPoint findInjectionPoint(Throwable failure) { + private @Nullable InjectionPoint findInjectionPoint(Throwable failure) { UnsatisfiedDependencyException unsatisfiedDependencyException = findCause(failure, UnsatisfiedDependencyException.class); if (unsatisfiedDependencyException == null) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertyMapper.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertyMapper.java index 434542590f6..e767ee51bef 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertyMapper.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertyMapper.java @@ -24,6 +24,8 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.function.SingletonSupplier; @@ -62,11 +64,11 @@ public final class PropertyMapper { private static final PropertyMapper INSTANCE = new PropertyMapper(null, null); - private final PropertyMapper parent; + private final @Nullable PropertyMapper parent; - private final SourceOperator sourceOperator; + private final @Nullable SourceOperator sourceOperator; - private PropertyMapper(PropertyMapper parent, SourceOperator sourceOperator) { + private PropertyMapper(@Nullable PropertyMapper parent, @Nullable SourceOperator sourceOperator) { this.parent = parent; this.sourceOperator = sourceOperator; } @@ -119,7 +121,7 @@ public final class PropertyMapper { * @param value the value * @return a {@link Source} that can be used to complete the mapping */ - public Source from(T value) { + public Source from(@Nullable T value) { return from(() -> value); } @@ -356,7 +358,7 @@ public final class PropertyMapper { } @Override - public T get() { + public @Nullable T get() { try { return this.supplier.get(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertySourcesDeducer.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertySourcesDeducer.java index ea1f1e15674..c1896621fa7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertySourcesDeducer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/PropertySourcesDeducer.java @@ -20,6 +20,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @@ -55,7 +56,7 @@ class PropertySourcesDeducer { return sources; } - private PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() { + private @Nullable PropertySourcesPlaceholderConfigurer getSinglePropertySourcesPlaceholderConfigurer() { // Take care not to cause early instantiation of all FactoryBeans Map beans = this.applicationContext .getBeansOfType(PropertySourcesPlaceholderConfigurer.class, false, false); @@ -69,7 +70,7 @@ class PropertySourcesDeducer { return null; } - private MutablePropertySources extractEnvironmentPropertySources() { + private @Nullable MutablePropertySources extractEnvironmentPropertySources() { Environment environment = this.applicationContext.getEnvironment(); if (environment instanceof ConfigurableEnvironment configurableEnvironment) { return configurableEnvironment.getPropertySources(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AbstractBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AbstractBindHandler.java index fc6d926ff68..30c5064f63c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AbstractBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AbstractBindHandler.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.util.Assert; @@ -47,24 +49,25 @@ public abstract class AbstractBindHandler implements BindHandler { } @Override - public Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { + public @Nullable Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { return this.parent.onStart(name, target, context); } @Override - public Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + public @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, + Object result) { return this.parent.onSuccess(name, target, context, result); } @Override - public Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + public @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { return this.parent.onFailure(name, target, context, error); } @Override - public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) - throws Exception { + public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, + @Nullable Object result) throws Exception { this.parent.onFinish(name, target, context, result); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java index 7a34d24d8c9..9abd857c872 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateBinder.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.bind; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -42,7 +44,7 @@ abstract class AggregateBinder { * @param source the configuration property source or {@code null} for all sources. * @return if recursive binding is supported */ - protected abstract boolean isAllowRecursiveBinding(ConfigurationPropertySource source); + protected abstract boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source); /** * Perform binding for the aggregate. @@ -52,7 +54,8 @@ abstract class AggregateBinder { * @return the bound aggregate or null */ @SuppressWarnings("unchecked") - final Object bind(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder) { + final @Nullable Object bind(ConfigurationPropertyName name, Bindable target, + AggregateElementBinder elementBinder) { Object result = bindAggregate(name, target, elementBinder); Supplier value = target.getValue(); if (result == null || value == null) { @@ -68,7 +71,7 @@ abstract class AggregateBinder { * @param elementBinder an element binder * @return the bound result */ - protected abstract Object bindAggregate(ConfigurationPropertyName name, Bindable target, + protected abstract @Nullable Object bindAggregate(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder); /** @@ -96,7 +99,7 @@ abstract class AggregateBinder { private final Supplier supplier; - private T supplied; + private @Nullable T supplied; public AggregateSupplier(Supplier supplier) { this.supplier = supplier; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateElementBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateElementBinder.java index 644ba4657d4..9b60a6606b6 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateElementBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/AggregateElementBinder.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -35,7 +37,7 @@ interface AggregateElementBinder { * @param target the target bindable * @return a bound object or {@code null} */ - default Object bind(ConfigurationPropertyName name, Bindable target) { + default @Nullable Object bind(ConfigurationPropertyName name, Bindable target) { return bind(name, target, null); } @@ -47,6 +49,6 @@ interface AggregateElementBinder { * @param source the source of the elements or {@code null} to use all sources * @return a bound object or {@code null} */ - Object bind(ConfigurationPropertyName name, Bindable target, ConfigurationPropertySource source); + @Nullable Object bind(ConfigurationPropertyName name, Bindable target, @Nullable ConfigurationPropertySource source); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ArrayBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ArrayBinder.java index e9f44c4166f..28031bc44ea 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ArrayBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ArrayBinder.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.core.ResolvableType; @@ -38,7 +40,7 @@ class ArrayBinder extends IndexedElementsBinder { } @Override - protected Object bindAggregate(ConfigurationPropertyName name, Bindable target, + protected @Nullable Object bindAggregate(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder) { IndexedCollectionSupplier result = new IndexedCollectionSupplier(ArrayList::new); ResolvableType aggregateType = target.getType(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java index 55f7b18a6d8..71e26ccbd8d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConstructorProvider.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.bind; import java.lang.reflect.Constructor; +import org.jspecify.annotations.Nullable; + /** * Strategy interface used to determine a specific constructor to use when binding. * @@ -42,7 +44,7 @@ public interface BindConstructorProvider { * @return the bind constructor or {@code null} * @since 3.0.0 */ - default Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { + default @Nullable Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { return getBindConstructor(Bindable.of(type), isNestedConstructorBinding); } @@ -54,6 +56,6 @@ public interface BindConstructorProvider { * binding * @return the bind constructor or {@code null} */ - Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding); + @Nullable Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindContext.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindContext.java index f4921a4d881..30607f68850 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindContext.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; @@ -53,6 +55,6 @@ public interface BindContext { * the property has not yet been determined. * @return the configuration property (may be {@code null}). */ - ConfigurationProperty getConfigurationProperty(); + @Nullable ConfigurationProperty getConfigurationProperty(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java index 2e1c839830e..29ac4e3fb23 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindConverter.java @@ -27,6 +27,8 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeanUtils; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.SimpleTypeConverter; @@ -43,6 +45,7 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.Resource; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** @@ -54,12 +57,12 @@ import org.springframework.util.CollectionUtils; */ final class BindConverter { - private static BindConverter sharedInstance; + private static @Nullable BindConverter sharedInstance; private final List delegates; - private BindConverter(List conversionServices, - Consumer propertyEditorInitializer) { + private BindConverter(@Nullable List conversionServices, + @Nullable Consumer propertyEditorInitializer) { List delegates = new ArrayList<>(); delegates.add(new TypeConverterConversionService(propertyEditorInitializer)); boolean hasApplication = false; @@ -75,12 +78,12 @@ final class BindConverter { this.delegates = Collections.unmodifiableList(delegates); } - boolean canConvert(Object source, ResolvableType targetType, Annotation... targetAnnotations) { + boolean canConvert(@Nullable Object source, ResolvableType targetType, Annotation... targetAnnotations) { return canConvert(TypeDescriptor.forObject(source), new ResolvableTypeDescriptor(targetType, targetAnnotations)); } - private boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + private boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { for (ConversionService service : this.delegates) { if (service.canConvert(sourceType, targetType)) { return true; @@ -89,12 +92,12 @@ final class BindConverter { return false; } - T convert(Object source, Bindable target) { + @Nullable T convert(@Nullable Object source, Bindable target) { return convert(source, target.getType(), target.getAnnotations()); } @SuppressWarnings("unchecked") - T convert(Object source, ResolvableType targetType, Annotation... targetAnnotations) { + @Nullable T convert(@Nullable Object source, ResolvableType targetType, Annotation... targetAnnotations) { if (source == null) { return null; } @@ -102,7 +105,7 @@ final class BindConverter { new ResolvableTypeDescriptor(targetType, targetAnnotations)); } - private Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + private @Nullable Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { ConversionException failure = null; for (ConversionService delegate : this.delegates) { try { @@ -119,8 +122,8 @@ final class BindConverter { throw (failure != null) ? failure : new ConverterNotFoundException(sourceType, targetType); } - static BindConverter get(List conversionServices, - Consumer propertyEditorInitializer) { + static BindConverter get(@Nullable List conversionServices, + @Nullable Consumer propertyEditorInitializer) { boolean sharedApplicationConversionService = (conversionServices == null) || (conversionServices.size() == 1 && conversionServices.get(0) == ApplicationConversionService.getSharedInstance()); if (propertyEditorInitializer == null && sharedApplicationConversionService) { @@ -154,20 +157,29 @@ final class BindConverter { */ private static class TypeConverterConversionService extends GenericConversionService { - TypeConverterConversionService(Consumer initializer) { + TypeConverterConversionService(@Nullable Consumer initializer) { ApplicationConversionService.addDelimitedStringConverters(this); addConverter(new TypeConverterConverter(initializer)); } @Override - public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) { + public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { // Prefer conversion service to handle things like String to char[]. - if (targetType.isArray() && targetType.getElementTypeDescriptor().isPrimitive()) { + if (isPrimitiveArray(targetType)) { return false; } return super.canConvert(sourceType, targetType); } + private static boolean isPrimitiveArray(TypeDescriptor targetType) { + if (!targetType.isArray()) { + return false; + } + TypeDescriptor elementTypeDescriptor = targetType.getElementTypeDescriptor(); + Assert.state(elementTypeDescriptor != null, "'elementTypeDescriptor' must not be null"); + return elementTypeDescriptor.isPrimitive(); + } + } /** @@ -184,13 +196,13 @@ final class BindConverter { EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded); } - private final Consumer initializer; + private final @Nullable Consumer initializer; // SimpleTypeConverter is not thread-safe to use for conversion but we can use it // in a thread-safe way to check if conversion is possible. private final SimpleTypeConverter matchesOnlyTypeConverter; - TypeConverterConverter(Consumer initializer) { + TypeConverterConverter(@Nullable Consumer initializer) { this.initializer = initializer; this.matchesOnlyTypeConverter = createTypeConverter(); } @@ -225,7 +237,7 @@ final class BindConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return createTypeConverter().convertIfNecessary(source, targetType.getType(), targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindException.java index 760f8bb5265..b90c8176f74 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.origin.Origin; @@ -32,11 +34,12 @@ public class BindException extends RuntimeException implements OriginProvider { private final Bindable target; - private final ConfigurationProperty property; + private final @Nullable ConfigurationProperty property; private final ConfigurationPropertyName name; - BindException(ConfigurationPropertyName name, Bindable target, ConfigurationProperty property, Throwable cause) { + BindException(ConfigurationPropertyName name, Bindable target, @Nullable ConfigurationProperty property, + @Nullable Throwable cause) { super(buildMessage(name, target), cause); this.name = name; this.target = target; @@ -63,16 +66,16 @@ public class BindException extends RuntimeException implements OriginProvider { * Return the configuration property name of the item that was being bound. * @return the configuration property name */ - public ConfigurationProperty getProperty() { + public @Nullable ConfigurationProperty getProperty() { return this.property; } @Override - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return Origin.from(this.name); } - private static String buildMessage(ConfigurationPropertyName name, Bindable target) { + private static String buildMessage(@Nullable ConfigurationPropertyName name, Bindable target) { StringBuilder message = new StringBuilder(); message.append("Failed to bind properties"); message.append((name != null) ? " under '" + name + "'" : ""); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindHandler.java index 67188e27217..bb90fe85ea1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindHandler.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertyName; /** @@ -43,7 +45,7 @@ public interface BindHandler { * @param context the bind context * @return the actual item that should be used for binding (may be {@code null}) */ - default Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { + default @Nullable Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { return target; } @@ -56,7 +58,8 @@ public interface BindHandler { * @param result the bound result (never {@code null}) * @return the actual result that should be used (may be {@code null}) */ - default Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + default @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, + Object result) { return result; } @@ -86,8 +89,8 @@ public interface BindHandler { * @return the actual result that should be used (may be {@code null}). * @throws Exception if the binding isn't valid */ - default Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + default @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { throw error; } @@ -101,8 +104,8 @@ public interface BindHandler { * @param result the bound result (may be {@code null}) * @throws Exception if the binding isn't valid */ - default void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) - throws Exception { + default void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, + @Nullable Object result) throws Exception { } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindResult.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindResult.java index b70c50cb77f..e8c81c74b9a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindResult.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindResult.java @@ -21,6 +21,9 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -37,9 +40,9 @@ public final class BindResult { private static final BindResult UNBOUND = new BindResult<>(null); - private final T value; + private final @Nullable T value; - private BindResult(T value) { + private BindResult(@Nullable T value) { this.value = value; } @@ -97,7 +100,8 @@ public final class BindResult { * {@code null}) * @return the value, if bound, otherwise {@code other} */ - public T orElse(T other) { + @Contract("!null -> !null") + public @Nullable T orElse(@Nullable T other) { return (this.value != null) ? this.value : other; } @@ -144,7 +148,7 @@ public final class BindResult { } @SuppressWarnings("unchecked") - static BindResult of(T value) { + static BindResult of(@Nullable T value) { if (value == null) { return (BindResult) UNBOUND; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java index 57886303c09..52d8477d9b9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Bindable.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Set; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.core.ResolvableType; import org.springframework.core.style.ToStringCreator; @@ -51,16 +53,16 @@ public final class Bindable { private final ResolvableType boxedType; - private final Supplier value; + private final @Nullable Supplier value; private final Annotation[] annotations; private final EnumSet bindRestrictions; - private final BindMethod bindMethod; + private final @Nullable BindMethod bindMethod; - private Bindable(ResolvableType type, ResolvableType boxedType, Supplier value, Annotation[] annotations, - EnumSet bindRestrictions, BindMethod bindMethod) { + private Bindable(ResolvableType type, ResolvableType boxedType, @Nullable Supplier value, + Annotation[] annotations, EnumSet bindRestrictions, @Nullable BindMethod bindMethod) { this.type = type; this.boxedType = boxedType; this.value = value; @@ -89,7 +91,7 @@ public final class Bindable { * Return a supplier that provides the object value or {@code null}. * @return the value or {@code null} */ - public Supplier getValue() { + public @Nullable Supplier getValue() { return this.value; } @@ -108,7 +110,7 @@ public final class Bindable { * @return the associated annotation or {@code null} */ @SuppressWarnings("unchecked") - public A getAnnotation(Class type) { + public @Nullable A getAnnotation(Class type) { for (Annotation annotation : this.annotations) { if (type.isInstance(annotation)) { return (A) annotation; @@ -133,7 +135,7 @@ public final class Bindable { * @return the bind method or {@code null} * @since 3.0.8 */ - public BindMethod getBindMethod() { + public @Nullable BindMethod getBindMethod() { return this.bindMethod; } @@ -169,7 +171,7 @@ public final class Bindable { return creator.toString(); } - private boolean nullSafeEquals(Object o1, Object o2) { + private boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { return ObjectUtils.nullSafeEquals(o1, o2); } @@ -178,7 +180,7 @@ public final class Bindable { * @param annotations the annotations * @return an updated {@link Bindable} */ - public Bindable withAnnotations(Annotation... annotations) { + public Bindable withAnnotations(Annotation @Nullable ... annotations) { return new Bindable<>(this.type, this.boxedType, this.value, (annotations != null) ? annotations : NO_ANNOTATIONS, NO_BIND_RESTRICTIONS, this.bindMethod); } @@ -189,9 +191,8 @@ public final class Bindable { * @param existingValue the existing value * @return an updated {@link Bindable} */ - public Bindable withExistingValue(T existingValue) { - Assert.isTrue( - existingValue == null || this.type.isArray() || this.boxedType.resolve().isInstance(existingValue), + public Bindable withExistingValue(@Nullable T existingValue) { + Assert.isTrue(existingValue == null || this.type.isArray() || boxedTypeIsInstanceOf(existingValue), () -> "'existingValue' must be an instance of " + this.type); Assert.state(this.bindMethod != BindMethod.VALUE_OBJECT, () -> "An existing value cannot be provided when binding as a value object"); @@ -200,12 +201,17 @@ public final class Bindable { BindMethod.JAVA_BEAN); } + private boolean boxedTypeIsInstanceOf(T existingValue) { + Class resolved = this.boxedType.resolve(); + return resolved != null && resolved.isInstance(existingValue); + } + /** * Create an updated {@link Bindable} instance with a value supplier. * @param suppliedValue the supplier for the value * @return an updated {@link Bindable} */ - public Bindable withSuppliedValue(Supplier suppliedValue) { + public Bindable withSuppliedValue(@Nullable Supplier suppliedValue) { return new Bindable<>(this.type, this.boxedType, suppliedValue, this.annotations, this.bindRestrictions, this.bindMethod); } @@ -231,7 +237,7 @@ public final class Bindable { * @return an updated {@link Bindable} * @since 3.0.8 */ - public Bindable withBindMethod(BindMethod bindMethod) { + public Bindable withBindMethod(@Nullable BindMethod bindMethod) { Assert.state(bindMethod != BindMethod.VALUE_OBJECT || this.value == null, () -> "Value object binding cannot be used with an existing or supplied value"); return new Bindable<>(this.type, this.boxedType, this.value, this.annotations, this.bindRestrictions, diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java index 06474e3e2df..717158df3fa 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java @@ -31,6 +31,7 @@ import kotlin.jvm.JvmClassMappingKt; import kotlin.reflect.KClass; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; @@ -83,7 +84,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { } @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { registerHints(hints); } @@ -149,7 +150,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { private final Class type; - private final Constructor bindConstructor; + private final @Nullable Constructor bindConstructor; private final BeanProperties bean; @@ -160,14 +161,25 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { } private Processor(Bindable bindable, boolean nestedType, Set> seen) { - this.type = bindable.getType().getRawClass(); + this.type = getRawClass(bindable); this.bindConstructor = (bindable.getBindMethod() != BindMethod.JAVA_BEAN) - ? BindConstructorProvider.DEFAULT.getBindConstructor(bindable.getType().resolve(), nestedType) - : null; + ? BindConstructorProvider.DEFAULT.getBindConstructor(getBindableType(bindable), nestedType) : null; this.bean = JavaBeanBinder.BeanProperties.of(bindable); this.seen = seen; } + private static Class getBindableType(Bindable bindable) { + Class resolved = bindable.getType().resolve(); + Assert.state(resolved != null, "'resolved' must not be null"); + return resolved; + } + + private static Class getRawClass(Bindable bindable) { + Class rawClass = bindable.getType().getRawClass(); + Assert.state(rawClass != null, "'rawClass' must not be null"); + return rawClass; + } + void process(ReflectionHints hints) { if (this.seen.contains(this.type)) { return; @@ -203,6 +215,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { } private void handleValueObjectProperties(ReflectionHints hints) { + Assert.state(this.bindConstructor != null, "'bindConstructor' must not be null"); for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) { String propertyName = this.bindConstructor.getParameters()[i].getName(); ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i); @@ -253,7 +266,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { new Processor(Bindable.of(type), true, this.seen).process(hints); } - private Class getComponentClass(ResolvableType type) { + private @Nullable Class getComponentClass(ResolvableType type) { ResolvableType componentType = getComponentType(type); if (componentType == null) { return null; @@ -265,7 +278,7 @@ public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar { return componentType.toClass(); } - private ResolvableType getComponentType(ResolvableType type) { + private @Nullable ResolvableType getComponentType(ResolvableType type) { if (type.isArray()) { return type.getComponentType(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index 1a4dea7045c..0e4dc6d1248 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -30,6 +30,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.PropertyEditorRegistry; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.context.properties.bind.Bindable.BindRestriction; @@ -44,6 +46,7 @@ import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.env.Environment; import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; @@ -80,7 +83,12 @@ public class Binder { * @param sources the sources used for binding */ public Binder(ConfigurationPropertySource... sources) { - this((sources != null) ? Arrays.asList(sources) : null, null, null, null); + this(sourcesAsList(sources), null, null, null); + } + + private static List sourcesAsList(ConfigurationPropertySource[] sources) { + Assert.notNull(sources, "'sources' must not be null"); + return Arrays.asList(sources); } /** @@ -97,7 +105,7 @@ public class Binder { * @param sources the sources used for binding * @param placeholdersResolver strategy to resolve any property placeholders */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver) { this(sources, placeholdersResolver, null, null); } @@ -108,8 +116,8 @@ public class Binder { * @param conversionService the conversion service to convert values (or {@code null} * to use {@link ApplicationConversionService}) */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, - ConversionService conversionService) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver, + @Nullable ConversionService conversionService) { this(sources, placeholdersResolver, conversionService, null); } @@ -123,8 +131,9 @@ public class Binder { * that can convert values (or {@code null} if no initialization is required). Often * used to call {@link ConfigurableListableBeanFactory#copyRegisteredEditorsTo}. */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, - ConversionService conversionService, Consumer propertyEditorInitializer) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver, + @Nullable ConversionService conversionService, + @Nullable Consumer propertyEditorInitializer) { this(sources, placeholdersResolver, conversionService, propertyEditorInitializer, null); } @@ -141,9 +150,10 @@ public class Binder { * binding * @since 2.2.0 */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, - ConversionService conversionService, Consumer propertyEditorInitializer, - BindHandler defaultBindHandler) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver, + @Nullable ConversionService conversionService, + @Nullable Consumer propertyEditorInitializer, + @Nullable BindHandler defaultBindHandler) { this(sources, placeholdersResolver, conversionService, propertyEditorInitializer, defaultBindHandler, null); } @@ -162,9 +172,10 @@ public class Binder { * constructor to use when binding * @since 2.2.1 */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, - ConversionService conversionService, Consumer propertyEditorInitializer, - BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver, + @Nullable ConversionService conversionService, + @Nullable Consumer propertyEditorInitializer, + @Nullable BindHandler defaultBindHandler, @Nullable BindConstructorProvider constructorProvider) { this(sources, placeholdersResolver, (conversionService != null) ? Collections.singletonList(conversionService) : (List) null, @@ -186,9 +197,10 @@ public class Binder { * constructor to use when binding * @since 2.5.0 */ - public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, - List conversionServices, Consumer propertyEditorInitializer, - BindHandler defaultBindHandler, BindConstructorProvider constructorProvider) { + public Binder(Iterable sources, @Nullable PlaceholdersResolver placeholdersResolver, + @Nullable List conversionServices, + @Nullable Consumer propertyEditorInitializer, + @Nullable BindHandler defaultBindHandler, @Nullable BindConstructorProvider constructorProvider) { Assert.notNull(sources, "'sources' must not be null"); for (ConfigurationPropertySource source : sources) { Assert.notNull(source, "'sources' must not contain null elements"); @@ -258,7 +270,7 @@ public class Binder { * @param the bound type * @return the binding result (never {@code null}) */ - public BindResult bind(String name, Bindable target, BindHandler handler) { + public BindResult bind(String name, Bindable target, @Nullable BindHandler handler) { return bind(ConfigurationPropertyName.of(name), target, handler); } @@ -271,7 +283,7 @@ public class Binder { * @param the bound type * @return the binding result (never {@code null}) */ - public BindResult bind(ConfigurationPropertyName name, Bindable target, BindHandler handler) { + public BindResult bind(ConfigurationPropertyName name, Bindable target, @Nullable BindHandler handler) { T bound = bind(name, target, handler, false); return BindResult.of(bound); } @@ -333,11 +345,14 @@ public class Binder { * @return the bound or created object * @since 2.2.0 */ - public T bindOrCreate(ConfigurationPropertyName name, Bindable target, BindHandler handler) { + @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/1232 + public T bindOrCreate(ConfigurationPropertyName name, Bindable target, @Nullable BindHandler handler) { return bind(name, target, handler, true); } - private T bind(ConfigurationPropertyName name, Bindable target, BindHandler handler, boolean create) { + @Contract("_, _, _, true -> !null") + private @Nullable T bind(ConfigurationPropertyName name, Bindable target, @Nullable BindHandler handler, + boolean create) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(target, "'target' must not be null"); handler = (handler != null) ? handler : this.defaultBindHandler; @@ -345,8 +360,9 @@ public class Binder { return bind(name, target, handler, context, false, create); } - private T bind(ConfigurationPropertyName name, Bindable target, BindHandler handler, Context context, - boolean allowRecursiveBinding, boolean create) { + @Contract("_, _, _, _, _, true -> !null") + private @Nullable T bind(ConfigurationPropertyName name, Bindable target, BindHandler handler, + Context context, boolean allowRecursiveBinding, boolean create) { try (ConfigurationPropertyCaching.CacheOverride cacheOverride = this.configurationPropertyCaching.override()) { try { Bindable replacementTarget = handler.onStart(name, target, context); @@ -363,8 +379,9 @@ public class Binder { } } - private T handleBindResult(ConfigurationPropertyName name, Bindable target, BindHandler handler, - Context context, Object result, boolean create) throws Exception { + @Contract("_, _, _, _, _, true -> null") + private @Nullable T handleBindResult(ConfigurationPropertyName name, Bindable target, BindHandler handler, + Context context, @Nullable Object result, boolean create) throws Exception { if (result != null) { result = handler.onSuccess(name, target, context, result); result = context.getConverter().convert(result, target); @@ -372,21 +389,23 @@ public class Binder { if (result == null && create) { result = fromDataObjectBinders(target.getBindMethod(), (dataObjectBinder) -> dataObjectBinder.create(target, context)); - result = handler.onCreate(name, target, context, result); - result = context.getConverter().convert(result, target); if (result == null) { IllegalStateException ex = new IllegalStateException( "Unable to create instance for " + target.getType()); - this.dataObjectBinders.get(target.getBindMethod()) + List dataObjectBinders = this.dataObjectBinders.get(target.getBindMethod()); + Assert.state(dataObjectBinders != null, "'dataObjectBinders' must not be null"); + dataObjectBinders .forEach((dataObjectBinder) -> dataObjectBinder.onUnableToCreateInstance(target, context, ex)); throw ex; } + result = handler.onCreate(name, target, context, result); + result = context.getConverter().convert(result, target); } handler.onFinish(name, target, context, result); return context.getConverter().convert(result, target); } - private T handleBindError(ConfigurationPropertyName name, Bindable target, BindHandler handler, + private @Nullable T handleBindError(ConfigurationPropertyName name, Bindable target, BindHandler handler, Context context, Exception error) { try { Object result = handler.onFailure(name, target, context, error); @@ -400,7 +419,7 @@ public class Binder { } } - private Object bindObject(ConfigurationPropertyName name, Bindable target, BindHandler handler, + private @Nullable Object bindObject(ConfigurationPropertyName name, Bindable target, BindHandler handler, Context context, boolean allowRecursiveBinding) { ConfigurationProperty property = findProperty(name, target, context); if (property == null && context.depth != 0 && containsNoDescendantOf(context.getSources(), name)) { @@ -426,7 +445,7 @@ public class Binder { return bindDataObject(name, target, handler, context, allowRecursiveBinding); } - private AggregateBinder getAggregateBinder(Bindable target, Context context) { + private @Nullable AggregateBinder getAggregateBinder(Bindable target, Context context) { Class resolvedType = target.getType().resolve(Object.class); if (Map.class.isAssignableFrom(resolvedType)) { return new MapBinder(context); @@ -450,7 +469,7 @@ public class Binder { return context.withIncreasedDepth(() -> aggregateBinder.bind(name, target, elementBinder)); } - private ConfigurationProperty findProperty(ConfigurationPropertyName name, Bindable target, + private @Nullable ConfigurationProperty findProperty(ConfigurationPropertyName name, Bindable target, Context context) { if (name.isEmpty() || target.hasBindRestriction(BindRestriction.NO_DIRECT_PROPERTY)) { return null; @@ -464,7 +483,7 @@ public class Binder { return null; } - private Object bindProperty(Bindable target, Context context, ConfigurationProperty property) { + private @Nullable Object bindProperty(Bindable target, Context context, ConfigurationProperty property) { context.setConfigurationProperty(property); Object result = property.getValue(); result = this.placeholdersResolver.resolvePlaceholders(result); @@ -472,7 +491,7 @@ public class Binder { return result; } - private Object bindDataObject(ConfigurationPropertyName name, Bindable target, BindHandler handler, + private @Nullable Object bindDataObject(ConfigurationPropertyName name, Bindable target, BindHandler handler, Context context, boolean allowRecursiveBinding) { if (isUnbindableBean(name, target, context)) { return null; @@ -488,8 +507,11 @@ public class Binder { (dataObjectBinder) -> dataObjectBinder.bind(name, target, context, propertyBinder))); } - private Object fromDataObjectBinders(BindMethod bindMethod, Function operation) { - for (DataObjectBinder dataObjectBinder : this.dataObjectBinders.get(bindMethod)) { + private @Nullable Object fromDataObjectBinders(@Nullable BindMethod bindMethod, + Function operation) { + List dataObjectBinders = this.dataObjectBinders.get(bindMethod); + Assert.state(dataObjectBinders != null, "'dataObjectBinders' must not be null"); + for (DataObjectBinder dataObjectBinder : dataObjectBinders) { Object bound = operation.apply(dataObjectBinder); if (bound != null) { return bound; @@ -541,7 +563,7 @@ public class Binder { * @return a {@link Binder} instance * @since 2.2.0 */ - public static Binder get(Environment environment, BindHandler defaultBindHandler) { + public static Binder get(Environment environment, @Nullable BindHandler defaultBindHandler) { Iterable sources = ConfigurationPropertySources.get(environment); PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment); return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler); @@ -562,7 +584,7 @@ public class Binder { private final Deque> constructorBindings = new ArrayDeque<>(); - private ConfigurationProperty configurationProperty; + private @Nullable ConfigurationProperty configurationProperty; private void increaseDepth() { this.depth++; @@ -572,7 +594,7 @@ public class Binder { this.depth--; } - private T withSource(ConfigurationPropertySource source, Supplier supplier) { + private T withSource(@Nullable ConfigurationPropertySource source, Supplier supplier) { if (source == null) { return supplier.get(); } @@ -663,7 +685,7 @@ public class Binder { } @Override - public ConfigurationProperty getConfigurationProperty() { + public @Nullable ConfigurationProperty getConfigurationProperty() { return this.configurationProperty; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java index 931e948256b..a9493c09865 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BoundPropertiesTrackingBindHandler.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.bind; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.util.Assert; @@ -38,7 +40,8 @@ public class BoundPropertiesTrackingBindHandler extends AbstractBindHandler { } @Override - public Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + public @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, + Object result) { if (context.getConfigurationProperty() != null && name.equals(context.getConfigurationProperty().getName())) { this.consumer.accept(context.getConfigurationProperty()); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java index 6e00979f427..8625a38f10d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/CollectionBinder.java @@ -20,6 +20,8 @@ import java.util.Collection; import java.util.List; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.core.CollectionFactory; @@ -38,7 +40,7 @@ class CollectionBinder extends IndexedElementsBinder> { } @Override - protected Object bindAggregate(ConfigurationPropertyName name, Bindable target, + protected @Nullable Object bindAggregate(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder) { ResolvableType aggregateType = ResolvableType.forClassWithGenerics(List.class, target.getType().asCollection().getGenerics()); @@ -68,7 +70,7 @@ class CollectionBinder extends IndexedElementsBinder> { } } - private Collection getExistingIfPossible(Supplier> existing) { + private @Nullable Collection getExistingIfPossible(Supplier> existing) { try { return existing.get(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectBinder.java index 75e366ee3b2..dfd41d8616d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectBinder.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -40,7 +42,7 @@ interface DataObjectBinder { * @param propertyBinder property binder * @return a bound instance or {@code null} */ - T bind(ConfigurationPropertyName name, Bindable target, Context context, + @Nullable T bind(ConfigurationPropertyName name, Bindable target, Context context, DataObjectPropertyBinder propertyBinder); /** @@ -51,7 +53,7 @@ interface DataObjectBinder { * @param context the bind context * @return the created instance */ - T create(Bindable target, Context context); + @Nullable T create(Bindable target, Context context); /** * Callback that can be used to add additional suppressed exceptions when an instance diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectPropertyBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectPropertyBinder.java index c76f2a0c9d9..ee7f21d475c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectPropertyBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DataObjectPropertyBinder.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + /** * Binder that can be used by {@link DataObjectBinder} implementations to bind the data * object properties. @@ -32,6 +34,6 @@ interface DataObjectPropertyBinder { * @param target the target bindable * @return the bound value or {@code null} */ - Object bindProperty(String propertyName, Bindable target); + @Nullable Object bindProperty(String propertyName, Bindable target); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java index b6b2cfd6395..4f5d0ef5435 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultBindConstructorProvider.java @@ -21,6 +21,8 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.KotlinDetector; @@ -38,7 +40,7 @@ import org.springframework.util.ClassUtils; class DefaultBindConstructorProvider implements BindConstructorProvider { @Override - public Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding) { + public @Nullable Constructor getBindConstructor(Bindable bindable, boolean isNestedConstructorBinding) { Constructors constructors = Constructors.getConstructors(bindable.getType().resolve(), isNestedConstructorBinding); if (constructors.getBind() != null && constructors.isDeducedBindConstructor() @@ -51,7 +53,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { } @Override - public Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { + public @Nullable Constructor getBindConstructor(Class type, boolean isNestedConstructorBinding) { Constructors constructors = Constructors.getConstructors(type, isNestedConstructorBinding); return constructors.getBind(); } @@ -65,13 +67,13 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { private final boolean hasAutowired; - private final Constructor bind; + private final @Nullable Constructor bind; private final boolean deducedBindConstructor; private final boolean immutableType; - private Constructors(boolean hasAutowired, Constructor bind, boolean deducedBindConstructor, + private Constructors(boolean hasAutowired, @Nullable Constructor bind, boolean deducedBindConstructor, boolean immutableType) { this.hasAutowired = hasAutowired; this.bind = bind; @@ -83,7 +85,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { return this.hasAutowired; } - Constructor getBind() { + @Nullable Constructor getBind() { return this.bind; } @@ -95,7 +97,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { return this.immutableType; } - static Constructors getConstructors(Class type, boolean isNestedConstructorBinding) { + static Constructors getConstructors(@Nullable Class type, boolean isNestedConstructorBinding) { if (type == null) { return NONE; } @@ -160,8 +162,8 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { return candidateAnnotations; } - private static Constructor getConstructorBindingAnnotated(Class type, Constructor[] candidates, - MergedAnnotations[] mergedAnnotations) { + private static @Nullable Constructor getConstructorBindingAnnotated(Class type, + Constructor[] candidates, MergedAnnotations[] mergedAnnotations) { Constructor result = null; for (int i = 0; i < candidates.length; i++) { if (mergedAnnotations[i].isPresent(ConstructorBinding.class)) { @@ -176,7 +178,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { } - private static Constructor deduceBindConstructor(Class type, Constructor[] candidates) { + private static @Nullable Constructor deduceBindConstructor(Class type, Constructor[] candidates) { if (candidates.length == 1 && candidates[0].getParameterCount() > 0) { if (type.isMemberClass() && Modifier.isPrivate(candidates[0].getModifiers())) { return null; @@ -199,7 +201,7 @@ class DefaultBindConstructorProvider implements BindConstructorProvider { return KotlinDetector.isKotlinPresent() && KotlinDetector.isKotlinType(type); } - private static Constructor deduceKotlinBindConstructor(Class type) { + private static @Nullable Constructor deduceKotlinBindConstructor(Class type) { Constructor primaryConstructor = BeanUtils.findPrimaryConstructor(type); if (primaryConstructor != null && primaryConstructor.getParameterCount() > 0) { return primaryConstructor; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java index de5890e688e..ade04745e8e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java @@ -24,6 +24,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -55,7 +57,7 @@ abstract class IndexedElementsBinder extends AggregateBinder { } @Override - protected boolean isAllowRecursiveBinding(ConfigurationPropertySource source) { + protected boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source) { return source == null || source instanceof IterableConfigurationPropertySource; } @@ -93,7 +95,7 @@ abstract class IndexedElementsBinder extends AggregateBinder { } private void bindValue(Bindable target, Collection collection, ResolvableType aggregateType, - ResolvableType elementType, Object value) { + ResolvableType elementType, @Nullable Object value) { if (value == null || (value instanceof CharSequence charSequence && charSequence.isEmpty())) { return; } @@ -157,7 +159,7 @@ abstract class IndexedElementsBinder extends AggregateBinder { return root.append((i < INDEXES.length) ? INDEXES[i] : "[" + i + "]"); } - private C convert(Object value, ResolvableType type, Annotation... annotations) { + private @Nullable C convert(@Nullable Object value, ResolvableType type, Annotation... annotations) { value = getContext().getPlaceholdersResolver().resolvePlaceholders(value); return getContext().getConverter().convert(value, type, annotations); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java index ae1c24a0d1f..92cdbced79d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/JavaBeanBinder.java @@ -33,6 +33,8 @@ import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeanUtils; import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -41,6 +43,7 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS import org.springframework.core.BridgeMethodResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; /** * {@link DataObjectBinder} for mutable Java Beans. @@ -57,7 +60,7 @@ class JavaBeanBinder implements DataObjectBinder { static final JavaBeanBinder INSTANCE = new JavaBeanBinder(); @Override - public T bind(ConfigurationPropertyName name, Bindable target, Context context, + public @Nullable T bind(ConfigurationPropertyName name, Bindable target, Context context, DataObjectPropertyBinder propertyBinder) { boolean hasKnownBindableProperties = target.getValue() != null && hasKnownBindableProperties(name, context); Bean bean = Bean.get(target, context, hasKnownBindableProperties); @@ -71,7 +74,7 @@ class JavaBeanBinder implements DataObjectBinder { @Override @SuppressWarnings("unchecked") - public T create(Bindable target, Context context) { + public @Nullable T create(Bindable target, Context context) { Class type = (Class) target.getType().resolve(); return (type != null) ? BeanUtils.instantiateClass(type) : null; } @@ -186,18 +189,17 @@ class JavaBeanBinder implements DataObjectBinder { } protected void addProperties(Method[] declaredMethods, Field[] declaredFields) { + @Nullable Method[] methods = new Method[declaredMethods.length]; for (int i = 0; i < declaredMethods.length; i++) { - if (!isCandidate(declaredMethods[i])) { - declaredMethods[i] = null; - } + methods[i] = isCandidate(declaredMethods[i]) ? declaredMethods[i] : null; } - for (Method method : declaredMethods) { + for (Method method : methods) { addMethodIfPossible(method, "is", 0, BeanProperty::addGetter); } - for (Method method : declaredMethods) { + for (Method method : methods) { addMethodIfPossible(method, "get", 0, BeanProperty::addGetter); } - for (Method method : declaredMethods) { + for (Method method : methods) { addMethodIfPossible(method, "set", 1, BeanProperty::addSetter); } for (Field field : declaredFields) { @@ -213,7 +215,7 @@ class JavaBeanBinder implements DataObjectBinder { && !Class.class.equals(method.getDeclaringClass()) && method.getName().indexOf('$') == -1; } - private void addMethodIfPossible(Method method, String prefix, int parameterCount, + private void addMethodIfPossible(@Nullable Method method, String prefix, int parameterCount, BiConsumer consumer) { if (method != null && method.getParameterCount() == parameterCount && method.getName().startsWith(prefix) && method.getName().length() > prefix.length()) { @@ -279,7 +281,7 @@ class JavaBeanBinder implements DataObjectBinder { } @SuppressWarnings("unchecked") - static Bean get(Bindable bindable, Context context, boolean canCallGetValue) { + static @Nullable Bean get(Bindable bindable, Context context, boolean canCallGetValue) { ResolvableType type = bindable.getType(); Class resolvedType = type.resolve(Object.class); Supplier value = bindable.getValue(); @@ -334,7 +336,7 @@ class JavaBeanBinder implements DataObjectBinder { private final Supplier factory; - private T instance; + private @Nullable T instance; BeanSupplier(Supplier factory) { this.factory = factory; @@ -359,11 +361,11 @@ class JavaBeanBinder implements DataObjectBinder { private final ResolvableType declaringClassType; - private Method getter; + private @Nullable Method getter; - private Method setter; + private @Nullable Method setter; - private Field field; + private @Nullable Field field; BeanProperty(String name, ResolvableType declaringClassType) { this.name = DataObjectPropertyName.toDashedForm(name); @@ -401,11 +403,12 @@ class JavaBeanBinder implements DataObjectBinder { MethodParameter methodParameter = new MethodParameter(this.setter, 0); return ResolvableType.forMethodParameter(methodParameter, this.declaringClassType); } + Assert.state(this.getter != null, "'getter' must not be null"); MethodParameter methodParameter = new MethodParameter(this.getter, -1); return ResolvableType.forMethodParameter(methodParameter, this.declaringClassType); } - Annotation[] getAnnotations() { + Annotation @Nullable [] getAnnotations() { try { return (this.field != null) ? this.field.getDeclaredAnnotations() : null; } @@ -414,11 +417,12 @@ class JavaBeanBinder implements DataObjectBinder { } } - Supplier getValue(Supplier instance) { + @Nullable Supplier getValue(Supplier instance) { if (this.getter == null) { return null; } return () -> { + Assert.state(this.getter != null, "'getter' must not be null"); try { this.getter.setAccessible(true); return this.getter.invoke(instance.get()); @@ -443,6 +447,7 @@ class JavaBeanBinder implements DataObjectBinder { } void setValue(Supplier instance, Object value) { + Assert.state(this.setter != null, "'setter' must not be null"); try { this.setter.setAccessible(true); this.setter.invoke(instance.get(), value); @@ -452,15 +457,15 @@ class JavaBeanBinder implements DataObjectBinder { } } - Method getGetter() { + @Nullable Method getGetter() { return this.getter; } - Method getSetter() { + @Nullable Method getSetter() { return this.setter; } - Field getField() { + @Nullable Field getField() { return this.field; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java index cadd2aec67f..f1e57fa32ee 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Properties; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.Binder.Context; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; @@ -47,12 +49,12 @@ class MapBinder extends AggregateBinder> { } @Override - protected boolean isAllowRecursiveBinding(ConfigurationPropertySource source) { + protected boolean isAllowRecursiveBinding(@Nullable ConfigurationPropertySource source) { return true; } @Override - protected Object bindAggregate(ConfigurationPropertyName name, Bindable target, + protected @Nullable Object bindAggregate(ConfigurationPropertyName name, Bindable target, AggregateElementBinder elementBinder) { Bindable resolvedTarget = resolveTarget(target); boolean hasDescendants = hasDescendants(name); @@ -119,7 +121,7 @@ class MapBinder extends AggregateBinder> { } } - private Map getExistingIfPossible(Supplier> existing) { + private @Nullable Map getExistingIfPossible(Supplier> existing) { try { return existing.get(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PlaceholdersResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PlaceholdersResolver.java index d585f2a995f..0881fb63b71 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PlaceholdersResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PlaceholdersResolver.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.PropertyResolver; /** @@ -39,6 +41,6 @@ public interface PlaceholdersResolver { * @param value the source value * @return a value with placeholders resolved */ - Object resolvePlaceholders(Object value); + @Nullable Object resolvePlaceholders(@Nullable Object value); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java index 5fb1586daf7..3863ae1e444 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/PropertySourcesPlaceholdersResolver.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; @@ -33,7 +35,7 @@ import org.springframework.util.SystemPropertyUtils; */ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver { - private final Iterable> sources; + private final @Nullable Iterable> sources; private final PropertyPlaceholderHelper helper; @@ -41,11 +43,12 @@ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver this(getSources(environment), null); } - public PropertySourcesPlaceholdersResolver(Iterable> sources) { + public PropertySourcesPlaceholdersResolver(@Nullable Iterable> sources) { this(sources, null); } - public PropertySourcesPlaceholdersResolver(Iterable> sources, PropertyPlaceholderHelper helper) { + public PropertySourcesPlaceholdersResolver(@Nullable Iterable> sources, + @Nullable PropertyPlaceholderHelper helper) { this.sources = sources; this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, @@ -54,14 +57,14 @@ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver } @Override - public Object resolvePlaceholders(Object value) { + public @Nullable Object resolvePlaceholders(@Nullable Object value) { if (value instanceof String string) { return this.helper.replacePlaceholders(string, this::resolvePlaceholder); } return value; } - protected String resolvePlaceholder(String placeholder) { + protected @Nullable String resolvePlaceholder(String placeholder) { if (this.sources != null) { for (PropertySource source : this.sources) { Object value = source.getProperty(placeholder); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java index 85194c9f535..cf86edaf2fd 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java @@ -37,6 +37,7 @@ import kotlin.reflect.KParameter; import kotlin.reflect.jvm.ReflectJvmMapping; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanUtils; import org.springframework.boot.context.properties.bind.Binder.Context; @@ -72,13 +73,15 @@ class ValueObjectBinder implements DataObjectBinder { } @Override - public T bind(ConfigurationPropertyName name, Bindable target, Binder.Context context, + public @Nullable T bind(ConfigurationPropertyName name, Bindable target, Binder.Context context, DataObjectPropertyBinder propertyBinder) { ValueObject valueObject = ValueObject.get(target, context, this.constructorProvider, Discoverer.LENIENT); if (valueObject == null) { return null; } - context.pushConstructorBoundTypes(target.getType().resolve()); + Class targetType = target.getType().resolve(); + Assert.state(targetType != null, "'targetType' must not be null"); + context.pushConstructorBoundTypes(targetType); List parameters = valueObject.getConstructorParameters(); List args = new ArrayList<>(parameters.size()); boolean bound = false; @@ -94,7 +97,7 @@ class ValueObjectBinder implements DataObjectBinder { } @Override - public T create(Bindable target, Binder.Context context) { + public @Nullable T create(Bindable target, Binder.Context context) { ValueObject valueObject = ValueObject.get(target, context, this.constructorProvider, Discoverer.LENIENT); if (valueObject == null) { return null; @@ -117,7 +120,7 @@ class ValueObjectBinder implements DataObjectBinder { } } - private T getDefaultValue(Binder.Context context, ConstructorParameter parameter) { + private @Nullable T getDefaultValue(Binder.Context context, ConstructorParameter parameter) { ResolvableType type = parameter.getType(); Annotation[] annotations = parameter.getAnnotations(); for (Annotation annotation : annotations) { @@ -132,7 +135,7 @@ class ValueObjectBinder implements DataObjectBinder { return null; } - private T convertDefaultValue(BindConverter converter, String[] defaultValue, ResolvableType type, + private @Nullable T convertDefaultValue(BindConverter converter, String[] defaultValue, ResolvableType type, Annotation[] annotations) { try { return converter.convert(defaultValue, type, annotations); @@ -147,7 +150,7 @@ class ValueObjectBinder implements DataObjectBinder { } @SuppressWarnings("unchecked") - private T getNewDefaultValueInstanceIfPossible(Binder.Context context, ResolvableType type) { + private @Nullable T getNewDefaultValueInstanceIfPossible(Binder.Context context, ResolvableType type) { Class resolved = (Class) type.resolve(); Assert.state(resolved == null || isEmptyDefaultValueAllowed(resolved), () -> "Parameter of type " + type + " must have a non-empty default value."); @@ -207,7 +210,7 @@ class ValueObjectBinder implements DataObjectBinder { abstract List getConstructorParameters(); @SuppressWarnings("unchecked") - static ValueObject get(Bindable bindable, Binder.Context context, + static @Nullable ValueObject get(Bindable bindable, Binder.Context context, BindConstructorProvider constructorProvider, ParameterNameDiscoverer parameterNameDiscoverer) { Class resolvedType = (Class) bindable.getType().resolve(); if (resolvedType == null || resolvedType.isEnum() || Modifier.isAbstract(resolvedType.getModifiers())) { @@ -224,7 +227,7 @@ class ValueObjectBinder implements DataObjectBinder { } @SuppressWarnings("unchecked") - private static ValueObject get(Bindable bindable, Binder.Context context, + private static @Nullable ValueObject get(Bindable bindable, Binder.Context context, BindConstructorProvider constructorProvider, ParameterNameDiscoverer parameterNameDiscoverer, Class resolvedType) { Constructor bindConstructor = constructorProvider.getBindConstructor(bindable, @@ -297,7 +300,7 @@ class ValueObjectBinder implements DataObjectBinder { return this.constructorParameters; } - static ValueObject get(Constructor bindConstructor, ResolvableType type, + static @Nullable ValueObject get(Constructor bindConstructor, ResolvableType type, ParameterNameDiscoverer parameterNameDiscoverer) { KFunction kotlinConstructor = ReflectJvmMapping.getKotlinFunction(bindConstructor); if (kotlinConstructor != null) { @@ -327,9 +330,9 @@ class ValueObjectBinder implements DataObjectBinder { } @SuppressWarnings("unchecked") - static ValueObject get(Constructor bindConstructor, ResolvableType type, + static @Nullable ValueObject get(Constructor bindConstructor, ResolvableType type, ParameterNameDiscoverer parameterNameDiscoverer) { - String[] names = parameterNameDiscoverer.getParameterNames(bindConstructor); + @Nullable String @Nullable [] names = parameterNameDiscoverer.getParameterNames(bindConstructor); if (names == null) { return null; } @@ -338,7 +341,7 @@ class ValueObjectBinder implements DataObjectBinder { } private static List parseConstructorParameters(Constructor constructor, - ResolvableType type, String[] names) { + ResolvableType type, @Nullable String[] names) { Parameter[] parameters = constructor.getParameters(); List result = new ArrayList<>(parameters.length); for (int i = 0; i < parameters.length; i++) { @@ -349,6 +352,7 @@ class ValueObjectBinder implements DataObjectBinder { ResolvableType parameterType = ResolvableType.forMethodParameter(new MethodParameter(constructor, i), type); Annotation[] annotations = parameters[i].getDeclaredAnnotations(); + Assert.state(name != null, "'name' must not be null"); result.add(new ConstructorParameter(name, parameterType, annotations)); } return Collections.unmodifiableList(result); @@ -373,7 +377,7 @@ class ValueObjectBinder implements DataObjectBinder { this.annotations = annotations; } - Object bind(DataObjectPropertyBinder propertyBinder) { + @Nullable Object bind(DataObjectPropertyBinder propertyBinder) { return propertyBinder.bindProperty(this.name, Bindable.of(this.type).withAnnotations(this.annotations)); } @@ -416,8 +420,8 @@ class ValueObjectBinder implements DataObjectBinder { } @Override - public String[] getParameterNames(Constructor constructor) { - String[] names = this.delegate.getParameterNames(constructor); + public @Nullable String @Nullable [] getParameterNames(Constructor constructor) { + @Nullable String @Nullable [] names = this.delegate.getParameterNames(constructor); if (names != null) { return names; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreErrorsBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreErrorsBindHandler.java index 3bee4991bb1..05b02257fc9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreErrorsBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreErrorsBindHandler.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind.handler; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.AbstractBindHandler; import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindHandler; @@ -39,8 +41,8 @@ public class IgnoreErrorsBindHandler extends AbstractBindHandler { } @Override - public Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + public @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { return (target.getValue() != null) ? target.getValue().get() : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java index fffc366ddd2..33ecc3255de 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/IgnoreTopLevelConverterNotFoundBindHandler.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind.handler; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.AbstractBindHandler; import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindHandler; @@ -48,8 +50,8 @@ public class IgnoreTopLevelConverterNotFoundBindHandler extends AbstractBindHand } @Override - public Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + public @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { if (context.getDepth() == 0 && error instanceof ConverterNotFoundException) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java index bc28800159f..ea64c2ae8de 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java @@ -21,6 +21,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.AbstractBindHandler; import org.springframework.boot.context.properties.bind.BindContext; import org.springframework.boot.context.properties.bind.BindHandler; @@ -61,20 +63,21 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler { } @Override - public Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { + public @Nullable Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { this.attemptedNames.add(name); return super.onStart(name, target, context); } @Override - public Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + public @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, + Object result) { this.boundNames.add(name); return super.onSuccess(name, target, context, result); } @Override - public Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + public @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { if (error instanceof UnboundConfigurationPropertiesException) { throw error; } @@ -82,8 +85,8 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler { } @Override - public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) - throws Exception { + public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, + @Nullable Object result) throws Exception { if (context.getDepth() == 0) { checkNoUnboundElements(name, context); } @@ -143,7 +146,7 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler { return this.attemptedNames.contains(ConfigurationPropertyName.of(nestedZeroethProperty)); } - private Indexed getIndexed(ConfigurationPropertyName candidate) { + private @Nullable Indexed getIndexed(ConfigurationPropertyName candidate) { for (int i = 0; i < candidate.getNumberOfElements(); i++) { if (candidate.isNumericIndex(i)) { return new Indexed(candidate.chop(i).toString(), diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/package-info.java index 4f8cb2bffaf..b9a255f65a7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/package-info.java @@ -18,4 +18,7 @@ * General {@link org.springframework.boot.context.properties.bind.BindHandler * BindHandler} implementations. */ +@NullMarked package org.springframework.boot.context.properties.bind.handler; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/package-info.java index 4f990a223b8..511c55f69f7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/package-info.java @@ -17,4 +17,7 @@ /** * Support for {@code @ConfigurationProperties} binding. */ +@NullMarked package org.springframework.boot.context.properties.bind; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/BindValidationException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/BindValidationException.java index 252fb6788f7..8105a80a194 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/BindValidationException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/BindValidationException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind.validation; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -45,7 +47,7 @@ public class BindValidationException extends RuntimeException { return this.validationErrors; } - private static String getMessage(ValidationErrors errors) { + private static String getMessage(@Nullable ValidationErrors errors) { StringBuilder message = new StringBuilder("Binding validation errors"); if (errors != null) { message.append(" on ").append(errors.getName()); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/OriginTrackedFieldError.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/OriginTrackedFieldError.java index a3f398d2d46..e63c385b1b1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/OriginTrackedFieldError.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/OriginTrackedFieldError.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.bind.validation; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; import org.springframework.validation.FieldError; @@ -28,9 +30,9 @@ import org.springframework.validation.FieldError; */ final class OriginTrackedFieldError extends FieldError implements OriginProvider { - private final Origin origin; + private final @Nullable Origin origin; - private OriginTrackedFieldError(FieldError fieldError, Origin origin) { + private OriginTrackedFieldError(FieldError fieldError, @Nullable Origin origin) { super(fieldError.getObjectName(), fieldError.getField(), fieldError.getRejectedValue(), fieldError.isBindingFailure(), fieldError.getCodes(), fieldError.getArguments(), fieldError.getDefaultMessage()); @@ -38,7 +40,7 @@ final class OriginTrackedFieldError extends FieldError implements OriginProvider } @Override - public Origin getOrigin() { + public @Nullable Origin getOrigin() { return this.origin; } @@ -50,7 +52,7 @@ final class OriginTrackedFieldError extends FieldError implements OriginProvider return super.toString() + "; origin " + this.origin; } - static FieldError of(FieldError fieldError, Origin origin) { + static @Nullable FieldError of(@Nullable FieldError fieldError, @Nullable Origin origin) { if (fieldError == null || origin == null) { return fieldError; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationBindHandler.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationBindHandler.java index 9be9e09a6c4..06e4708019c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationBindHandler.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationBindHandler.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.NotReadablePropertyException; import org.springframework.boot.context.properties.bind.AbstractBindHandler; import org.springframework.boot.context.properties.bind.BindContext; @@ -32,6 +34,7 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.validation.AbstractBindingResult; import org.springframework.validation.BeanPropertyBindingResult; @@ -54,7 +57,7 @@ public class ValidationBindHandler extends AbstractBindHandler { private final Set boundProperties = new LinkedHashSet<>(); - private BindValidationException exception; + private @Nullable BindValidationException exception; public ValidationBindHandler(Validator... validators) { this.validators = validators; @@ -66,13 +69,14 @@ public class ValidationBindHandler extends AbstractBindHandler { } @Override - public Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { + public @Nullable Bindable onStart(ConfigurationPropertyName name, Bindable target, BindContext context) { this.boundTypes.put(name, target.getType()); return super.onStart(name, target, context); } @Override - public Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + public @Nullable Object onSuccess(ConfigurationPropertyName name, Bindable target, BindContext context, + Object result) { this.boundResults.put(name, result); if (context.getConfigurationProperty() != null) { this.boundProperties.add(context.getConfigurationProperty()); @@ -81,8 +85,8 @@ public class ValidationBindHandler extends AbstractBindHandler { } @Override - public Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, Exception error) - throws Exception { + public @Nullable Object onFailure(ConfigurationPropertyName name, Bindable target, BindContext context, + Exception error) throws Exception { Object result = super.onFailure(name, target, context, error); if (result != null) { clear(); @@ -100,17 +104,19 @@ public class ValidationBindHandler extends AbstractBindHandler { } @Override - public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) - throws Exception { + public void onFinish(ConfigurationPropertyName name, Bindable target, BindContext context, + @Nullable Object result) throws Exception { validate(name, target, context, result); super.onFinish(name, target, context, result); } - private void validate(ConfigurationPropertyName name, Bindable target, BindContext context, Object result) { + private void validate(ConfigurationPropertyName name, Bindable target, BindContext context, + @Nullable Object result) { if (this.exception == null) { Object validationTarget = getValidationTarget(target, context, result); Class validationType = target.getBoxedType().resolve(); if (validationTarget != null) { + Assert.state(validationType != null, "'validationType' must not be null"); validateAndPush(name, validationTarget, validationType); } } @@ -119,7 +125,7 @@ public class ValidationBindHandler extends AbstractBindHandler { } } - private Object getValidationTarget(Bindable target, BindContext context, Object result) { + private @Nullable Object getValidationTarget(Bindable target, BindContext context, @Nullable Object result) { if (result != null) { return result; } @@ -150,7 +156,7 @@ public class ValidationBindHandler extends AbstractBindHandler { private final ConfigurationPropertyName name; protected ValidationResult(ConfigurationPropertyName name, Object target) { - super(target, null); + super(target, ""); this.name = name; } @@ -160,7 +166,7 @@ public class ValidationBindHandler extends AbstractBindHandler { } @Override - public Class getFieldType(String field) { + public @Nullable Class getFieldType(@Nullable String field) { ResolvableType type = getBoundField(ValidationBindHandler.this.boundTypes, field); Class resolved = (type != null) ? type.resolve() : null; if (resolved != null) { @@ -170,7 +176,7 @@ public class ValidationBindHandler extends AbstractBindHandler { } @Override - protected Object getActualFieldValue(String field) { + protected @Nullable Object getActualFieldValue(String field) { Object boundField = getBoundField(ValidationBindHandler.this.boundResults, field); if (boundField != null) { return boundField; @@ -196,7 +202,10 @@ public class ValidationBindHandler extends AbstractBindHandler { return false; } - private T getBoundField(Map boundFields, String field) { + private @Nullable T getBoundField(Map boundFields, @Nullable String field) { + if (field == null) { + return null; + } try { ConfigurationPropertyName name = getName(field); T bound = boundFields.get(name); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationErrors.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationErrors.java index f2a4fdc54c6..9c2b25dc801 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationErrors.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationErrors.java @@ -22,6 +22,8 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; @@ -66,24 +68,24 @@ public class ValidationErrors implements Iterable { return Collections.unmodifiableList(converted); } - private ObjectError convertError(ConfigurationPropertyName name, Set boundProperties, - ObjectError error) { + private @Nullable ObjectError convertError(ConfigurationPropertyName name, + Set boundProperties, ObjectError error) { if (error instanceof FieldError fieldError) { return convertFieldError(name, boundProperties, fieldError); } return error; } - private FieldError convertFieldError(ConfigurationPropertyName name, Set boundProperties, - FieldError error) { + private @Nullable FieldError convertFieldError(ConfigurationPropertyName name, + Set boundProperties, FieldError error) { if (error instanceof OriginProvider) { return error; } return OriginTrackedFieldError.of(error, findFieldErrorOrigin(name, boundProperties, error)); } - private Origin findFieldErrorOrigin(ConfigurationPropertyName name, Set boundProperties, - FieldError error) { + private @Nullable Origin findFieldErrorOrigin(ConfigurationPropertyName name, + Set boundProperties, FieldError error) { for (ConfigurationProperty boundProperty : boundProperties) { if (isForError(name, boundProperty.getName(), error)) { return Origin.from(boundProperty); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/package-info.java index db3c88b687e..3a81eb9c2be 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/package-info.java @@ -17,4 +17,7 @@ /** * Binding validation support. */ +@NullMarked package org.springframework.boot.context.properties.bind.validation; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/package-info.java index f74ec510ed5..afc24b596a5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/package-info.java @@ -20,4 +20,7 @@ * @see org.springframework.boot.context.properties.ConfigurationProperties * @see org.springframework.boot.context.properties.EnableConfigurationProperties */ +@NullMarked package org.springframework.boot.context.properties; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java index 5ad99bba06b..ba2ce75d12c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -38,12 +40,12 @@ class AliasedConfigurationPropertySource implements ConfigurationPropertySource } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { Assert.notNull(name, "'name' must not be null"); ConfigurationProperty result = getSource().getConfigurationProperty(name); if (result == null) { ConfigurationPropertyName aliasedName = getAliases().getNameForAlias(name); - result = getSource().getConfigurationProperty(aliasedName); + result = (aliasedName != null) ? getSource().getConfigurationProperty(aliasedName) : null; } return result; } @@ -74,7 +76,7 @@ class AliasedConfigurationPropertySource implements ConfigurationPropertySource } @Override - public Object getUnderlyingSource() { + public @Nullable Object getUnderlyingSource() { return this.source.getUnderlyingSource(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/CachingConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/CachingConfigurationPropertySource.java index b728be6a571..134055506a6 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/CachingConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/CachingConfigurationPropertySource.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + /** * Interface used to indicate that a {@link ConfigurationPropertySource} supports * {@link ConfigurationPropertyCaching}. @@ -36,7 +38,7 @@ interface CachingConfigurationPropertySource { * @return a {@link ConfigurationPropertyCaching} instance or {@code null} if the * source does not support caching. */ - static ConfigurationPropertyCaching find(ConfigurationPropertySource source) { + static @Nullable ConfigurationPropertyCaching find(ConfigurationPropertySource source) { if (source instanceof CachingConfigurationPropertySource cachingSource) { return cachingSource.getCaching(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationProperty.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationProperty.java index 361e0efbe5f..5cfd4bc9475 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationProperty.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationProperty.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; import org.springframework.boot.origin.OriginTrackedValue; @@ -38,16 +40,16 @@ public final class ConfigurationProperty implements OriginProvider, Comparable sources = ConfigurationPropertySources.get(environment); return get(sources, underlyingSource); } @@ -99,7 +101,8 @@ public interface ConfigurationPropertyCaching { * must match * @return a caching instance that controls the matching source */ - static ConfigurationPropertyCaching get(Iterable sources, Object underlyingSource) { + static ConfigurationPropertyCaching get(Iterable sources, + @Nullable Object underlyingSource) { Assert.notNull(sources, "'sources' must not be null"); if (underlyingSource == null) { return new ConfigurationPropertySourcesCaching(sources); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java index a21df2b7416..ca13444dadc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertyName.java @@ -25,6 +25,9 @@ import java.util.Map; import java.util.function.Function; import java.util.function.IntFunction; +import org.jspecify.annotations.Nullable; + +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -63,15 +66,15 @@ public final class ConfigurationPropertyName implements Comparable !null") + static @Nullable ConfigurationPropertyName of(CharSequence name, boolean returnNullIfInvalid) { + Elements elements = elementsOf(name, returnNullIfInvalid, ElementsParser.DEFAULT_CAPACITY); return (elements != null) ? new ConfigurationPropertyName(elements) : null; } + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1232 private static Elements probablySingleElementOf(CharSequence name) { return elementsOf(name, false, 1); } - private static Elements elementsOf(CharSequence name, boolean returnNullIfInvalid) { - return elementsOf(name, returnNullIfInvalid, ElementsParser.DEFAULT_CAPACITY); - } - - private static Elements elementsOf(CharSequence name, boolean returnNullIfInvalid, int parserCapacity) { + @Contract("_, false, _ -> !null") + private static @Nullable Elements elementsOf(@Nullable CharSequence name, boolean returnNullIfInvalid, + int parserCapacity) { if (name == null) { Assert.isTrue(returnNullIfInvalid, "'name' must not be null"); return null; @@ -727,7 +734,7 @@ public final class ConfigurationPropertyName implements Comparable elementValueProcessor) { + @Nullable Function elementValueProcessor) { Assert.notNull(name, "Name must not be null"); if (name.isEmpty()) { return EMPTY; @@ -814,10 +821,10 @@ public final class ConfigurationPropertyName implements Comparable valueProcessor) { + Elements parse(@Nullable Function valueProcessor) { int length = this.source.length(); int openBracketCount = 0; int start = 0; @@ -1059,7 +1066,8 @@ public final class ConfigurationPropertyName implements Comparable valueProcessor) { + private void add(int start, int end, ElementType type, + @Nullable Function valueProcessor) { if ((end - start) < 1 || type == ElementType.EMPTY) { return; } @@ -1097,7 +1105,7 @@ public final class ConfigurationPropertyName implements Comparable e.getValue().contains(alias)) diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java index 1b4d90f747a..d43be31a561 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.source; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.core.env.PropertySource; import org.springframework.util.StringUtils; @@ -38,10 +40,10 @@ public interface ConfigurationPropertySource { /** * Return a single {@link ConfigurationProperty} from the source or {@code null} if no * property can be found. - * @param name the name of the property (must not be {@code null}) + * @param name the name of the property * @return the associated object or {@code null}. */ - ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name); + @Nullable ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name); /** * Returns if the source contains any descendants of the specified name. May return @@ -81,7 +83,7 @@ public interface ConfigurationPropertySource { * @return a {@link ConfigurationPropertySource} instance supporting a prefix * @since 2.5.0 */ - default ConfigurationPropertySource withPrefix(String prefix) { + default ConfigurationPropertySource withPrefix(@Nullable String prefix) { return (StringUtils.hasText(prefix)) ? new PrefixedConfigurationPropertySource(this, prefix) : this; } @@ -89,7 +91,7 @@ public interface ConfigurationPropertySource { * Return the underlying source that is actually providing the properties. * @return the underlying property source or {@code null}. */ - default Object getUnderlyingSource() { + default @Nullable Object getUnderlyingSource() { return null; } @@ -100,7 +102,7 @@ public interface ConfigurationPropertySource { * @return an adapted source or {@code null} {@link SpringConfigurationPropertySource} * @since 2.4.0 */ - static ConfigurationPropertySource from(PropertySource source) { + static @Nullable ConfigurationPropertySource from(PropertySource source) { if (source instanceof ConfigurationPropertySourcesPropertySource) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java index d9af43db099..855fc609184 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.properties.source; import java.util.Collections; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.Environment; @@ -28,6 +30,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource.StubPropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -95,12 +98,13 @@ public final class ConfigurationPropertySources { sources.addFirst(attached); } - private static boolean isUsingSources(PropertySource attached, MutablePropertySources sources) { + @Contract("null, _ -> false") + private static boolean isUsingSources(@Nullable PropertySource attached, MutablePropertySources sources) { return attached instanceof ConfigurationPropertySourcesPropertySource && ((SpringConfigurationPropertySources) attached.getSource()).isUsingSources(sources); } - static PropertySource getAttached(MutablePropertySources sources) { + static @Nullable PropertySource getAttached(@Nullable MutablePropertySources sources) { return (sources != null) ? sources.get(ATTACHED_PROPERTY_SOURCE_NAME) : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesCaching.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesCaching.java index 57aa04c84ab..48a6ea2e74b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesCaching.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesCaching.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + /** * {@link ConfigurationPropertyCaching} for an {@link Iterable iterable} set of * {@link ConfigurationPropertySource} instances. @@ -29,9 +31,9 @@ import java.util.function.Consumer; */ class ConfigurationPropertySourcesCaching implements ConfigurationPropertyCaching { - private final Iterable sources; + private final @Nullable Iterable sources; - ConfigurationPropertySourcesCaching(Iterable sources) { + ConfigurationPropertySourcesCaching(@Nullable Iterable sources) { this.sources = sources; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java index 8dfe9a03a42..105a9d277e9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.env.AbstractPropertyResolver; import org.springframework.core.env.MutablePropertySources; @@ -58,21 +60,21 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol } @Override - public String getProperty(String key) { + public @Nullable String getProperty(String key) { return getProperty(key, String.class, true); } @Override - public T getProperty(String key, Class targetValueType) { + public @Nullable T getProperty(String key, Class targetValueType) { return getProperty(key, targetValueType, true); } @Override - protected String getPropertyAsRawString(String key) { + protected @Nullable String getPropertyAsRawString(String key) { return getProperty(key, String.class, false); } - private T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { + private @Nullable T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { Object value = findPropertyValue(key); if (value == null) { return null; @@ -90,7 +92,7 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol } } - private Object findPropertyValue(String key) { + private @Nullable Object findPropertyValue(String key) { ConfigurationPropertySourcesPropertySource attached = getAttached(); if (attached != null) { ConfigurationPropertyName name = ConfigurationPropertyName.of(key, true); @@ -107,7 +109,7 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol return this.defaultResolver.getProperty(key, Object.class, false); } - private ConfigurationPropertySourcesPropertySource getAttached() { + private @Nullable ConfigurationPropertySourcesPropertySource getAttached() { ConfigurationPropertySourcesPropertySource attached = (ConfigurationPropertySourcesPropertySource) ConfigurationPropertySources .getAttached(this.propertySources); Iterable attachedSource = (attached != null) ? attached.getSource() : null; @@ -129,7 +131,7 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol } @Override - public T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { + public @Nullable T getProperty(String key, Class targetValueType, boolean resolveNestedPlaceholders) { return super.getProperty(key, targetValueType, resolveNestedPlaceholders); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertySource.java index cf636489bf0..2147ce4aa5a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertySource.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.Environment; @@ -43,17 +45,17 @@ class ConfigurationPropertySourcesPropertySource extends PropertySource> lastMappedConfigurationPropertyName; + private @Nullable LastMapping> lastMappedConfigurationPropertyName; - private LastMapping lastMappedPropertyName; + private @Nullable LastMapping lastMappedPropertyName; private DefaultPropertyMapper() { } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java index 71c6b605cc2..bfc33fb13ea 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java @@ -18,6 +18,8 @@ package org.springframework.boot.context.properties.source; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -41,7 +43,7 @@ class FilteredConfigurationPropertiesSource implements ConfigurationPropertySour } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { boolean filtered = getFilter().test(name); return filtered ? getSource().getConfigurationProperty(name) : null; } @@ -57,7 +59,7 @@ class FilteredConfigurationPropertiesSource implements ConfigurationPropertySour } @Override - public Object getUnderlyingSource() { + public @Nullable Object getUnderlyingSource() { return this.source.getUnderlyingSource(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java index 6ef648e7e18..f6ec890f703 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java @@ -20,6 +20,8 @@ import java.util.Arrays; import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + /** * A filtered {@link IterableConfigurationPropertySource}. * @@ -29,14 +31,14 @@ import java.util.stream.Stream; class FilteredIterableConfigurationPropertiesSource extends FilteredConfigurationPropertiesSource implements IterableConfigurationPropertySource { - private ConfigurationPropertyName[] filteredNames; + private ConfigurationPropertyName @Nullable [] filteredNames; private int numerOfFilteredNames; FilteredIterableConfigurationPropertiesSource(IterableConfigurationPropertySource source, Predicate filter) { super(source, filter); - ConfigurationPropertyName[] filterableNames = getFilterableNames(source); + @Nullable ConfigurationPropertyName[] filterableNames = getFilterableNames(source); if (filterableNames != null) { this.filteredNames = new ConfigurationPropertyName[filterableNames.length]; this.numerOfFilteredNames = 0; @@ -51,7 +53,8 @@ class FilteredIterableConfigurationPropertiesSource extends FilteredConfiguratio } } - private ConfigurationPropertyName[] getFilterableNames(IterableConfigurationPropertySource source) { + private @Nullable ConfigurationPropertyName @Nullable [] getFilterableNames( + IterableConfigurationPropertySource source) { if (source instanceof SpringIterableConfigurationPropertySource springPropertySource && springPropertySource.isImmutablePropertySource()) { return springPropertySource.getConfigurationPropertyNames(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java index db2d23b58e0..ef9612366db 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/InvalidConfigurationPropertyValueException.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -29,9 +31,9 @@ public class InvalidConfigurationPropertyValueException extends RuntimeException private final String name; - private final Object value; + private final @Nullable Object value; - private final String reason; + private final @Nullable String reason; /** * Creates a new instance for the specified property {@code name} and {@code value}, @@ -42,11 +44,12 @@ public class InvalidConfigurationPropertyValueException extends RuntimeException * Starts with an upper-case and ends with a dot. Several sentences and carriage * returns are allowed. */ - public InvalidConfigurationPropertyValueException(String name, Object value, String reason) { + public InvalidConfigurationPropertyValueException(String name, @Nullable Object value, @Nullable String reason) { this(name, value, reason, null); } - InvalidConfigurationPropertyValueException(String name, Object value, String reason, Throwable cause) { + InvalidConfigurationPropertyValueException(String name, @Nullable Object value, @Nullable String reason, + @Nullable Throwable cause) { super("Property " + name + " with value '" + value + "' is invalid: " + reason, cause); Assert.notNull(name, "'name' must not be null"); this.name = name; @@ -66,7 +69,7 @@ public class InvalidConfigurationPropertyValueException extends RuntimeException * Return the invalid value, can be {@code null}. * @return the invalid value */ - public Object getValue() { + public @Nullable Object getValue() { return this.value; } @@ -74,7 +77,7 @@ public class InvalidConfigurationPropertyValueException extends RuntimeException * Return the reason why the value is invalid. * @return the reason */ - public String getReason() { + public @Nullable String getReason() { return this.reason; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java index 3a322d147bb..ca22264f5e8 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java @@ -20,6 +20,8 @@ import java.util.Iterator; import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.OriginTrackedValue; import org.springframework.util.StringUtils; @@ -75,7 +77,7 @@ public interface IterableConfigurationPropertySource } @Override - default IterableConfigurationPropertySource withPrefix(String prefix) { + default IterableConfigurationPropertySource withPrefix(@Nullable String prefix) { return (StringUtils.hasText(prefix)) ? new PrefixedIterableConfigurationPropertySource(this, prefix) : this; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java index 02e3971439e..9f46fd063f1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java @@ -22,6 +22,8 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.MapPropertySource; import org.springframework.util.Assert; @@ -75,7 +77,7 @@ public class MapConfigurationPropertySource implements IterableConfigurationProp * @param name the name * @param value the value */ - public void put(Object name, Object value) { + public void put(@Nullable Object name, Object value) { this.source.put((name != null) ? name.toString() : null, value); } @@ -85,7 +87,7 @@ public class MapConfigurationPropertySource implements IterableConfigurationProp } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { return this.delegate.getConfigurationProperty(name); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java index 1039b36ffc7..64c4cf9d107 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MutuallyExclusiveConfigurationPropertiesException.java @@ -26,6 +26,9 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -78,7 +81,8 @@ public class MutuallyExclusiveConfigurationPropertiesException extends RuntimeEx return this.mutuallyExclusiveNames; } - private static Set asSet(Collection collection) { + @Contract("null -> null; !null -> !null") + private static @Nullable Set asSet(@Nullable Collection collection) { return (collection != null) ? new LinkedHashSet<>(collection) : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java index 549203b291b..2688ab99e87 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PrefixedConfigurationPropertySource.java @@ -16,6 +16,8 @@ package org.springframework.boot.context.properties.source; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -41,7 +43,7 @@ class PrefixedConfigurationPropertySource implements ConfigurationPropertySource } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { ConfigurationProperty configurationProperty = this.source.getConfigurationProperty(getPrefixedName(name)); if (configurationProperty == null) { return null; @@ -60,7 +62,7 @@ class PrefixedConfigurationPropertySource implements ConfigurationPropertySource } @Override - public Object getUnderlyingSource() { + public @Nullable Object getUnderlyingSource() { return this.source.getUnderlyingSource(); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SoftReferenceConfigurationPropertyCache.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SoftReferenceConfigurationPropertyCache.java index c54484bb5c8..33f92d7d47b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SoftReferenceConfigurationPropertyCache.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SoftReferenceConfigurationPropertyCache.java @@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.function.UnaryOperator; +import org.jspecify.annotations.Nullable; + /** * Simple cache that uses a {@link SoftReference} to cache a value for as long as * possible. @@ -39,11 +41,11 @@ class SoftReferenceConfigurationPropertyCache implements ConfigurationPropert private final boolean neverExpire; - private volatile Duration timeToLive; + private volatile @Nullable Duration timeToLive; private volatile SoftReference value = new SoftReference<>(null); - private volatile Instant lastAccessed = now(); + private volatile @Nullable Instant lastAccessed = now(); SoftReferenceConfigurationPropertyCache(boolean neverExpire) { this.neverExpire = neverExpire; @@ -60,7 +62,7 @@ class SoftReferenceConfigurationPropertyCache implements ConfigurationPropert } @Override - public void setTimeToLive(Duration timeToLive) { + public void setTimeToLive(@Nullable Duration timeToLive) { this.timeToLive = (timeToLive == null || timeToLive.isZero()) ? null : timeToLive; } @@ -126,7 +128,7 @@ class SoftReferenceConfigurationPropertyCache implements ConfigurationPropert return Instant.now(); } - protected T getValue() { + protected @Nullable T getValue() { return this.value.get(); } @@ -137,8 +139,8 @@ class SoftReferenceConfigurationPropertyCache implements ConfigurationPropert /** * An active {@link CacheOverride} with a stored time-to-live. */ - private record ActiveCacheOverride(SoftReferenceConfigurationPropertyCache cache, Duration timeToLive, - Instant lastAccessed, AtomicBoolean active) implements CacheOverride { + private record ActiveCacheOverride(SoftReferenceConfigurationPropertyCache cache, @Nullable Duration timeToLive, + @Nullable Instant lastAccessed, AtomicBoolean active) implements CacheOverride { ActiveCacheOverride(SoftReferenceConfigurationPropertyCache cache) { this(cache, cache.timeToLive, cache.lastAccessed, new AtomicBoolean()); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java index bd747c08be2..e90fb763bcc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java @@ -20,6 +20,8 @@ import java.util.Locale; import java.util.Map; import java.util.Random; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.PropertySourceOrigin; @@ -80,7 +82,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource { } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(@Nullable ConfigurationPropertyName name) { if (name == null) { return null; } @@ -101,7 +103,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource { return null; } - protected final Object getPropertySourceProperty(String name) { + protected final @Nullable Object getPropertySourceProperty(String name) { // Save calls to SystemEnvironmentPropertySource.resolvePropertyName(...) // since we've already done the mapping PropertySource propertySource = getPropertySource(); @@ -109,7 +111,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource { : getSystemEnvironmentProperty(((SystemEnvironmentPropertySource) propertySource).getSource(), name); } - Object getSystemEnvironmentProperty(Map systemEnvironment, String name) { + @Nullable Object getSystemEnvironmentProperty(Map systemEnvironment, String name) { Object value = systemEnvironment.get(name); return (value != null) ? value : systemEnvironment.get(name.toLowerCase(Locale.ROOT)); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java index 37ca3699119..f9521804564 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySources.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.OriginLookup; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; @@ -78,7 +80,7 @@ class SpringConfigurationPropertySources implements Iterable>> iterators; - private ConfigurationPropertySource next; + private @Nullable ConfigurationPropertySource next; private final Function, ConfigurationPropertySource> adapter; @@ -104,7 +106,7 @@ class SpringConfigurationPropertySources implements Iterable cache; - private volatile ConfigurationPropertyName[] configurationPropertyNames; + private volatile @Nullable ConfigurationPropertyName @Nullable [] configurationPropertyNames; - private final Map containsDescendantOfCache; + private final @Nullable Map containsDescendantOfCache; SpringIterableConfigurationPropertySource(EnumerablePropertySource propertySource, boolean systemEnvironmentSource, PropertyMapper... mappers) { @@ -99,7 +102,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } @Override - public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { + public @Nullable ConfigurationProperty getConfigurationProperty(@Nullable ConfigurationPropertyName name) { if (name == null) { return null; } @@ -118,13 +121,13 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } @Override - protected Object getSystemEnvironmentProperty(Map systemEnvironment, String name) { + protected @Nullable Object getSystemEnvironmentProperty(Map systemEnvironment, String name) { return getCache().getSystemEnvironmentProperty(name); } @Override public Stream stream() { - ConfigurationPropertyName[] names = getConfigurationPropertyNames(); + @Nullable ConfigurationPropertyName[] names = getConfigurationPropertyNames(); return Arrays.stream(names).filter(Objects::nonNull); } @@ -160,7 +163,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } private boolean ancestorOfCheck(ConfigurationPropertyName name) { - ConfigurationPropertyName[] candidates = getConfigurationPropertyNames(); + @Nullable ConfigurationPropertyName[] candidates = getConfigurationPropertyNames(); for (ConfigurationPropertyName candidate : candidates) { if (candidate != null && this.ancestorOfCheck.test(name, candidate)) { return true; @@ -169,11 +172,11 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope return false; } - ConfigurationPropertyName[] getConfigurationPropertyNames() { + @Nullable ConfigurationPropertyName[] getConfigurationPropertyNames() { if (!isImmutablePropertySource()) { return getCache().getConfigurationPropertyNames(getPropertySource().getPropertyNames()); } - ConfigurationPropertyName[] configurationPropertyNames = this.configurationPropertyNames; + @Nullable ConfigurationPropertyName[] configurationPropertyNames = this.configurationPropertyNames; if (configurationPropertyNames == null) { configurationPropertyNames = getCache() .getConfigurationPropertyNames(getPropertySource().getPropertyNames()); @@ -225,7 +228,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope private final boolean systemEnvironmentSource; - private volatile Data data; + private volatile @Nullable Data data; Cache(PropertyMapper[] mappers, boolean immutable, boolean captureDescendants, boolean systemEnvironmentSource) { @@ -293,11 +296,12 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope return new HashMap<>((Map) propertySource.getSource()); } - private Map cloneOrCreate(Map source, int size) { + private Map cloneOrCreate(@Nullable Map source, int size) { return (source != null) ? new LinkedHashMap<>(source) : new LinkedHashMap<>(size); } - private void addParents(Set descendants, ConfigurationPropertyName name) { + private void addParents(@Nullable Set descendants, + @Nullable ConfigurationPropertyName name) { if (descendants == null || name == null || name.isEmpty()) { return; } @@ -315,12 +319,15 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } Set getMapped(ConfigurationPropertyName configurationPropertyName) { - return this.data.mappings().getOrDefault(configurationPropertyName, Collections.emptySet()); + Data data = this.data; + Assert.state(data != null, "'data' must not be null"); + return data.mappings().getOrDefault(configurationPropertyName, Collections.emptySet()); } - ConfigurationPropertyName[] getConfigurationPropertyNames(String[] propertyNames) { + @Nullable ConfigurationPropertyName[] getConfigurationPropertyNames(String[] propertyNames) { Data data = this.data; - ConfigurationPropertyName[] names = data.configurationPropertyNames(); + Assert.state(data != null, "'data' must not be null"); + @Nullable ConfigurationPropertyName[] names = data.configurationPropertyNames(); if (names != null) { return names; } @@ -335,18 +342,25 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope return names; } - Set getDescendants() { - return this.data.descendants(); + @Nullable Set getDescendants() { + Data data = this.data; + Assert.state(data != null, "'data' must not be null"); + return data.descendants(); } - Object getSystemEnvironmentProperty(String name) { - return this.data.systemEnvironmentCopy().get(name); + @Nullable Object getSystemEnvironmentProperty(String name) { + Data data = this.data; + Assert.state(data != null, "'data' must not be null"); + Map systemEnvironmentCopy = data.systemEnvironmentCopy(); + Assert.state(systemEnvironmentCopy != null, "'systemEnvironmentCopy' must not be null"); + return systemEnvironmentCopy.get(name); } private record Data(Map> mappings, - Map reverseMappings, Set descendants, - ConfigurationPropertyName[] configurationPropertyNames, Map systemEnvironmentCopy, - String[] lastUpdated) { + Map reverseMappings, + @Nullable Set descendants, + ConfigurationPropertyName @Nullable [] configurationPropertyNames, + @Nullable Map systemEnvironmentCopy, String @Nullable [] lastUpdated) { } @@ -357,11 +371,11 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope */ private static class ConfigurationPropertyNamesIterator implements Iterator { - private final ConfigurationPropertyName[] names; + private final @Nullable ConfigurationPropertyName[] names; private int index = 0; - ConfigurationPropertyNamesIterator(ConfigurationPropertyName[] names) { + ConfigurationPropertyNamesIterator(@Nullable ConfigurationPropertyName[] names) { this.names = names; } @@ -372,7 +386,7 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope } @Override - public ConfigurationPropertyName next() { + public @Nullable ConfigurationPropertyName next() { skipNulls(); if (this.index >= this.names.length) { throw new NoSuchElementException(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/package-info.java index 45a11303d21..1f1bd8e69a3 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/properties/source/package-info.java @@ -19,4 +19,7 @@ * * @see org.springframework.boot.context.properties.source.ConfigurationPropertySource */ +@NullMarked package org.springframework.boot.context.properties.source; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java index feb037f7173..4e201b8b2fe 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/ApplicationConversionService.java @@ -25,6 +25,8 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.beans.factory.config.BeanDefinition; @@ -71,7 +73,7 @@ public class ApplicationConversionService extends FormattingConversionService { private static final ResolvableType STRING = ResolvableType.forClass(String.class); - private static volatile ApplicationConversionService sharedInstance; + private static volatile @Nullable ApplicationConversionService sharedInstance; private final boolean unmodifiable; @@ -79,11 +81,11 @@ public class ApplicationConversionService extends FormattingConversionService { this(null); } - public ApplicationConversionService(StringValueResolver embeddedValueResolver) { + public ApplicationConversionService(@Nullable StringValueResolver embeddedValueResolver) { this(embeddedValueResolver, false); } - private ApplicationConversionService(StringValueResolver embeddedValueResolver, boolean unmodifiable) { + private ApplicationConversionService(@Nullable StringValueResolver embeddedValueResolver, boolean unmodifiable) { if (embeddedValueResolver != null) { setEmbeddedValueResolver(embeddedValueResolver); } @@ -305,7 +307,7 @@ public class ApplicationConversionService extends FormattingConversionService { * @since 3.5.0 */ public static Map addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory, - String qualifier) { + @Nullable String qualifier) { ConfigurableListableBeanFactory configurableBeanFactory = getConfigurableListableBeanFactory(beanFactory); Map beans = getBeans(beanFactory, qualifier); beans.forEach((beanName, bean) -> { @@ -317,7 +319,8 @@ public class ApplicationConversionService extends FormattingConversionService { return beans; } - private static ConfigurableListableBeanFactory getConfigurableListableBeanFactory(ListableBeanFactory beanFactory) { + private static @Nullable ConfigurableListableBeanFactory getConfigurableListableBeanFactory( + ListableBeanFactory beanFactory) { if (beanFactory instanceof ConfigurableApplicationContext applicationContext) { return applicationContext.getBeanFactory(); } @@ -327,7 +330,7 @@ public class ApplicationConversionService extends FormattingConversionService { return null; } - private static Map getBeans(ListableBeanFactory beanFactory, String qualifier) { + private static Map getBeans(ListableBeanFactory beanFactory, @Nullable String qualifier) { Map beans = new LinkedHashMap<>(); beans.putAll(getBeans(beanFactory, Printer.class, qualifier)); beans.putAll(getBeans(beanFactory, Parser.class, qualifier)); @@ -338,12 +341,13 @@ public class ApplicationConversionService extends FormattingConversionService { return beans; } - private static Map getBeans(ListableBeanFactory beanFactory, Class type, String qualifier) { + private static Map getBeans(ListableBeanFactory beanFactory, Class type, + @Nullable String qualifier) { return (!StringUtils.hasLength(qualifier)) ? beanFactory.getBeansOfType(type) : BeanFactoryAnnotationUtils.qualifiedBeansOfType(beanFactory, type, qualifier); } - static void addBean(FormatterRegistry registry, Object bean, ResolvableType beanType) { + static void addBean(FormatterRegistry registry, Object bean, @Nullable ResolvableType beanType) { if (bean instanceof GenericConverter converterBean) { addBean(registry, converterBean, beanType, GenericConverter.class, registry::addConverter, (Runnable) null); } @@ -357,6 +361,7 @@ public class ApplicationConversionService extends FormattingConversionService { } else if (bean instanceof Formatter formatterBean) { addBean(registry, formatterBean, beanType, Formatter.class, registry::addFormatter, () -> { + Assert.state(beanType != null, "beanType is missing"); registry.addConverter(new PrinterBeanAdapter(formatterBean, beanType)); registry.addConverter(new ParserBeanAdapter(formatterBean, beanType)); }); @@ -369,14 +374,15 @@ public class ApplicationConversionService extends FormattingConversionService { } } - private static void addBean(FormatterRegistry registry, B bean, ResolvableType beanType, Class type, - Consumer standardRegistrar, BiFunction> beanAdapterFactory) { + private static void addBean(FormatterRegistry registry, B bean, @Nullable ResolvableType beanType, + Class type, Consumer standardRegistrar, + BiFunction> beanAdapterFactory) { addBean(registry, bean, beanType, type, standardRegistrar, () -> registry.addConverter(beanAdapterFactory.apply(bean, beanType))); } - private static void addBean(FormatterRegistry registry, B bean, ResolvableType beanType, Class type, - Consumer standardRegistrar, Runnable beanAdapterRegistrar) { + private static void addBean(FormatterRegistry registry, B bean, @Nullable ResolvableType beanType, + Class type, Consumer standardRegistrar, @Nullable Runnable beanAdapterRegistrar) { if (beanType != null && beanAdapterRegistrar != null && ResolvableType.forInstance(bean).as(type).hasUnresolvableGenerics()) { beanAdapterRegistrar.run(); @@ -436,11 +442,12 @@ public class ApplicationConversionService extends FormattingConversionService { } @SuppressWarnings({ "unchecked", "rawtypes" }) - protected final Object convert(Object source, TypeDescriptor targetType, Converter converter) { + protected final @Nullable Object convert(@Nullable Object source, TypeDescriptor targetType, + Converter converter) { return (source != null) ? ((Converter) converter).convert(source) : convertNull(targetType); } - private Object convertNull(TypeDescriptor targetType) { + private @Nullable Object convertNull(TypeDescriptor targetType) { return (targetType.getObjectType() != Optional.class) ? null : Optional.empty(); } @@ -466,7 +473,7 @@ public class ApplicationConversionService extends FormattingConversionService { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return (source != null) ? print(source) : ""; } @@ -492,7 +499,7 @@ public class ApplicationConversionService extends FormattingConversionService { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { String text = (String) source; return (!StringUtils.hasText(text)) ? null : parse(text); } @@ -527,7 +534,7 @@ public class ApplicationConversionService extends FormattingConversionService { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return convert(source, targetType, bean()); } @@ -550,7 +557,7 @@ public class ApplicationConversionService extends FormattingConversionService { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return convert(source, targetType, getConverter(targetType::getObjectType)); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/ArrayToDelimitedStringConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/ArrayToDelimitedStringConverter.java index 48141ed887e..1226927f00b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/ArrayToDelimitedStringConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/ArrayToDelimitedStringConverter.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; @@ -50,7 +52,7 @@ final class ArrayToDelimitedStringConverter implements ConditionalGenericConvert } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { List list = Arrays.asList(ObjectUtils.toObjectArray(source)); return this.delegate.convert(list, sourceType, targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java index 9a88b4ceae1..f873ce225ab 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/CharSequenceToObjectConverter.java @@ -19,6 +19,8 @@ package org.springframework.boot.convert; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; @@ -96,8 +98,8 @@ class CharSequenceToObjectConverter implements ConditionalGenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { - return this.conversionService.convert(source.toString(), STRING, targetType); + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + return this.conversionService.convert((source != null) ? source.toString() : null, STRING, targetType); } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/CollectionToDelimitedStringConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/CollectionToDelimitedStringConverter.java index 699d01ed56e..709d3ea4e18 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/CollectionToDelimitedStringConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/CollectionToDelimitedStringConverter.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; @@ -54,7 +56,7 @@ final class CollectionToDelimitedStringConverter implements ConditionalGenericCo } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToArrayConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToArrayConverter.java index f36c206ab60..523464002af 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToArrayConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToArrayConverter.java @@ -20,6 +20,8 @@ import java.lang.reflect.Array; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; @@ -52,7 +54,7 @@ final class DelimitedStringToArrayConverter implements ConditionalGenericConvert } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } @@ -63,6 +65,7 @@ final class DelimitedStringToArrayConverter implements ConditionalGenericConvert Delimiter delimiter = targetType.getAnnotation(Delimiter.class); String[] elements = getElements(source, (delimiter != null) ? delimiter.value() : ","); TypeDescriptor elementDescriptor = targetType.getElementTypeDescriptor(); + Assert.state(elementDescriptor != null, "elementDescriptor is missing"); Object target = Array.newInstance(elementDescriptor.getType(), elements.length); for (int i = 0; i < elements.length; i++) { String sourceElement = elements[i]; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToCollectionConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToCollectionConverter.java index 6f26ecc6d46..6556d2d2c04 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToCollectionConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/DelimitedStringToCollectionConverter.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.Set; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.core.CollectionFactory; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; @@ -55,7 +57,7 @@ final class DelimitedStringToCollectionConverter implements ConditionalGenericCo } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } @@ -75,7 +77,7 @@ final class DelimitedStringToCollectionConverter implements ConditionalGenericCo return target; } - private Collection createCollection(TypeDescriptor targetType, TypeDescriptor elementDescriptor, + private Collection createCollection(TypeDescriptor targetType, @Nullable TypeDescriptor elementDescriptor, int length) { return CollectionFactory.createCollection(targetType.getType(), (elementDescriptor != null) ? elementDescriptor.getType() : null, length); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationStyle.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationStyle.java index e0ea07bc154..54d445951c2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationStyle.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationStyle.java @@ -22,6 +22,8 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -40,7 +42,7 @@ public enum DurationStyle { SIMPLE("^([+-]?\\d+)([a-zA-Z]{0,2})$") { @Override - public Duration parse(String value, ChronoUnit unit) { + public Duration parse(String value, @Nullable ChronoUnit unit) { try { Matcher matcher = matcher(value); Assert.state(matcher.matches(), "Does not match simple duration pattern"); @@ -54,7 +56,7 @@ public enum DurationStyle { } @Override - public String print(Duration value, ChronoUnit unit) { + public String print(Duration value, @Nullable ChronoUnit unit) { return Unit.fromChronoUnit(unit).print(value); } @@ -66,7 +68,7 @@ public enum DurationStyle { ISO8601("^[+-]?[pP].*$") { @Override - public Duration parse(String value, ChronoUnit unit) { + public Duration parse(String value, @Nullable ChronoUnit unit) { try { return Duration.parse(value); } @@ -76,7 +78,7 @@ public enum DurationStyle { } @Override - public String print(Duration value, ChronoUnit unit) { + public String print(Duration value, @Nullable ChronoUnit unit) { return value.toString(); } @@ -112,7 +114,7 @@ public enum DurationStyle { * will default to ms) * @return a duration */ - public abstract Duration parse(String value, ChronoUnit unit); + public abstract Duration parse(String value, @Nullable ChronoUnit unit); /** * Print the specified duration. @@ -129,7 +131,7 @@ public enum DurationStyle { * @param unit the value to use for printing * @return the printed result */ - public abstract String print(Duration value, ChronoUnit unit); + public abstract String print(Duration value, @Nullable ChronoUnit unit); /** * Detect the style then parse the value to return a duration. @@ -151,7 +153,7 @@ public enum DurationStyle { * @throws IllegalArgumentException if the value is not a known style or cannot be * parsed */ - public static Duration detectAndParse(String value, ChronoUnit unit) { + public static Duration detectAndParse(String value, @Nullable ChronoUnit unit) { return detect(value).parse(value, unit); } @@ -235,7 +237,7 @@ public enum DurationStyle { return this.longValue.apply(value); } - public static Unit fromChronoUnit(ChronoUnit chronoUnit) { + public static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) { if (chronoUnit == null) { return Unit.MILLIS; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToNumberConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToNumberConverter.java index 3d610fb0c50..decd31d3694 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToNumberConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToNumberConverter.java @@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -41,19 +43,19 @@ final class DurationToNumberConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } return convert((Duration) source, getDurationUnit(sourceType), targetType.getObjectType()); } - private ChronoUnit getDurationUnit(TypeDescriptor sourceType) { + private @Nullable ChronoUnit getDurationUnit(TypeDescriptor sourceType) { DurationUnit annotation = sourceType.getAnnotation(DurationUnit.class); return (annotation != null) ? annotation.value() : null; } - private Object convert(Duration source, ChronoUnit unit, Class type) { + private Object convert(Duration source, @Nullable ChronoUnit unit, Class type) { try { return type.getConstructor(String.class) .newInstance(String.valueOf(DurationStyle.Unit.fromChronoUnit(unit).longValue(source))); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToStringConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToStringConverter.java index 5e2f559e504..5daabc09bc1 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToStringConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/DurationToStringConverter.java @@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -40,24 +42,24 @@ final class DurationToStringConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return null; } return convert((Duration) source, getDurationStyle(sourceType), getDurationUnit(sourceType)); } - private ChronoUnit getDurationUnit(TypeDescriptor sourceType) { + private @Nullable ChronoUnit getDurationUnit(TypeDescriptor sourceType) { DurationUnit annotation = sourceType.getAnnotation(DurationUnit.class); return (annotation != null) ? annotation.value() : null; } - private DurationStyle getDurationStyle(TypeDescriptor sourceType) { + private @Nullable DurationStyle getDurationStyle(TypeDescriptor sourceType) { DurationFormat annotation = sourceType.getAnnotation(DurationFormat.class); return (annotation != null) ? annotation.value() : null; } - private String convert(Duration source, DurationStyle style, ChronoUnit unit) { + private String convert(Duration source, @Nullable DurationStyle style, @Nullable ChronoUnit unit) { style = (style != null) ? style : DurationStyle.ISO8601; return style.print(source, unit); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/LenientObjectToEnumConverterFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/LenientObjectToEnumConverterFactory.java index 4e188fe15c2..3ee2092506e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/LenientObjectToEnumConverterFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/LenientObjectToEnumConverterFactory.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.util.Assert; @@ -68,7 +70,7 @@ abstract class LenientObjectToEnumConverterFactory implements ConverterFactor } @Override - public E convert(T source) { + public @Nullable E convert(T source) { String value = source.toString().trim(); if (value.isEmpty()) { return null; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDataSizeConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDataSizeConverter.java index 083d6aadb51..c588b192a71 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDataSizeConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDataSizeConverter.java @@ -19,6 +19,8 @@ package org.springframework.boot.convert; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -40,7 +42,7 @@ final class NumberToDataSizeConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return this.delegate.convert((source != null) ? source.toString() : null, TypeDescriptor.valueOf(String.class), targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDurationConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDurationConverter.java index 54fcab331af..326891f8d2f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDurationConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToDurationConverter.java @@ -20,6 +20,8 @@ import java.time.Duration; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -42,7 +44,7 @@ final class NumberToDurationConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return this.delegate.convert((source != null) ? source.toString() : null, TypeDescriptor.valueOf(String.class), targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToPeriodConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToPeriodConverter.java index 40076d9911f..2aec8e8dfda 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToPeriodConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/NumberToPeriodConverter.java @@ -20,6 +20,8 @@ import java.time.Period; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -43,7 +45,7 @@ final class NumberToPeriodConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { return this.delegate.convert((source != null) ? source.toString() : null, TypeDescriptor.valueOf(String.class), targetType); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodStyle.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodStyle.java index 783d2891435..1feaafe59d3 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodStyle.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodStyle.java @@ -22,6 +22,8 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -42,7 +44,7 @@ public enum PeriodStyle { Pattern.CASE_INSENSITIVE) { @Override - public Period parse(String value, ChronoUnit unit) { + public Period parse(String value, @Nullable ChronoUnit unit) { try { if (NUMERIC.matcher(value).matches()) { return Unit.fromChronoUnit(unit).parse(value); @@ -81,7 +83,7 @@ public enum PeriodStyle { } @Override - public String print(Period value, ChronoUnit unit) { + public String print(Period value, @Nullable ChronoUnit unit) { if (value.isZero()) { return Unit.fromChronoUnit(unit).print(value); } @@ -106,7 +108,7 @@ public enum PeriodStyle { ISO8601("^[+-]?P.*$", Pattern.CASE_INSENSITIVE) { @Override - public Period parse(String value, ChronoUnit unit) { + public Period parse(String value, @Nullable ChronoUnit unit) { try { return Period.parse(value); } @@ -116,7 +118,7 @@ public enum PeriodStyle { } @Override - public String print(Period value, ChronoUnit unit) { + public String print(Period value, @Nullable ChronoUnit unit) { return value.toString(); } @@ -154,7 +156,7 @@ public enum PeriodStyle { * will default to d) * @return a period */ - public abstract Period parse(String value, ChronoUnit unit); + public abstract Period parse(String value, @Nullable ChronoUnit unit); /** * Print the specified period. @@ -171,7 +173,7 @@ public enum PeriodStyle { * @param unit the value to use for printing * @return the printed result */ - public abstract String print(Period value, ChronoUnit unit); + public abstract String print(Period value, @Nullable ChronoUnit unit); /** * Detect the style then parse the value to return a period. @@ -193,7 +195,7 @@ public enum PeriodStyle { * @throws IllegalArgumentException if the value is not a known style or cannot be * parsed */ - public static Period detectAndParse(String value, ChronoUnit unit) { + public static Period detectAndParse(String value, @Nullable ChronoUnit unit) { return detect(value).parse(value, unit); } @@ -239,11 +241,11 @@ public enum PeriodStyle { private final String suffix; - private final Function intValue; + private final @Nullable Function intValue; private final Function factory; - Unit(ChronoUnit chronoUnit, String suffix, Function intValue, + Unit(ChronoUnit chronoUnit, String suffix, @Nullable Function intValue, Function factory) { this.chronoUnit = chronoUnit; this.suffix = suffix; @@ -268,7 +270,7 @@ public enum PeriodStyle { return this.intValue.apply(value); } - private static Unit fromChronoUnit(ChronoUnit chronoUnit) { + private static Unit fromChronoUnit(@Nullable ChronoUnit chronoUnit) { if (chronoUnit == null) { return Unit.DAYS; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodToStringConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodToStringConverter.java index dd8eac02122..70f53433dac 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodToStringConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/PeriodToStringConverter.java @@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -42,24 +44,24 @@ final class PeriodToStringConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (ObjectUtils.isEmpty(source)) { return null; } return convert((Period) source, getPeriodStyle(sourceType), getPeriodUnit(sourceType)); } - private PeriodStyle getPeriodStyle(TypeDescriptor sourceType) { + private @Nullable PeriodStyle getPeriodStyle(TypeDescriptor sourceType) { PeriodFormat annotation = sourceType.getAnnotation(PeriodFormat.class); return (annotation != null) ? annotation.value() : null; } - private String convert(Period source, PeriodStyle style, ChronoUnit unit) { + private String convert(Period source, @Nullable PeriodStyle style, @Nullable ChronoUnit unit) { style = (style != null) ? style : PeriodStyle.ISO8601; return style.print(source, unit); } - private ChronoUnit getPeriodUnit(TypeDescriptor sourceType) { + private @Nullable ChronoUnit getPeriodUnit(TypeDescriptor sourceType) { PeriodUnit annotation = sourceType.getAnnotation(PeriodUnit.class); return (annotation != null) ? annotation.value() : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java index 1cd4c50827f..bc4c20bb270 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDataSizeConverter.java @@ -19,6 +19,8 @@ package org.springframework.boot.convert; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -41,19 +43,19 @@ final class StringToDataSizeConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (ObjectUtils.isEmpty(source)) { return null; } return convert(source.toString(), getDataUnit(targetType)); } - private DataUnit getDataUnit(TypeDescriptor targetType) { + private @Nullable DataUnit getDataUnit(TypeDescriptor targetType) { DataSizeUnit annotation = targetType.getAnnotation(DataSizeUnit.class); return (annotation != null) ? annotation.value() : null; } - private DataSize convert(String source, DataUnit unit) { + private DataSize convert(String source, @Nullable DataUnit unit) { return DataSize.parse(source, unit); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java index 58dd62326d6..82deeddc082 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToDurationConverter.java @@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -42,24 +44,24 @@ final class StringToDurationConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (ObjectUtils.isEmpty(source)) { return null; } return convert(source.toString(), getStyle(targetType), getDurationUnit(targetType)); } - private DurationStyle getStyle(TypeDescriptor targetType) { + private @Nullable DurationStyle getStyle(TypeDescriptor targetType) { DurationFormat annotation = targetType.getAnnotation(DurationFormat.class); return (annotation != null) ? annotation.value() : null; } - private ChronoUnit getDurationUnit(TypeDescriptor targetType) { + private @Nullable ChronoUnit getDurationUnit(TypeDescriptor targetType) { DurationUnit annotation = targetType.getAnnotation(DurationUnit.class); return (annotation != null) ? annotation.value() : null; } - private Duration convert(String source, DurationStyle style, ChronoUnit unit) { + private Duration convert(String source, @Nullable DurationStyle style, @Nullable ChronoUnit unit) { style = (style != null) ? style : DurationStyle.detect(source); return style.parse(source, unit); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToPeriodConverter.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToPeriodConverter.java index 93b1c1cbb63..5f4587d1a88 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToPeriodConverter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/StringToPeriodConverter.java @@ -21,6 +21,8 @@ import java.time.temporal.ChronoUnit; import java.util.Collections; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; @@ -43,24 +45,24 @@ final class StringToPeriodConverter implements GenericConverter { } @Override - public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (ObjectUtils.isEmpty(source)) { return null; } return convert(source.toString(), getStyle(targetType), getPeriodUnit(targetType)); } - private PeriodStyle getStyle(TypeDescriptor targetType) { + private @Nullable PeriodStyle getStyle(TypeDescriptor targetType) { PeriodFormat annotation = targetType.getAnnotation(PeriodFormat.class); return (annotation != null) ? annotation.value() : null; } - private ChronoUnit getPeriodUnit(TypeDescriptor targetType) { + private @Nullable ChronoUnit getPeriodUnit(TypeDescriptor targetType) { PeriodUnit annotation = targetType.getAnnotation(PeriodUnit.class); return (annotation != null) ? annotation.value() : null; } - private Period convert(String source, PeriodStyle style, ChronoUnit unit) { + private Period convert(String source, @Nullable PeriodStyle style, @Nullable ChronoUnit unit) { style = (style != null) ? style : PeriodStyle.detect(source); return style.parse(source, unit); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/convert/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/convert/package-info.java index 7f8f7786b45..665c0776082 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/convert/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/convert/package-info.java @@ -17,4 +17,7 @@ /** * Support for type conversion. */ +@NullMarked package org.springframework.boot.convert; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/AbstractFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/AbstractFailureAnalyzer.java index e44973c6691..9ebf196ad5c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/AbstractFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/AbstractFailureAnalyzer.java @@ -16,7 +16,10 @@ package org.springframework.boot.diagnostics; +import org.jspecify.annotations.Nullable; + import org.springframework.core.ResolvableType; +import org.springframework.util.Assert; /** * Abstract base class for most {@code FailureAnalyzer} implementations. @@ -29,7 +32,7 @@ import org.springframework.core.ResolvableType; public abstract class AbstractFailureAnalyzer implements FailureAnalyzer { @Override - public FailureAnalysis analyze(Throwable failure) { + public @Nullable FailureAnalysis analyze(Throwable failure) { T cause = findCause(failure, getCauseType()); return (cause != null) ? analyze(failure, cause) : null; } @@ -41,7 +44,7 @@ public abstract class AbstractFailureAnalyzer implements Fa * @param cause the actual found cause * @return the analysis or {@code null} */ - protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause); + protected abstract @Nullable FailureAnalysis analyze(Throwable rootFailure, T cause); /** * Return the cause type being handled by the analyzer. By default the class generic @@ -50,11 +53,15 @@ public abstract class AbstractFailureAnalyzer implements Fa */ @SuppressWarnings("unchecked") protected Class getCauseType() { - return (Class) ResolvableType.forClass(AbstractFailureAnalyzer.class, getClass()).resolveGeneric(); + Class type = (Class) ResolvableType + .forClass(AbstractFailureAnalyzer.class, getClass()) + .resolveGeneric(); + Assert.state(type != null, "Unable to resolve generic"); + return type; } @SuppressWarnings("unchecked") - protected final E findCause(Throwable failure, Class type) { + protected final @Nullable E findCause(Throwable failure, Class type) { while (failure != null) { if (type.isInstance(failure)) { return (E) failure; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalysis.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalysis.java index 882db28ad6f..0574cad0f6a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalysis.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalysis.java @@ -16,6 +16,8 @@ package org.springframework.boot.diagnostics; +import org.jspecify.annotations.Nullable; + /** * The result of analyzing a failure. * @@ -26,7 +28,7 @@ public class FailureAnalysis { private final String description; - private final String action; + private final @Nullable String action; private final Throwable cause; @@ -38,8 +40,8 @@ public class FailureAnalysis { * @param action the action * @param cause the cause */ - public FailureAnalysis(String description, String action, Throwable cause) { - this.description = description; + public FailureAnalysis(@Nullable String description, @Nullable String action, Throwable cause) { + this.description = (description != null) ? description : ""; this.action = action; this.cause = cause; } @@ -56,7 +58,7 @@ public class FailureAnalysis { * Returns the action, if any, to be taken to address the failure. * @return the action or {@code null} */ - public String getAction() { + public @Nullable String getAction() { return this.action; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzer.java index d86822e93d7..c98c46b5799 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzer.java @@ -16,6 +16,8 @@ package org.springframework.boot.diagnostics; +import org.jspecify.annotations.Nullable; + /** * A {@code FailureAnalyzer} is used to analyze a failure and provide diagnostic * information that can be displayed to the user. @@ -32,6 +34,6 @@ public interface FailureAnalyzer { * @param failure the failure * @return the analysis or {@code null} */ - FailureAnalysis analyze(Throwable failure); + @Nullable FailureAnalysis analyze(Throwable failure); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java index 07feb7cb4ee..6c967fa3a48 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/FailureAnalyzers.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.SpringBootExceptionReporter; @@ -51,23 +52,23 @@ final class FailureAnalyzers implements SpringBootExceptionReporter { private final List analyzers; - public FailureAnalyzers(ConfigurableApplicationContext context) { + public FailureAnalyzers(@Nullable ConfigurableApplicationContext context) { this(context, SpringFactoriesLoader.forDefaultResourceLocation((context != null) ? context.getClassLoader() : null)); } - FailureAnalyzers(ConfigurableApplicationContext context, SpringFactoriesLoader springFactoriesLoader) { + FailureAnalyzers(@Nullable ConfigurableApplicationContext context, SpringFactoriesLoader springFactoriesLoader) { this.springFactoriesLoader = springFactoriesLoader; this.analyzers = loadFailureAnalyzers(context, this.springFactoriesLoader); } - private static List loadFailureAnalyzers(ConfigurableApplicationContext context, + private static List loadFailureAnalyzers(@Nullable ConfigurableApplicationContext context, SpringFactoriesLoader springFactoriesLoader) { return springFactoriesLoader.load(FailureAnalyzer.class, getArgumentResolver(context), FailureHandler.logging(logger)); } - private static ArgumentResolver getArgumentResolver(ConfigurableApplicationContext context) { + private static @Nullable ArgumentResolver getArgumentResolver(@Nullable ConfigurableApplicationContext context) { if (context == null) { return null; } @@ -82,7 +83,7 @@ final class FailureAnalyzers implements SpringBootExceptionReporter { return report(analysis); } - private FailureAnalysis analyze(Throwable failure, List analyzers) { + private @Nullable FailureAnalysis analyze(Throwable failure, List analyzers) { for (FailureAnalyzer analyzer : analyzers) { try { FailureAnalysis analysis = analyzer.analyze(failure); @@ -97,7 +98,7 @@ final class FailureAnalyzers implements SpringBootExceptionReporter { return null; } - private boolean report(FailureAnalysis analysis) { + private boolean report(@Nullable FailureAnalysis analysis) { List reporters = this.springFactoriesLoader.load(FailureAnalysisReporter.class); if (analysis == null || reporters.isEmpty()) { return false; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/AbstractInjectionFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/AbstractInjectionFailureAnalyzer.java index ec5ff7bbc68..6e357c13edc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/AbstractInjectionFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/AbstractInjectionFailureAnalyzer.java @@ -16,12 +16,17 @@ package org.springframework.boot.diagnostics.analyzer; +import java.lang.reflect.Method; + +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalyzer; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -36,11 +41,11 @@ import org.springframework.util.ClassUtils; public abstract class AbstractInjectionFailureAnalyzer extends AbstractFailureAnalyzer { @Override - protected final FailureAnalysis analyze(Throwable rootFailure, T cause) { + protected final @Nullable FailureAnalysis analyze(Throwable rootFailure, T cause) { return analyze(rootFailure, cause, getDescription(rootFailure)); } - private String getDescription(Throwable rootFailure) { + private @Nullable String getDescription(Throwable rootFailure) { UnsatisfiedDependencyException unsatisfiedDependency = findMostNestedCause(rootFailure, UnsatisfiedDependencyException.class); if (unsatisfiedDependency != null) { @@ -55,7 +60,7 @@ public abstract class AbstractInjectionFailureAnalyzer exte } @SuppressWarnings("unchecked") - private C findMostNestedCause(Throwable root, Class type) { + private @Nullable C findMostNestedCause(Throwable root, Class type) { Throwable candidate = root; C result = null; while (candidate != null) { @@ -67,7 +72,7 @@ public abstract class AbstractInjectionFailureAnalyzer exte return result; } - private String getDescription(UnsatisfiedDependencyException ex) { + private @Nullable String getDescription(UnsatisfiedDependencyException ex) { InjectionPoint injectionPoint = ex.getInjectionPoint(); if (injectionPoint != null) { if (injectionPoint.getField() != null) { @@ -80,9 +85,10 @@ public abstract class AbstractInjectionFailureAnalyzer exte injectionPoint.getMethodParameter().getParameterIndex(), injectionPoint.getMethodParameter().getDeclaringClass().getName()); } + Method method = injectionPoint.getMethodParameter().getMethod(); + Assert.state(method != null, "Neither constructor nor method is available"); return String.format("Parameter %d of method %s in %s", - injectionPoint.getMethodParameter().getParameterIndex(), - injectionPoint.getMethodParameter().getMethod().getName(), + injectionPoint.getMethodParameter().getParameterIndex(), method.getName(), injectionPoint.getMethodParameter().getDeclaringClass().getName()); } } @@ -109,6 +115,6 @@ public abstract class AbstractInjectionFailureAnalyzer exte * @param description the description of the injection point or {@code null} * @return the analysis or {@code null} */ - protected abstract FailureAnalysis analyze(Throwable rootFailure, T cause, String description); + protected abstract @Nullable FailureAnalysis analyze(Throwable rootFailure, T cause, @Nullable String description); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java index 1d0e7d84b24..91cccbbeb6a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BeanCurrentlyInCreationFailureAnalyzer.java @@ -18,6 +18,9 @@ package org.springframework.boot.diagnostics.analyzer; import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCurrentlyInCreationException; @@ -38,7 +41,7 @@ import org.springframework.util.StringUtils; */ class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer { - private final AbstractAutowireCapableBeanFactory beanFactory; + private final @Nullable AbstractAutowireCapableBeanFactory beanFactory; BeanCurrentlyInCreationFailureAnalyzer(BeanFactory beanFactory) { if (beanFactory instanceof AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) { @@ -50,7 +53,7 @@ class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer beansInCycle = new ArrayList<>(); Throwable candidate = rootFailure; int cycleStart = -1; @@ -136,7 +139,7 @@ class BeanCurrentlyInCreationFailureAnalyzer extends AbstractFailureAnalyzer { @Override - protected FailureAnalysis analyze(Throwable rootFailure, BindException cause) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, BindException cause) { Throwable rootCause = cause.getCause(); if (rootCause instanceof BindValidationException || rootCause instanceof UnboundConfigurationPropertiesException) { @@ -65,7 +67,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { return getFailureAnalysis(description.toString(), cause, missingParametersAnalysis); } - private void buildDescription(StringBuilder description, ConfigurationProperty property) { + private void buildDescription(StringBuilder description, @Nullable ConfigurationProperty property) { if (property != null) { description.append(String.format("%n Property: %s", property.getName())); description.append(String.format("%n Value: \"%s\"", property.getValue())); @@ -90,7 +92,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { return getExceptionTypeAndMessage(cause); } - private Throwable getRootCause(Throwable cause) { + private @Nullable Throwable getRootCause(@Nullable Throwable cause) { Throwable rootCause = cause; while (rootCause != null && rootCause.getCause() != null) { rootCause = rootCause.getCause(); @@ -104,7 +106,7 @@ class BindFailureAnalyzer extends AbstractFailureAnalyzer { } private FailureAnalysis getFailureAnalysis(String description, BindException cause, - FailureAnalysis missingParametersAnalysis) { + @Nullable FailureAnalysis missingParametersAnalysis) { StringBuilder action = new StringBuilder("Update your application's configuration"); Collection validValues = findValidValues(cause); if (!validValues.isEmpty()) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindValidationFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindValidationFailureAnalyzer.java index 38033fd4c04..585ec1ea0a7 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindValidationFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/BindValidationFailureAnalyzer.java @@ -18,11 +18,14 @@ package org.springframework.boot.diagnostics.analyzer; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.origin.Origin; +import org.springframework.util.Assert; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; @@ -36,7 +39,7 @@ import org.springframework.validation.ObjectError; class BindValidationFailureAnalyzer extends AbstractFailureAnalyzer { @Override - protected FailureAnalysis analyze(Throwable rootFailure, Throwable cause) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, Throwable cause) { ExceptionDetails details = getBindValidationExceptionDetails(rootFailure); if (details == null) { return null; @@ -44,11 +47,12 @@ class BindValidationFailureAnalyzer extends AbstractFailureAnalyzer { return analyzeBindValidationException(details); } - private ExceptionDetails getBindValidationExceptionDetails(Throwable rootFailure) { + private @Nullable ExceptionDetails getBindValidationExceptionDetails(Throwable rootFailure) { BindValidationException validationException = findCause(rootFailure, BindValidationException.class); if (validationException != null) { BindException bindException = findCause(rootFailure, BindException.class); List errors = validationException.getValidationErrors().getAllErrors(); + Assert.state(bindException != null, "BindException not found"); return new ExceptionDetails(errors, bindException.getTarget().getType(), validationException); } org.springframework.validation.BindException bindException = findCause(rootFailure, @@ -89,17 +93,17 @@ class BindValidationFailureAnalyzer extends AbstractFailureAnalyzer { private final List errors; - private final Object target; + private final @Nullable Object target; private final Throwable cause; - ExceptionDetails(List errors, Object target, Throwable cause) { + ExceptionDetails(List errors, @Nullable Object target, Throwable cause) { this.errors = errors; this.target = target; this.cause = cause; } - Object getTarget() { + @Nullable Object getTarget() { return this.target; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyNameFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyNameFailureAnalyzer.java index 4c1e87dddcf..fb15817a439 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyNameFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyNameFailureAnalyzer.java @@ -18,6 +18,8 @@ package org.springframework.boot.diagnostics.analyzer; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyNameException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; @@ -40,7 +42,8 @@ class InvalidConfigurationPropertyNameFailureAnalyzer return new FailureAnalysis(buildDescription(cause, exception), action, cause); } - private String buildDescription(InvalidConfigurationPropertyNameException cause, BeanCreationException exception) { + private String buildDescription(InvalidConfigurationPropertyNameException cause, + @Nullable BeanCreationException exception) { StringBuilder description = new StringBuilder( String.format("Configuration property name '%s' is not valid:%n", cause.getName())); String invalid = cause.getInvalidCharacters().stream().map(this::quote).collect(Collectors.joining(", ")); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzer.java index d85906c09a0..0563dd00b02 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzer.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; @@ -43,9 +45,9 @@ import org.springframework.util.StringUtils; class InvalidConfigurationPropertyValueFailureAnalyzer extends AbstractFailureAnalyzer { - private final ConfigurableEnvironment environment; + private final @Nullable ConfigurableEnvironment environment; - InvalidConfigurationPropertyValueFailureAnalyzer(Environment environment) { + InvalidConfigurationPropertyValueFailureAnalyzer(@Nullable Environment environment) { this.environment = (ConfigurableEnvironment) environment; } @@ -70,7 +72,10 @@ class InvalidConfigurationPropertyValueFailureAnalyzer .toList(); } - private Origin getOrigin(Descriptor descriptor) { + private @Nullable Origin getOrigin(@Nullable Descriptor descriptor) { + if (descriptor == null) { + return null; + } Origin origin = descriptor.origin; if (origin instanceof PropertySourceOrigin propertySourceOrigin) { origin = propertySourceOrigin.getOrigin(); @@ -128,23 +133,23 @@ class InvalidConfigurationPropertyValueFailureAnalyzer private static final class Descriptor { - private final String propertySource; + private final @Nullable String propertySource; - private final Object value; + private final @Nullable Object value; - private final Origin origin; + private final @Nullable Origin origin; - private Descriptor(String propertySource, Object value, Origin origin) { + private Descriptor(@Nullable String propertySource, @Nullable Object value, @Nullable Origin origin) { this.propertySource = propertySource; this.value = value; this.origin = origin; } - String getPropertySource() { + @Nullable String getPropertySource() { return this.propertySource; } - Object getValue() { + @Nullable Object getValue() { return this.value; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MissingParameterNamesFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MissingParameterNamesFailureAnalyzer.java index 7481a976aaa..bc38450145a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MissingParameterNamesFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MissingParameterNamesFailureAnalyzer.java @@ -19,6 +19,8 @@ package org.springframework.boot.diagnostics.analyzer; import java.util.HashSet; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalyzer; import org.springframework.core.Ordered; @@ -46,7 +48,7 @@ class MissingParameterNamesFailureAnalyzer implements FailureAnalyzer { """; @Override - public FailureAnalysis analyze(Throwable failure) { + public @Nullable FailureAnalysis analyze(Throwable failure) { return analyzeForMissingParameters(failure); } @@ -55,12 +57,12 @@ class MissingParameterNamesFailureAnalyzer implements FailureAnalyzer { * @param failure the failure to analyze * @return a failure analysis or {@code null} */ - static FailureAnalysis analyzeForMissingParameters(Throwable failure) { + static @Nullable FailureAnalysis analyzeForMissingParameters(Throwable failure) { return analyzeForMissingParameters(failure, failure, new HashSet<>()); } - private static FailureAnalysis analyzeForMissingParameters(Throwable rootFailure, Throwable cause, - Set seen) { + private static @Nullable FailureAnalysis analyzeForMissingParameters(Throwable rootFailure, + @Nullable Throwable cause, Set seen) { if (cause != null && seen.add(cause)) { if (isSpringParametersException(cause)) { return getAnalysis(rootFailure, cause); @@ -89,7 +91,7 @@ class MissingParameterNamesFailureAnalyzer implements FailureAnalyzer { return elements.length > 0 && isSpringClass(elements[0].getClassName()); } - private static boolean isSpringClass(String className) { + private static boolean isSpringClass(@Nullable String className) { return className != null && className.startsWith("org.springframework."); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MutuallyExclusiveConfigurationPropertiesFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MutuallyExclusiveConfigurationPropertiesFailureAnalyzer.java index 32d46258630..6e40c2e6a54 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MutuallyExclusiveConfigurationPropertiesFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/MutuallyExclusiveConfigurationPropertiesFailureAnalyzer.java @@ -25,6 +25,8 @@ import java.util.TreeSet; import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; @@ -53,7 +55,8 @@ class MutuallyExclusiveConfigurationPropertiesFailureAnalyzer } @Override - protected FailureAnalysis analyze(Throwable rootFailure, MutuallyExclusiveConfigurationPropertiesException cause) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, + MutuallyExclusiveConfigurationPropertiesException cause) { List descriptors = new ArrayList<>(); for (String name : cause.getConfiguredNames()) { List descriptorsForName = getDescriptors(name); @@ -115,9 +118,9 @@ class MutuallyExclusiveConfigurationPropertiesFailureAnalyzer private final String propertyName; - private final Origin origin; + private final @Nullable Origin origin; - private Descriptor(String propertyName, Origin origin) { + private Descriptor(String propertyName, @Nullable Origin origin) { this.propertyName = propertyName; this.origin = origin; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java index 4ad22fef830..700c421c3d2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoSuchMethodFailureAnalyzer.java @@ -23,6 +23,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.util.ClassUtils; @@ -38,12 +40,13 @@ import org.springframework.util.ClassUtils; class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer { @Override - protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) { NoSuchMethodDescriptor callerDescriptor = getCallerMethodDescriptor(cause); if (callerDescriptor == null) { return null; } - NoSuchMethodDescriptor calledDescriptor = getNoSuchMethodDescriptor(cause.getMessage()); + String message = cause.getMessage(); + NoSuchMethodDescriptor calledDescriptor = getNoSuchMethodDescriptor((message != null) ? message : ""); if (calledDescriptor == null) { return null; } @@ -52,20 +55,20 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer findCandidates(String className) { + private @Nullable List findCandidates(String className) { try { return Collections.list(NoSuchMethodFailureAnalyzer.class.getClassLoader() .getResources(ClassUtils.convertClassNameToResourcePath(className) + ".class")); @@ -123,7 +126,7 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer load(String className) { + private @Nullable Class load(String className) { try { return Class.forName(className, false, getClass().getClassLoader()); } @@ -132,7 +135,7 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer getTypeHierarchy(Class type) { + private @Nullable List getTypeHierarchy(Class type) { try { List typeHierarchy = new ArrayList<>(); while (type != null && !type.equals(Object.class)) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoUniqueBeanDefinitionFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoUniqueBeanDefinitionFailureAnalyzer.java index 5a14e839315..cfc012db2a2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoUniqueBeanDefinitionFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/NoUniqueBeanDefinitionFailureAnalyzer.java @@ -16,6 +16,8 @@ package org.springframework.boot.diagnostics.analyzer; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; @@ -42,8 +44,8 @@ class NoUniqueBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnal } @Override - protected FailureAnalysis analyze(Throwable rootFailure, NoUniqueBeanDefinitionException cause, - String description) { + protected @Nullable FailureAnalysis analyze(Throwable rootFailure, NoUniqueBeanDefinitionException cause, + @Nullable String description) { String[] beanNames = extractBeanNames(cause); if (beanNames == null) { return null; @@ -85,10 +87,13 @@ class NoUniqueBeanDefinitionFailureAnalyzer extends AbstractInjectionFailureAnal return (resourceDescription != null) ? resourceDescription : "unknown location"; } - private String[] extractBeanNames(NoUniqueBeanDefinitionException cause) { - if (cause.getMessage().contains("but found")) { - return StringUtils.commaDelimitedListToStringArray( - cause.getMessage().substring(cause.getMessage().lastIndexOf(':') + 1).trim()); + private String @Nullable [] extractBeanNames(NoUniqueBeanDefinitionException cause) { + String message = cause.getMessage(); + if (message == null) { + message = ""; + } + if (message.contains("but found")) { + return StringUtils.commaDelimitedListToStringArray(message.substring(message.lastIndexOf(':') + 1).trim()); } return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/UnboundConfigurationPropertyFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/UnboundConfigurationPropertyFailureAnalyzer.java index 7ff431a760b..63c2149c320 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/UnboundConfigurationPropertyFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/UnboundConfigurationPropertyFailureAnalyzer.java @@ -16,11 +16,14 @@ package org.springframework.boot.diagnostics.analyzer; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException; import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.util.Assert; /** * An {@link AbstractFailureAnalyzer} that performs analysis of failures caused by any @@ -34,6 +37,7 @@ class UnboundConfigurationPropertyFailureAnalyzer @Override protected FailureAnalysis analyze(Throwable rootFailure, UnboundConfigurationPropertiesException cause) { BindException exception = findCause(rootFailure, BindException.class); + Assert.state(exception != null, "BindException not found"); return analyzeUnboundConfigurationPropertiesException(exception, cause); } @@ -48,7 +52,7 @@ class UnboundConfigurationPropertyFailureAnalyzer return getFailureAnalysis(description, cause); } - private void buildDescription(StringBuilder description, ConfigurationProperty property) { + private void buildDescription(StringBuilder description, @Nullable ConfigurationProperty property) { if (property != null) { description.append(String.format("%n Property: %s", property.getName())); description.append(String.format("%n Value: \"%s\"", property.getValue())); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzer.java b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzer.java index 26f80bfa827..1d84dad9374 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzer.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/ValidationExceptionFailureAnalyzer.java @@ -18,6 +18,7 @@ package org.springframework.boot.diagnostics.analyzer; import jakarta.validation.NoProviderFoundException; import jakarta.validation.ValidationException; +import org.jspecify.annotations.Nullable; import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; import org.springframework.boot.diagnostics.FailureAnalysis; @@ -38,10 +39,13 @@ class ValidationExceptionFailureAnalyzer extends AbstractFailureAnalyzer imp } @Override - public Value getProperty(String name) { + public @Nullable Value getProperty(String name) { PropertyFile propertyFile = this.propertyFiles.get(name); return (propertyFile != null) ? propertyFile.getContent() : null; } @Override - public Origin getOrigin(String name) { + public @Nullable Origin getOrigin(String name) { PropertyFile propertyFile = this.propertyFiles.get(name); return (propertyFile != null) ? propertyFile.getOrigin() : null; } @@ -182,7 +184,7 @@ public class ConfigTreePropertySource extends EnumerablePropertySource imp private final Origin origin; - private final PropertyFileContent cachedContent; + private final @Nullable PropertyFileContent cachedContent; private final boolean autoTrimTrailingNewLine; @@ -273,7 +275,7 @@ public class ConfigTreePropertySource extends EnumerablePropertySource imp private final boolean autoTrimTrailingNewLine; - private volatile byte[] content; + private volatile byte @Nullable [] content; private PropertyFileContent(Path path, Resource resource, Origin origin, boolean cacheContent, boolean autoTrimTrailingNewLine) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java b/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java index 6d6554b9a0b..8ddc9b77bdc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java @@ -22,6 +22,8 @@ import java.util.function.Function; import javax.lang.model.element.Modifier; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.AotDetector; import org.springframework.aot.generate.GeneratedClass; import org.springframework.aot.generate.GenerationContext; @@ -70,7 +72,7 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica private int order = DEFAULT_ORDER; - private final Function postProcessorsFactory; + private final Function<@Nullable ClassLoader, EnvironmentPostProcessorsFactory> postProcessorsFactory; /** * Create a new {@link EnvironmentPostProcessorApplicationListener} with @@ -86,7 +88,7 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica * @param postProcessorsFactory the post processors factory */ private EnvironmentPostProcessorApplicationListener( - Function postProcessorsFactory) { + Function<@Nullable ClassLoader, EnvironmentPostProcessorsFactory> postProcessorsFactory) { this.postProcessorsFactory = postProcessorsFactory; this.deferredLogs = new DeferredLogs(); } @@ -145,7 +147,7 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica this.deferredLogs.switchOverAll(); } - List getEnvironmentPostProcessors(ResourceLoader resourceLoader, + List getEnvironmentPostProcessors(@Nullable ResourceLoader resourceLoader, ConfigurableBootstrapContext bootstrapContext) { ClassLoader classLoader = (resourceLoader != null) ? resourceLoader.getClassLoader() : null; EnvironmentPostProcessorsFactory postProcessorsFactory = this.postProcessorsFactory.apply(classLoader); @@ -157,8 +159,9 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica if (AotDetector.useGeneratedArtifacts()) { ClassLoader classLoader = (springApplication.getResourceLoader() != null) ? springApplication.getResourceLoader().getClassLoader() : null; - String postProcessorClassName = springApplication.getMainApplicationClass().getName() + "__" - + AOT_FEATURE_NAME; + Class mainApplicationClass = springApplication.getMainApplicationClass(); + Assert.state(mainApplicationClass != null, "mainApplicationClass not found"); + String postProcessorClassName = mainApplicationClass.getName() + "__" + AOT_FEATURE_NAME; if (ClassUtils.isPresent(postProcessorClassName, classLoader)) { postProcessors.add(0, instantiateEnvironmentPostProcessor(postProcessorClassName, classLoader)); } @@ -166,7 +169,7 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica } private EnvironmentPostProcessor instantiateEnvironmentPostProcessor(String postProcessorClassName, - ClassLoader classLoader) { + @Nullable ClassLoader classLoader) { try { Class initializerClass = ClassUtils.resolveClassName(postProcessorClassName, classLoader); Assert.isAssignable(EnvironmentPostProcessor.class, initializerClass); @@ -194,7 +197,7 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica static class EnvironmentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { @Override - public BeanFactoryInitializationAotContribution processAheadOfTime( + public @Nullable BeanFactoryInitializationAotContribution processAheadOfTime( ConfigurableListableBeanFactory beanFactory) { Environment environment = beanFactory.getBean(ConfigurableApplicationContext.ENVIRONMENT_BEAN_NAME, Environment.class); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java index 81dad8a0eb3..a066eba7fc4 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorsFactory.java @@ -18,6 +18,8 @@ package org.springframework.boot.env; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -47,7 +49,7 @@ public interface EnvironmentPostProcessorsFactory { * @param classLoader the source class loader * @return an {@link EnvironmentPostProcessorsFactory} instance */ - static EnvironmentPostProcessorsFactory fromSpringFactories(ClassLoader classLoader) { + static EnvironmentPostProcessorsFactory fromSpringFactories(@Nullable ClassLoader classLoader) { return new SpringFactoriesEnvironmentPostProcessorsFactory( SpringFactoriesLoader.forDefaultResourceLocation(classLoader)); } @@ -80,7 +82,7 @@ public interface EnvironmentPostProcessorsFactory { * @return an {@link EnvironmentPostProcessorsFactory} instance * @since 2.4.8 */ - static EnvironmentPostProcessorsFactory of(ClassLoader classLoader, String... classNames) { + static EnvironmentPostProcessorsFactory of(@Nullable ClassLoader classLoader, String... classNames) { return new ReflectionEnvironmentPostProcessorsFactory(classLoader, classNames); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedMapPropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedMapPropertySource.java index 4b13052a73b..8eb51eb513b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedMapPropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedMapPropertySource.java @@ -18,6 +18,8 @@ package org.springframework.boot.env; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; import org.springframework.boot.origin.OriginTrackedValue; @@ -60,7 +62,7 @@ public final class OriginTrackedMapPropertySource extends MapPropertySource impl } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { Object value = super.getProperty(name); if (value instanceof OriginTrackedValue originTrackedValue) { return originTrackedValue.getValue(); @@ -69,7 +71,7 @@ public final class OriginTrackedMapPropertySource extends MapPropertySource impl } @Override - public Origin getOrigin(String name) { + public @Nullable Origin getOrigin(String name) { Object value = super.getProperty(name); if (value instanceof OriginTrackedValue originTrackedValue) { return originTrackedValue.getOrigin(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java b/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java index ceb06e45fb7..25818122533 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; @@ -95,7 +96,7 @@ class OriginTrackedYamlLoader extends YamlProcessor { } @Override - public Object getData() throws NoSuchElementException { + public @Nullable Object getData() throws NoSuchElementException { Object data = super.getData(); if (data instanceof CharSequence charSequence && charSequence.isEmpty()) { return null; @@ -133,7 +134,7 @@ class OriginTrackedYamlLoader extends YamlProcessor { return OriginTrackedValue.of(getValue(value), origin); } - private Object getValue(Object value) { + private Object getValue(@Nullable Object value) { return (value != null) ? value : ""; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceRuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceRuntimeHints.java index 6093ad55c9c..749ca55ab8c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceRuntimeHints.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceRuntimeHints.java @@ -16,6 +16,8 @@ package org.springframework.boot.env; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.TypeReference; @@ -28,7 +30,7 @@ import org.springframework.aot.hint.TypeReference; class PropertySourceRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.reflection() .registerTypeIfPresent(classLoader, "org.yaml.snakeyaml.Yaml", (typeHint) -> typeHint.onReachableType(TypeReference.of(YamlPropertySourceLoader.class))); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java b/core/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java index 2999cce6133..7724d745209 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/RandomValuePropertySource.java @@ -25,6 +25,7 @@ import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MutablePropertySources; @@ -81,7 +82,7 @@ public class RandomValuePropertySource extends PropertySource { } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { if (!name.startsWith(PREFIX)) { return null; } @@ -110,7 +111,7 @@ public class RandomValuePropertySource extends PropertySource { return getRandomBytes(); } - private String getRange(String type, String prefix) { + private @Nullable String getRange(String type, String prefix) { if (type.startsWith(prefix)) { int startIndex = prefix.length() + 1; if (type.length() > startIndex) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java index 0101a911b36..bb97a6e434d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/ReflectionEnvironmentPostProcessorsFactory.java @@ -21,12 +21,14 @@ import java.util.Arrays; import java.util.List; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; import org.springframework.boot.BootstrapContext; import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.util.Instantiator; +import org.springframework.util.Assert; /** * {@link EnvironmentPostProcessorsFactory} implementation that uses reflection to create @@ -36,22 +38,22 @@ import org.springframework.boot.util.Instantiator; */ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProcessorsFactory { - private final List> classes; + private final @Nullable List> classes; - private ClassLoader classLoader; + private @Nullable ClassLoader classLoader; - private final List classNames; + private @Nullable final List classNames; ReflectionEnvironmentPostProcessorsFactory(Class... classes) { this.classes = new ArrayList<>(Arrays.asList(classes)); this.classNames = null; } - ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, String... classNames) { + ReflectionEnvironmentPostProcessorsFactory(@Nullable ClassLoader classLoader, String... classNames) { this(classLoader, Arrays.asList(classNames)); } - ReflectionEnvironmentPostProcessorsFactory(ClassLoader classLoader, List classNames) { + ReflectionEnvironmentPostProcessorsFactory(@Nullable ClassLoader classLoader, List classNames) { this.classes = null; this.classLoader = classLoader; this.classNames = classNames; @@ -68,8 +70,11 @@ class ReflectionEnvironmentPostProcessorsFactory implements EnvironmentPostProce parameters.add(BootstrapContext.class, bootstrapContext); parameters.add(BootstrapRegistry.class, bootstrapContext); }); - return (this.classes != null) ? instantiator.instantiateTypes(this.classes) - : instantiator.instantiate(this.classLoader, this.classNames); + if (this.classes != null) { + return instantiator.instantiateTypes(this.classes); + } + Assert.state(this.classNames != null, "'classNames' must not be null"); + return instantiator.instantiate(this.classLoader, this.classNames); } } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/SpringApplicationJsonEnvironmentPostProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/env/SpringApplicationJsonEnvironmentPostProcessor.java index ffcbdaa3faa..eb2edf13f70 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/SpringApplicationJsonEnvironmentPostProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/SpringApplicationJsonEnvironmentPostProcessor.java @@ -24,6 +24,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.SpringApplication; import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParserFactory; @@ -119,7 +121,7 @@ public class SpringApplicationJsonEnvironmentPostProcessor implements Environmen return result; } - private void flatten(String prefix, Map result, Map map) { + private void flatten(@Nullable String prefix, Map result, Map map) { String namePrefix = (prefix != null) ? prefix + "." : ""; map.forEach((key, value) -> extract(namePrefix + key, result, value)); } @@ -214,7 +216,7 @@ public class SpringApplicationJsonEnvironmentPostProcessor implements Environmen return PropertySourceOrigin.get(this.propertySource, this.propertyName); } - static JsonPropertyValue get(PropertySource propertySource) { + static @Nullable JsonPropertyValue get(PropertySource propertySource) { for (String candidate : CANDIDATES) { Object value = propertySource.getProperty(candidate); if (value instanceof String string && StringUtils.hasLength(string)) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java b/core/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java index a2fe614fbb1..3d3507ff0aa 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/SystemEnvironmentPropertySourceEnvironmentPostProcessor.java @@ -18,6 +18,8 @@ package org.springframework.boot.env; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.SpringApplication; import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginLookup; @@ -58,7 +60,7 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements @SuppressWarnings("unchecked") private void replacePropertySource(ConfigurableEnvironment environment, String sourceName, - PropertySource propertySource, String environmentPrefix) { + PropertySource propertySource, @Nullable String environmentPrefix) { Map originalSource = (Map) propertySource.getSource(); SystemEnvironmentPropertySource source = new OriginAwareSystemEnvironmentPropertySource(sourceName, originalSource, environmentPrefix); @@ -80,14 +82,15 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements protected static class OriginAwareSystemEnvironmentPropertySource extends SystemEnvironmentPropertySource implements OriginLookup { - private final String prefix; + private final @Nullable String prefix; - OriginAwareSystemEnvironmentPropertySource(String name, Map source, String environmentPrefix) { + OriginAwareSystemEnvironmentPropertySource(String name, Map source, + @Nullable String environmentPrefix) { super(name, source); this.prefix = determinePrefix(environmentPrefix); } - private String determinePrefix(String environmentPrefix) { + private @Nullable String determinePrefix(@Nullable String environmentPrefix) { if (!StringUtils.hasText(environmentPrefix)) { return null; } @@ -103,12 +106,12 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements } @Override - public Object getProperty(String name) { + public @Nullable Object getProperty(String name) { return super.getProperty(name); } @Override - public Origin getOrigin(String key) { + public @Nullable Origin getOrigin(String key) { String property = resolvePropertyName(key); if (super.containsProperty(property)) { return new SystemEnvironmentOrigin(property); @@ -117,7 +120,7 @@ public class SystemEnvironmentPropertySourceEnvironmentPostProcessor implements } @Override - public String getPrefix() { + public @Nullable String getPrefix() { return this.prefix; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/env/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/env/package-info.java index d92a84ffbbe..d71a1992632 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/env/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/env/package-info.java @@ -17,4 +17,7 @@ /** * Spring {@link org.springframework.core.env.Environment} support. */ +@NullMarked package org.springframework.boot.env; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java index eef781606a5..eb69f7ac9f8 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/BuildProperties.java @@ -21,6 +21,8 @@ import java.time.Instant; import java.time.format.DateTimeFormatter; import java.util.Properties; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.info.BuildProperties.BuildPropertiesRuntimeHints; @@ -47,7 +49,7 @@ public class BuildProperties extends InfoProperties { * Return the groupId of the project or {@code null}. * @return the group */ - public String getGroup() { + public @Nullable String getGroup() { return get("group"); } @@ -55,7 +57,7 @@ public class BuildProperties extends InfoProperties { * Return the artifactId of the project or {@code null}. * @return the artifact */ - public String getArtifact() { + public @Nullable String getArtifact() { return get("artifact"); } @@ -63,7 +65,7 @@ public class BuildProperties extends InfoProperties { * Return the name of the project or {@code null}. * @return the name */ - public String getName() { + public @Nullable String getName() { return get("name"); } @@ -71,7 +73,7 @@ public class BuildProperties extends InfoProperties { * Return the version of the project or {@code null}. * @return the version */ - public String getVersion() { + public @Nullable String getVersion() { return get("version"); } @@ -83,7 +85,7 @@ public class BuildProperties extends InfoProperties { * @return the build time * @see #get(String) */ - public Instant getTime() { + public @Nullable Instant getTime() { return getInstant("time"); } @@ -109,7 +111,7 @@ public class BuildProperties extends InfoProperties { static class BuildPropertiesRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources().registerPattern("META-INF/build-info.properties"); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java index d9b37718510..9134343da0f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/GitProperties.java @@ -25,6 +25,8 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.boot.info.GitProperties.GitPropertiesRuntimeHints; @@ -50,7 +52,7 @@ public class GitProperties extends InfoProperties { * Return the name of the branch or {@code null}. * @return the branch */ - public String getBranch() { + public @Nullable String getBranch() { return get("branch"); } @@ -58,7 +60,7 @@ public class GitProperties extends InfoProperties { * Return the full id of the commit or {@code null}. * @return the full commit id */ - public String getCommitId() { + public @Nullable String getCommitId() { return get("commit.id"); } @@ -66,7 +68,7 @@ public class GitProperties extends InfoProperties { * Return the abbreviated id of the commit or {@code null}. * @return the short commit id */ - public String getShortCommitId() { + public @Nullable String getShortCommitId() { String shortId = get("commit.id.abbrev"); if (shortId != null) { return shortId; @@ -86,7 +88,7 @@ public class GitProperties extends InfoProperties { * @return the commit time * @see #get(String) */ - public Instant getCommitTime() { + public @Nullable Instant getCommitTime() { return getInstant("commit.time"); } @@ -119,7 +121,7 @@ public class GitProperties extends InfoProperties { static class GitPropertiesRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { hints.resources().registerPattern("git.properties"); } @@ -128,14 +130,14 @@ public class GitProperties extends InfoProperties { /** * Coercer used to convert a source value to epoch time. */ - private record Coercer(Function action, Predicate ignoredExceptions) { + private record Coercer(Function action, Predicate ignoredExceptions) { /** * Attempt to convert the specified value to epoch time. * @param value the value to coerce to * @return the epoch time in milliseconds or {@code null} */ - String apply(String value) { + @Nullable String apply(String value) { try { Long result = this.action.apply(value); return (result != null) ? String.valueOf(result) : null; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/InfoProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/info/InfoProperties.java index 721e8b876a9..849f12a83e5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/InfoProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/InfoProperties.java @@ -21,6 +21,8 @@ import java.util.Iterator; import java.util.Map; import java.util.Properties; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.util.Assert; @@ -50,7 +52,7 @@ public class InfoProperties implements Iterable { * @param key the key of the property * @return the property value */ - public String get(String key) { + public @Nullable String get(String key) { return this.entries.getProperty(key); } @@ -60,7 +62,7 @@ public class InfoProperties implements Iterable { * @param key the key of the property * @return the property value */ - public Instant getInstant(String key) { + public @Nullable Instant getInstant(String key) { String s = get(key); if (s != null) { try { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java b/core/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java index b8fa1d8e459..a3e0b91236a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/ProcessInfo.java @@ -24,6 +24,8 @@ import java.lang.management.PlatformManagedObject; import java.lang.reflect.Method; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.util.ClassUtils; /** @@ -46,7 +48,7 @@ public class ProcessInfo { private final long parentPid; - private final String owner; + private final @Nullable String owner; public ProcessInfo() { ProcessHandle process = ProcessHandle.current(); @@ -94,7 +96,7 @@ public class ProcessInfo { * @since 3.5.0 */ @SuppressWarnings("unchecked") - public VirtualThreadsInfo getVirtualThreads() { + public @Nullable VirtualThreadsInfo getVirtualThreads() { if (!VIRTUAL_THREAD_SCHEDULER_CLASS_PRESENT) { return null; } @@ -127,7 +129,7 @@ public class ProcessInfo { return this.parentPid; } - public String getOwner() { + public @Nullable String getOwner() { return this.owner; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java b/core/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java index 3147235f66a..46da37d4e64 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/SslInfo.java @@ -30,10 +30,13 @@ import java.util.function.Function; import javax.security.auth.x500.X500Principal; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.info.SslInfo.CertificateValidityInfo.Status; import org.springframework.boot.ssl.NoSuchSslBundleException; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslBundles; +import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** @@ -106,7 +109,7 @@ public class SslInfo { this.certificateChains = extractCertificateChains(sslBundle.getStores().getKeyStore()); } - private List extractCertificateChains(KeyStore keyStore) { + private List extractCertificateChains(@Nullable KeyStore keyStore) { if (keyStore == null) { return Collections.emptyList(); } @@ -171,44 +174,46 @@ public class SslInfo { */ public final class CertificateInfo { - private final X509Certificate certificate; + private final @Nullable X509Certificate certificate; private CertificateInfo(Certificate certificate) { this.certificate = (certificate instanceof X509Certificate x509Certificate) ? x509Certificate : null; } - public String getSubject() { + public @Nullable String getSubject() { return extract(X509Certificate::getSubjectX500Principal, X500Principal::getName); } - public String getIssuer() { + public @Nullable String getIssuer() { return extract(X509Certificate::getIssuerX500Principal, X500Principal::getName); } - public String getSerialNumber() { + public @Nullable String getSerialNumber() { return extract(X509Certificate::getSerialNumber, (serial) -> serial.toString(16)); } - public String getVersion() { + public @Nullable String getVersion() { return extract((certificate) -> "V" + certificate.getVersion()); } - public String getSignatureAlgorithmName() { + public @Nullable String getSignatureAlgorithmName() { return extract(X509Certificate::getSigAlgName); } - public Instant getValidityStarts() { + public @Nullable Instant getValidityStarts() { return extract(X509Certificate::getNotBefore, Date::toInstant); } - public Instant getValidityEnds() { + public @Nullable Instant getValidityEnds() { return extract(X509Certificate::getNotAfter, Date::toInstant); } - public CertificateValidityInfo getValidity() { + public @Nullable CertificateValidityInfo getValidity() { return extract((certificate) -> { Instant starts = getValidityStarts(); Instant ends = getValidityEnds(); + Assert.state(starts != null, "Validity start not found"); + Assert.state(ends != null, "Validity end not found"); CertificateValidityInfo.Status validity = checkValidity(starts, ends); return switch (validity) { case VALID -> CertificateValidityInfo.VALID; @@ -230,11 +235,12 @@ public class SslInfo { return CertificateValidityInfo.Status.VALID; } - private R extract(Function valueExtractor, Function resultExtractor) { + private @Nullable R extract(Function valueExtractor, + Function resultExtractor) { return extract(valueExtractor.andThen(resultExtractor)); } - private R extract(Function extractor) { + private @Nullable R extract(Function extractor) { return (this.certificate != null) ? extractor.apply(this.certificate) : null; } @@ -249,9 +255,9 @@ public class SslInfo { private final Status status; - private final String message; + private final @Nullable String message; - CertificateValidityInfo(Status status, String message, Object... messageArgs) { + CertificateValidityInfo(Status status, @Nullable String message, Object... messageArgs) { this.status = status; this.message = (message != null) ? message.formatted(messageArgs) : null; } @@ -260,7 +266,7 @@ public class SslInfo { return this.status; } - public String getMessage() { + public @Nullable String getMessage() { return this.message; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/info/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/info/package-info.java index d85367f0285..c5337f27f04 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/info/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/info/package-info.java @@ -17,4 +17,7 @@ /** * Support for providing information about an application. */ +@NullMarked package org.springframework.boot.info; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java b/core/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java index d553464e617..c47abba6847 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/io/ApplicationResourceLoader.java @@ -24,6 +24,8 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.ContextResource; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.FileSystemResource; @@ -76,7 +78,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { * @return a {@link ResourceLoader} instance * @since 3.4.0 */ - public static ResourceLoader get(ClassLoader classLoader) { + public static ResourceLoader get(@Nullable ClassLoader classLoader) { return get(classLoader, SpringFactoriesLoader.forDefaultResourceLocation(classLoader)); } @@ -90,7 +92,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { * @return a {@link ResourceLoader} instance * @since 3.4.0 */ - public static ResourceLoader get(ClassLoader classLoader, SpringFactoriesLoader springFactoriesLoader) { + public static ResourceLoader get(@Nullable ClassLoader classLoader, SpringFactoriesLoader springFactoriesLoader) { return get(classLoader, springFactoriesLoader, null); } @@ -105,8 +107,8 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { * @return a {@link ResourceLoader} instance * @since 3.5.0 */ - public static ResourceLoader get(ClassLoader classLoader, SpringFactoriesLoader springFactoriesLoader, - Path workingDirectory) { + public static ResourceLoader get(@Nullable ClassLoader classLoader, SpringFactoriesLoader springFactoriesLoader, + @Nullable Path workingDirectory) { return get(ApplicationFileSystemResourceLoader.get(classLoader, workingDirectory), springFactoriesLoader); } @@ -171,9 +173,10 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { private static final ResourceLoader shared = new ApplicationFileSystemResourceLoader(null, null); - private final Path workingDirectory; + private final @Nullable Path workingDirectory; - private ApplicationFileSystemResourceLoader(ClassLoader classLoader, Path workingDirectory) { + private ApplicationFileSystemResourceLoader(@Nullable ClassLoader classLoader, + @Nullable Path workingDirectory) { super(classLoader); this.workingDirectory = workingDirectory; } @@ -187,13 +190,13 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { if (!resource.isFile()) { return resource; } - return resolveFile(resource); + return resolveFile(resource, this.workingDirectory); } - private Resource resolveFile(Resource resource) { + private Resource resolveFile(Resource resource, Path workingDirectory) { try { File file = resource.getFile(); - return new ApplicationResource(this.workingDirectory.resolve(file.toPath())); + return new ApplicationResource(workingDirectory.resolve(file.toPath())); } catch (FileNotFoundException ex) { return resource; @@ -208,7 +211,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { return new ApplicationResource(path); } - static ResourceLoader get(ClassLoader classLoader, Path workingDirectory) { + static ResourceLoader get(@Nullable ClassLoader classLoader, @Nullable Path workingDirectory) { if (classLoader == null && workingDirectory != null) { throw new IllegalArgumentException( "It's not possible to use null as 'classLoader' but specify a 'workingDirectory'"); @@ -237,7 +240,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { * @return the file path of the resource or {@code null} if the it is not possible * to represent the resource as a {@link FileSystemResource}. */ - String resolveFilePath(String location, Resource resource); + @Nullable String resolveFilePath(String location, Resource resource); } @@ -281,7 +284,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { } @Override - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return this.resourceLoader.getClassLoader(); } @@ -300,7 +303,7 @@ public class ApplicationResourceLoader extends DefaultResourceLoader { return (filePath != null) ? new ApplicationResource(filePath) : resource; } - private String getFilePath(String location, Resource resource) { + private @Nullable String getFilePath(String location, Resource resource) { for (FilePathResolver filePathResolver : this.filePathResolvers) { String filePath = filePathResolver.resolveFilePath(location, resource); if (filePath != null) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/io/Base64ProtocolResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/io/Base64ProtocolResolver.java index 696f26f105e..2cfc5b1fc5c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/io/Base64ProtocolResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/io/Base64ProtocolResolver.java @@ -18,6 +18,8 @@ package org.springframework.boot.io; import java.util.Base64; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ProtocolResolver; import org.springframework.core.io.Resource; @@ -33,7 +35,7 @@ class Base64ProtocolResolver implements ProtocolResolver { private static final String BASE64_PREFIX = "base64:"; @Override - public Resource resolve(String location, ResourceLoader resourceLoader) { + public @Nullable Resource resolve(String location, ResourceLoader resourceLoader) { if (location.startsWith(BASE64_PREFIX)) { String value = location.substring(BASE64_PREFIX.length()); return new ByteArrayResource(decode(value)); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/io/ClassPathResourceFilePathResolver.java b/core/spring-boot/src/main/java/org/springframework/boot/io/ClassPathResourceFilePathResolver.java index f0d70a1a8ba..1d1edc377e2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/io/ClassPathResourceFilePathResolver.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/io/ClassPathResourceFilePathResolver.java @@ -16,6 +16,8 @@ package org.springframework.boot.io; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.io.ApplicationResourceLoader.FilePathResolver; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -29,7 +31,7 @@ import org.springframework.core.io.ResourceLoader; class ClassPathResourceFilePathResolver implements ApplicationResourceLoader.FilePathResolver { @Override - public String resolveFilePath(String location, Resource resource) { + public @Nullable String resolveFilePath(String location, Resource resource) { return (resource instanceof ClassPathResource && !isClassPathUrl(location)) ? location : null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/io/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/io/package-info.java index aa7879c0f69..531a671387f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/io/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/io/package-info.java @@ -17,4 +17,7 @@ /** * Support for loading resources. */ +@NullMarked package org.springframework.boot.io; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/AbstractJsonParser.java b/core/spring-boot/src/main/java/org/springframework/boot/json/AbstractJsonParser.java index 1b0a137ebce..bf08e2565a0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/AbstractJsonParser.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/AbstractJsonParser.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.concurrent.Callable; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.util.ReflectionUtils; /** @@ -40,7 +42,7 @@ public abstract class AbstractJsonParser implements JsonParser { return trimParse(json, "[", parser); } - protected final T trimParse(String json, String prefix, Function parser) { + protected final T trimParse(@Nullable String json, String prefix, Function parser) { String trimmed = (json != null) ? json.trim() : ""; if (trimmed.startsWith(prefix)) { return parser.apply(trimmed); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/BasicJsonParser.java b/core/spring-boot/src/main/java/org/springframework/boot/json/BasicJsonParser.java index 9e4a575bc9e..00957e63229 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/BasicJsonParser.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/BasicJsonParser.java @@ -22,6 +22,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -80,11 +82,17 @@ public class BasicJsonParser extends AbstractJsonParser { Map map = new LinkedHashMap<>(); json = trimEdges(json, '{', '}').trim(); for (String pair : tokenize(json)) { - String[] values = StringUtils.trimArrayElements(StringUtils.split(pair, ":")); - Assert.state(values[0].startsWith("\"") && values[0].endsWith("\""), + String[] split = StringUtils.split(pair, ":"); + @Nullable String[] values = (split != null) ? StringUtils.trimArrayElements(split) : null; + Assert.state(values != null, () -> "Unable to parse '%s'".formatted(pair)); + String rawKey = values[0]; + String rawValue = values[1]; + Assert.state(rawKey != null, () -> "rawKew is null in '%s'".formatted(pair)); + Assert.state(rawKey.startsWith("\"") && rawKey.endsWith("\""), "Expecting double-quotes around field names"); - String key = trimEdges(values[0], '"', '"'); - Object value = parseInternal(nesting, values[1]); + String key = trimEdges(rawKey, '"', '"'); + Assert.state(rawValue != null, () -> "rawValue is null in '%s'".formatted(pair)); + Object value = parseInternal(nesting, rawValue); map.put(key, value); } return map; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java index 8f159268aec..2d72f2b45dc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java @@ -21,6 +21,7 @@ import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.jspecify.annotations.Nullable; /** * Thin wrapper to adapt Jackson 2 {@link ObjectMapper} to {@link JsonParser}. @@ -35,7 +36,7 @@ public class JacksonJsonParser extends AbstractJsonParser { private static final ListTypeReference LIST_TYPE = new ListTypeReference(); - private ObjectMapper objectMapper; // Late binding + private @Nullable ObjectMapper objectMapper; // Late binding /** * Creates an instance with the specified {@link ObjectMapper}. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java index da1b79b47bc..5fefc1c4dc5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicBooleanSer import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicIntegerSerializer; import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicLongSerializer; import com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; @@ -39,7 +40,7 @@ import org.springframework.util.ClassUtils; class JacksonRuntimeHints implements RuntimeHintsRegistrar { @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { if (!ClassUtils.isPresent("com.fasterxml.jackson.databind.ser.BasicSerializerFactory", classLoader)) { return; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParseException.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParseException.java index 8782d258a39..3f9700c7a8b 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParseException.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParseException.java @@ -16,6 +16,8 @@ package org.springframework.boot.json; +import org.jspecify.annotations.Nullable; + /** * {@link IllegalArgumentException} thrown when source JSON is invalid. * @@ -29,7 +31,7 @@ public class JsonParseException extends IllegalArgumentException { this(null); } - public JsonParseException(Throwable cause) { + public JsonParseException(@Nullable Throwable cause) { super("Cannot parse JSON", cause); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java index ba0fed73a10..b2446e8812f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonValueWriter.java @@ -29,6 +29,8 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.json.JsonWriter.MemberPath; import org.springframework.boot.json.JsonWriter.NameProcessor; import org.springframework.boot.json.JsonWriter.ValueProcessor; @@ -94,7 +96,7 @@ class JsonValueWriter { * written * @param value the value */ - void write(N name, V value) { + void write(@Nullable N name, @Nullable V value) { if (name != null) { writePair(name, value); } @@ -117,7 +119,7 @@ class JsonValueWriter { * @param the value type * @param value the value to write */ - void write(V value) { + void write(@Nullable V value) { value = processValue(value); if (value == null) { append("null"); @@ -158,7 +160,7 @@ class JsonValueWriter { * @see #writePairs(Consumer) * @see #writeElements(Consumer) */ - void start(Series series) { + void start(@Nullable Series series) { if (series != null) { int nestingDepth = this.activeSeries.size(); Assert.state(nestingDepth <= this.maxNestingDepth, @@ -174,7 +176,7 @@ class JsonValueWriter { * @param series the series type being ended (must match {@link #start(Series)}) * @see #start(Series) */ - void end(Series series) { + void end(@Nullable Series series) { if (series != null) { this.activeSeries.pop(); append(series.closeChar); @@ -242,7 +244,7 @@ class JsonValueWriter { pairs.accept(this::writePair); } - private void writePair(N name, V value) { + private void writePair(N name, @Nullable V value) { this.path = this.path.child(name.toString()); if (!isFilteredPath()) { String processedName = processName(name.toString()); @@ -335,7 +337,7 @@ class JsonValueWriter { return name; } - private V processValue(V value) { + private @Nullable V processValue(@Nullable V value) { for (JsonWriterFiltersAndProcessors filtersAndProcessors : this.filtersAndProcessors) { for (ValueProcessor valueProcessor : filtersAndProcessors.valueProcessors()) { value = processValue(value, valueProcessor); @@ -344,9 +346,9 @@ class JsonValueWriter { return value; } - @SuppressWarnings({ "unchecked", "unchecked" }) - private V processValue(V value, ValueProcessor valueProcessor) { - return (V) LambdaSafe.callback(ValueProcessor.class, valueProcessor, this.path, value) + @SuppressWarnings({ "unchecked" }) + private @Nullable V processValue(@Nullable V value, ValueProcessor valueProcessor) { + return (V) LambdaSafe.callback(ValueProcessor.class, valueProcessor, this.path, new Object[] { value }) .invokeAnd((call) -> call.processValue(this.path, value)) .get(value); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java index f5aca17c868..5f3d900eac5 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonWriter.java @@ -29,6 +29,8 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; +import org.jspecify.annotations.NullUnmarked; + import org.springframework.boot.json.JsonValueWriter.Series; import org.springframework.boot.json.JsonWriter.Member.Extractor; import org.springframework.util.Assert; @@ -73,6 +75,7 @@ import org.springframework.util.StringUtils; * @author Moritz Halbritter * @since 3.4.0 */ +@NullUnmarked @FunctionalInterface public interface JsonWriter { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/package-info.java b/core/spring-boot/src/main/java/org/springframework/boot/json/package-info.java index d0d71493605..7d7c2ed446e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/json/package-info.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/json/package-info.java @@ -19,4 +19,7 @@ * * @see org.springframework.boot.json.JsonParser */ +@NullMarked package org.springframework.boot.json; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java index 9ed1c0f231c..1bc91f4bd8d 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/AbstractLoggingSystem.java @@ -24,8 +24,11 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.springframework.util.SystemPropertyUtils; @@ -53,7 +56,8 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { } @Override - public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { + public void initialize(LoggingInitializationContext initializationContext, @Nullable String configLocation, + @Nullable LogFile logFile) { if (StringUtils.hasLength(configLocation)) { initializeWithSpecificConfig(initializationContext, configLocation, logFile); return; @@ -62,12 +66,13 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { } private void initializeWithSpecificConfig(LoggingInitializationContext initializationContext, String configLocation, - LogFile logFile) { + @Nullable LogFile logFile) { configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); loadConfiguration(initializationContext, configLocation, logFile); } - private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) { + private void initializeWithConventions(LoggingInitializationContext initializationContext, + @Nullable LogFile logFile) { String config = getSelfInitializationConfig(); if (config != null && logFile == null) { // self initialization has occurred, reinitialize in case of property changes @@ -90,7 +95,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * will have been applied. * @return the self initialization config or {@code null} */ - protected String getSelfInitializationConfig() { + protected @Nullable String getSelfInitializationConfig() { return findConfig(getStandardConfigLocations()); } @@ -99,11 +104,11 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * this method checks {@link #getSpringConfigLocations()}. * @return the spring initialization config or {@code null} */ - protected String getSpringInitializationConfig() { + protected @Nullable String getSpringInitializationConfig() { return findConfig(getSpringConfigLocations()); } - private String findConfig(String[] locations) { + private @Nullable String findConfig(String[] locations) { for (String location : locations) { ClassPathResource resource = new ClassPathResource(location, this.classLoader); if (resource.exists()) { @@ -130,8 +135,8 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { String[] locations = getStandardConfigLocations(); for (int i = 0; i < locations.length; i++) { String extension = StringUtils.getFilenameExtension(locations[i]); - locations[i] = locations[i].substring(0, locations[i].length() - extension.length() - 1) + "-spring." - + extension; + int extensionLength = (extension != null) ? (extension.length() + 1) : 0; + locations[i] = locations[i].substring(0, locations[i].length() - extensionLength) + "-spring." + extension; } return locations; } @@ -141,7 +146,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * @param initializationContext the logging initialization context * @param logFile the file to load or {@code null} if no log file is to be written */ - protected abstract void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile); + protected abstract void loadDefaults(LoggingInitializationContext initializationContext, @Nullable LogFile logFile); /** * Load a specific configuration. @@ -150,7 +155,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * @param logFile the file to load or {@code null} if no log file is to be written */ protected abstract void loadConfiguration(LoggingInitializationContext initializationContext, String location, - LogFile logFile); + @Nullable LogFile logFile); /** * Reinitialize the logging system if required. Called when @@ -174,7 +179,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { return defaultPath; } - protected final void applySystemProperties(Environment environment, LogFile logFile) { + protected final void applySystemProperties(Environment environment, @Nullable LogFile logFile) { new LoggingSystemProperties(environment, getDefaultValueResolver(environment), null).apply(logFile); } @@ -184,11 +189,12 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * @return the default value resolver * @since 3.2.0 */ - protected Function getDefaultValueResolver(Environment environment) { + protected Function<@Nullable String, @Nullable String> getDefaultValueResolver(Environment environment) { String defaultLogCorrelationPattern = getDefaultLogCorrelationPattern(); return (name) -> { - if (StringUtils.hasLength(defaultLogCorrelationPattern) - && LoggingSystemProperty.CORRELATION_PATTERN.getApplicationPropertyName().equals(name) + String applicationPropertyName = LoggingSystemProperty.CORRELATION_PATTERN.getApplicationPropertyName(); + Assert.state(applicationPropertyName != null, "applicationPropertyName must not be null"); + if (StringUtils.hasLength(defaultLogCorrelationPattern) && applicationPropertyName.equals(name) && environment.getProperty(LoggingSystem.EXPECT_CORRELATION_ID_PROPERTY, Boolean.class, false)) { return defaultLogCorrelationPattern; } @@ -202,7 +208,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { * @return the default log correlation pattern * @since 3.2.0 */ - protected String getDefaultLogCorrelationPattern() { + protected @Nullable String getDefaultLogCorrelationPattern() { return null; } @@ -227,11 +233,11 @@ public abstract class AbstractLoggingSystem extends LoggingSystem { this.nativeToSystem.putIfAbsent(nativeLevel, system); } - public LogLevel convertNativeToSystem(T level) { + public @Nullable LogLevel convertNativeToSystem(T level) { return this.nativeToSystem.get(level); } - public T convertSystemToNative(LogLevel level) { + public @Nullable T convertSystemToNative(@Nullable LogLevel level) { return this.systemToNative.get(level); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java index cd30a1eaf29..d7b7a7ea50e 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/CorrelationIdFormatter.java @@ -28,6 +28,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -84,7 +86,7 @@ public final class CorrelationIdFormatter { * @param resolver the resolver used to resolve named values * @return a formatted correlation id */ - public String format(UnaryOperator resolver) { + public String format(UnaryOperator<@Nullable String> resolver) { StringBuilder result = new StringBuilder(); formatTo(resolver, result); return result.toString(); @@ -96,7 +98,7 @@ public final class CorrelationIdFormatter { * @param resolver the resolver used to resolve named values * @param appendable the appendable for the formatted correlation id */ - public void formatTo(UnaryOperator resolver, Appendable appendable) { + public void formatTo(UnaryOperator<@Nullable String> resolver, Appendable appendable) { Predicate canResolve = (part) -> StringUtils.hasLength(resolver.apply(part.name())); try { if (this.parts.stream().anyMatch(canResolve)) { @@ -128,7 +130,7 @@ public final class CorrelationIdFormatter { * @param spec a comma-separated specification * @return a new {@link CorrelationIdFormatter} instance */ - public static CorrelationIdFormatter of(String spec) { + public static CorrelationIdFormatter of(@Nullable String spec) { try { return (!StringUtils.hasText(spec)) ? DEFAULT : of(List.of(spec.split(","))); } @@ -142,7 +144,7 @@ public final class CorrelationIdFormatter { * @param spec a pre-separated specification * @return a new {@link CorrelationIdFormatter} instance */ - public static CorrelationIdFormatter of(String[] spec) { + public static CorrelationIdFormatter of(String @Nullable [] spec) { return of((spec != null) ? List.of(spec) : Collections.emptyList()); } @@ -169,7 +171,7 @@ public final class CorrelationIdFormatter { private static final Pattern pattern = Pattern.compile("^(.+?)\\((\\d+)\\)$"); - String resolve(UnaryOperator resolver) { + String resolve(UnaryOperator<@Nullable String> resolver) { String resolved = resolver.apply(name()); if (resolved == null) { return blank(); diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java index 18fd7452417..f19f6ea61cc 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/DeferredLog.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; @@ -35,9 +36,9 @@ import org.springframework.util.Assert; */ public class DeferredLog implements Log { - private Log destination; + private @Nullable Log destination; - private final Supplier destinationSupplier; + private final @Nullable Supplier destinationSupplier; private final Lines lines; @@ -109,7 +110,7 @@ public class DeferredLog implements Log { } @Override - public void trace(Object message, Throwable t) { + public void trace(Object message, @Nullable Throwable t) { log(LogLevel.TRACE, message, t); } @@ -119,7 +120,7 @@ public class DeferredLog implements Log { } @Override - public void debug(Object message, Throwable t) { + public void debug(Object message, @Nullable Throwable t) { log(LogLevel.DEBUG, message, t); } @@ -129,7 +130,7 @@ public class DeferredLog implements Log { } @Override - public void info(Object message, Throwable t) { + public void info(Object message, @Nullable Throwable t) { log(LogLevel.INFO, message, t); } @@ -139,7 +140,7 @@ public class DeferredLog implements Log { } @Override - public void warn(Object message, Throwable t) { + public void warn(Object message, @Nullable Throwable t) { log(LogLevel.WARN, message, t); } @@ -149,7 +150,7 @@ public class DeferredLog implements Log { } @Override - public void error(Object message, Throwable t) { + public void error(Object message, @Nullable Throwable t) { log(LogLevel.ERROR, message, t); } @@ -159,11 +160,11 @@ public class DeferredLog implements Log { } @Override - public void fatal(Object message, Throwable t) { + public void fatal(Object message, @Nullable Throwable t) { log(LogLevel.FATAL, message, t); } - private void log(LogLevel level, Object message, Throwable t) { + private void log(LogLevel level, Object message, @Nullable Throwable t) { synchronized (this.lines) { if (this.destination != null) { logTo(this.destination, level, message, t); @@ -176,6 +177,7 @@ public class DeferredLog implements Log { void switchOver() { synchronized (this.lines) { + Assert.state(this.destinationSupplier != null, "destinationSupplier hasn't been set"); this.destination = this.destinationSupplier.get(); } } @@ -245,7 +247,7 @@ public class DeferredLog implements Log { return destination; } - static void logTo(Log log, LogLevel level, Object message, Throwable throwable) { + static void logTo(Log log, LogLevel level, Object message, @Nullable Throwable throwable) { switch (level) { case TRACE -> log.trace(message, throwable); case DEBUG -> log.debug(message, throwable); @@ -260,7 +262,8 @@ public class DeferredLog implements Log { private final List lines = new ArrayList<>(); - void add(Supplier destinationSupplier, LogLevel level, Object message, Throwable throwable) { + void add(@Nullable Supplier destinationSupplier, LogLevel level, Object message, + @Nullable Throwable throwable) { this.lines.add(new Line(destinationSupplier, level, message, throwable)); } @@ -277,15 +280,16 @@ public class DeferredLog implements Log { static class Line { - private final Supplier destinationSupplier; + private final @Nullable Supplier destinationSupplier; private final LogLevel level; private final Object message; - private final Throwable throwable; + private final @Nullable Throwable throwable; - Line(Supplier destinationSupplier, LogLevel level, Object message, Throwable throwable) { + Line(@Nullable Supplier destinationSupplier, LogLevel level, Object message, + @Nullable Throwable throwable) { this.destinationSupplier = destinationSupplier; this.level = level; this.message = message; @@ -293,6 +297,7 @@ public class DeferredLog implements Log { } Log getDestination() { + Assert.state(this.destinationSupplier != null, "destinationSupplier hasn't been set"); return this.destinationSupplier.get(); } @@ -304,7 +309,7 @@ public class DeferredLog implements Log { return this.message; } - Throwable getThrowable() { + @Nullable Throwable getThrowable() { return this.throwable; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/DelegatingLoggingSystemFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/DelegatingLoggingSystemFactory.java index 83681611149..7ed3a8ec1e3 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/DelegatingLoggingSystemFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/DelegatingLoggingSystemFactory.java @@ -19,6 +19,8 @@ package org.springframework.boot.logging; import java.util.List; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + /** * {@link LoggingSystemFactory} that delegates to other factories. * @@ -26,18 +28,18 @@ import java.util.function.Function; */ class DelegatingLoggingSystemFactory implements LoggingSystemFactory { - private final Function> delegates; + private final @Nullable Function> delegates; /** * Create a new {@link DelegatingLoggingSystemFactory} instance. * @param delegates a function that provides the delegates */ - DelegatingLoggingSystemFactory(Function> delegates) { + DelegatingLoggingSystemFactory(@Nullable Function> delegates) { this.delegates = delegates; } @Override - public LoggingSystem getLoggingSystem(ClassLoader classLoader) { + public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) { List delegates = (this.delegates != null) ? this.delegates.apply(classLoader) : null; if (delegates != null) { for (LoggingSystemFactory delegate : delegates) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java index 0bbaa18d7bc..bf6b54562d9 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LogFile.java @@ -19,6 +19,8 @@ package org.springframework.boot.logging; import java.io.File; import java.util.Properties; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.Environment; import org.springframework.core.env.PropertyResolver; import org.springframework.util.Assert; @@ -51,9 +53,9 @@ public class LogFile { */ public static final String FILE_PATH_PROPERTY = "logging.file.path"; - private final String file; + private final @Nullable String file; - private final String path; + private final @Nullable String path; /** * Create a new {@link LogFile} instance. @@ -68,7 +70,7 @@ public class LogFile { * @param file a reference to the file to write * @param path a reference to the logging path to use if {@code file} is not specified */ - LogFile(String file, String path) { + LogFile(@Nullable String file, @Nullable String path) { Assert.isTrue(StringUtils.hasLength(file) || StringUtils.hasLength(path), "'file' or 'path' must not be empty"); this.file = file; this.path = path; @@ -90,7 +92,7 @@ public class LogFile { put(properties, LoggingSystemProperty.LOG_FILE, toString()); } - private void put(Properties properties, LoggingSystemProperty property, String value) { + private void put(Properties properties, LoggingSystemProperty property, @Nullable String value) { if (StringUtils.hasLength(value)) { properties.put(property.getEnvironmentVariableName(), value); } @@ -111,7 +113,7 @@ public class LogFile { * @return a {@link LogFile} or {@code null} if the environment didn't contain any * suitable properties */ - public static LogFile get(PropertyResolver propertyResolver) { + public static @Nullable LogFile get(PropertyResolver propertyResolver) { String file = propertyResolver.getProperty(FILE_NAME_PROPERTY); String path = propertyResolver.getProperty(FILE_PATH_PROPERTY); if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LogLevel.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LogLevel.java index 375cc1d0746..8fd2cde0cbe 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LogLevel.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LogLevel.java @@ -17,6 +17,7 @@ package org.springframework.boot.logging; import org.apache.commons.logging.Log; +import org.jspecify.annotations.Nullable; /** * Logging levels supported by a {@link LoggingSystem}. @@ -40,9 +41,9 @@ public enum LogLevel { OFF(null); - private final LogMethod logMethod; + private final @Nullable LogMethod logMethod; - LogLevel(LogMethod logMethod) { + LogLevel(@Nullable LogMethod logMethod) { this.logMethod = logMethod; } @@ -63,7 +64,7 @@ public enum LogLevel { * @param cause the cause to log * @since 3.1.0 */ - public void log(Log logger, Object message, Throwable cause) { + public void log(@Nullable Log logger, Object message, @Nullable Throwable cause) { if (logger != null && this.logMethod != null) { this.logMethod.log(logger, message, cause); } @@ -72,7 +73,7 @@ public enum LogLevel { @FunctionalInterface private interface LogMethod { - void log(Log logger, Object message, Throwable cause); + void log(Log logger, Object message, @Nullable Throwable cause); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java index 9c4e6ac404b..bcd3c524510 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerConfiguration.java @@ -18,6 +18,8 @@ package org.springframework.boot.logging; import java.util.Objects; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -32,7 +34,7 @@ public final class LoggerConfiguration { private final String name; - private final LevelConfiguration levelConfiguration; + private final @Nullable LevelConfiguration levelConfiguration; private final LevelConfiguration inheritedLevelConfiguration; @@ -42,7 +44,7 @@ public final class LoggerConfiguration { * @param configuredLevel the configured level of the logger * @param effectiveLevel the effective level of the logger */ - public LoggerConfiguration(String name, LogLevel configuredLevel, LogLevel effectiveLevel) { + public LoggerConfiguration(String name, @Nullable LogLevel configuredLevel, LogLevel effectiveLevel) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(effectiveLevel, "'effectiveLevel' must not be null"); this.name = name; @@ -57,7 +59,7 @@ public final class LoggerConfiguration { * @param inheritedLevelConfiguration the inherited level configuration * @since 2.7.13 */ - public LoggerConfiguration(String name, LevelConfiguration levelConfiguration, + public LoggerConfiguration(String name, @Nullable LevelConfiguration levelConfiguration, LevelConfiguration inheritedLevelConfiguration) { Assert.notNull(name, "'name' must not be null"); Assert.notNull(inheritedLevelConfiguration, "'inheritedLevelConfiguration' must not be null"); @@ -79,7 +81,7 @@ public final class LoggerConfiguration { * @return the configured level of the logger * @see #getLevelConfiguration(ConfigurationScope) */ - public LogLevel getConfiguredLevel() { + public @Nullable LogLevel getConfiguredLevel() { LevelConfiguration configuration = getLevelConfiguration(ConfigurationScope.DIRECT); return (configuration != null) ? configuration.getLevel() : null; } @@ -99,7 +101,9 @@ public final class LoggerConfiguration { * @since 2.7.13 */ public LevelConfiguration getLevelConfiguration() { - return getLevelConfiguration(ConfigurationScope.INHERITED); + LevelConfiguration result = getLevelConfiguration(ConfigurationScope.INHERITED); + Assert.state(result != null, "Inherited level configuration must not be null"); + return result; } /** @@ -110,7 +114,7 @@ public final class LoggerConfiguration { * configuration * @since 2.7.13 */ - public LevelConfiguration getLevelConfiguration(ConfigurationScope scope) { + public @Nullable LevelConfiguration getLevelConfiguration(ConfigurationScope scope) { return (scope != ConfigurationScope.DIRECT) ? this.inheritedLevelConfiguration : this.levelConfiguration; } @@ -169,9 +173,9 @@ public final class LoggerConfiguration { private final String name; - private final LogLevel logLevel; + private final @Nullable LogLevel logLevel; - private LevelConfiguration(String name, LogLevel logLevel) { + private LevelConfiguration(String name, @Nullable LogLevel logLevel) { this.name = name; this.logLevel = logLevel; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroup.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroup.java index e808dc6a017..f9b3d84efb0 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroup.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroup.java @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; +import org.jspecify.annotations.Nullable; + /** * A single logger group. * @@ -34,7 +36,7 @@ public final class LoggerGroup { private final List members; - private LogLevel configuredLevel; + private @Nullable LogLevel configuredLevel; LoggerGroup(String name, List members) { this.name = name; @@ -53,7 +55,7 @@ public final class LoggerGroup { return !this.members.isEmpty(); } - public LogLevel getConfiguredLevel() { + public @Nullable LogLevel getConfiguredLevel() { return this.configuredLevel; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java index 0e514cd1cf1..9c3bdcbaaa2 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggerGroups.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.jspecify.annotations.Nullable; + /** * Logger groups configured through the Spring Environment. * @@ -52,7 +54,7 @@ public final class LoggerGroups implements Iterable { this.groups.put(loggerGroup.getName(), loggerGroup); } - public LoggerGroup get(String name) { + public @Nullable LoggerGroup get(String name) { return this.groups.get(name); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingInitializationContext.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingInitializationContext.java index 7cfd88c551c..8f65a53c819 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingInitializationContext.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingInitializationContext.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -27,13 +29,13 @@ import org.springframework.core.env.Environment; */ public class LoggingInitializationContext { - private final ConfigurableEnvironment environment; + private final @Nullable ConfigurableEnvironment environment; /** * Create a new {@link LoggingInitializationContext} instance. * @param environment the Spring environment. */ - public LoggingInitializationContext(ConfigurableEnvironment environment) { + public LoggingInitializationContext(@Nullable ConfigurableEnvironment environment) { this.environment = environment; } @@ -41,7 +43,7 @@ public class LoggingInitializationContext { * Return the Spring environment if available. * @return the {@link Environment} or {@code null} */ - public Environment getEnvironment() { + public @Nullable Environment getEnvironment() { return this.environment; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java index 088c6211e99..b81e76fde1c 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystem.java @@ -22,6 +22,8 @@ import java.util.EnumSet; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.util.Assert; @@ -91,7 +93,8 @@ public abstract class LoggingSystem { * @param logFile the log output file that should be written or {@code null} for * console only output */ - public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) { + public void initialize(LoggingInitializationContext initializationContext, @Nullable String configLocation, + @Nullable LogFile logFile) { } /** @@ -107,7 +110,7 @@ public abstract class LoggingSystem { * shutdown is required. * @return the shutdown handler, or {@code null} */ - public Runnable getShutdownHandler() { + public @Nullable Runnable getShutdownHandler() { return null; } @@ -127,7 +130,7 @@ public abstract class LoggingSystem { * @param level the log level ({@code null} can be used to remove any custom level for * the logger and use the default configuration instead) */ - public void setLogLevel(String loggerName, LogLevel level) { + public void setLogLevel(@Nullable String loggerName, @Nullable LogLevel level) { throw new UnsupportedOperationException("Unable to set log level"); } @@ -147,7 +150,7 @@ public abstract class LoggingSystem { * @return the current configuration * @since 1.5.0 */ - public LoggerConfiguration getLoggerConfiguration(String loggerName) { + public @Nullable LoggerConfiguration getLoggerConfiguration(String loggerName) { throw new UnsupportedOperationException("Unable to get logger configuration"); } @@ -192,7 +195,7 @@ public abstract class LoggingSystem { } @Override - public void setLogLevel(String loggerName, LogLevel level) { + public void setLogLevel(@Nullable String loggerName, @Nullable LogLevel level) { } @@ -202,7 +205,7 @@ public abstract class LoggingSystem { } @Override - public LoggerConfiguration getLoggerConfiguration(String loggerName) { + public @Nullable LoggerConfiguration getLoggerConfiguration(String loggerName) { return null; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemFactory.java index f1a0b26d036..bf55e5bb041 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemFactory.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemFactory.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging; +import org.jspecify.annotations.Nullable; + import org.springframework.core.io.support.SpringFactoriesLoader; /** @@ -33,7 +35,7 @@ public interface LoggingSystemFactory { * @param classLoader the class loader to use * @return a logging system */ - LoggingSystem getLoggingSystem(ClassLoader classLoader); + @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader); /** * Return a {@link LoggingSystemFactory} backed by {@code spring.factories}. diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index 6c96c2ae620..40390b80a93 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -22,6 +22,8 @@ import java.nio.charset.StandardCharsets; import java.util.function.BiConsumer; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -53,7 +55,7 @@ public class LoggingSystemProperties { private final Environment environment; - private final Function defaultValueResolver; + private final Function<@Nullable String, @Nullable String> defaultValueResolver; private final BiConsumer setter; @@ -72,7 +74,7 @@ public class LoggingSystemProperties { * properties * @since 2.4.2 */ - public LoggingSystemProperties(Environment environment, BiConsumer setter) { + public LoggingSystemProperties(Environment environment, @Nullable BiConsumer setter) { this(environment, null, setter); } @@ -84,8 +86,9 @@ public class LoggingSystemProperties { * properties * @since 3.2.0 */ - public LoggingSystemProperties(Environment environment, Function defaultValueResolver, - BiConsumer setter) { + public LoggingSystemProperties(Environment environment, + @Nullable Function<@Nullable String, @Nullable String> defaultValueResolver, + @Nullable BiConsumer setter) { Assert.notNull(environment, "'environment' must not be null"); this.environment = environment; this.defaultValueResolver = (defaultValueResolver != null) ? defaultValueResolver : (name) -> null; @@ -97,7 +100,7 @@ public class LoggingSystemProperties { * @return the {@link Console} to use * @since 3.5.0 */ - protected Console getConsole() { + protected @Nullable Console getConsole() { return System.console(); } @@ -105,7 +108,7 @@ public class LoggingSystemProperties { apply(null); } - public final void apply(LogFile logFile) { + public final void apply(@Nullable LogFile logFile) { PropertyResolver resolver = getPropertyResolver(); apply(logFile, resolver); } @@ -121,7 +124,7 @@ public class LoggingSystemProperties { return this.environment; } - protected void apply(LogFile logFile, PropertyResolver resolver) { + protected void apply(@Nullable LogFile logFile, PropertyResolver resolver) { setSystemProperty(LoggingSystemProperty.APPLICATION_NAME, resolver); setSystemProperty(LoggingSystemProperty.APPLICATION_GROUP, resolver); setSystemProperty(LoggingSystemProperty.PID, new ApplicationPid().toString()); @@ -162,28 +165,28 @@ public class LoggingSystemProperties { } private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver) { - setSystemProperty(property, resolver, Function.identity()); + setSystemProperty(property, resolver, (i) -> i); } private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, - Function mapper) { + Function<@Nullable String, @Nullable String> mapper) { setSystemProperty(property, resolver, null, mapper); } private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, String defaultValue) { - setSystemProperty(property, resolver, defaultValue, Function.identity()); + setSystemProperty(property, resolver, defaultValue, (i) -> i); } - private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, String defaultValue, - Function mapper) { + private void setSystemProperty(LoggingSystemProperty property, PropertyResolver resolver, + @Nullable String defaultValue, Function<@Nullable String, @Nullable String> mapper) { if (property.getIncludePropertyName() != null) { if (!resolver.getProperty(property.getIncludePropertyName(), Boolean.class, Boolean.TRUE)) { return; } } - String value = (property.getApplicationPropertyName() != null) - ? resolver.getProperty(property.getApplicationPropertyName()) : null; - value = (value != null) ? value : this.defaultValueResolver.apply(property.getApplicationPropertyName()); + String applicationPropertyName = property.getApplicationPropertyName(); + String value = (applicationPropertyName != null) ? resolver.getProperty(applicationPropertyName) : null; + value = (value != null) ? value : this.defaultValueResolver.apply(applicationPropertyName); value = (value != null) ? value : defaultValue; value = mapper.apply(value); setSystemProperty(property.getEnvironmentVariableName(), value); @@ -197,7 +200,7 @@ public class LoggingSystemProperties { setSystemProperty(property.getEnvironmentVariableName(), value); } - private String thresholdMapper(String input) { + private @Nullable String thresholdMapper(@Nullable String input) { // YAML converts an unquoted OFF to false if ("false".equals(input)) { return "OFF"; @@ -210,7 +213,7 @@ public class LoggingSystemProperties { * @param name the property name * @param value the value */ - protected final void setSystemProperty(String name, String value) { + protected final void setSystemProperty(String name, @Nullable String value) { this.setter.accept(name, value); } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java index cc14a1398d1..890e12e4e6f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperty.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging; +import org.jspecify.annotations.Nullable; + /** * Logging system properties that can later be used by log configuration files. * @@ -115,19 +117,20 @@ public enum LoggingSystemProperty { private final String environmentVariableName; - private final String applicationPropertyName; + private final @Nullable String applicationPropertyName; - private final String includePropertyName; + private final @Nullable String includePropertyName; LoggingSystemProperty(String environmentVariableName) { this(environmentVariableName, null); } - LoggingSystemProperty(String environmentVariableName, String applicationPropertyName) { + LoggingSystemProperty(String environmentVariableName, @Nullable String applicationPropertyName) { this(environmentVariableName, applicationPropertyName, null); } - LoggingSystemProperty(String environmentVariableName, String applicationPropertyName, String includePropertyName) { + LoggingSystemProperty(String environmentVariableName, @Nullable String applicationPropertyName, + @Nullable String includePropertyName) { this.environmentVariableName = environmentVariableName; this.applicationPropertyName = applicationPropertyName; this.includePropertyName = includePropertyName; @@ -146,11 +149,11 @@ public enum LoggingSystemProperty { * @return the application property name * @since 3.4.0 */ - public String getApplicationPropertyName() { + public @Nullable String getApplicationPropertyName() { return this.applicationPropertyName; } - String getIncludePropertyName() { + @Nullable String getIncludePropertyName() { return this.includePropertyName; } diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java index a58a6389466..2ea225aea02 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java @@ -29,6 +29,8 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.ToIntFunction; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -62,12 +64,13 @@ public final class StandardStackTracePrinter implements StackTracePrinter { private final Function frameFormatter; - private final ToIntFunction frameHasher; + private final @Nullable ToIntFunction frameHasher; - private StandardStackTracePrinter(EnumSet