From 12a6b787127862e62c0019b13f26dd0e5071b609 Mon Sep 17 00:00:00 2001
From: Piotr Olaszewski
Date: Tue, 28 Oct 2025 11:03:35 +0100
Subject: [PATCH 1/5] Add JSpecify to spring-shell-autoconfigure
Signed-off-by: Piotr Olaszewski
---
.mvn/jvm.config | 10 ++++
pom.xml | 56 +++++++++++++++++++
spring-shell-autoconfigure/pom.xml | 5 ++
.../boot/CompleterAutoConfiguration.java | 11 +++-
.../boot/LineReaderAutoConfiguration.java | 5 +-
.../shell/boot/SpringShellProperties.java | 40 ++++++-------
.../boot/UserConfigAutoConfiguration.java | 17 ++++--
.../shell/boot/condition/package-info.java | 4 ++
.../shell/boot/package-info.java | 4 ++
spring-shell-standard/pom.xml | 5 ++
spring-shell-table/pom.xml | 5 ++
spring-shell-test-autoconfigure/pom.xml | 5 ++
spring-shell-test/pom.xml | 6 +-
13 files changed, 143 insertions(+), 30 deletions(-)
create mode 100644 .mvn/jvm.config
create mode 100644 spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/package-info.java
create mode 100644 spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/package-info.java
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000..32599cefe
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/pom.xml b/pom.xml
index 19c49a7e2..877f13a6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,6 +56,9 @@
2.20.0
2.0.17
3.1.1
+ 2.36.0
+ 0.12.7
+ 1.0.0
1.3.1
@@ -228,6 +231,59 @@
+
+
+ nullaway
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+
+
+ default-compile
+ none
+
+
+ java-compile
+ compile
+
+ compile
+
+
+
+
+ com.google.errorprone
+ error_prone_core
+ ${errorprone.version}
+
+
+ com.uber.nullaway
+ nullaway
+ ${nullaway.version}
+
+
+
+ -XDcompilePolicy=simple
+ --should-stop=ifError=FLOW
+ -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR
+ -XepOpt:NullAway:OnlyNullMarked=true
+ -XepOpt:NullAway:CustomContractAnnotations=org.springframework.lang.Contract
+
+
+
+
+
+
+
+
+
+
+
maven-central
diff --git a/spring-shell-autoconfigure/pom.xml b/spring-shell-autoconfigure/pom.xml
index ba8bbd5ff..7a78a8312 100644
--- a/spring-shell-autoconfigure/pom.xml
+++ b/spring-shell-autoconfigure/pom.xml
@@ -47,6 +47,11 @@
${spring-boot.version}
true
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java
index 5b1a75365..23ba08183 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java
@@ -31,6 +31,9 @@
import org.springframework.shell.CompletionProposal;
import org.springframework.shell.Shell;
+/**
+ * @author Piotr Olaszewski
+ */
@AutoConfiguration
public class CompleterAutoConfiguration {
@@ -39,15 +42,17 @@ public class CompleterAutoConfiguration {
@Bean
public CompleterAdapter completer() {
- CompleterAdapter completerAdapter = new CompleterAdapter();
- completerAdapter.setShell(shell);
- return completerAdapter;
+ return new CompleterAdapter(shell);
}
public static class CompleterAdapter implements Completer {
private Shell shell;
+ public CompleterAdapter(Shell shell) {
+ this.shell = shell;
+ }
+
@Override
public void complete(LineReader reader, ParsedLine line, List candidates) {
CompletingParsedLine cpl = (line instanceof CompletingParsedLine) ? ((CompletingParsedLine) line) : t -> t;
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java
index bfba557d0..016bf31e6 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/LineReaderAutoConfiguration.java
@@ -42,6 +42,9 @@
import org.springframework.shell.config.UserConfigPathProvider;
import org.springframework.util.StringUtils;
+/**
+ * @author Piotr Olaszewski
+ */
@AutoConfiguration
@EnableConfigurationProperties(SpringShellProperties.class)
public class LineReaderAutoConfiguration {
@@ -59,7 +62,7 @@ public class LineReaderAutoConfiguration {
private org.jline.reader.History jLineHistory;
@Value("${spring.application.name:spring-shell}.log")
- private String fallbackHistoryFileName;
+ private String fallbackHistoryFileName = "spring-shell.log";
private SpringShellProperties springShellProperties;
private UserConfigPathProvider userConfigPathProvider;
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
index 50b63754d..1f64e28ca 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
@@ -15,12 +15,14 @@
*/
package org.springframework.shell.boot;
+import org.jspecify.annotations.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for shell.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
@ConfigurationProperties(prefix = "spring.shell")
public class SpringShellProperties {
@@ -118,36 +120,36 @@ public void setContext(Context context) {
public static class Config {
- private String env;
- private String location;
+ private @Nullable String env;
+ private @Nullable String location;
- public String getEnv() {
+ public @Nullable String getEnv() {
return env;
}
- public void setEnv(String env) {
+ public void setEnv(@Nullable String env) {
this.env = env;
}
- public String getLocation() {
+ public @Nullable String getLocation() {
return location;
}
- public void setLocation(String location) {
+ public void setLocation(@Nullable String location) {
this.location = location;
}
}
public static class History {
- private String name;
+ private @Nullable String name;
private boolean enabled = true;
- public String getName() {
+ public @Nullable String getName() {
return name;
}
- public void setName(String name) {
+ public void setName(@Nullable String name) {
this.name = name;
}
@@ -189,7 +191,7 @@ public void setEnabled(boolean enabled) {
public static class Noninteractive {
private boolean enabled = true;
- private String primaryCommand;
+ private @Nullable String primaryCommand;
public boolean isEnabled() {
return enabled;
@@ -199,24 +201,24 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
- public String getPrimaryCommand() {
+ public @Nullable String getPrimaryCommand() {
return primaryCommand;
}
- public void setPrimaryCommand(String primaryCommand) {
+ public void setPrimaryCommand(@Nullable String primaryCommand) {
this.primaryCommand = primaryCommand;
}
}
public static class Theme {
- private String name;
+ private @Nullable String name;
- public String getName() {
+ public @Nullable String getName() {
return name;
}
- public void setName(String name) {
+ public void setName(@Nullable String name) {
this.name = name;
}
}
@@ -334,7 +336,7 @@ public void setEnabled(boolean enabled) {
public static class CompletionCommand {
private boolean enabled = true;
- private String rootCommand;
+ private @Nullable String rootCommand;
public boolean isEnabled() {
return enabled;
@@ -344,11 +346,11 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
- public String getRootCommand() {
+ public @Nullable String getRootCommand() {
return rootCommand;
}
- public void setRootCommand(String rootCommand) {
+ public void setRootCommand(@Nullable String rootCommand) {
this.rootCommand = rootCommand;
}
}
@@ -625,7 +627,7 @@ public void setClose(boolean close) {
}
}
- public static enum OptionNamingCase {
+ public enum OptionNamingCase {
NOOP,
CAMEL,
SNAKE,
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/UserConfigAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/UserConfigAutoConfiguration.java
index 6fe68b615..06856a3a5 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/UserConfigAutoConfiguration.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/UserConfigAutoConfiguration.java
@@ -19,6 +19,7 @@
import java.nio.file.Paths;
import java.util.function.Function;
+import org.jspecify.annotations.Nullable;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -26,6 +27,9 @@
import org.springframework.shell.config.UserConfigPathProvider;
import org.springframework.util.StringUtils;
+/**
+ * @author Piotr Olaszewski
+ */
@AutoConfiguration
@EnableConfigurationProperties(SpringShellProperties.class)
public class UserConfigAutoConfiguration {
@@ -42,14 +46,15 @@ public UserConfigPathProvider userConfigPathProvider(SpringShellProperties sprin
static class LocationResolver {
- private final static String XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
- private final static String APP_DATA = "APP_DATA";
+ private static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
+ private static final String APP_DATA = "APP_DATA";
private static final String USERCONFIG_PLACEHOLDER = "{userconfig}";
- private Function pathProvider = (path) -> Paths.get(path);
- private final String configDirEnv;
- private final String configDirLocation;
- LocationResolver(String configDirEnv, String configDirLocation) {
+ private final Function pathProvider = Paths::get;
+ private final @Nullable String configDirEnv;
+ private final @Nullable String configDirLocation;
+
+ LocationResolver(@Nullable String configDirEnv, @Nullable String configDirLocation) {
this.configDirEnv = configDirEnv;
this.configDirLocation = configDirLocation;
}
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/package-info.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/package-info.java
new file mode 100644
index 000000000..5113395c8
--- /dev/null
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/condition/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.boot.condition;
+
+import org.jspecify.annotations.NullMarked;
\ No newline at end of file
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/package-info.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/package-info.java
new file mode 100644
index 000000000..0fe2e52b5
--- /dev/null
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.boot;
+
+import org.jspecify.annotations.NullMarked;
\ No newline at end of file
diff --git a/spring-shell-standard/pom.xml b/spring-shell-standard/pom.xml
index 26577df71..23949a8b1 100644
--- a/spring-shell-standard/pom.xml
+++ b/spring-shell-standard/pom.xml
@@ -41,6 +41,11 @@
spring-shell-tui
${project.parent.version}
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
diff --git a/spring-shell-table/pom.xml b/spring-shell-table/pom.xml
index d28c81298..ecab319bb 100644
--- a/spring-shell-table/pom.xml
+++ b/spring-shell-table/pom.xml
@@ -36,6 +36,11 @@
spring-beans
${spring-framework.version}
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
diff --git a/spring-shell-test-autoconfigure/pom.xml b/spring-shell-test-autoconfigure/pom.xml
index ed38fc14a..2c33c5f34 100644
--- a/spring-shell-test-autoconfigure/pom.xml
+++ b/spring-shell-test-autoconfigure/pom.xml
@@ -56,6 +56,11 @@
junit-jupiter
${junit-jupiter.version}
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
diff --git a/spring-shell-test/pom.xml b/spring-shell-test/pom.xml
index 5bd4509f1..fe2a39a8b 100644
--- a/spring-shell-test/pom.xml
+++ b/spring-shell-test/pom.xml
@@ -51,7 +51,11 @@
assertj-core
${assertj.version}
-
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
From 92a517a163b31a1d0495f3d46051acb358061988 Mon Sep 17 00:00:00 2001
From: Piotr Olaszewski
Date: Thu, 30 Oct 2025 20:49:33 +0100
Subject: [PATCH 2/5] Add JSpecify to spring-shell-core
Signed-off-by: Piotr Olaszewski
---
spring-shell-core/pom.xml | 5 +
.../springframework/shell/Availability.java | 8 +-
.../org/springframework/shell/Command.java | 10 +-
.../shell/CommandNotFound.java | 13 +-
.../shell/CompletingParsedLine.java | 3 +-
.../shell/CompletionContext.java | 35 ++--
.../shell/CompletionProposal.java | 13 +-
.../shell/CoreResourcesRuntimeHints.java | 4 +-
.../shell/DefaultShellApplicationRunner.java | 0
.../springframework/shell/InputProvider.java | 5 +-
.../shell/JnaRuntimeHints.java | 6 +-
.../shell/ResultHandlerService.java | 3 +-
.../java/org/springframework/shell/Shell.java | 22 ++-
.../springframework/shell/ValueResult.java | 5 +-
.../shell/command/CommandAlias.java | 13 +-
.../shell/command/CommandCatalog.java | 8 +-
.../shell/command/CommandContext.java | 13 +-
.../CommandContextMethodArgumentResolver.java | 4 +-
.../command/CommandExceptionResolver.java | 4 +-
.../shell/command/CommandExecution.java | 51 ++++--
.../shell/command/CommandHandlingResult.java | 20 +--
.../shell/command/CommandOption.java | 48 ++---
.../shell/command/CommandParser.java | 28 +--
.../CommandParserExceptionResolver.java | 4 +-
.../shell/command/CommandRegistration.java | 168 +++++++++---------
.../ExceptionResolverMethodResolver.java | 14 +-
.../MethodCommandExceptionResolver.java | 15 +-
.../command/annotation/package-info.java | 4 +
.../support/CommandAnnotationUtils.java | 3 +-
.../CommandRegistrationBeanRegistrar.java | 7 +-
.../CommandRegistrationFactoryBean.java | 35 ++--
.../support/CommandScanRegistrar.java | 11 +-
.../support/OptionMethodArgumentResolver.java | 5 +-
.../annotation/support/package-info.java | 4 +
.../invocation/InvocableShellMethod.java | 48 +++--
.../ShellMethodArgumentResolverComposite.java | 9 +-
.../command/invocation/package-info.java | 4 +
.../shell/command/package-info.java | 4 +
.../shell/command/parser/Ast.java | 9 +-
.../command/parser/CommandArgumentNode.java | 9 +-
.../shell/command/parser/CommandModel.java | 27 +--
.../shell/command/parser/DirectiveNode.java | 9 +-
.../shell/command/parser/Lexer.java | 3 +
.../shell/command/parser/MessageResult.java | 7 +-
.../shell/command/parser/Parser.java | 26 ++-
.../shell/command/parser/ParserMessage.java | 7 +-
.../shell/command/parser/package-info.java | 4 +
.../shell/command/support/package-info.java | 4 +
...RegistrationOptionsCompletionResolver.java | 9 +-
.../shell/completion/package-info.java | 4 +
.../shell/config/package-info.java | 4 +
.../shell/context/package-info.java | 4 +
.../shell/exit/package-info.java | 4 +
.../shell/jline/ExtendedDefaultParser.java | 6 +-
.../shell/jline/FileInputProvider.java | 4 +-
.../jline/NonInteractiveShellRunner.java | 8 +-
.../shell/jline/package-info.java | 5 +-
.../springframework/shell/package-info.java | 5 +-
.../CommandNotFoundMessageProvider.java | 12 +-
.../result/CommandNotFoundResultHandler.java | 4 +-
.../result/GenericResultHandlerService.java | 60 +++----
.../ResultHandlerNotFoundException.java | 11 +-
.../shell/result/ThrowableResultHandler.java | 12 +-
.../shell/result/package-info.java | 3 +
...bstractArgumentMethodArgumentResolver.java | 24 ++-
.../shell/support/package-info.java | 4 +
.../org/springframework/shell/ShellTests.java | 8 +
...CommandExecutionCustomConversionTests.java | 13 +-
.../shell/command/CommandExecutionTests.java | 13 +-
.../tui/component/ConfirmationInput.java | 30 ++--
.../tui/component/MultiItemSelector.java | 16 +-
.../shell/tui/component/PathInput.java | 16 +-
.../shell/tui/component/PathSearch.java | 28 +--
.../tui/component/SingleItemSelector.java | 16 +-
.../shell/tui/component/StringInput.java | 56 +++---
.../tui/component/ViewComponentExecutor.java | 4 +-
.../context/BaseComponentContext.java | 13 +-
.../component/context/ComponentContext.java | 7 +-
.../tui/component/context/package-info.java | 4 +
.../component/flow/BaseConfirmationInput.java | 28 +--
.../component/flow/BaseMultiItemSelector.java | 30 ++--
.../tui/component/flow/BasePathInput.java | 30 ++--
.../flow/BaseSingleItemSelector.java | 38 ++--
.../tui/component/flow/BaseStringInput.java | 34 ++--
.../tui/component/flow/ComponentFlow.java | 114 ++++++++----
.../tui/component/flow/package-info.java | 4 +
.../message/ShellMessageBuilder.java | 6 +-
.../message/ShellMessageHeaderAccessor.java | 20 +--
.../StaticShellMessageHeaderAccessor.java | 20 +--
.../tui/component/message/package-info.java | 4 +
.../shell/tui/component/package-info.java | 4 +
.../component/support/AbstractComponent.java | 24 ++-
.../support/AbstractSelectorComponent.java | 77 ++++----
.../support/AbstractTextComponent.java | 57 +++---
.../tui/component/support/Matchable.java | 7 +-
.../tui/component/support/SelectorItem.java | 6 +-
.../tui/component/support/SelectorList.java | 9 +-
.../tui/component/support/package-info.java | 4 +
.../shell/tui/component/view/TerminalUI.java | 45 +++--
.../tui/component/view/TerminalUIBuilder.java | 10 +-
.../view/control/AbstractControl.java | 17 +-
.../component/view/control/AbstractView.java | 52 +++---
.../tui/component/view/control/AppView.java | 94 +++++++---
.../tui/component/view/control/BoxView.java | 6 +-
.../component/view/control/ButtonView.java | 12 +-
.../tui/component/view/control/Control.java | 3 +-
.../component/view/control/DialogView.java | 29 +--
.../tui/component/view/control/GridView.java | 15 +-
.../tui/component/view/control/InputView.java | 9 +-
.../tui/component/view/control/ListView.java | 13 +-
.../component/view/control/MenuBarView.java | 30 ++--
.../tui/component/view/control/MenuView.java | 23 ++-
.../component/view/control/ProgressView.java | 22 +--
.../component/view/control/StatusBarView.java | 32 ++--
.../tui/component/view/control/View.java | 6 +-
.../component/view/control/ViewService.java | 6 +-
.../view/control/cell/AbstractListCell.java | 8 +-
.../view/control/cell/package-info.java | 4 +
.../component/view/control/package-info.java | 4 +
.../view/event/DefaultEventLoop.java | 9 +-
.../tui/component/view/event/KeyEvent.java | 6 +-
.../tui/component/view/event/KeyHandler.java | 7 +-
.../component/view/event/MouseHandler.java | 7 +-
.../component/view/event/package-info.java | 4 +
.../view/event/processor/package-info.java | 4 +
.../tui/component/view/package-info.java | 4 +
.../component/view/screen/DefaultScreen.java | 18 +-
.../tui/component/view/screen/Screen.java | 5 +-
.../tui/component/view/screen/ScreenItem.java | 5 +-
.../component/view/screen/package-info.java | 4 +
.../shell/tui/geom/package-info.java | 4 +
.../shell/tui/style/PartsTextRenderer.java | 18 +-
.../StringToStyleExpressionRenderer.java | 15 +-
.../shell/tui/style/StyleSettings.java | 11 +-
.../shell/tui/style/TemplateExecutor.java | 6 +-
.../shell/tui/style/ThemeRegistry.java | 4 +-
.../shell/tui/style/ThemeResolver.java | 13 +-
.../shell/tui/style/package-info.java | 4 +
.../tui/support/search/SearchMatchResult.java | 13 +-
.../tui/support/search/package-info.java | 4 +
.../tui/style/PartsTextRendererTests.java | 72 ++++++++
.../StringToStyleExpressionRendererTests.java | 10 ++
142 files changed, 1474 insertions(+), 897 deletions(-)
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/DefaultShellApplicationRunner.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/annotation/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/invocation/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/parser/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/command/support/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/completion/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/config/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/context/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/exit/package-info.java
create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/support/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/context/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/flow/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/message/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/support/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/control/cell/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/control/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/event/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/event/processor/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/component/view/screen/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/geom/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/style/package-info.java
create mode 100644 spring-shell-tui/src/main/java/org/springframework/shell/tui/support/search/package-info.java
diff --git a/spring-shell-core/pom.xml b/spring-shell-core/pom.xml
index 96f457043..fcab87209 100644
--- a/spring-shell-core/pom.xml
+++ b/spring-shell-core/pom.xml
@@ -61,6 +61,11 @@
jakarta.validation-api
${jakarta.validation-api.version}
+
+ org.jspecify
+ jspecify
+ ${jspecify.version}
+
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Availability.java b/spring-shell-core/src/main/java/org/springframework/shell/Availability.java
index 0175eba61..475f87372 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Availability.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Availability.java
@@ -16,6 +16,7 @@
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -23,12 +24,13 @@
* a reason.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public class Availability {
- private final String reason;
+ private final @Nullable String reason;
- private Availability(String reason) {
+ private Availability(@Nullable String reason) {
this.reason = reason;
}
@@ -45,7 +47,7 @@ public boolean isAvailable() {
return reason == null;
}
- public String getReason() {
+ public @Nullable String getReason() {
return reason;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Command.java b/spring-shell-core/src/main/java/org/springframework/shell/Command.java
index dbd5712e9..a5586953f 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Command.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Command.java
@@ -18,9 +18,13 @@
import java.util.Objects;
+import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
+/**
+ * @author Piotr Olaszewski
+ */
public interface Command {
/**
@@ -45,10 +49,10 @@ public Help(String description) {
this(description, null);
}
- public Help(String description, String group) {
- this.group = StringUtils.hasText(group) ? group : "";
- Assert.isTrue(StringUtils.hasText(description), "Command description cannot be null or empty in group='" + this.group + "'");
+ public Help(String description, @Nullable String group) {
+ Assert.isTrue(StringUtils.hasText(description), "Command description cannot be null or empty");
this.description = description;
+ this.group = StringUtils.hasText(group) ? group : "";
}
public String getDescription() {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CommandNotFound.java b/spring-shell-core/src/main/java/org/springframework/shell/CommandNotFound.java
index 61ba13cea..ff88c16c4 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/CommandNotFound.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CommandNotFound.java
@@ -20,22 +20,25 @@
import java.util.Map;
import java.util.stream.Collectors;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.command.CommandRegistration;
/**
* A result to be handled by the {@link ResultHandler} when no command could be mapped to user input
+ *
+ * @author Piotr Olaszewski
*/
public class CommandNotFound extends RuntimeException {
private final List words;
- private final Map registrations;
- private final String text;
+ private final @Nullable Map registrations;
+ private final @Nullable String text;
public CommandNotFound(List words) {
this(words, null, null);
}
- public CommandNotFound(List words, Map registrations, String text) {
+ public CommandNotFound(List words, @Nullable Map registrations, @Nullable String text) {
this.words = words;
this.registrations = registrations;
this.text = text;
@@ -60,7 +63,7 @@ public List getWords(){
*
* @return known command registrations
*/
- public Map getRegistrations() {
+ public @Nullable Map getRegistrations() {
return registrations;
}
@@ -69,7 +72,7 @@ public Map getRegistrations() {
*
* @return raw text input
*/
- public String getText() {
+ public @Nullable String getText() {
return text;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CompletingParsedLine.java b/spring-shell-core/src/main/java/org/springframework/shell/CompletingParsedLine.java
index 30d5c0788..e841f454e 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/CompletingParsedLine.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CompletingParsedLine.java
@@ -24,9 +24,10 @@
* should be escaped/quoted.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
@FunctionalInterface
public interface CompletingParsedLine {
- public CharSequence emit(CharSequence candidate);
+ CharSequence emit(CharSequence candidate);
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CompletionContext.java b/spring-shell-core/src/main/java/org/springframework/shell/CompletionContext.java
index 5a42c1232..74ec22691 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/CompletionContext.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CompletionContext.java
@@ -16,17 +16,19 @@
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
+import org.springframework.shell.command.CommandOption;
+import org.springframework.shell.command.CommandRegistration;
+
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
-import org.springframework.shell.command.CommandOption;
-import org.springframework.shell.command.CommandRegistration;
-
/**
* Represents the buffer context in which completion was triggered.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public class CompletionContext {
@@ -36,17 +38,17 @@ public class CompletionContext {
private final int position;
- private final CommandOption commandOption;
+ private final @Nullable CommandOption commandOption;
- private final CommandRegistration commandRegistration;
+ private final @Nullable CommandRegistration commandRegistration;
/**
*
- * @param words words in the buffer, excluding words for the command name
+ * @param words words in the buffer, excluding words for the command name
* @param wordIndex the index of the word the cursor is in
- * @param position the position inside the current word where the cursor is
+ * @param position the position inside the current word where the cursor is
*/
- public CompletionContext(List words, int wordIndex, int position, CommandRegistration commandRegistration, CommandOption commandOption) {
+ public CompletionContext(List words, int wordIndex, int position, @Nullable CommandRegistration commandRegistration, @Nullable CommandOption commandOption) {
this.words = words;
this.wordIndex = wordIndex;
this.position = position;
@@ -66,11 +68,11 @@ public int getPosition() {
return position;
}
- public CommandOption getCommandOption() {
+ public @Nullable CommandOption getCommandOption() {
return commandOption;
}
- public CommandRegistration getCommandRegistration() {
+ public @Nullable CommandRegistration getCommandRegistration() {
return commandRegistration;
}
@@ -80,7 +82,10 @@ public String upToCursor() {
if (!start.isEmpty()) {
start += " ";
}
- start += currentWord().substring(0, position);
+ String currentWord = currentWord();
+ if (currentWord != null) {
+ start += currentWord.substring(0, position);
+ }
}
return start;
}
@@ -88,11 +93,11 @@ public String upToCursor() {
/**
* Return the whole word the cursor is in, or {@code null} if the cursor is past the last word.
*/
- public String currentWord() {
+ public @Nullable String currentWord() {
return wordIndex >= 0 && wordIndex < words.size() ? words.get(wordIndex) : null;
}
- public String currentWordUpToCursor() {
+ public @Nullable String currentWordUpToCursor() {
String currentWord = currentWord();
return currentWord != null ? currentWord.substring(0, getPosition()) : null;
}
@@ -101,7 +106,7 @@ public String currentWordUpToCursor() {
* Return a copy of this context, as if the first {@literal nbWords} were not present
*/
public CompletionContext drop(int nbWords) {
- return new CompletionContext(new ArrayList(words.subList(nbWords, words.size())), wordIndex - nbWords,
+ return new CompletionContext(new ArrayList<>(words.subList(nbWords, words.size())), wordIndex - nbWords,
position, commandRegistration, commandOption);
}
@@ -115,7 +120,7 @@ public CompletionContext commandOption(CommandOption commandOption) {
/**
* Return a copy of this context with given command registration.
*/
- public CompletionContext commandRegistration(CommandRegistration commandRegistration) {
+ public CompletionContext commandRegistration(@Nullable CommandRegistration commandRegistration) {
return new CompletionContext(words, wordIndex, position, commandRegistration, commandOption);
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java b/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java
index d3c0fa99a..a6b8bf255 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java
@@ -16,12 +16,15 @@
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
+
import java.util.Objects;
/**
* Represents a proposal for TAB completion, made not only of the text to append, but also metadata about the proposal.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public class CompletionProposal {
@@ -38,12 +41,12 @@ public class CompletionProposal {
/**
* The description for the proposal.
*/
- private String description;
+ private @Nullable String description;
/**
* The category of the proposal, which may be used to group proposals together.
*/
- private String category;
+ private @Nullable String category;
/**
* Whether the proposal should bypass escaping and quoting rules. This is useful for command proposals, which can
@@ -79,16 +82,16 @@ public CompletionProposal displayText(String displayText) {
return this;
}
- public String description() {
+ public @Nullable String description() {
return description;
}
- public CompletionProposal description(String description) {
+ public CompletionProposal description(@Nullable String description) {
this.description = description;
return this;
}
- public String category() {
+ public @Nullable String category() {
return category;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CoreResourcesRuntimeHints.java b/spring-shell-core/src/main/java/org/springframework/shell/CoreResourcesRuntimeHints.java
index b2107d9b9..a2a368a26 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/CoreResourcesRuntimeHints.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/CoreResourcesRuntimeHints.java
@@ -15,6 +15,7 @@
*/
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
@@ -22,11 +23,12 @@
* {@link RuntimeHintsRegistrar} for Shell Core resources.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
class CoreResourcesRuntimeHints implements RuntimeHintsRegistrar {
@Override
- public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.resources().registerPattern("org/springframework/shell/component/*.stg");
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/DefaultShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/DefaultShellApplicationRunner.java
new file mode 100644
index 000000000..e69de29bb
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/InputProvider.java b/spring-shell-core/src/main/java/org/springframework/shell/InputProvider.java
index aa888e07c..775798e5c 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/InputProvider.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/InputProvider.java
@@ -1,9 +1,12 @@
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
+
/**
* To be implemented by components able to provide a "line" of user input, whether interactively or by batch.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public interface InputProvider {
@@ -12,5 +15,5 @@ public interface InputProvider {
*
* Returning {@literal null} indicates end of input, requesting shell exit.
*/
- Input readInput();
+ @Nullable Input readInput();
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/JnaRuntimeHints.java b/spring-shell-core/src/main/java/org/springframework/shell/JnaRuntimeHints.java
index 8db5fa9f4..f7cd7b9ac 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/JnaRuntimeHints.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/JnaRuntimeHints.java
@@ -19,6 +19,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ProxyHints;
@@ -29,10 +30,13 @@
import org.springframework.aot.hint.TypeHint;
import org.springframework.aot.hint.TypeReference;
+/**
+ * @author Piotr Olaszewski
+ */
public class JnaRuntimeHints implements RuntimeHintsRegistrar {
@Override
- public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
ResourceHints resource = hints.resources();
ProxyHints proxy = hints.proxies();
ReflectionHints reflection = hints.reflection();
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java b/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java
index 4f3e91306..c94c9355c 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ResultHandlerService.java
@@ -15,13 +15,14 @@
*/
package org.springframework.shell;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.lang.Nullable;
/**
* A service interface for result handling.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface ResultHandlerService {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
index c5c867cf2..9b88cd9d2 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java
@@ -30,6 +30,7 @@
import jakarta.validation.ValidatorFactory;
import org.jline.terminal.Terminal;
import org.jline.utils.Signals;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,10 +59,11 @@
*
* @author Eric Bottard
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class Shell {
- private final static Logger log = LoggerFactory.getLogger(Shell.class);
+ private static final Logger log = LoggerFactory.getLogger(Shell.class);
private final ResultHandlerService resultHandlerService;
/**
@@ -73,12 +75,12 @@ public class Shell {
private final Terminal terminal;
private final CommandCatalog commandRegistry;
protected List completionResolvers = new ArrayList<>();
- private CommandExecutionHandlerMethodArgumentResolvers argumentResolvers;
+ private @Nullable CommandExecutionHandlerMethodArgumentResolvers argumentResolvers;
private ConversionService conversionService = new DefaultConversionService();
private final ShellContext shellContext;
private final ExitCodeMappings exitCodeMappings;
- private Exception handlingResultNonInt = null;
- private CommandHandlingResult processExceptionNonInt = null;
+ private @Nullable Exception handlingResultNonInt = null;
+ private @Nullable CommandHandlingResult processExceptionNonInt = null;
/**
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid
@@ -123,7 +125,7 @@ public void setExceptionResolvers(List exceptionResolv
this.exceptionResolvers = exceptionResolvers;
}
- private ExitCodeExceptionProvider exitCodeExceptionProvider;
+ private @Nullable ExitCodeExceptionProvider exitCodeExceptionProvider;
@Autowired(required = false)
public void setExitCodeExceptionProvider(ExitCodeExceptionProvider exitCodeExceptionProvider) {
@@ -191,7 +193,7 @@ else if (processExceptionNonInt != null && processExceptionNonInt.exitCode() !=
* result
*
*/
- protected Object evaluate(Input input) {
+ protected @Nullable Object evaluate(Input input) {
if (noInput(input)) {
return NO_INPUT;
}
@@ -354,8 +356,10 @@ public List complete(CompletionContext context) {
// Try to complete arguments
List matchedArgOptions = new ArrayList<>();
- if (argsContext.getWords().size() > 0 && argsContext.getWordIndex() > 0 && argsContext.getWords().size() > argsContext.getWordIndex()) {
- matchedArgOptions.addAll(matchOptions(registration.getOptions(), argsContext.getWords().get(argsContext.getWordIndex() - 1)));
+ if (!argsContext.getWords().isEmpty() && argsContext.getWordIndex() > 0 && argsContext.getWords().size() > argsContext.getWordIndex()) {
+ if (registration != null) {
+ matchedArgOptions.addAll(matchOptions(registration.getOptions(), argsContext.getWords().get(argsContext.getWordIndex() - 1)));
+ }
}
List argProposals = matchedArgOptions.stream()
@@ -448,7 +452,7 @@ private CompletionProposal toCommandProposal(String command, CommandRegistration
*
* @return a valid command name, or {@literal null} if none matched
*/
- private String findLongestCommand(String prefix, boolean filterHidden) {
+ private @Nullable String findLongestCommand(String prefix, boolean filterHidden) {
Map registrations = commandRegistry.getRegistrations();
if (filterHidden) {
registrations = Utils.removeHiddenCommands(registrations);
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ValueResult.java b/spring-shell-core/src/main/java/org/springframework/shell/ValueResult.java
index 7ae4fce47..ea434939f 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/ValueResult.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ValueResult.java
@@ -19,12 +19,14 @@
import java.util.List;
import java.util.stream.Collectors;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
/**
* A result for a successful resolve operation.
*
* @author Camilo Gonzalez
+ * @author Piotr Olaszewski
*/
public class ValueResult {
@@ -40,8 +42,7 @@ public ValueResult(MethodParameter methodParameter, Object resolvedValue) {
this(methodParameter, resolvedValue, new BitSet(), new BitSet());
}
- public ValueResult(MethodParameter methodParameter, Object resolvedValue, BitSet wordsUsed,
- BitSet wordsUsedForValue) {
+ public ValueResult(MethodParameter methodParameter, Object resolvedValue, @Nullable BitSet wordsUsed, @Nullable BitSet wordsUsedForValue) {
this.methodParameter = methodParameter;
this.resolvedValue = resolvedValue;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandAlias.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandAlias.java
index 3895c37fc..9fec89637 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandAlias.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandAlias.java
@@ -15,10 +15,13 @@
*/
package org.springframework.shell.command;
+import org.jspecify.annotations.Nullable;
+
/**
* Interface representing an alias in a command.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandAlias {
@@ -34,7 +37,7 @@ public interface CommandAlias {
*
* @return the group
*/
- String getGroup();
+ @Nullable String getGroup();
/**
* Gets an instance of a default {@link CommandAlias}.
@@ -43,7 +46,7 @@ public interface CommandAlias {
* @param group the group
* @return default command alias
*/
- public static CommandAlias of(String command, String group) {
+ public static CommandAlias of(String command, @Nullable String group) {
return new DefaultCommandAlias(command, group);
}
@@ -53,9 +56,9 @@ public static CommandAlias of(String command, String group) {
public static class DefaultCommandAlias implements CommandAlias {
private final String command;
- private final String group;
+ private final @Nullable String group;
- public DefaultCommandAlias(String command, String group) {
+ public DefaultCommandAlias(String command, @Nullable String group) {
this.command = command;
this.group = group;
}
@@ -66,7 +69,7 @@ public String getCommand() {
}
@Override
- public String getGroup() {
+ public @Nullable String getGroup() {
return group;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java
index 7e1c38a9c..0b1af893e 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandCatalog.java
@@ -23,6 +23,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.context.InteractionMode;
import org.springframework.shell.context.ShellContext;
@@ -30,6 +31,7 @@
* Interface defining contract to handle existing {@link CommandRegistration}s.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandCatalog {
@@ -89,9 +91,9 @@ static class DefaultCommandCatalog implements CommandCatalog {
private final Map commandRegistrations = new HashMap<>();
private final Collection resolvers = new ArrayList<>();
- private final ShellContext shellContext;
+ private final @Nullable ShellContext shellContext;
- DefaultCommandCatalog(Collection resolvers, ShellContext shellContext) {
+ DefaultCommandCatalog(@Nullable Collection resolvers, @Nullable ShellContext shellContext) {
this.shellContext = shellContext;
if (resolvers != null) {
this.resolvers.addAll(resolvers);
@@ -146,7 +148,7 @@ public Map getRegistrations() {
* effectively disables filtering as as we only care if mode is set to interactive
* or non-interactive.
*/
- private static Predicate> filterByInteractionMode(ShellContext shellContext) {
+ private static Predicate> filterByInteractionMode(@Nullable ShellContext shellContext) {
return e -> {
InteractionMode mim = e.getValue().getInteractionMode();
InteractionMode cim = shellContext != null ? shellContext.getInteractionMode() : InteractionMode.ALL;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContext.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContext.java
index 17daa5a32..b098667aa 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContext.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContext.java
@@ -21,15 +21,18 @@
import org.jline.terminal.Terminal;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.command.CommandParser.CommandParserResult;
import org.springframework.shell.command.CommandParser.CommandParserResults;
import org.springframework.shell.context.ShellContext;
+import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Interface containing information about current command execution.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandContext {
@@ -69,7 +72,7 @@ public interface CommandContext {
* @param name the option name
* @return mapped value
*/
- T getOptionValue(String name);
+ @Nullable T getOptionValue(String name);
/**
* Gets a terminal.
@@ -94,8 +97,10 @@ public interface CommandContext {
* @param commandRegistration the command registration
* @return a command context
*/
- static CommandContext of(String[] args, CommandParserResults results, Terminal terminal,
- CommandRegistration commandRegistration, ShellContext shellContext) {
+ static CommandContext of(String[] args, CommandParserResults results, @Nullable Terminal terminal,
+ CommandRegistration commandRegistration, @Nullable ShellContext shellContext) {
+ Assert.notNull(terminal, "'terminal' must not be null");
+ Assert.notNull(shellContext, "'shellContext' must not be null");
return new DefaultCommandContext(args, results, terminal, commandRegistration, shellContext);
}
@@ -141,7 +146,7 @@ public CommandRegistration getCommandRegistration() {
@Override
@SuppressWarnings("unchecked")
- public T getOptionValue(String name) {
+ public @Nullable T getOptionValue(String name) {
Optional find = find(name);
if (find.isPresent()) {
return (T) find.get().value();
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContextMethodArgumentResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContextMethodArgumentResolver.java
index b4a4957b7..81cfb8b89 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContextMethodArgumentResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandContextMethodArgumentResolver.java
@@ -17,6 +17,7 @@
import java.util.Optional;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
@@ -26,6 +27,7 @@
* {@link CommandContext}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class CommandContextMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -39,7 +41,7 @@ public boolean supportsParameter(MethodParameter parameter) {
}
@Override
- public Object resolveArgument(MethodParameter parameter, Message> message){
+ public @Nullable Object resolveArgument(MethodParameter parameter, Message> message){
CommandContext commandContext = message.getHeaders().get(HEADER_COMMAND_CONTEXT, CommandContext.class);
return parameter.isOptional() ? Optional.ofNullable(commandContext) : commandContext;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExceptionResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExceptionResolver.java
index d29a54a2b..6a2e6f287 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExceptionResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExceptionResolver.java
@@ -15,6 +15,7 @@
*/
package org.springframework.shell.command;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@@ -25,6 +26,7 @@
* with command.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandExceptionResolver {
@@ -40,5 +42,5 @@ public interface CommandExceptionResolver {
* @return a corresponding {@code HandlingResult} framework to handle, or
* {@code null} for default processing in the resolution chain
*/
- CommandHandlingResult resolve(Exception ex);
+ @Nullable CommandHandlingResult resolve(Exception ex);
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java
index 34bd7aa24..a6498a59f 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java
@@ -19,10 +19,13 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
import jakarta.validation.Validator;
import org.jline.terminal.Terminal;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order;
@@ -42,12 +45,14 @@
import org.springframework.shell.command.invocation.ShellMethodArgumentResolverComposite;
import org.springframework.shell.command.parser.ParserConfig;
import org.springframework.shell.context.ShellContext;
+import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Interface to evaluate a result from a command with an arguments.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandExecution {
@@ -57,7 +62,7 @@ public interface CommandExecution {
* @param args the command args
* @return evaluated execution
*/
- Object evaluate(String[] args);
+ @Nullable Object evaluate(String[] args);
/**
* Gets an instance of a default {@link CommandExecution}.
@@ -92,7 +97,7 @@ public static CommandExecution of(List extends HandlerMethodArgumentResolver>
* @param conversionService the conversion services
* @return default command execution
*/
- public static CommandExecution of(List extends HandlerMethodArgumentResolver> resolvers, Validator validator,
+ public static CommandExecution of(@Nullable List extends HandlerMethodArgumentResolver> resolvers, Validator validator,
Terminal terminal, ShellContext shellContext, ConversionService conversionService, CommandCatalog commandCatalog) {
return new DefaultCommandExecution(resolvers, validator, terminal, shellContext, conversionService, commandCatalog);
}
@@ -102,15 +107,15 @@ public static CommandExecution of(List extends HandlerMethodArgumentResolver>
*/
static class DefaultCommandExecution implements CommandExecution {
- private List extends HandlerMethodArgumentResolver> resolvers;
- private Validator validator;
- private Terminal terminal;
- private ShellContext shellContext;
- private ConversionService conversionService;
- private CommandCatalog commandCatalog;
+ private @Nullable List extends HandlerMethodArgumentResolver> resolvers;
+ private @Nullable Validator validator;
+ private @Nullable Terminal terminal;
+ private @Nullable ShellContext shellContext;
+ private @Nullable ConversionService conversionService;
+ private @Nullable CommandCatalog commandCatalog;
- public DefaultCommandExecution(List extends HandlerMethodArgumentResolver> resolvers, Validator validator,
- Terminal terminal, ShellContext shellContext, ConversionService conversionService, CommandCatalog commandCatalog) {
+ public DefaultCommandExecution(@Nullable List extends HandlerMethodArgumentResolver> resolvers, @Nullable Validator validator,
+ @Nullable Terminal terminal, @Nullable ShellContext shellContext, @Nullable ConversionService conversionService, @Nullable CommandCatalog commandCatalog) {
this.resolvers = resolvers;
this.validator = validator;
this.terminal = terminal;
@@ -119,10 +124,12 @@ public DefaultCommandExecution(List extends HandlerMethodArgumentResolver> res
this.commandCatalog = commandCatalog;
}
- public Object evaluate(String[] args) {
- CommandParser parser = CommandParser.of(conversionService, commandCatalog.getRegistrations(), new ParserConfig());
+ public @Nullable Object evaluate(String[] args) {
+ Map registrations = commandCatalog == null ? Map.of() : commandCatalog.getRegistrations();
+ CommandParser parser = CommandParser.of(conversionService, registrations, new ParserConfig());
CommandParserResults results = parser.parse(args);
CommandRegistration registration = results.registration();
+ Assert.state(registration != null, "'registration' must not be null");
// fast fail with availability before doing anything else
Availability availability = registration.getAvailability();
@@ -162,9 +169,9 @@ public Object evaluate(String[] args) {
CommandRegistration usedRegistration;
if (handleHelpOption) {
String command = registration.getCommand();
- CommandParser helpParser = CommandParser.of(conversionService, commandCatalog.getRegistrations(),
+ CommandParser helpParser = CommandParser.of(conversionService, registrations,
new ParserConfig());
- CommandRegistration helpCommandRegistration = commandCatalog.getRegistrations()
+ CommandRegistration helpCommandRegistration = registrations
.get(registration.getHelpOption().getCommand());
CommandParserResults helpResults = helpParser.parse(new String[] { "help", "--command", command });
results = helpResults;
@@ -177,7 +184,7 @@ public Object evaluate(String[] args) {
if (!results.errors().isEmpty()) {
throw new CommandParserExceptionsException("Command parser resulted errors", results.errors());
}
-
+ Assert.notNull(usedRegistration, "'usedRegistration' must not be null");
CommandContext ctx = CommandContext.of(args, results, terminal, usedRegistration, shellContext);
Object res = null;
@@ -186,10 +193,16 @@ public Object evaluate(String[] args) {
// pick the target to execute
if (targetInfo.getTargetType() == TargetType.FUNCTION) {
- res = targetInfo.getFunction().apply(ctx);
+ Function function = targetInfo.getFunction();
+ if (function != null) {
+ res = function.apply(ctx);
+ }
}
else if (targetInfo.getTargetType() == TargetType.CONSUMER) {
- targetInfo.getConsumer().accept(ctx);
+ Consumer consumer = targetInfo.getConsumer();
+ if (consumer != null) {
+ consumer.accept(ctx);
+ }
}
else if (targetInfo.getTargetType() == TargetType.METHOD) {
try {
@@ -222,7 +235,7 @@ else if (targetInfo.getTargetType() == TargetType.METHOD) {
if (resolvers != null) {
argumentResolvers.addResolvers(resolvers);
}
- if (!paramValues.isEmpty()) {
+ if (!paramValues.isEmpty() && conversionService != null) {
argumentResolvers.addResolver(new ParamNameHandlerMethodArgumentResolver(paramValues, conversionService));
}
invocableShellMethod.setMessageMethodArgumentResolvers(argumentResolvers);
@@ -262,7 +275,7 @@ public boolean supportsParameter(MethodParameter parameter) {
}
@Override
- public Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
+ public @Nullable Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
Object source = paramValues.get(parameter.getParameterName());
if (source == null) {
return null;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandHandlingResult.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandHandlingResult.java
index 000565a06..140dc634c 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandHandlingResult.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandHandlingResult.java
@@ -15,12 +15,13 @@
*/
package org.springframework.shell.command;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Holder for handling some processing, typically with {@link CommandExceptionResolver}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandHandlingResult {
@@ -29,8 +30,7 @@ public interface CommandHandlingResult {
*
* @return a message
*/
- @Nullable
- String message();
+ @Nullable String message();
/**
* Gets an exit code for this {@code CommandHandlingResult}. Exit code only has meaning
@@ -38,7 +38,7 @@ public interface CommandHandlingResult {
*
* @return an exit code
*/
- Integer exitCode();
+ @Nullable Integer exitCode();
/**
* Indicate whether this {@code CommandHandlingResult} has a result.
@@ -80,31 +80,31 @@ public static CommandHandlingResult of(@Nullable String message) {
* @param exitCode the exit code
* @return instance of {@code CommandHandlingResult}
*/
- public static CommandHandlingResult of(@Nullable String message, Integer exitCode) {
+ public static CommandHandlingResult of(@Nullable String message, @Nullable Integer exitCode) {
return new DefaultHandlingResult(message, exitCode);
}
static class DefaultHandlingResult implements CommandHandlingResult {
- private final String message;
- private final Integer exitCode;
+ private final @Nullable String message;
+ private final @Nullable Integer exitCode;
DefaultHandlingResult(String message) {
this(message, null);
}
- DefaultHandlingResult(String message, Integer exitCode) {
+ DefaultHandlingResult(@Nullable String message, @Nullable Integer exitCode) {
this.message = message;
this.exitCode = exitCode;
}
@Override
- public String message() {
+ public @Nullable String message() {
return message;
}
@Override
- public Integer exitCode() {
+ public @Nullable Integer exitCode() {
return exitCode;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandOption.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandOption.java
index 396db9526..98bc688f1 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandOption.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandOption.java
@@ -15,6 +15,7 @@
*/
package org.springframework.shell.command;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.shell.completion.CompletionResolver;
@@ -22,6 +23,7 @@
* Interface representing an option in a command.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandOption {
@@ -52,14 +54,14 @@ public interface CommandOption {
*
* @return description of an option
*/
- String getDescription();
+ @Nullable String getDescription();
/**
* Gets a {@link ResolvableType} of an option.
*
* @return type of an option
*/
- ResolvableType getType();
+ @Nullable ResolvableType getType();
/**
* Gets a flag if option is required.
@@ -73,7 +75,7 @@ public interface CommandOption {
*
* @return the default value
*/
- String getDefaultValue();
+ @Nullable String getDefaultValue();
/**
* Gets a positional value.
@@ -101,14 +103,14 @@ public interface CommandOption {
*
* @return the label
*/
- String getLabel();
+ @Nullable String getLabel();
/**
* Gets a completion function.
*
* @return the completion function
*/
- CompletionResolver getCompletion();
+ @Nullable CompletionResolver getCompletion();
/**
* Gets an instance of a default {@link CommandOption}.
@@ -131,7 +133,7 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
* @param type the type
* @return default command option
*/
- public static CommandOption of(String[] longNames, Character[] shortNames, String description,
+ public static CommandOption of(String @Nullable [] longNames, Character @Nullable [] shortNames, String description,
ResolvableType type) {
return of(longNames, null, shortNames, description, type, false, null, null, null, null, null, null);
}
@@ -153,9 +155,9 @@ public static CommandOption of(String[] longNames, Character[] shortNames, Strin
* @param completion the completion
* @return default command option
*/
- public static CommandOption of(String[] longNames, String[] longNamesModified, Character[] shortNames, String description,
- ResolvableType type, boolean required, String defaultValue, Integer position, Integer arityMin,
- Integer arityMax, String label, CompletionResolver completion) {
+ public static CommandOption of(String @Nullable [] longNames, String @Nullable [] longNamesModified, Character @Nullable [] shortNames, @Nullable String description,
+ @Nullable ResolvableType type, boolean required, @Nullable String defaultValue, @Nullable Integer position, @Nullable Integer arityMin,
+ @Nullable Integer arityMax, @Nullable String label, @Nullable CompletionResolver completion) {
return new DefaultCommandOption(longNames, longNamesModified, shortNames, description, type, required, defaultValue, position,
arityMin, arityMax, label, completion);
}
@@ -168,20 +170,20 @@ public static class DefaultCommandOption implements CommandOption {
private String[] longNames;
private String[] longNamesModified;
private Character[] shortNames;
- private String description;
- private ResolvableType type;
+ private @Nullable String description;
+ private @Nullable ResolvableType type;
private boolean required;
- private String defaultValue;
+ private @Nullable String defaultValue;
private int position;
private int arityMin;
private int arityMax;
- private String label;
- private CompletionResolver completion;
+ private @Nullable String label;
+ private @Nullable CompletionResolver completion;
- public DefaultCommandOption(String[] longNames, String[] longNamesModified, Character[] shortNames, String description,
- ResolvableType type, boolean required, String defaultValue, Integer position,
- Integer arityMin, Integer arityMax, String label,
- CompletionResolver completion) {
+ public DefaultCommandOption(String @Nullable [] longNames, String @Nullable [] longNamesModified, Character @Nullable [] shortNames, @Nullable String description,
+ @Nullable ResolvableType type, boolean required, @Nullable String defaultValue, @Nullable Integer position,
+ @Nullable Integer arityMin, @Nullable Integer arityMax, @Nullable String label,
+ @Nullable CompletionResolver completion) {
this.longNames = longNames != null ? longNames : new String[0];
this.longNamesModified = longNamesModified != null ? longNamesModified : new String[0];
this.shortNames = shortNames != null ? shortNames : new Character[0];
@@ -212,12 +214,12 @@ public Character[] getShortNames() {
}
@Override
- public String getDescription() {
+ public @Nullable String getDescription() {
return description;
}
@Override
- public ResolvableType getType() {
+ public @Nullable ResolvableType getType() {
return type;
}
@@ -227,7 +229,7 @@ public boolean isRequired() {
}
@Override
- public String getDefaultValue() {
+ public @Nullable String getDefaultValue() {
return defaultValue;
}
@@ -247,12 +249,12 @@ public int getArityMax() {
}
@Override
- public String getLabel() {
+ public @Nullable String getLabel() {
return label;
}
@Override
- public CompletionResolver getCompletion() {
+ public @Nullable CompletionResolver getCompletion() {
return completion;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java
index 58af7fa35..64a11e8cc 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParser.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.Map;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionService;
import org.springframework.shell.command.parser.Ast;
import org.springframework.shell.command.parser.Ast.DefaultAst;
@@ -36,6 +37,7 @@
* which this interface intercepts and translates into format we can understand.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandParser {
@@ -56,7 +58,7 @@ interface CommandParserResult {
*
* @return the value
*/
- Object value();
+ @Nullable Object value();
/**
* Gets an instance of a default {@link CommandParserResult}.
@@ -65,7 +67,7 @@ interface CommandParserResult {
* @param value the value
* @return a result
*/
- static CommandParserResult of(CommandOption option, Object value) {
+ static CommandParserResult of(CommandOption option, @Nullable Object value) {
return new DefaultCommandParserResult(option, value);
}
}
@@ -80,7 +82,7 @@ interface CommandParserResults {
*
* @return the registration
*/
- CommandRegistration registration();
+ @Nullable CommandRegistration registration();
/**
* Gets the results.
@@ -137,8 +139,8 @@ static CommandParserResults of(CommandRegistration registration, List registrations,
- ParserConfig config) {
+ static CommandParser of(@Nullable ConversionService conversionService, Map registrations,
+ ParserConfig config) {
return new AstCommandParser(registrations, config, conversionService);
}
@@ -147,12 +149,12 @@ static CommandParser of(ConversionService conversionService, Map results;
private List positional;
private List errors;
- DefaultCommandParserResults(CommandRegistration registration, List results,
+ DefaultCommandParserResults(@Nullable CommandRegistration registration, List results,
List positional, List errors) {
this.registration = registration;
this.results = results;
@@ -161,7 +163,7 @@ static class DefaultCommandParserResults implements CommandParserResults {
}
@Override
- public CommandRegistration registration() {
+ public @Nullable CommandRegistration registration() {
return registration;
}
@@ -187,9 +189,9 @@ public List errors() {
static class DefaultCommandParserResult implements CommandParserResult {
private CommandOption option;
- private Object value;
+ private @Nullable Object value;
- DefaultCommandParserResult(CommandOption option, Object value) {
+ DefaultCommandParserResult(CommandOption option, @Nullable Object value) {
this.option = option;
this.value = value;
}
@@ -200,7 +202,7 @@ public CommandOption option() {
}
@Override
- public Object value() {
+ public @Nullable Object value() {
return value;
}
}
@@ -212,10 +214,10 @@ static class AstCommandParser implements CommandParser {
private final Map registrations;
private final ParserConfig configuration;
- private final ConversionService conversionService;
+ private final @Nullable ConversionService conversionService;
public AstCommandParser(Map registrations, ParserConfig configuration,
- ConversionService conversionService) {
+ @Nullable ConversionService conversionService) {
this.registrations = registrations;
this.configuration = configuration;
this.conversionService = conversionService;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java
index ecc7fbdc2..8def46a83 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandParserExceptionResolver.java
@@ -19,17 +19,19 @@
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException;
/**
* Handles {@link CommandParserExceptionsException}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class CommandParserExceptionResolver implements CommandExceptionResolver {
@Override
- public CommandHandlingResult resolve(Exception ex) {
+ public @Nullable CommandHandlingResult resolve(Exception ex) {
if (ex instanceof CommandParserExceptionsException cpee) {
AttributedStringBuilder builder = new AttributedStringBuilder();
cpee.getParserExceptions().stream().forEach(e -> {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java
index 2f1b0edc1..9e0dc1545 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java
@@ -27,8 +27,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
-import org.springframework.lang.Nullable;
import org.springframework.shell.Availability;
import org.springframework.shell.completion.CompletionResolver;
import org.springframework.shell.context.InteractionMode;
@@ -41,6 +41,7 @@
* Interface defining a command registration endpoint.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface CommandRegistration {
@@ -63,7 +64,7 @@ public interface CommandRegistration {
*
* @return the group
*/
- String getGroup();
+ @Nullable String getGroup();
/**
* Returns if command is hidden.
@@ -77,7 +78,7 @@ public interface CommandRegistration {
*
* @return the description
*/
- String getDescription();
+ @Nullable String getDescription();
/**
* Get {@link Availability} for a command
@@ -339,30 +340,30 @@ public interface TargetInfo {
*
* @return the bean
*/
- Object getBean();
+ @Nullable Object getBean();
/**
* Get the bean method
*
* @return the bean method
*/
- Method getMethod();
+ @Nullable Method getMethod();
/**
* Get the function
*
* @return the function
*/
- Function getFunction();
+ @Nullable Function getFunction();
/**
* Get the consumer
*
* @return the consumer
*/
- Consumer getConsumer();
+ @Nullable Consumer getConsumer();
- static TargetInfo of(Object bean, Method method) {
+ static TargetInfo of(Object bean, @Nullable Method method) {
return new DefaultTargetInfo(TargetType.METHOD, bean, method, null, null);
}
@@ -381,13 +382,13 @@ enum TargetType {
static class DefaultTargetInfo implements TargetInfo {
private final TargetType targetType;
- private final Object bean;
- private final Method method;
- private final Function function;
- private final Consumer consumer;
+ private final @Nullable Object bean;
+ private final @Nullable Method method;
+ private final @Nullable Function function;
+ private final @Nullable Consumer consumer;
- public DefaultTargetInfo(TargetType targetType, Object bean, Method method,
- Function function, Consumer consumer) {
+ public DefaultTargetInfo(TargetType targetType, @Nullable Object bean, @Nullable Method method,
+ @Nullable Function function, @Nullable Consumer consumer) {
this.targetType = targetType;
this.bean = bean;
this.method = method;
@@ -401,22 +402,22 @@ public TargetType getTargetType() {
}
@Override
- public Object getBean() {
+ public @Nullable Object getBean() {
return bean;
}
@Override
- public Method getMethod() {
+ public @Nullable Method getMethod() {
return method;
}
@Override
- public Function getFunction() {
+ public @Nullable Function getFunction() {
return function;
}
@Override
- public Consumer getConsumer() {
+ public @Nullable Consumer getConsumer() {
return consumer;
}
}
@@ -563,38 +564,38 @@ public interface HelpOptionInfo {
*
* @return long names options for help
*/
- String[] getLongNames();
+ String @Nullable [] getLongNames();
/**
* Gets short names options for help.
*
* @return short names options for help
*/
- Character[] getShortNames();
+ Character @Nullable [] getShortNames();
/**
* Gets command for help.
*
* @return command for help
*/
- String getCommand();
+ @Nullable String getCommand();
static HelpOptionInfo of() {
return of(false, null, null, null);
}
- static HelpOptionInfo of(boolean enabled, String[] longNames, Character[] shortNames, String command) {
+ static HelpOptionInfo of(boolean enabled, String @Nullable[] longNames, Character @Nullable[] shortNames, @Nullable String command) {
return new DefaultHelpOptionInfo(enabled, longNames, shortNames, command);
}
static class DefaultHelpOptionInfo implements HelpOptionInfo {
- private final String command;
- private final String[] longNames;
- private final Character[] shortNames;
+ private final @Nullable String command;
+ private final String @Nullable [] longNames;
+ private final Character @Nullable [] shortNames;
private final boolean enabled;
- public DefaultHelpOptionInfo(boolean enabled, String[] longNames, Character[] shortNames, String command) {
+ public DefaultHelpOptionInfo(boolean enabled, String @Nullable [] longNames, Character @Nullable [] shortNames, @Nullable String command) {
this.command = command;
this.longNames = longNames;
this.shortNames = shortNames;
@@ -607,17 +608,17 @@ public boolean isEnabled() {
}
@Override
- public String[] getLongNames() {
+ public String @Nullable [] getLongNames() {
return longNames;
}
@Override
- public Character[] getShortNames() {
+ public Character @Nullable [] getShortNames() {
return shortNames;
}
@Override
- public String getCommand() {
+ public @Nullable String getCommand() {
return command;
}
}
@@ -690,7 +691,7 @@ public interface Builder {
* @param mode the interaction mode
* @return builder for chaining
*/
- Builder interactionMode(InteractionMode mode);
+ Builder interactionMode(@Nullable InteractionMode mode);
/**
* Define a description text for a command.
@@ -796,18 +797,18 @@ public interface Builder {
static class DefaultOptionSpec implements OptionSpec {
private BaseBuilder builder;
- private String[] longNames;
- private Character[] shortNames;
- private ResolvableType type;
- private String description;
+ private String[] longNames = new String[0];
+ private Character[] shortNames = new Character[0];
+ private @Nullable ResolvableType type;
+ private @Nullable String description;
private boolean required;
- private String defaultValue;
- private Integer position;
- private Integer arityMin;
- private Integer arityMax;
- private String label;
- private CompletionResolver completion;
- private Function optionNameModifier;
+ private @Nullable String defaultValue;
+ private @Nullable Integer position;
+ private @Nullable Integer arityMin;
+ private @Nullable Integer arityMax;
+ private @Nullable String label;
+ private @Nullable CompletionResolver completion;
+ private @Nullable Function optionNameModifier;
DefaultOptionSpec(BaseBuilder builder) {
this.builder = builder;
@@ -941,11 +942,11 @@ public Character[] getShortNames() {
return shortNames;
}
- public ResolvableType getType() {
+ public @Nullable ResolvableType getType() {
return type;
}
- public String getDescription() {
+ public @Nullable String getDescription() {
return description;
}
@@ -953,32 +954,31 @@ public boolean isRequired() {
return required;
}
- public String getDefaultValue() {
+ public @Nullable String getDefaultValue() {
return defaultValue;
}
- public Integer getPosition() {
+ public @Nullable Integer getPosition() {
return position;
}
- public Integer getArityMin() {
+ public @Nullable Integer getArityMin() {
return arityMin;
}
- public Integer getArityMax() {
+ public @Nullable Integer getArityMax() {
return arityMax;
}
- public String getLabel() {
+ public @Nullable String getLabel() {
return label;
}
- public CompletionResolver getCompletion() {
+ public @Nullable CompletionResolver getCompletion() {
return completion;
}
- @Nullable
- public Function getOptionNameModifier() {
+ public @Nullable Function getOptionNameModifier() {
if (optionNameModifier != null) {
return optionNameModifier;
}
@@ -992,10 +992,10 @@ public Function getOptionNameModifier() {
static class DefaultTargetSpec implements TargetSpec {
private BaseBuilder builder;
- private Object bean;
- private Method method;
- private Function function;
- private Consumer consumer;
+ private @Nullable Object bean;
+ private @Nullable Method method;
+ private @Nullable Function function;
+ private @Nullable Consumer consumer;
DefaultTargetSpec(BaseBuilder builder) {
this.builder = builder;
@@ -1037,8 +1037,8 @@ public Builder and() {
static class DefaultAliasSpec implements AliasSpec {
private BaseBuilder builder;
- private String[] commands;
- private String group;
+ private String[] commands = new String[0];
+ private @Nullable String group;
DefaultAliasSpec(BaseBuilder builder) {
this.builder = builder;
@@ -1125,9 +1125,9 @@ public Builder and() {
static class DefaultHelpOptionsSpec implements HelpOptionsSpec {
private BaseBuilder builder;
- private String command;
- private String[] longNames;
- private Character[] shortNames;
+ private @Nullable String command;
+ private String @Nullable [] longNames;
+ private Character @Nullable [] shortNames;
private boolean enabled = true;
DefaultHelpOptionsSpec(BaseBuilder builder) {
@@ -1138,8 +1138,8 @@ static class DefaultHelpOptionsSpec implements HelpOptionsSpec {
this.builder = otherBuilder;
this.builder.helpOptionsSpec = this;
this.command = otherSpec.command;
- this.longNames = otherSpec.longNames.clone();
- this.shortNames = otherSpec.shortNames.clone();
+ this.longNames = otherSpec.longNames != null ? otherSpec.longNames.clone() : null;
+ this.shortNames = otherSpec.shortNames != null ? otherSpec.shortNames.clone() : null;
this.enabled = otherSpec.enabled;
}
@@ -1177,22 +1177,22 @@ static class DefaultCommandRegistration implements CommandRegistration {
private String command;
private InteractionMode interactionMode;
- private String group;
+ private @Nullable String group;
private boolean hidden;
- private String description;
- private Supplier availability;
- private List options;
+ private @Nullable String description;
+ private @Nullable Supplier availability;
+ private @Nullable List options;
private List optionSpecs;
private DefaultTargetSpec targetSpec;
private List aliasSpecs;
- private DefaultExitCodeSpec exitCodeSpec;
- private DefaultErrorHandlingSpec errorHandlingSpec;
- private DefaultHelpOptionsSpec helpOptionsSpec;
+ private @Nullable DefaultExitCodeSpec exitCodeSpec;
+ private @Nullable DefaultErrorHandlingSpec errorHandlingSpec;
+ private @Nullable DefaultHelpOptionsSpec helpOptionsSpec;
- public DefaultCommandRegistration(String[] commands, InteractionMode interactionMode, String group,
- boolean hidden, String description, Supplier availability,
+ public DefaultCommandRegistration(String[] commands, InteractionMode interactionMode, @Nullable String group,
+ boolean hidden, @Nullable String description, @Nullable Supplier availability,
List optionSpecs, DefaultTargetSpec targetSpec, List aliasSpecs,
- DefaultExitCodeSpec exitCodeSpec, DefaultErrorHandlingSpec errorHandlingSpec, DefaultHelpOptionsSpec helpOptionsSpec) {
+ @Nullable DefaultExitCodeSpec exitCodeSpec, @Nullable DefaultErrorHandlingSpec errorHandlingSpec, @Nullable DefaultHelpOptionsSpec helpOptionsSpec) {
this.command = commandArrayToName(commands);
this.interactionMode = interactionMode;
this.group = group;
@@ -1218,7 +1218,7 @@ public InteractionMode getInteractionMode() {
}
@Override
- public String getGroup() {
+ public @Nullable String getGroup() {
return group;
}
@@ -1228,7 +1228,7 @@ public boolean isHidden() {
}
@Override
- public String getDescription() {
+ public @Nullable String getDescription() {
return description;
}
@@ -1333,19 +1333,19 @@ static class DefaultBuilder extends BaseBuilder {
static abstract class BaseBuilder implements Builder {
- private String[] commands;
+ private String @Nullable[] commands;
private InteractionMode interactionMode = InteractionMode.ALL;
- private String group;
+ private @Nullable String group;
private boolean hidden;
- private String description;
- private Supplier availability;
+ private @Nullable String description;
+ private @Nullable Supplier availability;
private List optionSpecs = new ArrayList<>();
private List aliasSpecs = new ArrayList<>();
- private DefaultTargetSpec targetSpec;
- private DefaultExitCodeSpec exitCodeSpec;
- private DefaultErrorHandlingSpec errorHandlingSpec;
- private DefaultHelpOptionsSpec helpOptionsSpec;
- private Function defaultOptionNameModifier;
+ private @Nullable DefaultTargetSpec targetSpec;
+ private @Nullable DefaultExitCodeSpec exitCodeSpec;
+ private @Nullable DefaultErrorHandlingSpec errorHandlingSpec;
+ private @Nullable DefaultHelpOptionsSpec helpOptionsSpec;
+ private @Nullable Function defaultOptionNameModifier;
@Override
public Builder command(String... commands) {
@@ -1360,7 +1360,7 @@ public Builder command(String... commands) {
}
@Override
- public Builder interactionMode(InteractionMode mode) {
+ public Builder interactionMode(@Nullable InteractionMode mode) {
this.interactionMode = mode != null ? mode : InteractionMode.ALL;
return this;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/ExceptionResolverMethodResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/ExceptionResolverMethodResolver.java
index 7c8e60e90..e018f5b62 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/ExceptionResolverMethodResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/ExceptionResolverMethodResolver.java
@@ -22,10 +22,10 @@
import java.util.List;
import java.util.Map;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.ExceptionDepthComparator;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils.MethodFilter;
@@ -33,6 +33,7 @@
/**
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class ExceptionResolverMethodResolver {
@@ -113,8 +114,7 @@ public boolean hasExceptionMappings() {
* @param exception the exception
* @return a Method to handle the exception, or {@code null} if none found
*/
- @Nullable
- public Method resolveMethod(Exception exception) {
+ public @Nullable Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
}
@@ -125,8 +125,7 @@ public Method resolveMethod(Exception exception) {
* @param exception the exception
* @return a Method to handle the exception, or {@code null} if none found
*/
- @Nullable
- public Method resolveMethodByThrowable(Throwable exception) {
+ public @Nullable Method resolveMethodByThrowable(Throwable exception) {
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
Throwable cause = exception.getCause();
@@ -145,8 +144,7 @@ public Method resolveMethodByThrowable(Throwable exception) {
* @param exceptionType the exception type
* @return a Method to handle the exception, or {@code null} if none found
*/
- @Nullable
- public Method resolveMethodByExceptionType(Class extends Throwable> exceptionType) {
+ public @Nullable Method resolveMethodByExceptionType(Class extends Throwable> exceptionType) {
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
method = getMappedMethod(exceptionType);
@@ -159,7 +157,7 @@ public Method resolveMethodByExceptionType(Class extends Throwable> exceptionT
* Return the {@link Method} mapped to the given exception type, or
* {@link #NO_MATCHING_EXCEPTION_HANDLER_METHOD} if none.
*/
- private Method getMappedMethod(Class extends Throwable> exceptionType) {
+ private @Nullable Method getMappedMethod(Class extends Throwable> exceptionType) {
List> matches = new ArrayList<>();
for (Class extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/MethodCommandExceptionResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/MethodCommandExceptionResolver.java
index f5d03f4c8..1f448fb1d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/MethodCommandExceptionResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/MethodCommandExceptionResolver.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import org.jline.terminal.Terminal;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,24 +34,27 @@
import org.springframework.shell.command.invocation.ShellMethodArgumentResolverComposite;
import org.springframework.util.Assert;
+/**
+ * @author Piotr Olaszewski
+ */
public class MethodCommandExceptionResolver implements CommandExceptionResolver {
private final static Logger log = LoggerFactory.getLogger(MethodCommandExceptionResolver.class);
private final Object bean;
- private final Terminal terminal;
+ private final @Nullable Terminal terminal;
public MethodCommandExceptionResolver(Object bean) {
this(bean, null);
}
- public MethodCommandExceptionResolver(Object bean, Terminal terminal) {
+ public MethodCommandExceptionResolver(Object bean, @Nullable Terminal terminal) {
Assert.notNull(bean, "Target bean must be set");
this.bean = bean;
this.terminal = terminal;
}
@Override
- public CommandHandlingResult resolve(Exception ex) {
+ public @Nullable CommandHandlingResult resolve(Exception ex) {
try {
ExceptionResolverMethodResolver resolver = new ExceptionResolverMethodResolver(bean.getClass());
Method exceptionResolverMethod = resolver.resolveMethodByThrowable(ex);
@@ -121,9 +125,8 @@ public boolean supportsParameter(MethodParameter parameter) {
}
@Override
- public Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
- Terminal terminal = message.getHeaders().get("terminal", Terminal.class);
- return terminal;
+ public @Nullable Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
+ return message.getHeaders().get("terminal", Terminal.class);
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/package-info.java
new file mode 100644
index 000000000..696b20117
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command.annotation;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandAnnotationUtils.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandAnnotationUtils.java
index f4f669fd7..f96fd6bda 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandAnnotationUtils.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandAnnotationUtils.java
@@ -19,8 +19,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.MergedAnnotation;
-import org.springframework.lang.Nullable;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.context.InteractionMode;
import org.springframework.util.StringUtils;
@@ -32,6 +32,7 @@
* defaults and every field may have its own logic.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
class CommandAnnotationUtils {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationBeanRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationBeanRegistrar.java
index c71a0aa96..0092471dc 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationBeanRegistrar.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationBeanRegistrar.java
@@ -20,6 +20,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
@@ -42,6 +43,7 @@
* {@link Command @Command} class.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public final class CommandRegistrationBeanRegistrar {
@@ -100,8 +102,7 @@ private void registerCommandMethodBeanDefinition(Class> commandBeanType, Strin
}
private BeanDefinition createCommandClassBeanDefinition(Class> type) {
- RootBeanDefinition definition = new RootBeanDefinition(type);
- return definition;
+ return new RootBeanDefinition(type);
}
private BeanDefinition createCommandMethodBeanDefinition(Class> commandBeanType, String commandBeanName,
@@ -118,7 +119,7 @@ private boolean containsBeanDefinition(String name) {
return containsBeanDefinition(this.beanFactory, name);
}
- private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
+ private boolean containsBeanDefinition(@Nullable BeanFactory beanFactory, String name) {
if (beanFactory instanceof ListableBeanFactory listableBeanFactory
&& listableBeanFactory.containsBeanDefinition(name)) {
return true;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java
index 5426a4571..5a943b75e 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandRegistrationFactoryBean.java
@@ -22,6 +22,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -55,10 +56,7 @@
import org.springframework.shell.command.invocation.InvocableShellMethod;
import org.springframework.shell.completion.CompletionProvider;
import org.springframework.shell.context.InteractionMode;
-import org.springframework.util.ClassUtils;
-import org.springframework.util.ObjectUtils;
-import org.springframework.util.ReflectionUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.*;
/**
* Factory bean used in {@link CommandRegistrationBeanRegistrar} to build
@@ -75,6 +73,7 @@
* This is internal class and not meant for generic use.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
class CommandRegistrationFactoryBean implements FactoryBean, ApplicationContextAware, InitializingBean {
@@ -84,18 +83,20 @@ class CommandRegistrationFactoryBean implements FactoryBean
public static final String COMMAND_METHOD_NAME = "commandMethodName";
public static final String COMMAND_METHOD_PARAMETERS = "commandMethodParameters";
+ @SuppressWarnings("NullAway.Init")
private ObjectProvider supplier;
+ @SuppressWarnings("NullAway.Init")
private ApplicationContext applicationContext;
+ @SuppressWarnings("NullAway.Init")
private Object commandBean;
- private Class> commandBeanType;
- private String commandBeanName;
- private String commandMethodName;
- private Class>[] commandMethodParameters;
+ private @Nullable Class> commandBeanType;
+ private @Nullable String commandBeanName;
+ private @Nullable String commandMethodName;
+ private Class> @Nullable [] commandMethodParameters;
@Override
public CommandRegistration getObject() throws Exception {
- CommandRegistration registration = buildRegistration();
- return registration;
+ return buildRegistration();
}
@Override
@@ -110,6 +111,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
@Override
public void afterPropertiesSet() throws Exception {
+ Assert.notNull(commandBeanName, "'commandBeanName' must not be null");
this.commandBean = applicationContext.getBean(commandBeanName);
this.supplier = applicationContext.getBeanProvider(CommandRegistration.BuilderSupplier.class);
}
@@ -135,7 +137,13 @@ private CommandRegistration.Builder getBuilder() {
}
private CommandRegistration buildRegistration() {
+ Assert.notNull(commandBeanType, "'commandBeanType' must not be null");
+ Assert.notNull(commandMethodName, "'commandMethodName' must not be null");
+
Method method = ReflectionUtils.findMethod(commandBeanType, commandMethodName, commandMethodParameters);
+
+ Assert.notNull(method, "'method' must not be null");
+
MergedAnnotation classAnn = MergedAnnotations.from(commandBeanType, SearchStrategy.TYPE_HIERARCHY)
.get(Command.class);
MergedAnnotation methodAnn = MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY)
@@ -211,8 +219,7 @@ private CommandRegistration buildRegistration() {
methodCommandExceptionResolver.exceptionResolverMethodResolver = exceptionResolverMethodResolver;
builder.withErrorHandling().resolver(methodCommandExceptionResolver);
- CommandRegistration registration = builder.build();
- return registration;
+ return builder.build();
}
private void onCommandParameter(MethodParameter mp, Builder builder) {
@@ -351,11 +358,13 @@ else if (ClassUtils.isAssignable(Boolean.class, parameterType)) {
private static class MethodCommandExceptionResolver implements CommandExceptionResolver {
+ @SuppressWarnings("NullAway.Init")
Object bean;
+ @SuppressWarnings("NullAway.Init")
ExceptionResolverMethodResolver exceptionResolverMethodResolver;
@Override
- public CommandHandlingResult resolve(Exception ex) {
+ public @Nullable CommandHandlingResult resolve(Exception ex) {
Method exceptionHandlerMethod = exceptionResolverMethodResolver.resolveMethodByThrowable(ex);
if (exceptionHandlerMethod == null) {
return null;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandScanRegistrar.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandScanRegistrar.java
index a06e329eb..fbd4333f2 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandScanRegistrar.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/CommandScanRegistrar.java
@@ -19,6 +19,8 @@
import java.util.LinkedHashSet;
import java.util.Set;
+import org.jspecify.annotations.Nullable;
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
@@ -33,6 +35,7 @@
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.command.annotation.CommandScan;
import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -41,6 +44,7 @@
*
* @author Janne Valkealahti
* @author Mahmoud Ben Hassine
+ * @author Piotr Olaszewski
*/
public class CommandScanRegistrar implements ImportBeanDefinitionRegistrar {
@@ -61,6 +65,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
private Set getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(CommandScan.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));
@@ -93,9 +98,11 @@ private ClassPathScanningCandidateComponentProvider getScanner(BeanDefinitionReg
return scanner;
}
- private void register(CommandRegistrationBeanRegistrar registrar, String className) throws LinkageError {
+ private void register(CommandRegistrationBeanRegistrar registrar, @Nullable String className) throws LinkageError {
try {
- register(registrar, ClassUtils.forName(className, null));
+ if (StringUtils.hasText(className)) {
+ register(registrar, ClassUtils.forName(className, null));
+ }
}
catch (ClassNotFoundException ex) {
// Ignore
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java
index 54e943f2b..144eb1337 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java
@@ -19,10 +19,10 @@
import java.util.List;
import java.util.stream.Collectors;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
-import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.shell.command.annotation.Option;
@@ -57,8 +57,7 @@ protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
}
@Override
- @Nullable
- protected Object resolveArgumentInternal(MethodParameter parameter, Message> message, List names)
+ protected @Nullable Object resolveArgumentInternal(MethodParameter parameter, Message> message, List names)
throws Exception {
for (String name : names) {
if (message.getHeaders().containsKey(ARGUMENT_PREFIX + name)) {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/package-info.java
new file mode 100644
index 000000000..5ca39f10b
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command.annotation.support;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/InvocableShellMethod.java b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/InvocableShellMethod.java
index 515095277..31249450a 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/InvocableShellMethod.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/InvocableShellMethod.java
@@ -27,6 +27,7 @@
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +41,6 @@
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.shell.ParameterValidationException;
@@ -62,6 +62,7 @@
* through the associated {@link BeanFactory}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class InvocableShellMethod {
@@ -72,8 +73,7 @@ public class InvocableShellMethod {
private final Object bean;
- @Nullable
- private final BeanFactory beanFactory;
+ private final @Nullable BeanFactory beanFactory;
private final Class> beanType;
@@ -83,21 +83,20 @@ public class InvocableShellMethod {
private final MethodParameter[] parameters;
- @Nullable
- private InvocableShellMethod resolvedFromHandlerMethod;
+ private @Nullable InvocableShellMethod resolvedFromHandlerMethod;
private ShellMethodArgumentResolverComposite resolvers = new ShellMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
- private Validator validator;
+ private @Nullable Validator validator;
private ConversionService conversionService = new DefaultConversionService();
/**
* Create an instance from a bean instance and a method.
*/
- public InvocableShellMethod(Object bean, Method method) {
+ public InvocableShellMethod(@Nullable Object bean, @Nullable Method method) {
Assert.notNull(bean, "Bean is required");
Assert.notNull(method, "Method is required");
this.bean = bean;
@@ -152,7 +151,7 @@ public InvocableShellMethod(String beanName, BeanFactory beanFactory, Method met
*
* @param conversionService the conversion service
*/
- public void setConversionService(ConversionService conversionService) {
+ public void setConversionService(@Nullable ConversionService conversionService) {
if (conversionService != null) {
this.conversionService = conversionService;
}
@@ -187,7 +186,7 @@ private InvocableShellMethod(InvocableShellMethod handlerMethod, Object handler)
this.resolvedFromHandlerMethod = handlerMethod;
}
- public void setValidator(Validator validator) {
+ public void setValidator(@Nullable Validator validator) {
this.validator = validator;
}
@@ -223,8 +222,7 @@ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDisc
* @see #getMethodArgumentValues
* @see #doInvoke
*/
- @Nullable
- public Object invoke(Message> message, Object... providedArgs) throws Exception {
+ public @Nullable Object invoke(@Nullable Message> message, Object @Nullable... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(message, providedArgs);
if (log.isTraceEnabled()) {
log.trace("Arguments: " + Arrays.toString(args));
@@ -237,7 +235,7 @@ public Object invoke(Message> message, Object... providedArgs) throws Exceptio
* argument values and falling back to the configured argument resolvers.
* The resulting array will be passed into {@link #doInvoke}.
*/
- protected Object[] getMethodArgumentValues(Message> message, Object... providedArgs) throws Exception {
+ protected Object[] getMethodArgumentValues(@Nullable Message> message, Object @Nullable... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
@@ -251,7 +249,7 @@ protected Object[] getMethodArgumentValues(Message> message, Object... provide
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
boolean supports = this.resolvers.supportsParameter(parameter);
Object arg = null;
- if (supports) {
+ if (supports && message != null) {
arg = this.resolvers.resolveArgument(parameter, message);
}
else {
@@ -280,9 +278,9 @@ protected Object[] getMethodArgumentValues(Message> message, Object... provide
private static class ResolvedHolder {
boolean resolved;
MethodParameter parameter;
- Object arg;
+ @Nullable Object arg;
- public ResolvedHolder(boolean resolved, MethodParameter parameter, Object arg) {
+ public ResolvedHolder(boolean resolved, MethodParameter parameter, @Nullable Object arg) {
this.resolved = resolved;
this.parameter = parameter;
this.arg = arg;
@@ -292,8 +290,7 @@ public ResolvedHolder(boolean resolved, MethodParameter parameter, Object arg) {
/**
* Invoke the handler method with the given argument values.
*/
- @Nullable
- protected Object doInvoke(Object... args) throws Exception {
+ protected @Nullable Object doInvoke(Object... args) throws Exception {
try {
if (validator != null) {
Method bridgedMethod = getBridgedMethod();
@@ -410,8 +407,7 @@ public boolean isVoid() {
* @return the annotation, or {@code null} if none found
* @see AnnotatedElementUtils#findMergedAnnotation
*/
- @Nullable
- public A getMethodAnnotation(Class annotationType) {
+ public @Nullable A getMethodAnnotation(Class annotationType) {
return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
}
@@ -428,8 +424,7 @@ public boolean hasMethodAnnotation(Class annotationTyp
* Return the HandlerMethod from which this HandlerMethod instance was
* resolved via {@link #createWithResolvedBean()}.
*/
- @Nullable
- public InvocableShellMethod getResolvedFromHandlerMethod() {
+ public @Nullable InvocableShellMethod getResolvedFromHandlerMethod() {
return this.resolvedFromHandlerMethod;
}
@@ -482,8 +477,7 @@ public String toString() {
// Support methods for use in "InvocableHandlerMethod" sub-class variants..
- @Nullable
- protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
+ protected static @Nullable Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
if (!ObjectUtils.isEmpty(providedArgs)) {
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
@@ -552,7 +546,7 @@ public Class> getContainingClass() {
}
@Override
- public T getMethodAnnotation(Class annotationType) {
+ public @Nullable T getMethodAnnotation(Class annotationType) {
return InvocableShellMethod.this.getMethodAnnotation(annotationType);
}
@@ -573,8 +567,7 @@ public HandlerMethodParameter clone() {
*/
private class ReturnValueMethodParameter extends HandlerMethodParameter {
- @Nullable
- private final Object returnValue;
+ private final @Nullable Object returnValue;
public ReturnValueMethodParameter(@Nullable Object returnValue) {
super(-1);
@@ -599,8 +592,7 @@ public ReturnValueMethodParameter clone() {
private class AsyncResultMethodParameter extends HandlerMethodParameter {
- @Nullable
- private final Object returnValue;
+ private final @Nullable Object returnValue;
private final ResolvableType returnType;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/ShellMethodArgumentResolverComposite.java b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/ShellMethodArgumentResolverComposite.java
index ef3afcd35..91297e28d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/ShellMethodArgumentResolverComposite.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/ShellMethodArgumentResolverComposite.java
@@ -21,9 +21,9 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
-import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
@@ -34,6 +34,7 @@
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
+ * @author Piotr Olaszewski
*/
public class ShellMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@@ -111,8 +112,7 @@ public boolean supportsParameter(MethodParameter parameter) {
* @throws IllegalArgumentException if no suitable argument resolver is found
*/
@Override
- @Nullable
- public Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
+ public @Nullable Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
@@ -125,8 +125,7 @@ public Object resolveArgument(MethodParameter parameter, Message> message) thr
* Find a registered {@link HandlerMethodArgumentResolver} that supports
* the given method parameter.
*/
- @Nullable
- private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
+ private @Nullable HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/package-info.java
new file mode 100644
index 000000000..ae51493e9
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/invocation/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command.invocation;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/package-info.java
new file mode 100644
index 000000000..b0a2bc7d1
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Ast.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Ast.java
index a9c108c44..8a5d18f26 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Ast.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Ast.java
@@ -29,6 +29,7 @@
* arguments makes sense which happen later when ast tree is visited.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface Ast {
@@ -82,7 +83,9 @@ public AstResult generate(List tokens) {
break;
case OPTION:
optionNode = new OptionNode(token, token.getValue());
- commandNode.addChildNode(optionNode);
+ if (commandNode != null) {
+ commandNode.addChildNode(optionNode);
+ }
break;
case ARGUMENT:
if (optionNode != null) {
@@ -91,7 +94,9 @@ public AstResult generate(List tokens) {
}
else {
CommandArgumentNode commandArgumentNode = new CommandArgumentNode(token, commandNode);
- commandNode.addChildNode(commandArgumentNode);
+ if (commandNode != null) {
+ commandNode.addChildNode(commandArgumentNode);
+ }
}
break;
case DOUBLEDASH:
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandArgumentNode.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandArgumentNode.java
index 0f009ee28..43d2bca7c 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandArgumentNode.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandArgumentNode.java
@@ -15,21 +15,24 @@
*/
package org.springframework.shell.command.parser;
+import org.jspecify.annotations.Nullable;
+
/**
* Node representing a command argument.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public final class CommandArgumentNode extends TerminalAstNode {
- private final CommandNode parent;
+ private final @Nullable CommandNode parent;
- public CommandArgumentNode(Token token, CommandNode parent) {
+ public CommandArgumentNode(Token token, @Nullable CommandNode parent) {
super(token);
this.parent = parent;
}
- public CommandNode getParentCommandNode() {
+ public @Nullable CommandNode getParentCommandNode() {
return parent;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandModel.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandModel.java
index 8a65e51e5..a29247eb5 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandModel.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/CommandModel.java
@@ -21,7 +21,7 @@
import java.util.Map;
import java.util.Optional;
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.command.parser.ParserConfig.Feature;
@@ -30,6 +30,7 @@
* those are used with parser model.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class CommandModel {
@@ -41,12 +42,11 @@ public CommandModel(Map registrations, ParserConfig
buildModel(registrations);
}
- @Nullable
- CommandInfo getRootCommand(String command) {
+ @Nullable CommandInfo getRootCommand(String command) {
return rootCommands.get(command);
}
- CommandInfo resolve(List commands) {
+ @Nullable CommandInfo resolve(List commands) {
CommandInfo info = null;
boolean onRoot = true;
for (String commandx : commands) {
@@ -56,6 +56,9 @@ CommandInfo resolve(List commands) {
onRoot = false;
}
else {
+ if (info == null) {
+ continue;
+ }
Optional nextInfo = info.getChildren().stream()
.filter(i -> i.command.equals(command))
.findFirst();
@@ -103,7 +106,7 @@ void xxx(String command, CommandRegistration registration) {
// root1 sub1
// root1 sub2
- private CommandInfo getOrCreate(String[] commands, CommandRegistration registration) {
+ private @Nullable CommandInfo getOrCreate(String[] commands, CommandRegistration registration) {
CommandInfo ret = null;
CommandInfo parent = null;
@@ -122,6 +125,10 @@ private CommandInfo getOrCreate(String[] commands, CommandRegistration registrat
continue;
}
+ if (parent == null) {
+ continue;
+ }
+
CommandInfo children = parent.getChildren(command);
if (children == null) {
children = new CommandInfo(key, i < commands.length - 1 ? null : registration, parent);
@@ -135,7 +142,7 @@ private CommandInfo getOrCreate(String[] commands, CommandRegistration registrat
}
- if (ret.registration == null) {
+ if (ret != null && ret.registration == null) {
ret.registration = registration;
}
return ret;
@@ -170,12 +177,12 @@ private void buildModel(Map registrations) {
*/
static class CommandInfo {
String command;
- CommandRegistration registration;
- CommandInfo parent;
+ @Nullable CommandRegistration registration;
+ @Nullable CommandInfo parent;
// private List children = new ArrayList<>();
private Map children = new HashMap<>();
- CommandInfo(String command, CommandRegistration registration, CommandInfo parent) {
+ CommandInfo(String command, @Nullable CommandRegistration registration, @Nullable CommandInfo parent) {
this.registration = registration;
this.parent = parent;
this.command = command;
@@ -212,7 +219,7 @@ public void addChildred(String command, CommandInfo children) {
this.children.put(command, children);
}
- CommandInfo getChildren(String command) {
+ @Nullable CommandInfo getChildren(String command) {
return children.get(command);
// return children.stream()
// .filter(c -> c.command.equals(command))
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/DirectiveNode.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/DirectiveNode.java
index 433e93f55..d9e40f2a6 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/DirectiveNode.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/DirectiveNode.java
@@ -15,17 +15,20 @@
*/
package org.springframework.shell.command.parser;
+import org.jspecify.annotations.Nullable;
+
/**
* Node representing
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public final class DirectiveNode extends TerminalAstNode {
private final String name;
- private final String value;
+ private final @Nullable String value;
- public DirectiveNode(Token token, String name, String value) {
+ public DirectiveNode(Token token, String name, @Nullable String value) {
super(token);
this.name = name;
this.value = value;
@@ -35,7 +38,7 @@ public String getName() {
return name;
}
- public String getValue() {
+ public @Nullable String getValue() {
return value;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java
index 04a002cc4..9e5f3de63 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Lexer.java
@@ -28,6 +28,7 @@
import org.springframework.shell.command.parser.CommandModel.CommandInfo;
import org.springframework.shell.command.parser.ParserConfig.Feature;
+import org.springframework.util.Assert;
/**
* Interface to tokenize arguments into tokens. Generic language parser usually
@@ -40,6 +41,7 @@
* through parsing operation.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface Lexer {
@@ -191,6 +193,7 @@ public LexerResult tokenize(List arguments) {
currentCommand = currentCommand == null ? commandModel.getRootCommands().get(argumentToCheck)
: currentCommand.getChildren(argument);
tokenList.add(Token.of(argument, TokenType.COMMAND, i2));
+ Assert.state(currentCommand != null, "'currentCommand' must not be null");
validTokens = currentCommand.getValidTokens();
break;
case OPTION:
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/MessageResult.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/MessageResult.java
index 1300efbe2..cba04f307 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/MessageResult.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/MessageResult.java
@@ -15,12 +15,15 @@
*/
package org.springframework.shell.command.parser;
+import org.jspecify.annotations.Nullable;
+
/**
* Encapsulating {@link ParserMessage} with position and {@code inserts}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
-public record MessageResult(ParserMessage parserMessage, int position, Object[] inserts) {
+public record MessageResult(ParserMessage parserMessage, int position, Object @Nullable [] inserts) {
/**
* Constructs {@code MessageResult} with parser message, position and inserts.
@@ -30,7 +33,7 @@ public record MessageResult(ParserMessage parserMessage, int position, Object[]
* @param inserts the inserts
* @return a message result
*/
- public static MessageResult of(ParserMessage parserMessage, int position, Object... inserts) {
+ public static MessageResult of(ParserMessage parserMessage, int position, @Nullable Object... inserts) {
return new MessageResult(parserMessage, position, inserts);
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Parser.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Parser.java
index 84284546e..66b1c0edb 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Parser.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/Parser.java
@@ -24,6 +24,7 @@
import java.util.Set;
import java.util.stream.Collectors;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
@@ -42,6 +43,7 @@
* Interface to parse command line arguments.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public interface Parser {
@@ -65,13 +67,13 @@ public interface Parser {
* @param messageResults message results
* @param directiveResults directive result
*/
- public record ParseResult(CommandRegistration commandRegistration, List optionResults,
+ public record ParseResult(@Nullable CommandRegistration commandRegistration, List optionResults,
List argumentResults, List messageResults,
List directiveResults) {
- public record OptionResult(CommandOption option, Object value) {
+ public record OptionResult(CommandOption option, @Nullable Object value) {
- public static OptionResult of(CommandOption option, Object value) {
+ public static OptionResult of(CommandOption option, @Nullable Object value) {
return new OptionResult(option, value);
}
}
@@ -105,7 +107,7 @@ public DefaultParser(CommandModel commandModel, Lexer lexer, Ast ast, ParserConf
}
public DefaultParser(CommandModel commandModel, Lexer lexer, Ast ast, ParserConfig config,
- ConversionService conversionService) {
+ @Nullable ConversionService conversionService) {
this.commandModel = commandModel;
this.lexer = lexer;
this.ast = ast;
@@ -276,9 +278,17 @@ protected void onEnterOptionNode(OptionNode node) {
currentOptionArgument.clear();
CommandInfo info = commandModel.resolve(resolvedCommmand);
+ if (info == null) {
+ return;
+ }
+ CommandRegistration registration = info.registration;
+ if (registration == null) {
+ return;
+ }
+
String name = node.getName();
if (name.startsWith("--")) {
- info.registration.getOptions().forEach(option -> {
+ registration.getOptions().forEach(option -> {
Set longNames = Arrays.asList(option.getLongNames()).stream()
.map(n -> "--" + n)
.collect(Collectors.toSet());
@@ -291,7 +301,7 @@ protected void onEnterOptionNode(OptionNode node) {
}
else if (name.startsWith("-")) {
if (name.length() == 2) {
- info.registration.getOptions().forEach(option -> {
+ registration.getOptions().forEach(option -> {
Set shortNames = Arrays.asList(option.getShortNames()).stream()
.map(n -> "-" + Character.toString(n))
.collect(Collectors.toSet());
@@ -302,7 +312,7 @@ else if (name.startsWith("-")) {
});
}
else if (name.length() > 2) {
- info.registration.getOptions().forEach(option -> {
+ registration.getOptions().forEach(option -> {
Set shortNames = Arrays.asList(option.getShortNames()).stream()
.map(n -> "-" + Character.toString(n))
.collect(Collectors.toSet());
@@ -399,7 +409,7 @@ protected void onEnterOptionArgumentNode(OptionArgumentNode node) {
protected void onExitOptionArgumentNode(OptionArgumentNode node) {
}
- private Object convertOptionType(CommandOption option, Object value) {
+ private @Nullable Object convertOptionType(CommandOption option, @Nullable Object value) {
ResolvableType type = option.getType();
if (value == null && type != null && type.isAssignableFrom(boolean.class)) {
return true;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/ParserMessage.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/ParserMessage.java
index 9eb22488c..2b1629143 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/ParserMessage.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/ParserMessage.java
@@ -15,6 +15,8 @@
*/
package org.springframework.shell.command.parser;
+import org.jspecify.annotations.Nullable;
+
import java.text.MessageFormat;
/**
@@ -35,6 +37,7 @@
* is beyond its control.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public enum ParserMessage {
@@ -70,7 +73,7 @@ public Type getType() {
* @param inserts the inserts
* @return formatted message
*/
- public String formatMessage(Object... inserts) {
+ public String formatMessage(Object @Nullable ... inserts) {
return formatMessage(false, -1, inserts);
}
@@ -84,7 +87,7 @@ public String formatMessage(Object... inserts) {
* @param inserts the inserts
* @return formatted message
*/
- public String formatMessage(boolean useCode, int position, Object... inserts) {
+ public String formatMessage(boolean useCode, int position, Object @Nullable... inserts) {
StringBuilder msg = new StringBuilder();
if (useCode) {
msg.append(code);
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/parser/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/package-info.java
new file mode 100644
index 000000000..f9e156962
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/parser/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command.parser;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/support/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/command/support/package-info.java
new file mode 100644
index 000000000..f42dbf7b1
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/command/support/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.command.support;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/completion/RegistrationOptionsCompletionResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/completion/RegistrationOptionsCompletionResolver.java
index fa33ba387..83a0d345b 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/completion/RegistrationOptionsCompletionResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/completion/RegistrationOptionsCompletionResolver.java
@@ -22,27 +22,30 @@
import org.springframework.shell.CompletionContext;
import org.springframework.shell.CompletionProposal;
+import org.springframework.shell.command.CommandRegistration;
/**
* Default implementation of a {@link CompletionResolver}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class RegistrationOptionsCompletionResolver implements CompletionResolver {
@Override
public List apply(CompletionContext context) {
- if (context.getCommandRegistration() == null) {
+ CommandRegistration commandRegistration = context.getCommandRegistration();
+ if (commandRegistration == null) {
return Collections.emptyList();
}
List candidates = new ArrayList<>();
- context.getCommandRegistration().getOptions().stream()
+ commandRegistration.getOptions().stream()
.flatMap(o -> Stream.of(o.getLongNames()))
.map(ln -> "--" + ln)
.filter(ln -> !context.getWords().contains(ln))
.map(CompletionProposal::new)
.forEach(candidates::add);
- context.getCommandRegistration().getOptions().stream()
+ commandRegistration.getOptions().stream()
.flatMap(o -> Stream.of(o.getShortNames()))
.map(ln -> "-" + ln)
.filter(ln -> !context.getWords().contains(ln))
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/completion/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/completion/package-info.java
new file mode 100644
index 000000000..77c07db9e
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/completion/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.completion;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/config/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/config/package-info.java
new file mode 100644
index 000000000..a4bcab227
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/config/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.config;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/context/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/context/package-info.java
new file mode 100644
index 000000000..875413bd4
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/context/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.context;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/exit/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/exit/package-info.java
new file mode 100644
index 000000000..8569bed49
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/exit/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.exit;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/ExtendedDefaultParser.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/ExtendedDefaultParser.java
index cf4f1b726..a94149f43 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/ExtendedDefaultParser.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/ExtendedDefaultParser.java
@@ -26,6 +26,7 @@
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.CompletingParsedLine;
/**
@@ -34,6 +35,7 @@
*
* @author Original JLine author
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public class ExtendedDefaultParser implements Parser {
@@ -238,10 +240,10 @@ public class ExtendedArgumentList implements ParsedLine, CompletingParsedLine {
private final int cursor;
- private final String openingQuote;
+ private final @Nullable String openingQuote;
public ExtendedArgumentList(final String line, final List words, final int wordIndex,
- final int wordCursor, final int cursor, final String openingQuote) {
+ final int wordCursor, final int cursor, final @Nullable String openingQuote) {
this.line = line;
this.words = Collections.unmodifiableList(Objects.requireNonNull(words));
this.wordIndex = wordIndex;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java
index 2b421eceb..027c02ea3 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/FileInputProvider.java
@@ -18,6 +18,7 @@
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
@@ -32,6 +33,7 @@
* of line to signal line continuation.
*
* @author Eric Bottard
+ * @author Piotr Olaszewski
*/
public class FileInputProvider implements InputProvider, Closeable {
@@ -46,7 +48,7 @@ public FileInputProvider(Reader reader, Parser parser) {
}
@Override
- public Input readInput() {
+ public @Nullable Input readInput() {
StringBuilder sb = new StringBuilder();
boolean continued = false;
String line;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java
index 13399c7a2..c8e95a81d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellRunner.java
@@ -25,6 +25,7 @@
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultParser;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.Order;
import org.springframework.shell.Input;
import org.springframework.shell.InputProvider;
@@ -44,6 +45,7 @@
*
* @author Janne Valkealahti
* @author Chris Bono
+ * @author Piotr Olaszewski
*/
@Order(NonInteractiveShellRunner.PRECEDENCE)
public class NonInteractiveShellRunner implements ShellRunner {
@@ -60,7 +62,7 @@ public class NonInteractiveShellRunner implements ShellRunner {
private Parser lineParser;
- private String primaryCommand;
+ private @Nullable String primaryCommand;
private static final String SINGLE_QUOTE = "\'";
private static final String DOUBLE_QUOTE = "\"";
@@ -103,7 +105,7 @@ public NonInteractiveShellRunner(Shell shell, ShellContext shellContext) {
this(shell, shellContext, null);
}
- public NonInteractiveShellRunner(Shell shell, ShellContext shellContext, String primaryCommand) {
+ public NonInteractiveShellRunner(Shell shell, ShellContext shellContext, @Nullable String primaryCommand) {
this.shell = shell;
this.shellContext = shellContext;
this.primaryCommand = primaryCommand;
@@ -160,7 +162,7 @@ static class MultiParsedLineInputProvider implements InputProvider {
}
@Override
- public Input readInput() {
+ public @Nullable Input readInput() {
if (inputIdx == parsedLineInputs.size()) {
return null;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/package-info.java
index 743b2e970..ead160176 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/jline/package-info.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/package-info.java
@@ -19,4 +19,7 @@
*
* @author Eric Bottard
*/
-package org.springframework.shell.jline;
\ No newline at end of file
+@NullMarked
+package org.springframework.shell.jline;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/package-info.java
index 66ef91fc4..6567dedcb 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/package-info.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/package-info.java
@@ -19,4 +19,7 @@
*
* @author Eric Bottard
*/
-package org.springframework.shell;
\ No newline at end of file
+@NullMarked
+package org.springframework.shell;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundMessageProvider.java b/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundMessageProvider.java
index 66c9e1efa..87c22433d 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundMessageProvider.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundMessageProvider.java
@@ -19,6 +19,7 @@
import java.util.Map;
import java.util.function.Function;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.result.CommandNotFoundMessageProvider.ProviderContext;
@@ -26,11 +27,12 @@
* Provider for a message used within {@link CommandNotFoundResultHandler}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
@FunctionalInterface
public interface CommandNotFoundMessageProvider extends Function {
- static ProviderContext contextOf(Throwable error, List commands, Map registrations, String text) {
+ static ProviderContext contextOf(Throwable error, List commands, @Nullable Map registrations, @Nullable String text) {
return new ProviderContext() {
@Override
@@ -44,12 +46,12 @@ public List commands() {
}
@Override
- public Map registrations() {
+ public @Nullable Map registrations() {
return registrations;
}
@Override
- public String text() {
+ public @Nullable String text() {
return text;
}
};
@@ -79,14 +81,14 @@ interface ProviderContext {
*
* @return a command registrations
*/
- Map registrations();
+ @Nullable Map registrations();
/**
* Gets a raw input text.
*
* @return a raw input text
*/
- String text();
+ @Nullable String text();
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundResultHandler.java
index bdf6a0bd8..b00c844f8 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundResultHandler.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/CommandNotFoundResultHandler.java
@@ -19,6 +19,7 @@
* {@link CommandNotFoundMessageProvider} bean.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public final class CommandNotFoundResultHandler extends TerminalAwareResultHandler {
@@ -44,9 +45,8 @@ private static class DefaultProvider implements CommandNotFoundMessageProvider {
@Override
public String apply(ProviderContext context) {
- String message = new AttributedString(context.error().getMessage(),
+ return new AttributedString(context.error().getMessage(),
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi();
- return message;
}
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java
index e6c1c096e..1a1c24241 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/GenericResultHandlerService.java
@@ -15,43 +15,40 @@
*/
package org.springframework.shell.result;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentLinkedDeque;
-import java.util.concurrent.CopyOnWriteArraySet;
-
+import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.lang.Nullable;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.ResultHandlerService;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import static java.util.Objects.requireNonNull;
+
/**
* Base {@link ResultHandlerService} implementation suitable for use in most
* environments.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class GenericResultHandlerService implements ResultHandlerService {
private final ResultHandlers resultHandlers = new ResultHandlers();
@Override
- public void handle(Object source) {
+ public void handle(@Nullable Object source) {
handle(source, TypeDescriptor.forObject(source));
}
@Override
- public void handle(Object result, TypeDescriptor resultType) {
+ public void handle(@Nullable Object result, @Nullable TypeDescriptor resultType) {
if (result == null) {
return;
}
@@ -80,8 +77,8 @@ public void addResultHandler(ResultHandler> resultHandler) {
/**
* Add a plain result handler to this registry.
*
- * @param the type of result handler
- * @param resultType the class of a result type
+ * @param the type of result handler
+ * @param resultType the class of a result type
* @param resultHandler the result handler
*/
public void addResultHandler(Class resultType, ResultHandler super T> resultHandler) {
@@ -97,12 +94,11 @@ public void addResultHandler(GenericResultHandler handler) {
this.resultHandlers.add(handler);
}
- private GenericResultHandler getResultHandler(TypeDescriptor resultType) {
+ private @Nullable GenericResultHandler getResultHandler(@Nullable TypeDescriptor resultType) {
return this.resultHandlers.find(resultType);
}
- @Nullable
- private Object handleResultHandlerNotFound(
+ private @Nullable Object handleResultHandlerNotFound(
@Nullable Object source, @Nullable TypeDescriptor sourceType) {
if (source == null) {
return null;
@@ -113,8 +109,7 @@ private Object handleResultHandlerNotFound(
throw new ResultHandlerNotFoundException(sourceType);
}
- @Nullable
- private ResolvableType[] getRequiredTypeInfo(Class> handlerClass, Class> genericIfc) {
+ private ResolvableType @Nullable [] getRequiredTypeInfo(Class> handlerClass, Class> genericIfc) {
ResolvableType resolvableType = ResolvableType.forClass(handlerClass).as(genericIfc);
ResolvableType[] generics = resolvableType.getGenerics();
if (generics.length < 1) {
@@ -191,8 +186,7 @@ public void add(GenericResultHandler handler) {
Set> handlerTypes = handler.getHandlerTypes();
if (handlerTypes == null) {
this.globalHandlers.add(handler);
- }
- else {
+ } else {
for (Class> handlerType : handlerTypes) {
getMatchableConverters(handlerType).add(handler);
}
@@ -203,7 +197,11 @@ private ResultHandlersForType getMatchableConverters(Class> handlerType) {
return this.handlers.computeIfAbsent(handlerType, k -> new ResultHandlersForType());
}
- public GenericResultHandler find(TypeDescriptor resultType) {
+ public @Nullable GenericResultHandler find(@Nullable TypeDescriptor resultType) {
+ if (resultType == null) {
+ return null;
+ }
+
List> resultCandidates = getClassHierarchy(resultType.getType());
for (Class> resultCandidate : resultCandidates) {
GenericResultHandler handler = getRegisteredHandler(resultType, resultCandidate);
@@ -214,8 +212,7 @@ public GenericResultHandler find(TypeDescriptor resultType) {
return null;
}
- @Nullable
- private GenericResultHandler getRegisteredHandler(TypeDescriptor resultType, Class> handlerType) {
+ private @Nullable GenericResultHandler getRegisteredHandler(TypeDescriptor resultType, Class> handlerType) {
ResultHandlersForType resultHandlersForType = this.handlers.get(handlerType);
if (resultHandlersForType != null) {
GenericResultHandler handler = resultHandlersForType.getHandler(resultType);
@@ -261,14 +258,14 @@ private List> getClassHierarchy(Class> type) {
}
private void addInterfacesToClassHierarchy(Class> type, boolean asArray,
- List> hierarchy, Set> visited) {
+ List> hierarchy, Set> visited) {
for (Class> implementedInterface : type.getInterfaces()) {
addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
}
}
private void addToClassHierarchy(int index, Class> type, boolean asArray,
- List> hierarchy, Set> visited) {
+ List> hierarchy, Set> visited) {
if (asArray) {
type = Array.newInstance(type, 0).getClass();
}
@@ -278,7 +275,8 @@ private void addToClassHierarchy(int index, Class> type, boolean asArray,
}
}
- private static void invokeHandler(GenericResultHandler handler, Object result, TypeDescriptor resultType) {
- handler.handle(result, resultType);;
+ private static void invokeHandler(GenericResultHandler handler, Object result, @Nullable TypeDescriptor resultType) {
+ requireNonNull(resultType);
+ handler.handle(result, resultType);
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerNotFoundException.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerNotFoundException.java
index c4df57baf..070f9f3f5 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerNotFoundException.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerNotFoundException.java
@@ -15,13 +15,15 @@
*/
package org.springframework.shell.result;
+import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.lang.Nullable;
+/**
+ * @author Piotr Olaszewski
+ */
public class ResultHandlerNotFoundException extends ResultHandlingException {
- @Nullable
- private final TypeDescriptor resultType;
+ private final @Nullable TypeDescriptor resultType;
/**
* Create a new handling executor not found exception.
@@ -37,8 +39,7 @@ public ResultHandlerNotFoundException(@Nullable TypeDescriptor resultType) {
/**
* Return the source type that was requested to convert from.
*/
- @Nullable
- public TypeDescriptor getResultType() {
+ public @Nullable TypeDescriptor getResultType() {
return this.resultType;
}
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java
index daa2486ab..4a7037a41 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java
@@ -23,6 +23,7 @@
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.command.CommandCatalog;
@@ -40,6 +41,7 @@
*
* @author Eric Bottard
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class ThrowableResultHandler extends TerminalAwareResultHandler {
@@ -48,13 +50,13 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler interactiveRunner;
+ private final ObjectProvider interactiveRunner;
- private ShellContext shellContext;
+ private final ShellContext shellContext;
public ThrowableResultHandler(Terminal terminal, CommandCatalog commandCatalog, ShellContext shellContext,
ObjectProvider interactiveRunner) {
@@ -109,7 +111,7 @@ else if (result instanceof Error) {
/**
* Return the last error that was dealt with by this result handler.
*/
- public Throwable getLastError() {
+ public @Nullable Throwable getLastError() {
return lastError;
}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/result/package-info.java
index 414454cea..be8765e69 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/result/package-info.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/result/package-info.java
@@ -19,4 +19,7 @@
*
* @author Eric Bottard
*/
+@NullMarked
package org.springframework.shell.result;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/support/AbstractArgumentMethodArgumentResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/support/AbstractArgumentMethodArgumentResolver.java
index fa0d1c991..5ed2bbdda 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/support/AbstractArgumentMethodArgumentResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/support/AbstractArgumentMethodArgumentResolver.java
@@ -19,6 +19,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
@@ -27,7 +28,6 @@
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
-import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.ValueConstants;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
@@ -50,6 +50,7 @@
* value to the expected target method parameter type.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public abstract class AbstractArgumentMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -57,11 +58,10 @@ public abstract class AbstractArgumentMethodArgumentResolver implements HandlerM
private final ConversionService conversionService;
- @Nullable
- private final ConfigurableBeanFactory configurableBeanFactory;
- @Nullable
- private final BeanExpressionContext expressionContext;
+ private final @Nullable ConfigurableBeanFactory configurableBeanFactory;
+
+ private final @Nullable BeanExpressionContext expressionContext;
private final Map namedValueInfoCache = new ConcurrentHashMap<>(256);
@@ -87,7 +87,7 @@ protected AbstractArgumentMethodArgumentResolver(ConversionService conversionSer
}
@Override
- public Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
+ public @Nullable Object resolveArgument(MethodParameter parameter, Message> message) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
@@ -164,8 +164,7 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
* Resolve the given annotation-specified value,
* potentially containing placeholders and expressions.
*/
- @Nullable
- private Object resolveEmbeddedValuesAndExpressions(String value) {
+ private @Nullable Object resolveEmbeddedValuesAndExpressions(String value) {
if (this.configurableBeanFactory == null || this.expressionContext == null) {
return value;
}
@@ -186,8 +185,7 @@ private Object resolveEmbeddedValuesAndExpressions(String value) {
* @return the resolved argument. May be {@code null}
* @throws Exception in case of errors
*/
- @Nullable
- protected abstract Object resolveArgumentInternal(MethodParameter parameter, Message> message, List names)
+ protected abstract @Nullable Object resolveArgumentInternal(MethodParameter parameter, Message> message, List names)
throws Exception;
/**
@@ -206,8 +204,7 @@ protected abstract Object resolveArgumentInternal(MethodParameter parameter, Mes
* Specifically for booleans method parameters, use {@link Boolean#FALSE}.
* Also raise an ISE for primitive types.
*/
- @Nullable
- private Object handleNullValue(List name, @Nullable Object value, Class> paramType) {
+ private @Nullable Object handleNullValue(List name, @Nullable Object value, Class> paramType) {
if (value == null) {
if (Boolean.TYPE.equals(paramType)) {
return Boolean.FALSE;
@@ -242,8 +239,7 @@ protected static class NamedValueInfo {
private final boolean required;
- @Nullable
- private final String defaultValue;
+ private final @Nullable String defaultValue;
protected NamedValueInfo(List names, boolean required, @Nullable String defaultValue) {
this.names = names;
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/support/package-info.java b/spring-shell-core/src/main/java/org/springframework/shell/support/package-info.java
new file mode 100644
index 000000000..9ccb67479
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/support/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.shell.support;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java b/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java
index bb113ec98..6abd0f5c6 100644
--- a/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java
+++ b/spring-shell-core/src/test/java/org/springframework/shell/ShellTests.java
@@ -20,6 +20,7 @@
import java.util.Map;
import java.util.stream.Stream;
+import org.jline.terminal.Terminal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -30,6 +31,7 @@
import org.springframework.shell.command.CommandCatalog;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.completion.RegistrationOptionsCompletionResolver;
+import org.springframework.shell.context.ShellContext;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
@@ -54,6 +56,12 @@ class ShellTests {
@Mock
CommandCatalog commandRegistry;
+ @Mock
+ Terminal terminal;
+
+ @Mock
+ ShellContext shellContext;
+
@InjectMocks
private Shell shell;
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionCustomConversionTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionCustomConversionTests.java
index 6b2d75eb9..afe8840e6 100644
--- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionCustomConversionTests.java
+++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionCustomConversionTests.java
@@ -15,10 +15,15 @@
*/
package org.springframework.shell.command;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import org.jline.terminal.impl.DumbTerminal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -27,6 +32,7 @@
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
import org.springframework.shell.command.CommandRegistration.OptionArity;
+import org.springframework.shell.context.DefaultShellContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,14 +42,17 @@ class CommandExecutionCustomConversionTests {
private CommandCatalog commandCatalog;
@BeforeEach
- void setupCommandExecutionTests() {
+ void setupCommandExecutionTests() throws IOException {
commandCatalog = CommandCatalog.of();
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new StringToMyPojo2Converter());
List resolvers = new ArrayList<>();
resolvers.add(new ArgumentHeaderMethodArgumentResolver(conversionService, null));
resolvers.add(new CommandContextMethodArgumentResolver());
- execution = CommandExecution.of(resolvers, null, null, null, conversionService, commandCatalog);
+ ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ DumbTerminal terminal = new DumbTerminal("terminal", "ansi", in, out, StandardCharsets.UTF_8);
+ execution = CommandExecution.of(resolvers, null, terminal, new DefaultShellContext(), conversionService, commandCatalog);
}
@Test
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java
index d35ae6521..2f4e73368 100644
--- a/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java
+++ b/spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java
@@ -15,9 +15,14 @@
*/
package org.springframework.shell.command;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
+import org.jline.terminal.impl.DumbTerminal;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -31,6 +36,7 @@
import org.springframework.shell.CommandNotCurrentlyAvailable;
import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException;
import org.springframework.shell.command.CommandRegistration.OptionArity;
+import org.springframework.shell.context.DefaultShellContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -41,13 +47,16 @@ class CommandExecutionTests extends AbstractCommandTests {
private CommandCatalog commandCatalog;
@BeforeEach
- void setupCommandExecutionTests() {
+ void setupCommandExecutionTests() throws IOException {
commandCatalog = CommandCatalog.of();
ConversionService conversionService = new DefaultConversionService();
List resolvers = new ArrayList<>();
resolvers.add(new ArgumentHeaderMethodArgumentResolver(conversionService, null));
resolvers.add(new CommandContextMethodArgumentResolver());
- execution = CommandExecution.of(resolvers, null, null, null, conversionService, commandCatalog);
+ ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ DumbTerminal terminal = new DumbTerminal("terminal", "ansi", in, out, StandardCharsets.UTF_8);
+ execution = CommandExecution.of(resolvers, null, terminal, new DefaultShellContext(), conversionService, commandCatalog);
}
@Test
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ConfirmationInput.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ConfirmationInput.java
index 25a69dd90..81fd6402d 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ConfirmationInput.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ConfirmationInput.java
@@ -24,6 +24,7 @@
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,27 +38,28 @@
* Component for a confirmation question.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class ConfirmationInput extends AbstractTextComponent {
private final static Logger log = LoggerFactory.getLogger(ConfirmationInput.class);
private final boolean defaultValue;
- private ConfirmationInputContext currentContext;
+ private @Nullable ConfirmationInputContext currentContext;
public ConfirmationInput(Terminal terminal) {
this(terminal, null);
}
- public ConfirmationInput(Terminal terminal, String name) {
+ public ConfirmationInput(Terminal terminal, @Nullable String name) {
this(terminal, name, true, null);
}
- public ConfirmationInput(Terminal terminal, String name, boolean defaultValue) {
+ public ConfirmationInput(Terminal terminal, @Nullable String name, boolean defaultValue) {
this(terminal, name, defaultValue, null);
}
- public ConfirmationInput(Terminal terminal, String name, boolean defaultValue,
- Function> renderer) {
+ public ConfirmationInput(Terminal terminal, @Nullable String name, boolean defaultValue,
+ @Nullable Function> renderer) {
super(terminal, name, null);
setRenderer(renderer != null ? renderer : new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/confirmation-input-default.stg");
@@ -65,7 +67,7 @@ public ConfirmationInput(Terminal terminal, String name, boolean defaultValue,
}
@Override
- public ConfirmationInputContext getThisContext(ComponentContext> context) {
+ public ConfirmationInputContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
@@ -122,7 +124,7 @@ else if (context.getDefaultValue() != null) {
return false;
}
- private Boolean parseBoolean(String input) {
+ private @Nullable Boolean parseBoolean(@Nullable String input) {
if (!StringUtils.hasText(input)) {
return null;
}
@@ -141,7 +143,7 @@ private Boolean parseBoolean(String input) {
}
}
- private void checkInput(String input, ConfirmationInputContext context) {
+ private void checkInput(@Nullable String input, ConfirmationInputContext context) {
if (!StringUtils.hasText(input)) {
context.setMessage(null);
return;
@@ -163,7 +165,7 @@ public interface ConfirmationInputContext extends TextComponentContext
implements ConfirmationInputContext {
- private Boolean defaultValue;
+ private @Nullable Boolean defaultValue;
- public DefaultConfirmationInputContext(Boolean defaultValue) {
+ public DefaultConfirmationInputContext(@Nullable Boolean defaultValue) {
this.defaultValue = defaultValue;
}
@Override
- public Boolean getDefaultValue() {
+ public @Nullable Boolean getDefaultValue() {
return defaultValue;
}
@@ -212,7 +214,7 @@ public void setDefaultValue(Boolean defaultValue) {
@Override
public Map toTemplateModel() {
- Map attributes = super.toTemplateModel();
+ Map attributes = super.toTemplateModel();
attributes.put("defaultValue", getDefaultValue() != null ? getDefaultValue() : null);
Map model = new HashMap<>();
model.put("model", attributes);
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/MultiItemSelector.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/MultiItemSelector.java
index 4a50ffa86..8dd239d97 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/MultiItemSelector.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/MultiItemSelector.java
@@ -26,6 +26,7 @@
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.tui.component.context.ComponentContext;
import org.springframework.shell.tui.component.support.AbstractSelectorComponent;
import org.springframework.shell.tui.component.support.Enableable;
@@ -34,30 +35,34 @@
import org.springframework.shell.tui.component.support.Nameable;
import org.springframework.shell.tui.component.support.Selectable;
import org.springframework.shell.tui.component.MultiItemSelector.MultiItemSelectorContext;
+import org.springframework.util.StringUtils;
/**
* Component able to pick multiple items.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class MultiItemSelector>
extends AbstractSelectorComponent, I> {
- private MultiItemSelectorContext currentContext;
+ private @Nullable MultiItemSelectorContext currentContext;
- public MultiItemSelector(Terminal terminal, List items, String name, Comparator comparator) {
+ public MultiItemSelector(Terminal terminal, List items, @Nullable String name, @Nullable Comparator comparator) {
super(terminal, name, items, false, comparator);
setRenderer(new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/multi-item-selector-default.stg");
}
@Override
- public MultiItemSelectorContext getThisContext(ComponentContext> context) {
+ public MultiItemSelectorContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
currentContext = MultiItemSelectorContext.empty(getItemMapper());
- currentContext.setName(name);
+ if (StringUtils.hasText(name)) {
+ currentContext.setName(name);
+ }
currentContext.setTerminalWidth(getTerminal().getWidth());
if (currentContext.getItems() == null) {
currentContext.setItems(getItems());
@@ -144,7 +149,8 @@ public Map toTemplateModel() {
Map map = new HashMap<>();
map.put("name", is.getName());
map.put("selected", is.isSelected());
- map.put("onrow", getCursorRow().intValue() == is.getIndex());
+ Integer cursorRow = getCursorRow();
+ map.put("onrow", cursorRow != null && cursorRow == is.getIndex());
map.put("enabled", is.isEnabled());
return map;
})
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathInput.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathInput.java
index cb6a437b9..b45397783 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathInput.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathInput.java
@@ -27,6 +27,7 @@
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,35 +35,36 @@
import org.springframework.shell.tui.component.context.ComponentContext;
import org.springframework.shell.tui.component.support.AbstractTextComponent;
import org.springframework.shell.tui.component.support.AbstractTextComponent.TextComponentContext.MessageLevel;
-import org.springframework.util.StringUtils;;
+import org.springframework.util.StringUtils;
/**
* Component for a simple path input.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class PathInput extends AbstractTextComponent {
private final static Logger log = LoggerFactory.getLogger(PathInput.class);
- private PathInputContext currentContext;
+ private @Nullable PathInputContext currentContext;
private Function pathProvider = (path) -> Paths.get(path);
public PathInput(Terminal terminal) {
this(terminal, null);
}
- public PathInput(Terminal terminal, String name) {
+ public PathInput(Terminal terminal, @Nullable String name) {
this(terminal, name, null);
}
- public PathInput(Terminal terminal, String name, Function> renderer) {
+ public PathInput(Terminal terminal, @Nullable String name, @Nullable Function> renderer) {
super(terminal, name, null);
setRenderer(renderer != null ? renderer : new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/path-input-default.stg");
}
@Override
- public PathInputContext getThisContext(ComponentContext> context) {
+ public PathInputContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
@@ -135,7 +137,7 @@ protected Path resolvePath(String path) {
return this.pathProvider.apply(path);
}
- private void checkPath(String path, PathInputContext context) {
+ private void checkPath(@Nullable String path, PathInputContext context) {
if (!StringUtils.hasText(path)) {
context.setMessage(null);
return;
@@ -167,7 +169,7 @@ private static class DefaultPathInputContext extends BaseTextComponentContext toTemplateModel() {
- Map attributes = super.toTemplateModel();
+ Map attributes = super.toTemplateModel();
Map model = new HashMap<>();
model.put("model", attributes);
return model;
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathSearch.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathSearch.java
index 0a9069583..c53552eaf 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathSearch.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/PathSearch.java
@@ -44,6 +44,7 @@
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.jline.utils.InfoCmp.Capability;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,13 +74,14 @@
* sources.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class PathSearch extends AbstractTextComponent {
private final static Logger log = LoggerFactory.getLogger(PathSearch.class);
private final static String DEFAULT_TEMPLATE_LOCATION = "classpath:org/springframework/shell/component/path-search-default.stg";
private final PathSearchConfig config;
- private PathSearchContext currentContext;
+ private @Nullable PathSearchContext currentContext;
private Function pathProvider = (path) -> Paths.get(path);
private final SelectorList selectorList;
@@ -87,16 +89,16 @@ public PathSearch(Terminal terminal) {
this(terminal, null);
}
- public PathSearch(Terminal terminal, String name) {
+ public PathSearch(Terminal terminal, @Nullable String name) {
this(terminal, name, null);
}
- public PathSearch(Terminal terminal, String name, PathSearchConfig config) {
+ public PathSearch(Terminal terminal, @Nullable String name, @Nullable PathSearchConfig config) {
this(terminal, name, config, null);
}
- public PathSearch(Terminal terminal, String name, PathSearchConfig config,
- Function> renderer) {
+ public PathSearch(Terminal terminal, @Nullable String name, @Nullable PathSearchConfig config,
+ @Nullable Function> renderer) {
super(terminal, name, null);
setRenderer(renderer != null ? renderer : new DefaultRenderer());
setTemplateLocation(DEFAULT_TEMPLATE_LOCATION);
@@ -114,7 +116,7 @@ protected void bindKeyMap(KeyMap keyMap) {
}
@Override
- public PathSearchContext getThisContext(ComponentContext> context) {
+ public PathSearchContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
@@ -199,7 +201,7 @@ protected Path resolvePath(String path) {
return this.pathProvider.apply(path);
}
- private void inputUpdated(PathSearchContext context, String input) {
+ private void inputUpdated(PathSearchContext context, @Nullable String input) {
context.setMessage("Type ' ' to search", MessageLevel.INFO);
updateSelectorList(input, context);
selectorListUpdated(context);
@@ -214,7 +216,7 @@ private void selectorListUpdated(PathSearchContext context) {
context.setPathViewItems(pathViews);
}
- private void updateSelectorList(String path, PathSearchContext context) {
+ private void updateSelectorList(@Nullable String path, PathSearchContext context) {
if (path == null) {
// when user removes all input
this.selectorList.reset(Collections.emptyList());
@@ -365,7 +367,7 @@ public interface PathSearchContext extends TextComponentContext getPathViewItems();
+ @Nullable List getPathViewItems();
/**
* Sets a path view items.
@@ -501,11 +503,11 @@ else if (position == text.length() - 1) {
private static class DefaultPathSearchContext extends BaseTextComponentContext
implements PathSearchContext {
- private List pathViewItems;
- private PathSearchConfig pathSearchConfig;
+ private @Nullable List pathViewItems;
+ private PathSearchConfig pathSearchConfig = new PathSearchConfig();
@Override
- public List getPathViewItems() {
+ public @Nullable List getPathViewItems() {
return this.pathViewItems;
}
@@ -526,7 +528,7 @@ public void setPathSearchConfig(PathSearchConfig config) {
@Override
public Map toTemplateModel() {
- Map attributes = super.toTemplateModel();
+ Map attributes = super.toTemplateModel();
attributes.put("pathViewItems", getPathViewItems());
Map model = new HashMap<>();
model.put("model", attributes);
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/SingleItemSelector.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/SingleItemSelector.java
index 933813e5e..16b67a6e7 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/SingleItemSelector.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/SingleItemSelector.java
@@ -26,6 +26,7 @@
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
+import org.jspecify.annotations.Nullable;
import org.springframework.shell.tui.component.context.ComponentContext;
import org.springframework.shell.tui.component.support.AbstractSelectorComponent;
import org.springframework.shell.tui.component.support.Enableable;
@@ -34,30 +35,34 @@
import org.springframework.shell.tui.component.support.Nameable;
import org.springframework.shell.tui.component.support.Selectable;
import org.springframework.shell.tui.component.SingleItemSelector.SingleItemSelectorContext;
+import org.springframework.util.StringUtils;
/**
* Component able to pick single item.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class SingleItemSelector>
extends AbstractSelectorComponent, I> {
- private SingleItemSelectorContext currentContext;
+ private @Nullable SingleItemSelectorContext currentContext;
- public SingleItemSelector(Terminal terminal, List items, String name, Comparator comparator) {
+ public SingleItemSelector(Terminal terminal, List items, @Nullable String name, @Nullable Comparator comparator) {
super(terminal, name, items, true, comparator);
setRenderer(new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/single-item-selector-default.stg");
}
@Override
- public SingleItemSelectorContext getThisContext(ComponentContext> context) {
+ public SingleItemSelectorContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
currentContext = SingleItemSelectorContext.empty(getItemMapper());
- currentContext.setName(name);
+ if (StringUtils.hasText(name)) {
+ currentContext.setName(name);
+ }
currentContext.setTerminalWidth(getTerminal().getWidth());
if (currentContext.getItems() == null) {
currentContext.setItems(getItems());
@@ -154,7 +159,8 @@ public Map toTemplateModel() {
.map(is -> {
Map map = new HashMap<>();
map.put("name", is.getName());
- map.put("selected", getCursorRow().intValue() == is.getIndex());
+ Integer cursorRow = getCursorRow();
+ map.put("selected", cursorRow != null && cursorRow == is.getIndex());
return map;
})
.collect(Collectors.toList());
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/StringInput.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/StringInput.java
index 1f31f09fa..2eab8e034 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/StringInput.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/StringInput.java
@@ -24,6 +24,7 @@
import org.jline.keymap.KeyMap;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,24 +37,25 @@
* Component for a simple string input.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class StringInput extends AbstractTextComponent {
private final static Logger log = LoggerFactory.getLogger(StringInput.class);
- private final String defaultValue;
- private StringInputContext currentContext;
- private Character maskCharacter;
+ private final @Nullable String defaultValue;
+ private @Nullable StringInputContext currentContext;
+ private @Nullable Character maskCharacter;
public StringInput(Terminal terminal) {
this(terminal, null, null, null);
}
- public StringInput(Terminal terminal, String name, String defaultValue) {
+ public StringInput(Terminal terminal, @Nullable String name, @Nullable String defaultValue) {
this(terminal, name, defaultValue, null);
}
- public StringInput(Terminal terminal, String name, String defaultValue,
- Function> renderer) {
+ public StringInput(Terminal terminal, @Nullable String name, @Nullable String defaultValue,
+ @Nullable Function> renderer) {
super(terminal, name, null);
setRenderer(renderer != null ? renderer : new DefaultRenderer());
setTemplateLocation("classpath:org/springframework/shell/component/string-input-default.stg");
@@ -65,12 +67,12 @@ public StringInput(Terminal terminal, String name, String defaultValue,
*
* @param maskCharacter a mask character
*/
- public void setMaskCharacter(Character maskCharacter) {
+ public void setMaskCharacter(@Nullable Character maskCharacter) {
this.maskCharacter = maskCharacter;
}
@Override
- public StringInputContext getThisContext(ComponentContext> context) {
+ public StringInputContext getThisContext(@Nullable ComponentContext> context) {
if (context != null && currentContext == context) {
return currentContext;
}
@@ -110,7 +112,9 @@ protected boolean read(BindingReader bindingReader, KeyMap keyMap, Strin
if (StringUtils.hasLength(input)) {
input = input.length() > 1 ? input.substring(0, input.length() - 1) : null;
}
- context.setInput(input);
+ if (input != null) {
+ context.setInput(input);
+ }
break;
case OPERATION_EXIT:
if (StringUtils.hasText(context.getInput())) {
@@ -133,14 +137,14 @@ public interface StringInputContext extends TextComponentContext
implements StringInputContext {
- private String defaultValue;
- private Character maskCharacter;
+ private @Nullable String defaultValue;
+ private @Nullable Character maskCharacter;
- public DefaultStringInputContext(String defaultValue, Character maskCharacter) {
+ public DefaultStringInputContext(@Nullable String defaultValue, @Nullable Character maskCharacter) {
this.defaultValue = defaultValue;
this.maskCharacter = maskCharacter;
}
@Override
- public String getDefaultValue() {
+ public @Nullable String getDefaultValue() {
return defaultValue;
}
@Override
- public void setDefaultValue(String defaultValue) {
+ public void setDefaultValue(@Nullable String defaultValue) {
this.defaultValue = defaultValue;
}
@@ -223,12 +227,12 @@ public void setMaskCharacter(Character maskCharacter) {
}
@Override
- public String getMaskedInput() {
+ public @Nullable String getMaskedInput() {
return maybeMask(getInput());
}
@Override
- public String getMaskedResultValue() {
+ public @Nullable String getMaskedResultValue() {
return maybeMask(getResultValue());
}
@@ -238,13 +242,13 @@ public boolean hasMaskCharacter() {
}
@Override
- public Character getMaskCharacter() {
+ public @Nullable Character getMaskCharacter() {
return maskCharacter;
}
@Override
- public Map toTemplateModel() {
- Map attributes = super.toTemplateModel();
+ public Map toTemplateModel() {
+ Map attributes = super.toTemplateModel();
attributes.put("defaultValue", getDefaultValue() != null ? getDefaultValue() : null);
attributes.put("maskedInput", getMaskedInput());
attributes.put("maskedResultValue", getMaskedResultValue());
@@ -255,7 +259,7 @@ public Map toTemplateModel() {
return model;
}
- private String maybeMask(String str) {
+ private @Nullable String maybeMask(@Nullable String str) {
if (StringUtils.hasLength(str) && maskCharacter != null) {
return new String(new char[str.length()]).replace('\0', maskCharacter);
}
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ViewComponentExecutor.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ViewComponentExecutor.java
index 2d75477fd..2f131f4be 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ViewComponentExecutor.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/ViewComponentExecutor.java
@@ -18,6 +18,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,12 +30,13 @@
* component in a thread so that it doesn't need to block from a command.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
public class ViewComponentExecutor implements AutoCloseable {
private final Logger log = LoggerFactory.getLogger(ViewComponentExecutor.class);
private final SimpleAsyncTaskExecutor executor;
- private Future> future;
+ private @Nullable Future> future;
public ViewComponentExecutor() {
this.executor = new SimpleAsyncTaskExecutor();
diff --git a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/context/BaseComponentContext.java b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/context/BaseComponentContext.java
index f5d02716b..2c3427e6b 100644
--- a/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/context/BaseComponentContext.java
+++ b/spring-shell-tui/src/main/java/org/springframework/shell/tui/component/context/BaseComponentContext.java
@@ -15,6 +15,8 @@
*/
package org.springframework.shell.tui.component.context;
+import org.jspecify.annotations.Nullable;
+
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -25,12 +27,13 @@
* Base implementation of a {@link ComponentContext}.
*
* @author Janne Valkealahti
+ * @author Piotr Olaszewski
*/
@SuppressWarnings("unchecked")
public class BaseComponentContext> extends LinkedHashMap