diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolver.java
index ccea74b4..79deb656 100644
--- a/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolver.java
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 the original author or authors.
+ * Copyright 2015-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.
@@ -20,10 +20,19 @@ import java.util.List;
import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* Implementations of this interface are responsible, once the command has been identified, of transforming the textual
* input to an actual parameter object.
+ *
+ *
+ * An order can also be specified in case more than one {@link ParameterResolver} supports a {@link MethodParameter}.
+ * See {@link AnnotationAwareOrderComparator} for details..
+ *
+ *
+ * @author Eric Bottard
+ * @author Camilo Gonzalez
*/
public interface ParameterResolver {
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolverMissingException.java b/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolverMissingException.java
new file mode 100644
index 00000000..431ee7d8
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/ParameterResolverMissingException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.shell;
+
+import org.springframework.core.MethodParameter;
+
+/**
+ * Thrown when no {@link ParameterResolver} is found for a parameter during a {@link ParameterResolver#resolve}
+ * operation.
+ *
+ * @author Camilo Gonzalez
+ */
+public class ParameterResolverMissingException extends RuntimeException {
+ public ParameterResolverMissingException(MethodParameter parameter) {
+ super(String.format("No parameter resolver found for parameter with index %d (named '%s') of %s ", parameter.getParameterIndex(), parameter.getParameterName(), parameter.getMethod()));
+ }
+}
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 5d91d392..74230503 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
@@ -18,7 +18,6 @@ package org.springframework.shell;
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;
@@ -35,6 +34,7 @@ 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.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.ReflectionUtils;
/**
@@ -58,8 +58,7 @@ public class Shell implements CommandRegistry {
protected Map methodTargets = new HashMap<>();
- @Autowired
- protected List parameterResolvers = new ArrayList<>();
+ protected List parameterResolvers;
/**
* Marker object to distinguish unresolved arguments from {@code null}, which is a valid value.
@@ -83,6 +82,14 @@ public class Shell implements CommandRegistry {
resolver.register(registry);
}
methodTargets = registry.listCommands();
+ methodTargets.values()
+ .forEach(this::validateParameters);
+ }
+
+ @Autowired
+ public void setParameterResolvers(List resolvers) {
+ this.parameterResolvers = new ArrayList<>(resolvers);
+ AnnotationAwareOrderComparator.sort(parameterResolvers);
}
/**
@@ -141,7 +148,7 @@ public class Shell implements CommandRegistry {
}
resultHandler.handleResult(result);
}
-
+
/**
* Return true if the parsed input ends up being empty (e.g. hitting ENTER on an empty line or blank space).
*
@@ -185,10 +192,16 @@ public class Shell implements CommandRegistry {
// Try to complete arguments
MethodTarget methodTarget = methodTargets.get(best);
Method method = methodTarget.getMethod();
- Arrays.stream(method.getParameters())
- .map(Utils::createMethodParameter)
- .flatMap(mp -> findResolver(mp).complete(mp, argsContext).stream())
- .forEach(candidates::add);
+
+ List parameters = Utils.createMethodParameters(method).collect(Collectors.toList());
+ for (ParameterResolver resolver : parameterResolvers) {
+ for (int index = 0; index < parameters.size(); index++) {
+ MethodParameter parameter = parameters.get(index);
+ if (resolver.supports(parameter)) {
+ resolver.complete(parameter, argsContext).stream().forEach(candidates::add);
+ }
+ }
+ }
}
return candidates;
}
@@ -234,24 +247,34 @@ public class Shell implements CommandRegistry {
* resolved
*/
private Object[] resolveArgs(Method method, List wordsForArgs) {
- Parameter[] parameters = method.getParameters();
- Object[] args = new Object[parameters.length];
+ List parameters = Utils.createMethodParameters(method).collect(Collectors.toList());
+ Object[] args = new Object[parameters.size()];
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).resolvedValue();
+ for (ParameterResolver resolver : parameterResolvers) {
+ for (int argIndex = 0; argIndex < args.length; argIndex++) {
+ MethodParameter parameter = parameters.get(argIndex);
+ if (args[argIndex] == UNRESOLVED && resolver.supports(parameter)) {
+ args[argIndex] = resolver.resolve(parameter, wordsForArgs).resolvedValue();
+ }
+ }
}
return args;
}
-
- private ParameterResolver findResolver(MethodParameter parameter) {
- return parameterResolvers.stream()
- .filter(resolver -> resolver.supports(parameter))
- .findFirst()
- .orElseThrow(() -> new RuntimeException("resolver not found"));
+
+ /**
+ * Verifies that we have at least one {@link ParameterResolver} that supports each of
+ * the {@link MethodParameter}s in the method.
+ */
+ private void validateParameters(MethodTarget methodTarget) {
+ Utils.createMethodParameters(methodTarget.getMethod())
+ .forEach(parameter -> {
+ parameterResolvers.stream()
+ .filter(resolver -> resolver.supports(parameter))
+ .findFirst()
+ .orElseThrow(() -> new ParameterResolverMissingException(parameter));
+ });
}
-
-
+
/**
* Returns the longest command that can be matched as first word(s) in the given buffer.
*
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
index 19ed246a..3c91e33d 100644
--- a/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
+++ b/spring-shell-core/src/test/java/org/springframework/shell/ShellTest.java
@@ -24,14 +24,17 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.springframework.context.ApplicationContext;
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.mock;
import static org.mockito.Mockito.when;
/**
@@ -44,6 +47,9 @@ public class ShellTest {
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
+ @Rule
+ public ExpectedException thrown= ExpectedException.none();
+
@Mock
private InputProvider inputProvider;
@@ -152,6 +158,19 @@ public class ShellTest {
}
+ @Test
+ public void parametersSupported() throws Exception {
+ when(parameterResolver.supports(any())).thenReturn(false);
+ shell.applicationContext = mock(ApplicationContext.class);
+ when(shell.applicationContext.getBeansOfType(MethodTargetRegistrar.class))
+ .thenReturn(Collections.singletonMap("foo", r -> {
+ r.register("hw", MethodTarget.of("helloWorld", this, "hellow world"));
+ }));
+
+ thrown.expect(ParameterResolverMissingException.class);
+ shell.gatherMethodTargets();
+ }
+
private void helloWorld(String a) {
invoked = true;
}
diff --git a/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java b/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java
index ee0dd5ba..0747b43d 100644
--- a/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java
+++ b/spring-shell-jcommander-adapter/src/main/java/org/springframework/shell/jcommander/JCommanderParameterResolver.java
@@ -34,7 +34,6 @@ import org.springframework.shell.CompletionProposal;
import org.springframework.shell.ParameterDescription;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.ValueResult;
-import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import com.beust.jcommander.DynamicParameter;