diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/AbstractShell.java b/spring-shell2-core/src/main/java/org/springframework/shell2/AbstractShell.java
deleted file mode 100644
index becf0e11..00000000
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/AbstractShell.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright 2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import javax.annotation.PostConstruct;
-import javax.validation.ConstraintViolation;
-import javax.validation.Validation;
-import javax.validation.executable.ExecutableValidator;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.MethodParameter;
-import org.springframework.util.ReflectionUtils;
-
-/**
- * Base class implementing a shell loop.
- *
- *
Given some textual input, locate the {@link MethodTarget} to invoke and {@link ResultHandler#handleResult(Object) handle}
- * the result.
- *
- * Also provides hooks for code completion
- *
- * @author Eric Bottard
- */
-public abstract class AbstractShell implements Shell {
-
- @Autowired
- @Qualifier("main")
- ResultHandler resultHandler;
-
- @Autowired
- protected ApplicationContext applicationContext;
-
- protected Map methodTargets = new HashMap<>();
-
- @Autowired
- protected List parameterResolvers = new ArrayList<>();
-
- /**
- * Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
- */
- protected static final Object UNRESOLVED = new Object();
-
- @Override
- public Map listCommands() {
- return methodTargets;
- }
-
- @PostConstruct
- public void gatherMethodTargets() throws Exception {
- for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
- methodTargets.putAll(resolver.resolve());
- }
- }
-
- /**
- * The main program loop: acquire input, try to match it to a command and evaluate. Repeat until a
- * {@link ResultHandler} causes the process to exit.
- */
- public void run() throws IOException {
- while (true) {
- Input input = readInput();
- if (input.words().isEmpty()) {
- continue;
- }
-
-
- String line = input.rawText();
- List words = input.words();
-
- String command = findLongestCommand(line);
-
- if (command != null) {
- int wordsUsedForCommandKey = command.split(" ").length;
- MethodTarget methodTarget = methodTargets.get(command);
- List wordsForArgs = words.subList(wordsUsedForCommandKey, words.size());
- Method method = methodTarget.getMethod();
-
- Object result = null;
- try {
- Object[] args = resolveArgs(method, wordsForArgs);
- validateArgs(args, methodTarget);
- result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
- }
- catch (Exception e) {
- result = e;
- }
-
- resultHandler.handleResult(result);
-
- }
- else {
- System.out.println("No command found for " + words);
- }
- }
- }
-
- /**
- * Return text entered by user to invoke commands.
- */
- protected abstract Input readInput();
-
- /**
- * Gather completion proposals given some (incomplete) input the user has already typed in.
- * When and how this method is invoked is implementation specific and decided by subclasses.
- */
- public List complete(CompletionContext context) {
-
- String prefix = context.upToCursor();
-
- List candidates = new ArrayList<>();
- // Find the longest match for a command name with words in the buffer
- String best = findLongestCommand(prefix);
- if (best == null) { // no command found
- candidates.addAll(commandsStartingWith(prefix));
- return candidates;
- } // if we're here, we're either trying to complete args for command (will fall through)
- // or trying to complete command whose name starts with (which also happens to be a command)
- else if (prefix.equals(best)) {
- candidates.addAll(commandsStartingWith(best));
- } // valid command () followed by a suffix (but not necessarily [ args*])
- else if (!prefix.startsWith(best + " ")) {
- // must be an invalid command, can't do anything
- return candidates;
- }
-
- // Try to complete arguments
- MethodTarget methodTarget = methodTargets.get(best);
- Method method = methodTarget.getMethod();
- return Arrays.stream(method.getParameters())
- .map(Utils::createMethodParameter)
- .flatMap(mp -> findResolver(mp).complete(mp, context).stream())
- .collect(Collectors.toList());
- }
-
- private List commandsStartingWith(String prefix) {
- return methodTargets.entrySet().stream()
- .filter(e -> e.getKey().startsWith(prefix))
- .map(e -> toCompletionProposal(e.getKey(), e.getValue()))
- .collect(Collectors.toList());
- }
-
- private CompletionProposal toCompletionProposal(String command, MethodTarget methodTarget) {
- return new CompletionProposal(command)
- .category("Available commands")
- .description(methodTarget.getHelp());
- }
-
- private void validateArgs(Object[] args, MethodTarget methodTarget) {
- for (int i = 0; i < args.length; i++) {
- if (args[i] == UNRESOLVED) {
- MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
- throw new IllegalStateException("Could not resolve " + methodParameter);
- }
- }
- ExecutableValidator executableValidator = Validation
- .buildDefaultValidatorFactory().getValidator().forExecutables();
- Set> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
- methodTarget.getMethod(),
- args);
- if (constraintViolations.size() > 0) {
- System.out.println(constraintViolations);
- }
- }
-
- /**
- * Use all known {@link ParameterResolver}s to try to compute a value for each parameter of the method to
- * invoke.
- * @param method the method for which parameters should be computed
- * @param wordsForArgs the list of 'words' that should be converted to parameter values.
- * May include markers for passing parameters 'by name'
- * @return an array containing resolved parameter values, or {@link #UNRESOLVED} for parameters that could not be
- * resolved
- */
- private Object[] resolveArgs(Method method, List wordsForArgs) {
- Parameter[] parameters = method.getParameters();
- Object[] args = new Object[parameters.length];
- Arrays.fill(args, UNRESOLVED);
- for (int i = 0; i < parameters.length; i++) {
- MethodParameter methodParameter = Utils.createMethodParameter(method, i);
- args[i] = findResolver(methodParameter).resolve(methodParameter, wordsForArgs);
- }
- return args;
- }
-
- protected ParameterResolver findResolver(MethodParameter parameter) {
- return parameterResolvers.stream()
- .filter(resolver -> resolver.supports(parameter))
- .findFirst()
- .orElseThrow(() -> new RuntimeException("resolver not found"));
- }
-
-
- /**
- * Returns the longest command that can be matched as first word(s) in the given buffer.
- *
- * @return a valid command name, or {@literal null} if none matched
- */
- protected String findLongestCommand(String prefix) {
- String result = methodTargets.keySet().stream()
- .filter(prefix::startsWith)
- .reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
- return "".equals(result) ? null : result;
- }
-
-
-
-}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java b/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java
index d82c4a92..6fd01356 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/Bootstrap.java
@@ -16,11 +16,6 @@
package org.springframework.shell2;
-import java.io.IOException;
-
-import org.jline.terminal.Terminal;
-import org.jline.terminal.TerminalBuilder;
-
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@@ -41,7 +36,7 @@ public class Bootstrap {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(Bootstrap.class);
- context.getBean(JLineShell.class).run();
+ context.getBean(Shell.class).run();
}
@Bean
@@ -49,9 +44,4 @@ public class Bootstrap {
return new DefaultConversionService();
}
- @Bean
- public Terminal terminal() throws IOException {
- return TerminalBuilder.builder().build();
- }
-
}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/CommandNotFound.java b/spring-shell2-core/src/main/java/org/springframework/shell2/CommandNotFound.java
new file mode 100644
index 00000000..30e51b10
--- /dev/null
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/CommandNotFound.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * A result to be handled by the {@link ResultHandler} when no command could be mapped to user input
+ */
+public class CommandNotFound extends RuntimeException {
+
+ private final List words;
+
+ public CommandNotFound(List words) {
+ this.words = words;
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format("No command found for '%s'", words.stream().collect(Collectors.joining(" ")));
+ }
+}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/CommandRegistry.java b/spring-shell2-core/src/main/java/org/springframework/shell2/CommandRegistry.java
new file mode 100644
index 00000000..ca625b8d
--- /dev/null
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/CommandRegistry.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2;
+
+import java.util.Map;
+
+/**
+ * Implementing this interface allows sub-systems (such as the {@literal help} command) to
+ * discover available commands.
+ *
+ * @author Eric Bottard
+ */
+public interface CommandRegistry {
+
+
+ /**
+ * Return the mapping from command trigger keywords to implementation.
+ */
+ public Map listCommands();
+
+}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/Input.java b/spring-shell2-core/src/main/java/org/springframework/shell2/Input.java
index 6dbc0ed7..62ed87fe 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/Input.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/Input.java
@@ -16,6 +16,7 @@
package org.springframework.shell2;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -26,17 +27,7 @@ import java.util.List;
*/
public interface Input {
- Input EMPTY = new Input() {
- @Override
- public String rawText() {
- return "";
- }
-
- @Override
- public List words() {
- return Collections.emptyList();
- }
- };
+ Input EMPTY = () -> "";
/**
* Return the input as entered by the user.
@@ -48,5 +39,5 @@ public interface Input {
* to parsing rules (for example, handling quoted portions of the readInput as a single
* "word")
*/
- List words();
+ default List words() {return "".equals(rawText()) ? Collections.emptyList() : Arrays.asList(rawText().split(" "));}
}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/MethodTarget.java b/spring-shell2-core/src/main/java/org/springframework/shell2/MethodTarget.java
index faaa481e..dbf236d3 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/MethodTarget.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/MethodTarget.java
@@ -17,8 +17,11 @@
package org.springframework.shell2;
import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
/**
* Represents a shell command behavior, i.e. code to be executed when a command is requested.
@@ -37,11 +40,26 @@ public class MethodTarget {
Assert.notNull(method, "Method cannot be null");
Assert.notNull(bean, "Bean cannot be null");
Assert.hasText(help, "Help cannot be blank");
+ ReflectionUtils.makeAccessible(method);
this.method = method;
this.bean = bean;
this.help = help;
}
+ /**
+ * Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
+ * in case of overloaded method.
+ */
+ public static MethodTarget of(String name, Object bean, String help) {
+ Set found = new HashSet<>();
+ ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name));
+ if (found.size() != 1) {
+ throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s",
+ name, bean.getClass(), found));
+ }
+ return new MethodTarget(found.iterator().next(), bean, help);
+ }
+
public Method getMethod() {
return method;
}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/Shell.java b/spring-shell2-core/src/main/java/org/springframework/shell2/Shell.java
index 0e7f8130..3230e1b5 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/Shell.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/Shell.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,20 +16,229 @@
package org.springframework.shell2;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.executable.ExecutableValidator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.MethodParameter;
+import org.springframework.util.ReflectionUtils;
/**
- * Implementing this interface allows sub-systems (such as the {@literal help} command) to
- * discover available commands.
+ * Main class implementing a shell loop.
+ *
+ * Given some textual input, locate the {@link MethodTarget} to invoke and {@link ResultHandler#handleResult(Object) handle}
+ * the result.
+ *
+ * Also provides hooks for code completion
*
* @author Eric Bottard
*/
-public interface Shell {
+public class Shell implements CommandRegistry {
+
+ private final InputProvider inputProvider;
+
+ private final ResultHandler resultHandler;
+
+ @Autowired
+ protected ApplicationContext applicationContext;
+
+ protected Map methodTargets = new HashMap<>();
+
+ @Autowired
+ protected List parameterResolvers = new ArrayList<>();
+
+ /**
+ * Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
+ */
+ protected static final Object UNRESOLVED = new Object();
+
+ public Shell(InputProvider inputProvider, ResultHandler resultHandler) {
+ this.inputProvider = inputProvider;
+ this.resultHandler = resultHandler;
+ }
+
+ @Override
+ public Map listCommands() {
+ return methodTargets;
+ }
+
+ @PostConstruct
+ public void gatherMethodTargets() throws Exception {
+ for (MethodTargetResolver resolver : applicationContext.getBeansOfType(MethodTargetResolver.class).values()) {
+ methodTargets.putAll(resolver.resolve());
+ }
+ }
+
+ /**
+ * The main program loop: acquire input, try to match it to a command and evaluate. Repeat until a
+ * {@link ResultHandler} causes the process to exit.
+ */
+ public void run() throws IOException {
+ while (true) {
+ Input input;
+ try {
+ input = inputProvider.readInput();
+ }
+ catch (Exception e) {
+ resultHandler.handleResult(e);
+ continue;
+ }
+ if (input.words().isEmpty()) {
+ continue;
+ }
+
+
+ String line = input.rawText();
+ List words = input.words();
+
+ String command = findLongestCommand(line);
+
+ Object result;
+ if (command != null) {
+ int wordsUsedForCommandKey = command.split(" ").length;
+ MethodTarget methodTarget = methodTargets.get(command);
+ List wordsForArgs = words.subList(wordsUsedForCommandKey, words.size());
+ Method method = methodTarget.getMethod();
+
+ try {
+ Object[] args = resolveArgs(method, wordsForArgs);
+ validateArgs(args, methodTarget);
+ result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
+ }
+ catch (Exception e) {
+ result = e;
+ }
+ }
+ else {
+ result = new CommandNotFound(words);
+ }
+ resultHandler.handleResult(result);
+ }
+ }
+
+ /**
+ * Gather completion proposals given some (incomplete) input the user has already typed in.
+ * When and how this method is invoked is implementation specific and decided by the actual user interface.
+ */
+ public List complete(CompletionContext context) {
+
+ String prefix = context.upToCursor();
+
+ List candidates = new ArrayList<>();
+ // Find the longest match for a command name with words in the buffer
+ String best = findLongestCommand(prefix);
+ if (best == null) { // no command found
+ candidates.addAll(commandsStartingWith(prefix));
+ return candidates;
+ } // if we're here, we're either trying to complete args for command (will fall through)
+ // or trying to complete command whose name starts with (which also happens to be a command)
+ else if (prefix.equals(best)) {
+ candidates.addAll(commandsStartingWith(best));
+ } // valid command () followed by a suffix (but not necessarily [ args*])
+ else if (!prefix.startsWith(best + " ")) {
+ // must be an invalid command, can't do anything
+ return candidates;
+ }
+
+ // Try to complete arguments
+ MethodTarget methodTarget = methodTargets.get(best);
+ Method method = methodTarget.getMethod();
+ return Arrays.stream(method.getParameters())
+ .map(Utils::createMethodParameter)
+ .flatMap(mp -> findResolver(mp).complete(mp, context).stream())
+ .collect(Collectors.toList());
+ }
+
+ private List commandsStartingWith(String prefix) {
+ return methodTargets.entrySet().stream()
+ .filter(e -> e.getKey().startsWith(prefix))
+ .map(e -> toCompletionProposal(e.getKey(), e.getValue()))
+ .collect(Collectors.toList());
+ }
+
+ private CompletionProposal toCompletionProposal(String command, MethodTarget methodTarget) {
+ return new CompletionProposal(command)
+ .category("Available commands")
+ .description(methodTarget.getHelp());
+ }
+
+ private void validateArgs(Object[] args, MethodTarget methodTarget) {
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] == UNRESOLVED) {
+ MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
+ throw new IllegalStateException("Could not resolve " + methodParameter);
+ }
+ }
+ ExecutableValidator executableValidator = Validation
+ .buildDefaultValidatorFactory().getValidator().forExecutables();
+ Set> constraintViolations = executableValidator.validateParameters(methodTarget.getBean(),
+ methodTarget.getMethod(),
+ args);
+ if (constraintViolations.size() > 0) {
+ System.out.println(constraintViolations);
+ }
+ }
+
+ /**
+ * Use all known {@link ParameterResolver}s to try to compute a value for each parameter of the method to
+ * invoke.
+ * @param method the method for which parameters should be computed
+ * @param wordsForArgs the list of 'words' that should be converted to parameter values.
+ * May include markers for passing parameters 'by name'
+ * @return an array containing resolved parameter values, or {@link #UNRESOLVED} for parameters that could not be
+ * resolved
+ */
+ private Object[] resolveArgs(Method method, List wordsForArgs) {
+ Parameter[] parameters = method.getParameters();
+ Object[] args = new Object[parameters.length];
+ Arrays.fill(args, UNRESOLVED);
+ for (int i = 0; i < parameters.length; i++) {
+ MethodParameter methodParameter = Utils.createMethodParameter(method, i);
+ args[i] = findResolver(methodParameter).resolve(methodParameter, wordsForArgs);
+ }
+ return args;
+ }
+
+ private ParameterResolver findResolver(MethodParameter parameter) {
+ return parameterResolvers.stream()
+ .filter(resolver -> resolver.supports(parameter))
+ .findFirst()
+ .orElseThrow(() -> new RuntimeException("resolver not found"));
+ }
/**
- * Return the mapping from command trigger keywords to implementation.
+ * Returns the longest command that can be matched as first word(s) in the given buffer.
+ *
+ * @return a valid command name, or {@literal null} if none matched
*/
- public Map listCommands();
+ private String findLongestCommand(String prefix) {
+ String result = methodTargets.keySet().stream()
+ .filter(prefix::startsWith)
+ .reduce("", (c1, c2) -> c1.length() > c2.length() ? c1 : c2);
+ return "".equals(result) ? null : result;
+ }
+
+ public interface InputProvider {
+ /**
+ * Return text entered by user to invoke commands.
+ */
+ Input readInput();
+ }
+
}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/ExtendedDefaultParser.java b/spring-shell2-core/src/main/java/org/springframework/shell2/jline/ExtendedDefaultParser.java
similarity index 98%
rename from spring-shell2-core/src/main/java/org/springframework/shell2/ExtendedDefaultParser.java
rename to spring-shell2-core/src/main/java/org/springframework/shell2/jline/ExtendedDefaultParser.java
index 907a6251..c49a7827 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/ExtendedDefaultParser.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/jline/ExtendedDefaultParser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package org.springframework.shell2;
+package org.springframework.shell2.jline;
import java.util.Collections;
import java.util.LinkedList;
@@ -26,6 +26,8 @@ import org.jline.reader.EOFError;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
+import org.springframework.shell2.CompletingParsedLine;
+
/**
* Shameful copy-paste of JLine's {@link org.jline.reader.impl.DefaultParser} which
* creates {@link CompletingParsedLine}.
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java b/spring-shell2-core/src/main/java/org/springframework/shell2/jline/JLineShell.java
similarity index 62%
rename from spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java
rename to spring-shell2-core/src/main/java/org/springframework/shell2/jline/JLineShell.java
index 9e2398b0..57a0b499 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/JLineShell.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/jline/JLineShell.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package org.springframework.shell2;
+package org.springframework.shell2.jline;
+import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
@@ -29,12 +30,23 @@ import org.jline.reader.LineReaderBuilder;
import org.jline.reader.ParsedLine;
import org.jline.reader.UserInterruptException;
import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
+import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell2.CompletingParsedLine;
+import org.springframework.shell2.CompletionContext;
+import org.springframework.shell2.CompletionProposal;
+import org.springframework.shell2.ExitRequest;
+import org.springframework.shell2.Input;
+import org.springframework.shell2.ResultHandler;
+import org.springframework.shell2.Shell;
/**
* Shell implementation using JLine to capture input and trigger completions.
@@ -42,31 +54,58 @@ import org.springframework.stereotype.Component;
* @author Eric Bottard
* @author Florent Biville
*/
-@Component
-public class JLineShell extends AbstractShell {
-
- LineReader lineReader;
+@Configuration
+public class JLineShell {
@Autowired
- private Terminal terminal;
+ @Qualifier("main")
+ private ResultHandler resultHandler;
+ @Bean
+ public Terminal terminal() {
+ try {
+ return TerminalBuilder.builder().build();
+ }
+ catch (IOException e) {
+ throw new BeanCreationException("Could not create Terminal: " + e.getMessage());
+ }
+ }
+
+ @Bean
+ public Shell shell() {
+ return new Shell(new JLineInputProvider(lineReader()), resultHandler);
+ }
+
+ @Bean
+ public CompleterAdapter completer() {
+ return new CompleterAdapter();
+ }
+
+ /*
+ * Using setter injection to work around a circular dependency.
+ */
@PostConstruct
- public void init() throws Exception {
+ public void lateInit() {
+ completer().setShell(shell());
+ }
+
+ @Bean
+ public LineReader lineReader() {
ExtendedDefaultParser parser = new ExtendedDefaultParser();
parser.setEofOnUnclosedQuote(true);
parser.setEofOnEscapedNewLine(true);
LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()
- .terminal(terminal)
+ .terminal(terminal())
.appName("Foo")
- .completer(new CompleterAdapter())
+ .completer(completer())
.highlighter(new Highlighter() {
@Override
public AttributedString highlight(LineReader reader, String buffer) {
int l = 0;
String best = null;
- for (String command : methodTargets.keySet()) {
+ for (String command : shell().listCommands().keySet()) {
if (buffer.startsWith(command) && command.length() > l) {
l = command.length();
best = command;
@@ -82,24 +121,7 @@ public class JLineShell extends AbstractShell {
})
.parser(parser);
- lineReader = lineReaderBuilder.build();
-
- }
-
-
- @Override
- protected Input readInput() {
- try {
- lineReader.readLine(new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)).toAnsi(terminal));
- }
- catch (UserInterruptException e) {
- if (e.getPartialLine().isEmpty()) {
- resultHandler.handleResult(new ExitRequest(1));
- } else {
- return Input.EMPTY;
- }
- }
- return new JLineInput(lineReader.getParsedLine());
+ return lineReaderBuilder.build();
}
/**
@@ -115,19 +137,13 @@ public class JLineShell extends AbstractShell {
return words;
}
- // Overridden so it can be called from CompleterAdapter
-
-
- @Override
- public List complete(CompletionContext context) {
- return super.complete(context);
- }
-
/**
* A bridge between JLine's {@link Completer} contract and our own.
* @author Eric Bottard
*/
- private class CompleterAdapter implements Completer {
+ public static class CompleterAdapter implements Completer {
+
+ private Shell shell;
@Override
public void complete(LineReader reader, ParsedLine line, List candidates) {
@@ -135,7 +151,7 @@ public class JLineShell extends AbstractShell {
CompletionContext context = new CompletionContext(sanitizeInput(line.words()), line.wordIndex(), line.wordCursor());
- List proposals = JLineShell.this.complete(context);
+ List proposals = shell.complete(context);
proposals.stream()
.map(p -> new Candidate(
cpl.emit(p.value()).toString(),
@@ -148,6 +164,36 @@ public class JLineShell extends AbstractShell {
)
.forEach(candidates::add);
}
+
+ public void setShell(Shell shell) {
+ this.shell = shell;
+ }
+ }
+
+ public static class JLineInputProvider implements Shell.InputProvider {
+
+ private final LineReader lineReader;
+
+ public JLineInputProvider(LineReader lineReader) {
+ this.lineReader = lineReader;
+ }
+
+ @Override
+ public Input readInput() {
+ try {
+ lineReader.readLine(new AttributedString("shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)).toAnsi(lineReader.getTerminal()));
+ }
+ catch (UserInterruptException e) {
+ if (e.getPartialLine().isEmpty()) {
+ throw new ExitRequest(1);
+ } else {
+ return Input.EMPTY;
+ }
+ }
+ return new JLineInput(lineReader.getParsedLine());
+ }
+
+
}
private static class JLineInput implements Input {
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java
index 6c47cdce..287c89ca 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/AttributedCharSequenceResultHandler.java
@@ -29,14 +29,7 @@ import org.springframework.stereotype.Component;
* @author Eric Bottard
*/
@Component
-public class AttributedCharSequenceResultHandler implements ResultHandler {
-
- private final Terminal terminal;
-
- @Autowired
- public AttributedCharSequenceResultHandler(Terminal terminal) {
- this.terminal = terminal;
- }
+public class AttributedCharSequenceResultHandler extends TerminalAwareResultHandler implements ResultHandler {
@Override
public void handleResult(AttributedCharSequence result) {
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/CommandNotFoundResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/CommandNotFoundResultHandler.java
new file mode 100644
index 00000000..c175ffb2
--- /dev/null
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/CommandNotFoundResultHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.result;
+
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStyle;
+
+import org.springframework.shell2.CommandNotFound;
+import org.springframework.shell2.ResultHandler;
+import org.springframework.stereotype.Component;
+
+/**
+ * Used when no command can be matched for user input.
+ *
+ * Simply prints an error message, without printing the exception class.
+ *
+ * @author Eric Bottard
+ */
+@Component
+public class CommandNotFoundResultHandler extends TerminalAwareResultHandler implements ResultHandler {
+
+ @Override
+ public void handleResult(CommandNotFound result) {
+ terminal.writer().println(new AttributedString(result.getMessage(),
+ AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
+
+ }
+}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/TerminalAwareResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/TerminalAwareResultHandler.java
new file mode 100644
index 00000000..433bd96a
--- /dev/null
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/TerminalAwareResultHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2.result;
+
+import org.jline.terminal.Terminal;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * Base class for ResultHandlers that rely on JLine's {@link Terminal}.
+ *
+ * @author Eric Bottard
+ */
+public abstract class TerminalAwareResultHandler {
+ protected Terminal terminal;
+
+ @Autowired
+ public void setTerminal(Terminal terminal) {
+ this.terminal = terminal;
+ }
+}
diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java
index 33799c25..ec9e8d55 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/result/ThrowableResultHandler.java
@@ -28,11 +28,11 @@ import org.springframework.stereotype.Component;
* @author Eric Bottard
*/
@Component
-public class ThrowableResultHandler implements ResultHandler {
+public class ThrowableResultHandler extends TerminalAwareResultHandler implements ResultHandler {
@Override
public void handleResult(Throwable result) {
- System.out.println(new AttributedString(result.toString(),
+ terminal.writer().println(new AttributedString(result.toString(),
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
}
}
diff --git a/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java b/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java
deleted file mode 100644
index 74025c59..00000000
--- a/spring-shell2-core/src/test/java/org/springframework/shell2/JLineShellTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.shell2;
-
-import java.util.Collections;
-
-import org.jline.reader.LineReader;
-import org.jline.reader.UserInterruptException;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import org.springframework.shell2.result.TypeHierarchyResultHandler;
-
-/**
- * Unit tests for {@link JLineShell}.
- *
- * @author Eric Bottard
- */
-public class JLineShellTest {
-
- private JLineShell shell = new JLineShell();
-
- @Test
- public void testCtrlCInterception() throws Exception {
- shell.lineReader = mock(LineReader.class);
- AssertingExitRequestResultHandler resultHandler = new AssertingExitRequestResultHandler();
- shell.resultHandler = resultHandler;
- when(shell.lineReader.readLine(Mockito.any())).thenThrow(
- new UserInterruptException("some input"),
- new UserInterruptException("")
- );
-
- try {
- shell.run();
- }
- catch (BreakControlFlow breakControlFlow) {
-
- }
- assertThat(resultHandler.exited, is(true));
- }
-
- private static class AssertingExitRequestResultHandler implements ResultHandler {
-
- private boolean exited;
-
- @Override
- public void handleResult(ExitRequest result) {
- exited = true;
- throw new BreakControlFlow();
- }
- }
-
- private static class BreakControlFlow extends RuntimeException {
- }
-
-
-
-}
diff --git a/spring-shell2-core/src/test/java/org/springframework/shell2/ShellTest.java b/spring-shell2-core/src/test/java/org/springframework/shell2/ShellTest.java
new file mode 100644
index 00000000..e1bdc3a0
--- /dev/null
+++ b/spring-shell2-core/src/test/java/org/springframework/shell2/ShellTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.shell2;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for {@link Shell}.
+ *
+ * @author Eric Bottard
+ */
+public class ShellTest {
+
+ @Rule
+ public MockitoRule mockitoRule = MockitoJUnit.rule();
+
+ @Mock
+ private Shell.InputProvider inputProvider;
+
+ @Mock
+ private ResultHandler resultHandler;
+
+ @Mock
+ private ParameterResolver parameterResolver;
+
+ @InjectMocks
+ private Shell shell;
+
+ private boolean invoked;
+
+ @Before
+ public void setUp() {
+ shell.parameterResolvers = Arrays.asList(parameterResolver);
+ }
+
+ @Test
+ public void commandMatch() throws IOException {
+ when(parameterResolver.supports(any())).thenReturn(true);
+ when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?");
+ doThrow(new Exit()).when(resultHandler).handleResult(any());
+
+ shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, "Say hello"));
+
+ try {
+ shell.run();
+ fail("Exit expected");
+ }
+ catch (Exit expected) {
+
+ }
+
+ Assert.assertTrue(invoked);
+ }
+
+ @Test
+ public void commandNotFound() throws IOException {
+ when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?");
+ doThrow(new Exit()).when(resultHandler).handleResult(any(CommandNotFound.class));
+
+ shell.methodTargets = Collections.singletonMap("bonjour", MethodTarget.of("helloWorld", this, "Say hello"));
+
+ try {
+ shell.run();
+ fail("Exit expected");
+ }
+ catch (Exit expected) {
+
+ }
+ }
+
+ @Test
+ public void noCommand() throws IOException {
+ when(parameterResolver.supports(any())).thenReturn(true);
+ when(inputProvider.readInput()).thenReturn(() -> "", () -> "hello world how are you doing ?");
+ doThrow(new Exit()).when(resultHandler).handleResult(any());
+
+ shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, "Say hello"));
+
+ try {
+ shell.run();
+ fail("Exit expected");
+ }
+ catch (Exit expected) {
+
+ }
+
+ Assert.assertTrue(invoked);
+ }
+
+ @Test
+ public void commandThrowingAnException() throws IOException {
+ when(parameterResolver.supports(any())).thenReturn(true);
+ when(inputProvider.readInput()).thenReturn(() -> "fail");
+ doThrow(new Exit()).when(resultHandler).handleResult(any(SomeException.class));
+
+ shell.methodTargets = Collections.singletonMap("fail", MethodTarget.of("failing", this, "Will throw an exception"));
+
+ try {
+ shell.run();
+ fail("Exit expected");
+ }
+ catch (Exit expected) {
+
+ }
+
+ Assert.assertTrue(invoked);
+
+ }
+
+ private void helloWorld(String a) {
+ invoked = true;
+ }
+
+ private String failing() {
+ invoked = true;
+ throw new SomeException();
+ }
+
+
+ private static class Exit extends RuntimeException {
+ }
+
+ private static class SomeException extends RuntimeException {
+
+ }
+
+
+}
diff --git a/spring-shell2-standard-commands/src/main/java/org/springframework/shell2/standard/commands/Help.java b/spring-shell2-standard-commands/src/main/java/org/springframework/shell2/standard/commands/Help.java
index 418ef057..3615ae19 100644
--- a/spring-shell2-standard-commands/src/main/java/org/springframework/shell2/standard/commands/Help.java
+++ b/spring-shell2-standard-commands/src/main/java/org/springframework/shell2/standard/commands/Help.java
@@ -37,7 +37,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.shell2.MethodTarget;
import org.springframework.shell2.ParameterDescription;
import org.springframework.shell2.ParameterResolver;
-import org.springframework.shell2.Shell;
+import org.springframework.shell2.CommandRegistry;
import org.springframework.shell2.standard.CommandValueProvider;
import org.springframework.shell2.standard.ShellComponent;
import org.springframework.shell2.standard.ShellMethod;
@@ -54,7 +54,7 @@ public class Help {
private final List parameterResolvers;
- private Shell shell;
+ private CommandRegistry commandRegistry;
@Autowired
public Help(List parameterResolvers) {
@@ -62,8 +62,8 @@ public class Help {
}
@Autowired // ctor injection impossible b/c of circular dependency
- public void setShell(Shell shell) {
- this.shell = shell;
+ public void setCommandRegistry(CommandRegistry commandRegistry) {
+ this.commandRegistry = commandRegistry;
}
@ShellMethod(help = "Display help about available commands.", prefix = "-")
@@ -85,7 +85,7 @@ public class Help {
* Return a description of a specific command. Uses a layout inspired by *nix man pages.
*/
private CharSequence documentCommand(String command) {
- MethodTarget methodTarget = shell.listCommands().get(command);
+ MethodTarget methodTarget = commandRegistry.listCommands().get(command);
if (methodTarget == null) {
throw new IllegalArgumentException("Unknown command '" + command + "'");
}
@@ -181,7 +181,7 @@ public class Help {
}
// ALSO KNOWN AS
- Set aliases = shell.listCommands().entrySet().stream()
+ Set aliases = commandRegistry.listCommands().entrySet().stream()
.filter(e -> e.getValue().equals(methodTarget))
.map(Map.Entry::getKey)
.filter(c -> !command.equals(c))
@@ -203,7 +203,7 @@ public class Help {
}
private CharSequence listCommands() {
- Map> groupedByMethodTarget = shell.listCommands().entrySet().stream()
+ Map> groupedByMethodTarget = commandRegistry.listCommands().entrySet().stream()
.collect(Collectors.groupingBy(e -> e.getValue().getHelp(), // Use help() as the grouping key
mapping(Map.Entry::getKey, toCollection(TreeSet::new)))); // accumulate the command 'names' into a sorted set
diff --git a/spring-shell2-standard-commands/src/test/java/org/springframework/shell2/standard/commands/HelpTest.java b/spring-shell2-standard-commands/src/test/java/org/springframework/shell2/standard/commands/HelpTest.java
index 6b592431..84b43898 100644
--- a/spring-shell2-standard-commands/src/test/java/org/springframework/shell2/standard/commands/HelpTest.java
+++ b/spring-shell2-standard-commands/src/test/java/org/springframework/shell2/standard/commands/HelpTest.java
@@ -38,7 +38,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.shell2.standard.StandardParameterResolver;
import org.springframework.shell2.MethodTarget;
import org.springframework.shell2.ParameterResolver;
-import org.springframework.shell2.Shell;
+import org.springframework.shell2.CommandRegistry;
import org.springframework.shell2.standard.ShellComponent;
import org.springframework.shell2.standard.ShellMethod;
import org.springframework.shell2.standard.ShellOption;
@@ -93,7 +93,7 @@ public class HelpTest {
}
@Bean
- public Shell shell() {
+ public CommandRegistry shell() {
return () -> {
Map result = new HashMap<>();
Method method = ReflectionUtils.findMethod(Commands.class, "firstCommand", boolean.class, boolean.class, int.class, float[].class);
diff --git a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/CommandValueProvider.java b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/CommandValueProvider.java
index 6bed201e..42f5110a 100644
--- a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/CommandValueProvider.java
+++ b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/CommandValueProvider.java
@@ -19,15 +19,12 @@ package org.springframework.shell2.standard;
import java.util.List;
import java.util.stream.Collectors;
-import javax.annotation.PostConstruct;
-
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.MethodParameter;
import org.springframework.shell2.CompletionContext;
import org.springframework.shell2.CompletionProposal;
-import org.springframework.shell2.Shell;
+import org.springframework.shell2.CommandRegistry;
import org.springframework.stereotype.Component;
/**
@@ -38,17 +35,17 @@ import org.springframework.stereotype.Component;
@Component
public class CommandValueProvider extends ValueProviderSupport {
- private final Shell shell;
+ private final CommandRegistry commandRegistry;
@Lazy
@Autowired
- public CommandValueProvider(Shell shell) {
- this.shell = shell;
+ public CommandValueProvider(CommandRegistry commandRegistry) {
+ this.commandRegistry = commandRegistry;
}
@Override
public List complete(MethodParameter parameter, CompletionContext completionContext, String[] hints) {
- return shell.listCommands().keySet().stream()
+ return commandRegistry.listCommands().keySet().stream()
.map(CompletionProposal::new)
.collect(Collectors.toList());
}
diff --git a/spring-shell2-standard/src/test/java/org/springframework/shell2/standard/CommandValueProviderTest.java b/spring-shell2-standard/src/test/java/org/springframework/shell2/standard/CommandValueProviderTest.java
index 2d1c9a5a..10ac43c7 100644
--- a/spring-shell2-standard/src/test/java/org/springframework/shell2/standard/CommandValueProviderTest.java
+++ b/spring-shell2-standard/src/test/java/org/springframework/shell2/standard/CommandValueProviderTest.java
@@ -23,24 +23,19 @@ import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import org.assertj.core.api.Assertions;
-import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Answers;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.core.MethodParameter;
import org.springframework.shell2.CompletionContext;
import org.springframework.shell2.CompletionProposal;
import org.springframework.shell2.MethodTarget;
-import org.springframework.shell2.Shell;
+import org.springframework.shell2.CommandRegistry;
import org.springframework.shell2.Utils;
import org.springframework.util.ReflectionUtils;
@@ -52,7 +47,7 @@ import org.springframework.util.ReflectionUtils;
public class CommandValueProviderTest {
@Mock
- private Shell shell;
+ private CommandRegistry shell;
@Before
public void setUp() {