From cb2a26ceec65f83a85fdda3a492af874625ddc3f Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Tue, 22 Jul 2025 13:36:51 +0200 Subject: [PATCH] Add nullability annotations to core/spring-boot-docker-compose See gh-46587 --- .../docker/compose/core/ConnectionPorts.java | 4 ++- .../compose/core/DefaultConnectionPorts.java | 8 +++-- .../compose/core/DefaultDockerCompose.java | 7 +++-- .../compose/core/DefaultRunningService.java | 10 ++++--- .../boot/docker/compose/core/DockerCli.java | 18 +++++++----- .../docker/compose/core/DockerCliCommand.java | 29 ++++++++++++------- .../core/DockerCliComposePsResponse.java | 4 ++- .../core/DockerCliInspectResponse.java | 5 +++- .../docker/compose/core/DockerCompose.java | 6 ++-- .../compose/core/DockerComposeFile.java | 4 ++- .../compose/core/DockerComposeOrigin.java | 4 ++- .../boot/docker/compose/core/DockerEnv.java | 4 ++- .../boot/docker/compose/core/DockerHost.java | 16 +++++----- .../boot/docker/compose/core/ImageName.java | 8 +++-- .../docker/compose/core/ImageReference.java | 14 +++++---- .../compose/core/ProcessExitException.java | 4 ++- .../docker/compose/core/ProcessRunner.java | 13 +++++---- .../docker/compose/core/RunningService.java | 4 ++- .../docker/compose/core/package-info.java | 3 ++ .../DockerComposeLifecycleManager.java | 9 +++--- .../lifecycle/DockerComposeProperties.java | 8 +++-- .../lifecycle/DockerComposeSkipCheck.java | 6 ++-- .../lifecycle/ServiceNotReadyException.java | 4 ++- .../compose/lifecycle/package-info.java | 3 ++ ...DockerComposeConnectionDetailsFactory.java | 28 +++++++++--------- .../service/connection/package-info.java | 3 ++ .../compose/core/DockerCliCommandTests.java | 9 +++--- 27 files changed, 150 insertions(+), 85 deletions(-) diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ConnectionPorts.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ConnectionPorts.java index cd3cdee5fd4..058859636f4 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ConnectionPorts.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ConnectionPorts.java @@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core; import java.util.List; +import org.jspecify.annotations.Nullable; + /** * Provides access to the ports that can be used to connect to a {@link RunningService}. * @@ -52,6 +54,6 @@ public interface ConnectionPorts { * all host ports * @return a list of all host ports using the given protocol */ - List getAll(String protocol); + List getAll(@Nullable String protocol); } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultConnectionPorts.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultConnectionPorts.java index 04157ab2a9a..c0ca98163dd 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultConnectionPorts.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultConnectionPorts.java @@ -22,6 +22,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.Config; import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostConfig; import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostPort; @@ -57,7 +59,7 @@ class DefaultConnectionPorts implements ConnectionPorts { return (config != null) && "host".equals(config.networkMode()); } - private Map buildMappingsForNetworkSettings(NetworkSettings networkSettings) { + private Map buildMappingsForNetworkSettings(@Nullable NetworkSettings networkSettings) { if (networkSettings == null || CollectionUtils.isEmpty(networkSettings.ports())) { return Collections.emptyMap(); } @@ -73,7 +75,7 @@ class DefaultConnectionPorts implements ConnectionPorts { return Collections.unmodifiableMap(mappings); } - private boolean isIpV4(HostPort hostPort) { + private boolean isIpV4(@Nullable HostPort hostPort) { String ip = (hostPort != null) ? hostPort.hostIp() : null; return !StringUtils.hasLength(ip) || ip.contains("."); } @@ -108,7 +110,7 @@ class DefaultConnectionPorts implements ConnectionPorts { } @Override - public List getAll(String protocol) { + public List getAll(@Nullable String protocol) { List hostPorts = new ArrayList<>(); this.mappings.forEach((containerPort, hostPort) -> { if (protocol == null || protocol.equalsIgnoreCase(containerPort.protocol())) { diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java index 28b4abc51ae..19f922763b6 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultDockerCompose.java @@ -25,6 +25,8 @@ import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.logging.LogLevel; import org.springframework.util.Assert; @@ -41,7 +43,7 @@ class DefaultDockerCompose implements DockerCompose { private final DockerHost hostname; - DefaultDockerCompose(DockerCli cli, String host) { + DefaultDockerCompose(DockerCli cli, @Nullable String host) { this.cli = cli; this.hostname = DockerHost.get(host, () -> cli.run(new DockerCliCommand.Context())); } @@ -114,7 +116,8 @@ class DefaultDockerCompose implements DockerCompose { return inspectResponses.stream().collect(Collectors.toMap(DockerCliInspectResponse::id, Function.identity())); } - private DockerCliInspectResponse inspectContainer(String id, Map inspected) { + private @Nullable DockerCliInspectResponse inspectContainer(String id, + Map inspected) { DockerCliInspectResponse inspect = inspected.get(id); if (inspect != null) { return inspect; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultRunningService.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultRunningService.java index 2bd1d4528ed..b1c09e8a805 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultRunningService.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DefaultRunningService.java @@ -19,6 +19,8 @@ package org.springframework.boot.docker.compose.core; import java.util.Collections; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; import org.springframework.boot.origin.OriginProvider; @@ -45,10 +47,10 @@ class DefaultRunningService implements RunningService, OriginProvider { private final DockerEnv env; - private final DockerComposeFile composeFile; + private final @Nullable DockerComposeFile composeFile; - DefaultRunningService(DockerHost host, DockerComposeFile composeFile, DockerCliComposePsResponse composePsResponse, - DockerCliInspectResponse inspectResponse) { + DefaultRunningService(DockerHost host, @Nullable DockerComposeFile composeFile, + DockerCliComposePsResponse composePsResponse, DockerCliInspectResponse inspectResponse) { this.origin = new DockerComposeOrigin(composeFile, composePsResponse.name()); this.name = composePsResponse.name(); this.image = ImageReference @@ -101,7 +103,7 @@ class DefaultRunningService implements RunningService, OriginProvider { } @Override - public DockerComposeFile composeFile() { + public @Nullable DockerComposeFile composeFile() { return this.composeFile; } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java index c114a1dde99..cc9b3f39fa1 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java @@ -27,6 +27,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion; import org.springframework.boot.docker.compose.core.DockerCliCommand.Type; @@ -60,7 +61,7 @@ class DockerCli { * @param workingDirectory the working directory or {@code null} * @param dockerComposeOptions the Docker Compose options to use or {@code null}. */ - DockerCli(File workingDirectory, DockerComposeOptions dockerComposeOptions) { + DockerCli(@Nullable File workingDirectory, @Nullable DockerComposeOptions dockerComposeOptions) { this.processRunner = new ProcessRunner(workingDirectory); this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory, (key) -> new DockerCommands(this.processRunner)); @@ -82,7 +83,7 @@ class DockerCli { return dockerCommand.deserialize(json); } - private Consumer createOutputConsumer(LogLevel logLevel) { + private @Nullable Consumer createOutputConsumer(@Nullable LogLevel logLevel) { if (logLevel == null || logLevel == LogLevel.OFF) { return null; } @@ -123,7 +124,7 @@ class DockerCli { * Return the {@link DockerComposeFile} being used by this CLI instance. * @return the Docker Compose file */ - DockerComposeFile getDockerComposeFile() { + @Nullable DockerComposeFile getDockerComposeFile() { return this.dockerComposeOptions.composeFile(); } @@ -205,11 +206,14 @@ class DockerCli { * @param activeProfiles the profiles to activate * @param arguments the arguments to pass to Docker Compose */ - record DockerComposeOptions(DockerComposeFile composeFile, Set activeProfiles, List arguments) { + record DockerComposeOptions(@Nullable DockerComposeFile composeFile, Set activeProfiles, + List arguments) { - DockerComposeOptions { - activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet(); - arguments = (arguments != null) ? arguments : Collections.emptyList(); + DockerComposeOptions(@Nullable DockerComposeFile composeFile, @Nullable Set activeProfiles, + @Nullable List arguments) { + this.composeFile = composeFile; + this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet(); + this.arguments = (arguments != null) ? arguments : Collections.emptyList(); } static DockerComposeOptions none() { diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java index 067c50a0d49..86fedba694a 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliCommand.java @@ -78,8 +78,8 @@ abstract sealed class DockerCliCommand { @SuppressWarnings("unchecked") R deserialize(String json) { - if (this.responseType == Void.class) { - return null; + if (this.responseType == None.class) { + return (R) None.INSTANCE; } return (R) ((!this.listResponse) ? DockerJson.deserialize(json, this.responseType) : DockerJson.deserializeToList(json, this.responseType)); @@ -175,10 +175,10 @@ abstract sealed class DockerCliCommand { /** * The {@code docker compose up} command. */ - static final class ComposeUp extends DockerCliCommand { + static final class ComposeUp extends DockerCliCommand { ComposeUp(LogLevel logLevel, List arguments) { - super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments)); + super(Type.DOCKER_COMPOSE, logLevel, None.class, false, getCommand(arguments)); } private static String[] getCommand(List arguments) { @@ -196,10 +196,10 @@ abstract sealed class DockerCliCommand { /** * The {@code docker compose down} command. */ - static final class ComposeDown extends DockerCliCommand { + static final class ComposeDown extends DockerCliCommand { ComposeDown(Duration timeout, List arguments) { - super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments)); + super(Type.DOCKER_COMPOSE, None.class, false, getCommand(timeout, arguments)); } private static String[] getCommand(Duration timeout, List arguments) { @@ -216,10 +216,10 @@ abstract sealed class DockerCliCommand { /** * The {@code docker compose start} command. */ - static final class ComposeStart extends DockerCliCommand { + static final class ComposeStart extends DockerCliCommand { ComposeStart(LogLevel logLevel, List arguments) { - super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments)); + super(Type.DOCKER_COMPOSE, logLevel, None.class, false, getCommand(arguments)); } private static String[] getCommand(List arguments) { @@ -234,10 +234,10 @@ abstract sealed class DockerCliCommand { /** * The {@code docker compose stop} command. */ - static final class ComposeStop extends DockerCliCommand { + static final class ComposeStop extends DockerCliCommand { ComposeStop(Duration timeout, List arguments) { - super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments)); + super(Type.DOCKER_COMPOSE, None.class, false, getCommand(timeout, arguments)); } private static String[] getCommand(Duration timeout, List arguments) { @@ -268,6 +268,15 @@ abstract sealed class DockerCliCommand { } + static final class None { + + public static final None INSTANCE = new None(); + + private None() { + } + + } + /** * Docker compose version. * diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposePsResponse.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposePsResponse.java index d910384c1dd..5909b41b6b8 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposePsResponse.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliComposePsResponse.java @@ -16,6 +16,8 @@ package org.springframework.boot.docker.compose.core; +import org.jspecify.annotations.Nullable; + /** * Response from {@link DockerCliCommand.ComposePs docker compose ps}. * @@ -27,6 +29,6 @@ package org.springframework.boot.docker.compose.core; * @author Andy Wilkinson * @author Phillip Webb */ -record DockerCliComposePsResponse(String id, String name, String image, String state) { +record DockerCliComposePsResponse(String id, String name, @Nullable String image, String state) { } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliInspectResponse.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliInspectResponse.java index 335dea4d0dc..d26220ec83b 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliInspectResponse.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCliInspectResponse.java @@ -19,6 +19,8 @@ package org.springframework.boot.docker.compose.core; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + /** * Response from {@link DockerCliCommand.Inspect docker inspect}. * @@ -31,7 +33,8 @@ import java.util.Map; * @author Phillip Webb */ record DockerCliInspectResponse(String id, DockerCliInspectResponse.Config config, - DockerCliInspectResponse.NetworkSettings networkSettings, DockerCliInspectResponse.HostConfig hostConfig) { + DockerCliInspectResponse.NetworkSettings networkSettings, + DockerCliInspectResponse.@Nullable HostConfig hostConfig) { /** * Configuration for the container that is portable between hosts. diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java index fd3949c8cd4..4c82f793ef3 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCompose.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.boot.docker.compose.core.DockerCli.DockerComposeOptions; import org.springframework.boot.logging.LogLevel; @@ -126,7 +128,7 @@ public interface DockerCompose { * @param activeProfiles a set of the profiles that should be activated * @return a {@link DockerCompose} instance */ - static DockerCompose get(DockerComposeFile file, String hostname, Set activeProfiles) { + static DockerCompose get(DockerComposeFile file, @Nullable String hostname, Set activeProfiles) { return get(file, hostname, activeProfiles, Collections.emptyList()); } @@ -140,7 +142,7 @@ public interface DockerCompose { * @return a {@link DockerCompose} instance * @since 3.4.0 */ - static DockerCompose get(DockerComposeFile file, String hostname, Set activeProfiles, + static DockerCompose get(DockerComposeFile file, @Nullable String hostname, Set activeProfiles, List arguments) { DockerCli cli = new DockerCli(null, new DockerComposeOptions(file, activeProfiles, arguments)); return new DefaultDockerCompose(cli, hostname); diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java index 0fc52403603..37d6f956ff1 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeFile.java @@ -26,6 +26,8 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -107,7 +109,7 @@ public final class DockerComposeFile { * current directory * @return the located file or {@code null} if no Docker Compose file can be found */ - public static DockerComposeFile find(File workingDirectory) { + public static @Nullable DockerComposeFile find(@Nullable File workingDirectory) { File base = (workingDirectory != null) ? workingDirectory : new File("."); if (!base.exists()) { return null; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java index 834b37cc175..3e808eb321a 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerComposeOrigin.java @@ -16,6 +16,8 @@ package org.springframework.boot.docker.compose.core; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.origin.Origin; /** @@ -27,7 +29,7 @@ import org.springframework.boot.origin.Origin; * @author Andy Wilkinson * @since 3.1.0 */ -public record DockerComposeOrigin(DockerComposeFile composeFile, String serviceName) implements Origin { +public record DockerComposeOrigin(@Nullable DockerComposeFile composeFile, String serviceName) implements Origin { @Override public String toString() { diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerEnv.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerEnv.java index e7bc063a78f..d21e7f799bb 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerEnv.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerEnv.java @@ -21,6 +21,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; + import org.springframework.util.CollectionUtils; /** @@ -69,7 +71,7 @@ class DockerEnv { return this.map; } - private record Entry(String key, String value) { + private record Entry(String key, @Nullable String value) { } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerHost.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerHost.java index 644a3a639b3..1ede8a5e493 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerHost.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerHost.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + import org.springframework.util.StringUtils; /** @@ -69,7 +71,7 @@ final class DockerHost { * {@link DockerCliContextResponse} * @return a new docker host instance */ - static DockerHost get(String host, Supplier> contextsSupplier) { + static DockerHost get(@Nullable String host, Supplier> contextsSupplier) { return get(host, System::getenv, contextsSupplier); } @@ -81,7 +83,7 @@ final class DockerHost { * {@link DockerCliContextResponse} * @return a new docker host instance */ - static DockerHost get(String host, Function systemEnv, + static DockerHost get(@Nullable String host, Function systemEnv, Supplier> contextsSupplier) { host = (StringUtils.hasText(host)) ? host : fromServicesHostEnv(systemEnv); host = (StringUtils.hasText(host)) ? host : fromDockerHostEnv(systemEnv); @@ -94,24 +96,24 @@ final class DockerHost { return systemEnv.apply("SERVICES_HOST"); } - private static String fromDockerHostEnv(Function systemEnv) { + private static @Nullable String fromDockerHostEnv(Function systemEnv) { return fromEndpoint(systemEnv.apply("DOCKER_HOST")); } - private static String fromCurrentContext(Supplier> contextsSupplier) { + private static @Nullable String fromCurrentContext(Supplier> contextsSupplier) { DockerCliContextResponse current = getCurrentContext(contextsSupplier.get()); return (current != null) ? fromEndpoint(current.dockerEndpoint()) : null; } - private static DockerCliContextResponse getCurrentContext(List candidates) { + private static @Nullable DockerCliContextResponse getCurrentContext(List candidates) { return candidates.stream().filter(DockerCliContextResponse::current).findFirst().orElse(null); } - private static String fromEndpoint(String endpoint) { + private static @Nullable String fromEndpoint(@Nullable String endpoint) { return (StringUtils.hasLength(endpoint)) ? fromUri(URI.create(endpoint)) : null; } - private static String fromUri(URI uri) { + private static @Nullable String fromUri(URI uri) { try { return switch (uri.getScheme()) { case "http", "https", "tcp" -> uri.getHost(); diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageName.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageName.java index 3c7523d6213..a4da7f6182c 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageName.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageName.java @@ -16,6 +16,8 @@ package org.springframework.boot.docker.compose.core; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -38,7 +40,7 @@ class ImageName { private final String string; - ImageName(String domain, String path) { + ImageName(@Nullable String domain, String path) { Assert.hasText(path, "'path' must not be empty"); this.domain = getDomainOrDefault(domain); this.name = getNameWithDefaultPath(this.domain, path); @@ -90,7 +92,7 @@ class ImageName { return this.string; } - private String getDomainOrDefault(String domain) { + private String getDomainOrDefault(@Nullable String domain) { if (domain == null || LEGACY_DOMAIN.equals(domain)) { return DEFAULT_DOMAIN; } @@ -104,7 +106,7 @@ class ImageName { return name; } - static String parseDomain(String value) { + static @Nullable String parseDomain(String value) { int firstSlash = value.indexOf('/'); String candidate = (firstSlash != -1) ? value.substring(0, firstSlash) : null; if (candidate != null && Regex.DOMAIN.matcher(candidate).matches()) { diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageReference.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageReference.java index 7403e88c5c8..b9e80f943a7 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageReference.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ImageReference.java @@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core; import java.util.regex.Matcher; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -32,13 +34,13 @@ public final class ImageReference { private final ImageName name; - private final String tag; + private final @Nullable String tag; - private final String digest; + private final @Nullable String digest; private final String string; - private ImageReference(ImageName name, String tag, String digest) { + private ImageReference(ImageName name, @Nullable String tag, @Nullable String digest) { Assert.notNull(name, "'name' must not be null"); this.name = name; this.tag = tag; @@ -68,7 +70,7 @@ public final class ImageReference { * Return the tag from the reference or {@code null}. * @return the referenced tag */ - public String getTag() { + public @Nullable String getTag() { return this.tag; } @@ -76,7 +78,7 @@ public final class ImageReference { * Return the digest from the reference or {@code null}. * @return the referenced digest */ - public String getDigest() { + public @Nullable String getDigest() { return this.digest; } @@ -111,7 +113,7 @@ public final class ImageReference { return this.string; } - private String buildString(String name, String tag, String digest) { + private String buildString(String name, @Nullable String tag, @Nullable String digest) { StringBuilder string = new StringBuilder(name); if (tag != null) { string.append(":").append(tag); diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessExitException.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessExitException.java index 2df72148a2d..6693d922bb5 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessExitException.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessExitException.java @@ -16,6 +16,8 @@ package org.springframework.boot.docker.compose.core; +import org.jspecify.annotations.Nullable; + /** * Exception thrown by {@link ProcessRunner} when the process exits with a non-zero code. * @@ -37,7 +39,7 @@ class ProcessExitException extends RuntimeException { this(exitCode, command, stdOut, stdErr, null); } - ProcessExitException(int exitCode, String[] command, String stdOut, String stdErr, Throwable cause) { + ProcessExitException(int exitCode, String[] command, String stdOut, String stdErr, @Nullable Throwable cause) { super(buildMessage(exitCode, command, stdOut, stdErr), cause); this.exitCode = exitCode; this.command = command; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessRunner.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessRunner.java index b4cd4c0c2b7..9a603aeceda 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessRunner.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/ProcessRunner.java @@ -29,6 +29,7 @@ import java.util.function.Consumer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.log.LogMessage; @@ -47,7 +48,7 @@ class ProcessRunner { private static final Log logger = LogFactory.getLog(ProcessRunner.class); - private final File workingDirectory; + private final @Nullable File workingDirectory; /** * Create a new {@link ProcessRunner} instance. @@ -60,7 +61,7 @@ class ProcessRunner { * Create a new {@link ProcessRunner} instance. * @param workingDirectory the working directory for the process */ - ProcessRunner(File workingDirectory) { + ProcessRunner(@Nullable File workingDirectory) { this.workingDirectory = workingDirectory; } @@ -83,7 +84,7 @@ class ProcessRunner { * @return the output of the command * @throws ProcessExitException if execution failed */ - String run(Consumer outputConsumer, String... command) { + String run(@Nullable Consumer outputConsumer, String... command) { logger.trace(LogMessage.of(() -> "Running '%s'".formatted(String.join(" ", command)))); Process process = startProcess(command); ReaderThread stdOutReader = new ReaderThread(process.getInputStream(), "stdout", outputConsumer); @@ -133,13 +134,13 @@ class ProcessRunner { private final InputStream source; - private final Consumer outputConsumer; + private final @Nullable Consumer outputConsumer; private final StringBuilder output = new StringBuilder(); private final CountDownLatch latch = new CountDownLatch(1); - ReaderThread(InputStream source, String name, Consumer outputConsumer) { + ReaderThread(InputStream source, String name, @Nullable Consumer outputConsumer) { this.source = source; this.outputConsumer = outputConsumer; setName("OutputReader-" + name); @@ -174,7 +175,7 @@ class ProcessRunner { return this.output.toString(); } catch (InterruptedException ex) { - return null; + return ""; } } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java index c3a1b20b0b0..3d95f0ad373 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/RunningService.java @@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core; import java.util.Map; +import org.jspecify.annotations.Nullable; + /** * Provides details of a running Docker Compose service. * @@ -69,7 +71,7 @@ public interface RunningService { * @return the Docker Compose file * @since 3.5.0 */ - default DockerComposeFile composeFile() { + default @Nullable DockerComposeFile composeFile() { return null; } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java index 370e4fcee40..a45d9e2820a 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/package-info.java @@ -17,4 +17,7 @@ /** * Core interfaces and classes for working with Docker Compose. */ +@NullMarked package org.springframework.boot.docker.compose.core; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java index 5f36535a131..430b9afbc59 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeLifecycleManager.java @@ -23,6 +23,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.aot.AotDetector; import org.springframework.boot.SpringApplicationShutdownHandlers; @@ -57,11 +58,11 @@ class DockerComposeLifecycleManager { private static final String IGNORE_LABEL = "org.springframework.boot.ignore"; - private final File workingDirectory; + private final @Nullable File workingDirectory; private final ApplicationContext applicationContext; - private final ClassLoader classLoader; + private final @Nullable ClassLoader classLoader; private final SpringApplicationShutdownHandlers shutdownHandlers; @@ -80,10 +81,10 @@ class DockerComposeLifecycleManager { new DockerComposeSkipCheck(), null); } - DockerComposeLifecycleManager(File workingDirectory, ApplicationContext applicationContext, Binder binder, + DockerComposeLifecycleManager(@Nullable File workingDirectory, ApplicationContext applicationContext, Binder binder, SpringApplicationShutdownHandlers shutdownHandlers, DockerComposeProperties properties, Set> eventListeners, DockerComposeSkipCheck skipCheck, - ServiceReadinessChecks serviceReadinessChecks) { + @Nullable ServiceReadinessChecks serviceReadinessChecks) { this.workingDirectory = workingDirectory; this.applicationContext = applicationContext; this.classLoader = applicationContext.getClassLoader(); diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java index ae519b2a27c..8deb61cc5f5 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeProperties.java @@ -23,6 +23,8 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.docker.compose.core.RunningService; @@ -64,7 +66,7 @@ public class DockerComposeProperties { /** * Hostname or IP of the machine where the docker containers are started. */ - private String host; + private @Nullable String host; /** * Start configuration. @@ -109,11 +111,11 @@ public class DockerComposeProperties { this.lifecycleManagement = lifecycleManagement; } - public String getHost() { + public @Nullable String getHost() { return this.host; } - public void setHost(String host) { + public void setHost(@Nullable String host) { this.host = host; } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeSkipCheck.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeSkipCheck.java index 92c9b747829..dcc9152b3e2 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeSkipCheck.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/DockerComposeSkipCheck.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.SpringApplicationAotProcessor; import org.springframework.util.ClassUtils; @@ -44,7 +46,7 @@ class DockerComposeSkipCheck { SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped); } - boolean shouldSkip(ClassLoader classLoader, DockerComposeProperties.Skip properties) { + boolean shouldSkip(@Nullable ClassLoader classLoader, DockerComposeProperties.Skip properties) { if (properties.isInTests() && hasAtLeastOneRequiredClass(classLoader)) { Thread thread = Thread.currentThread(); for (StackTraceElement element : thread.getStackTrace()) { @@ -56,7 +58,7 @@ class DockerComposeSkipCheck { return false; } - private boolean hasAtLeastOneRequiredClass(ClassLoader classLoader) { + private boolean hasAtLeastOneRequiredClass(@Nullable ClassLoader classLoader) { for (String requiredClass : REQUIRED_CLASSES) { if (ClassUtils.isPresent(requiredClass, classLoader)) { return true; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/ServiceNotReadyException.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/ServiceNotReadyException.java index 6b07565e328..db0b61cc38c 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/ServiceNotReadyException.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/ServiceNotReadyException.java @@ -16,6 +16,8 @@ package org.springframework.boot.docker.compose.lifecycle; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.docker.compose.core.RunningService; /** @@ -33,7 +35,7 @@ class ServiceNotReadyException extends RuntimeException { this(service, message, null); } - ServiceNotReadyException(RunningService service, String message, Throwable cause) { + ServiceNotReadyException(RunningService service, String message, @Nullable Throwable cause) { super(message, cause); this.service = service; } diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/package-info.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/package-info.java index 487b9613566..9ea030415cb 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/package-info.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/lifecycle/package-info.java @@ -17,4 +17,7 @@ /** * Lifecycle management for Docker Compose with the context of a Spring application. */ +@NullMarked package org.springframework.boot.docker.compose.lifecycle; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java index 005ed94e4de..ceec32393ed 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/service/connection/DockerComposeConnectionDetailsFactory.java @@ -21,6 +21,8 @@ import java.util.Arrays; import java.util.Set; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory; import org.springframework.boot.docker.compose.core.DockerComposeFile; @@ -92,7 +94,7 @@ public abstract class DockerComposeConnectionDetailsFactory ciphersSet = null; if (StringUtils.hasLength(ciphers)) { ciphersSet = StringUtils.commaDelimitedListToSet(ciphers); @@ -213,7 +215,7 @@ public abstract class DockerComposeConnectionDetailsFactory command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1), List.of("--dry-run")); assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE); assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("stop", "--timeout", "1", "--dry-run"); - assertThat(command.deserialize("[]")).isNull(); + assertThat(command.deserialize("[]")).isSameAs(None.INSTANCE); } @Test