Skip to content

Commit 4bd956c

Browse files
committed
Add JSpecify to spring-shell-standard
1 parent 8eba1bc commit 4bd956c

File tree

19 files changed

+168
-107
lines changed

19 files changed

+168
-107
lines changed

.github/workflows/ci-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ jobs:
2020
cache: 'maven'
2121

2222
- name: Build with Maven
23-
run: ./mvnw --no-transfer-progress --batch-mode --update-snapshots verify
23+
run: ./mvnw --no-transfer-progress --batch-mode --update-snapshots verify -Pnullaway
2424

spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardCommandsAutoConfiguration.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@
4141
import org.springframework.shell.standard.commands.Stacktrace;
4242
import org.springframework.shell.standard.commands.Version;
4343
import org.springframework.shell.tui.style.TemplateExecutor;
44+
import org.springframework.util.Assert;
4445

4546
/**
4647
* Creates beans for standard commands.
4748
*
4849
* @author Eric Bottard
4950
* @author Mahmoud Ben Hassine
51+
* @author Piotr Olaszewski
5052
*/
5153
@AutoConfiguration
5254
@ConditionalOnClass({ Help.Command.class })
@@ -57,7 +59,9 @@ public class StandardCommandsAutoConfiguration {
5759
@ConditionalOnMissingBean(Help.Command.class)
5860
@ConditionalOnProperty(prefix = "spring.shell.command.help", value = "enabled", havingValue = "true", matchIfMissing = true)
5961
public Help help(SpringShellProperties properties, ObjectProvider<TemplateExecutor> templateExecutor) {
60-
Help help = new Help(templateExecutor.getIfAvailable());
62+
TemplateExecutor executor = templateExecutor.getIfAvailable();
63+
Assert.notNull(executor, "'executor' must not be null");
64+
Help help = new Help(executor);
6165
if (properties.getCommand().getHelp().getGroupingMode() == GroupingMode.FLAT) {
6266
help.setShowGroups(false);
6367
}
@@ -105,15 +109,19 @@ public History historyCommand(org.jline.reader.History jLineHistory) {
105109
@ConditionalOnMissingBean(Completion.Command.class)
106110
@Conditional(OnCompletionCommandCondition.class)
107111
public Completion completion(SpringShellProperties properties) {
108-
return new Completion(properties.getCommand().getCompletion().getRootCommand());
112+
String rootCommand = properties.getCommand().getCompletion().getRootCommand();
113+
Assert.hasText(rootCommand, "'rootCommand' must be specified");
114+
return new Completion(rootCommand);
109115
}
110116

111117
@Bean
112118
@ConditionalOnMissingBean(Version.Command.class)
113119
@ConditionalOnProperty(prefix = "spring.shell.command.version", value = "enabled", havingValue = "true", matchIfMissing = true)
114120
public Version version(SpringShellProperties properties, ObjectProvider<BuildProperties> buildProperties,
115121
ObjectProvider<GitProperties> gitProperties, ObjectProvider<TemplateExecutor> templateExecutor) {
116-
Version version = new Version(templateExecutor.getIfAvailable());
122+
TemplateExecutor executor = templateExecutor.getIfAvailable();
123+
Assert.notNull(executor, "'executor' must not be null");
124+
Version version = new Version(executor);
117125
VersionCommand versionProperties = properties.getCommand().getVersion();
118126
version.setTemplate(versionProperties.getTemplate());
119127
return version;

spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/StandardCommandsAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
class StandardCommandsAutoConfigurationTests {
3232

3333
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
34-
.withConfiguration(AutoConfigurations.of(StandardCommandsAutoConfiguration.class));
34+
.withConfiguration(AutoConfigurations.of(StandardCommandsAutoConfiguration.class, ThemingAutoConfiguration.class));
3535

3636
@Test
3737
void testCompletionCommand() {

spring-shell-core/src/main/java/org/springframework/shell/command/CommandRegistration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ public interface Builder {
707707
* @param availability the availability
708708
* @return builder for chaining
709709
*/
710-
Builder availability(Supplier<Availability> availability);
710+
Builder availability(@Nullable Supplier<Availability> availability);
711711

712712
/**
713713
* Define a group for a command.
@@ -1389,7 +1389,7 @@ public Builder hidden(boolean hidden) {
13891389
}
13901390

13911391
@Override
1392-
public Builder availability(Supplier<Availability> availability) {
1392+
public Builder availability(@Nullable Supplier<Availability> availability) {
13931393
this.availability = availability;
13941394
return this;
13951395
}

spring-shell-standard/src/main/java/org/springframework/shell/standard/AbstractShellComponent.java

Lines changed: 64 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -37,81 +37,91 @@
3737
* Base class helping to build shell components.
3838
*
3939
* @author Janne Valkealahti
40+
* @author Piotr Olaszewski
4041
*/
4142
public abstract class AbstractShellComponent implements ApplicationContextAware, InitializingBean, ResourceLoaderAware {
4243

43-
private ApplicationContext applicationContext;
44+
@SuppressWarnings("NullAway.Init")
45+
private ApplicationContext applicationContext;
4446

45-
private ResourceLoader resourceLoader;
47+
@SuppressWarnings("NullAway.Init")
48+
private ResourceLoader resourceLoader;
4649

47-
private ObjectProvider<Shell> shellProvider;
50+
@SuppressWarnings("NullAway.Init")
51+
private ObjectProvider<Shell> shellProvider;
4852

49-
private ObjectProvider<Terminal> terminalProvider;
53+
@SuppressWarnings("NullAway.Init")
54+
private ObjectProvider<Terminal> terminalProvider;
5055

51-
private ObjectProvider<CommandCatalog> commandCatalogProvider;
56+
@SuppressWarnings("NullAway.Init")
57+
private ObjectProvider<CommandCatalog> commandCatalogProvider;
5258

53-
private ObjectProvider<CompletionResolver> completionResolverProvider;
59+
@SuppressWarnings("NullAway.Init")
60+
private ObjectProvider<CompletionResolver> completionResolverProvider;
5461

55-
private ObjectProvider<TemplateExecutor> templateExecutorProvider;
62+
@SuppressWarnings("NullAway.Init")
63+
private ObjectProvider<TemplateExecutor> templateExecutorProvider;
5664

57-
private ObjectProvider<ThemeResolver> themeResolverProvider;
65+
@SuppressWarnings("NullAway.Init")
66+
private ObjectProvider<ThemeResolver> themeResolverProvider;
5867

59-
private ObjectProvider<ViewComponentBuilder> viewComponentBuilderProvider;
68+
@SuppressWarnings("NullAway.Init")
69+
private ObjectProvider<ViewComponentBuilder> viewComponentBuilderProvider;
6070

61-
@Override
62-
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
63-
this.applicationContext = applicationContext;
64-
}
71+
@Override
72+
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
73+
this.applicationContext = applicationContext;
74+
}
6575

66-
@Override
67-
public void setResourceLoader(ResourceLoader resourceLoader) {
68-
this.resourceLoader = resourceLoader;
69-
}
76+
@Override
77+
public void setResourceLoader(ResourceLoader resourceLoader) {
78+
this.resourceLoader = resourceLoader;
79+
}
7080

71-
@Override
72-
public void afterPropertiesSet() throws Exception {
73-
shellProvider = applicationContext.getBeanProvider(Shell.class);
74-
terminalProvider = applicationContext.getBeanProvider(Terminal.class);
75-
commandCatalogProvider = applicationContext.getBeanProvider(CommandCatalog.class);
76-
completionResolverProvider = applicationContext.getBeanProvider(CompletionResolver.class);
77-
templateExecutorProvider = applicationContext.getBeanProvider(TemplateExecutor.class);
78-
themeResolverProvider = applicationContext.getBeanProvider(ThemeResolver.class);
79-
viewComponentBuilderProvider = applicationContext.getBeanProvider(ViewComponentBuilder.class);
80-
}
81+
@Override
82+
public void afterPropertiesSet() throws Exception {
83+
shellProvider = applicationContext.getBeanProvider(Shell.class);
84+
terminalProvider = applicationContext.getBeanProvider(Terminal.class);
85+
commandCatalogProvider = applicationContext.getBeanProvider(CommandCatalog.class);
86+
completionResolverProvider = applicationContext.getBeanProvider(CompletionResolver.class);
87+
templateExecutorProvider = applicationContext.getBeanProvider(TemplateExecutor.class);
88+
themeResolverProvider = applicationContext.getBeanProvider(ThemeResolver.class);
89+
viewComponentBuilderProvider = applicationContext.getBeanProvider(ViewComponentBuilder.class);
90+
}
8191

82-
protected ApplicationContext getApplicationContext() {
83-
return applicationContext;
84-
}
92+
protected ApplicationContext getApplicationContext() {
93+
return applicationContext;
94+
}
8595

86-
protected ResourceLoader getResourceLoader() {
87-
return resourceLoader;
88-
}
96+
protected ResourceLoader getResourceLoader() {
97+
return resourceLoader;
98+
}
8999

90-
protected Shell getShell() {
91-
return shellProvider.getObject();
92-
}
100+
protected Shell getShell() {
101+
return shellProvider.getObject();
102+
}
93103

94-
protected Terminal getTerminal() {
95-
return terminalProvider.getObject();
96-
}
104+
protected Terminal getTerminal() {
105+
return terminalProvider.getObject();
106+
}
97107

98-
protected CommandCatalog getCommandCatalog() {
99-
return commandCatalogProvider.getObject();
100-
}
108+
protected CommandCatalog getCommandCatalog() {
109+
return commandCatalogProvider.getObject();
110+
}
101111

102-
protected Stream<CompletionResolver> getCompletionResolver() {
103-
return completionResolverProvider.orderedStream();
104-
}
112+
protected Stream<CompletionResolver> getCompletionResolver() {
113+
return completionResolverProvider.orderedStream();
114+
}
105115

106-
protected TemplateExecutor getTemplateExecutor() {
107-
return templateExecutorProvider.getObject();
108-
}
116+
protected TemplateExecutor getTemplateExecutor() {
117+
return templateExecutorProvider.getObject();
118+
}
109119

110-
protected ThemeResolver getThemeResolver() {
111-
return themeResolverProvider.getObject();
112-
}
120+
protected ThemeResolver getThemeResolver() {
121+
return themeResolverProvider.getObject();
122+
}
113123

114-
protected ViewComponentBuilder getViewComponentBuilder() {
115-
return viewComponentBuilderProvider.getObject();
116-
}
124+
protected ViewComponentBuilder getViewComponentBuilder() {
125+
return viewComponentBuilderProvider.getObject();
126+
}
117127
}

spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.shell.CompletionContext;
2929
import org.springframework.shell.CompletionProposal;
30+
import org.springframework.util.StringUtils;
3031

3132
import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
3233

@@ -36,15 +37,25 @@
3637
*
3738
* @author Eric Bottard
3839
* @author Janne Valkealahti
40+
* @author Piotr Olaszewski
3941
*/
4042
public class FileValueProvider implements ValueProvider {
4143

4244
@Override
4345
public List<CompletionProposal> complete(CompletionContext completionContext) {
4446
String input = completionContext.currentWordUpToCursor();
45-
int lastSlash = input.lastIndexOf(File.separatorChar);
46-
Path dir = lastSlash > -1 ? Paths.get(input.substring(0, lastSlash + 1)) : Paths.get("");
47-
String prefix = input.substring(lastSlash + 1);
47+
48+
Path dir = Paths.get("");
49+
String prefix;
50+
if (StringUtils.hasText(input)) {
51+
int lastSlash = input.lastIndexOf(File.separatorChar);
52+
if (lastSlash > -1) {
53+
dir = Paths.get(input.substring(0, lastSlash + 1));
54+
}
55+
prefix = input.substring(lastSlash + 1);
56+
} else {
57+
prefix = "";
58+
}
4859

4960
try {
5061
return Files

spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardMethodTargetRegistrar.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.stream.Collectors;
2929

3030
import org.jline.terminal.Terminal;
31+
import org.jspecify.annotations.Nullable;
3132
import org.slf4j.Logger;
3233
import org.slf4j.LoggerFactory;
3334

@@ -64,6 +65,7 @@
6465
* @author Florent Biville
6566
* @author Camilo Gonzalez
6667
* @author Janne Valkealahti
68+
* @author Piotr Olaszewski
6769
*/
6870
public class StandardMethodTargetRegistrar implements MethodTargetRegistrar {
6971

@@ -237,7 +239,7 @@ else if (ClassUtils.isAssignable(Boolean.class, parameterType)) {
237239
*/
238240
private String getOrInferGroup(Method method) {
239241
ShellMethod methodAnn = AnnotationUtils.getAnnotation(method, ShellMethod.class);
240-
if (!methodAnn.group().equals(ShellMethod.INHERITED)) {
242+
if (methodAnn != null && !methodAnn.group().equals(ShellMethod.INHERITED)) {
241243
return methodAnn.group();
242244
}
243245
Class<?> clazz = method.getDeclaringClass();
@@ -267,7 +269,7 @@ private String getOrInferGroup(Method method) {
267269
* selected</li>
268270
* </ol>
269271
*/
270-
private Supplier<Availability> findAvailabilityIndicator(String[] commandKeys, Object bean, Method method) {
272+
private @Nullable Supplier<Availability> findAvailabilityIndicator(String[] commandKeys, Object bean, Method method) {
271273
ShellMethodAvailability explicit = method.getAnnotation(ShellMethodAvailability.class);
272274
final Method indicator;
273275
if (explicit != null) {

spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardResourcesRuntimeHints.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,20 @@
1515
*/
1616
package org.springframework.shell.standard;
1717

18+
import org.jspecify.annotations.Nullable;
1819
import org.springframework.aot.hint.RuntimeHints;
1920
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2021

2122
/**
2223
* {@link RuntimeHintsRegistrar} for Shell Standard resources.
2324
*
2425
* @author Janne Valkealahti
26+
* @author Piotr Olaszewski
2527
*/
2628
class StandardResourcesRuntimeHints implements RuntimeHintsRegistrar {
2729

2830
@Override
29-
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
31+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
3032
hints.resources().registerPattern("org/springframework/shell/component/*.stg");
3133
}
3234
}

spring-shell-standard/src/main/java/org/springframework/shell/standard/commands/CommandAvailabilityInfoModel.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@
1515
*/
1616
package org.springframework.shell.standard.commands;
1717

18+
import org.jspecify.annotations.Nullable;
19+
1820
/**
1921
* Model encapsulating info about {@code command availability}.
2022
*
2123
* @author Janne Valkealahti
24+
* @author Piotr Olaszewski
2225
*/
2326
class CommandAvailabilityInfoModel {
2427

2528
private boolean available;
26-
private String reason;
29+
private @Nullable String reason;
2730

28-
CommandAvailabilityInfoModel(boolean available, String reason) {
31+
CommandAvailabilityInfoModel(boolean available, @Nullable String reason) {
2932
this.available = available;
3033
this.reason = reason;
3134
}
@@ -37,15 +40,15 @@ class CommandAvailabilityInfoModel {
3740
* @param reason the reason
3841
* @return a command parameter availability model
3942
*/
40-
static CommandAvailabilityInfoModel of(boolean available, String reason) {
43+
static CommandAvailabilityInfoModel of(boolean available, @Nullable String reason) {
4144
return new CommandAvailabilityInfoModel(available, reason);
4245
}
4346

4447
public boolean getAvailable() {
4548
return available;
4649
}
4750

48-
public String getReason() {
51+
public @Nullable String getReason() {
4952
return reason;
5053
}
5154
}

0 commit comments

Comments
 (0)