diff --git a/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterResolver.java b/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterResolver.java
index a74f971b..6dcc12f4 100644
--- a/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterResolver.java
+++ b/spring-shell2-core/src/main/java/org/springframework/shell2/ParameterResolver.java
@@ -17,6 +17,7 @@
package org.springframework.shell2;
import java.util.List;
+import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
@@ -39,8 +40,10 @@ public interface ParameterResolver {
/**
* Describe a supported parameter, so that integrated help can be generated.
+ *
Typical implementations will return a one element stream result, but some may return several (for
+ * example if binding several words to a POJO).
*/
- ParameterDescription describe(MethodParameter parameter);
+ Stream describe(MethodParameter parameter);
/**
* Invoked during TAB completion. If the {@link CompletionContext} can be interpreted as the start
diff --git a/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java b/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java
index a4b857d6..fe5cb4ec 100644
--- a/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java
+++ b/spring-shell2-jcommander-adapter/src/main/java/org/springframework/shell2/jcommander/JCommanderParameterResolver.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
import com.beust.jcommander.DynamicParameter;
import com.beust.jcommander.JCommander;
@@ -33,6 +34,7 @@ import org.springframework.shell2.CompletionContext;
import org.springframework.shell2.CompletionProposal;
import org.springframework.shell2.ParameterDescription;
import org.springframework.shell2.ParameterResolver;
+import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
/**
@@ -40,6 +42,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Eric Bottard
*/
+@Component
public class JCommanderParameterResolver implements ParameterResolver {
private static final Collection> JCOMMANDER_ANNOTATIONS =
@@ -80,7 +83,7 @@ public class JCommanderParameterResolver implements ParameterResolver {
}
@Override
- public ParameterDescription describe(MethodParameter parameter) {
+ public Stream describe(MethodParameter parameter) {
throw new UnsupportedOperationException();
}
diff --git a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
index 9a50492f..a8ef3dae 100644
--- a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
+++ b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/FieldCollins.java
@@ -22,7 +22,10 @@ import java.util.List;
import com.beust.jcommander.Parameter;
/**
- * Created by ericbottard on 15/12/15.
+ * A POJO with fields annotated with JCommander annotations.
+ *
+ * @author Eric Bottard
+ * @see MyLordCommands#genesis(FieldCollins)
*/
public class FieldCollins {
diff --git a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
index 37be2556..0428ffa1 100644
--- a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
+++ b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/JCommanderParameterResolverTest.java
@@ -28,7 +28,10 @@ import org.springframework.shell2.Utils;
import org.springframework.util.ReflectionUtils;
/**
- * Created by ericbottard on 15/12/15.
+ * Unit test for {@link JCommanderParameterResolver}.
+ *
+ * @author Eric Bottard
+ * @author Florent Biville
*/
public class JCommanderParameterResolverTest {
diff --git a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
index 236af2d5..0ef47ae6 100644
--- a/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
+++ b/spring-shell2-jcommander-adapter/src/test/java/org/springframework/shell2/jcommander/MyLordCommands.java
@@ -17,14 +17,22 @@
package org.springframework.shell2.jcommander;
/**
- * Created by ericbottard on 15/12/15.
+ * An hypothetical command class, with one method using JCommander args, and the other not.
+ *
+ * @author Eric Bottard
*/
public class MyLordCommands {
+ /**
+ * This method should be supported.
+ */
public void genesis(FieldCollins fieldCollins) {
}
+ /**
+ * This method is not.
+ */
public void apocalypse(String param) {
}
diff --git a/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/Args.java b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/Args.java
new file mode 100644
index 00000000..32c0309e
--- /dev/null
+++ b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/Args.java
@@ -0,0 +1,52 @@
+/*
+ * 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.samples.jcommander;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.beust.jcommander.Parameter;
+
+/**
+ * An example straight from the JCommander documentation.
+ *
+ * @author Eric Bottard
+ * @author Cédric Beust
+ */
+public class Args {
+ @Parameter
+ private List parameters = new ArrayList<>();
+
+ @Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
+ private Integer verbose = 1;
+
+ @Parameter(names = "-groups", description = "Comma-separated list of group names to be run")
+ private String groups;
+
+ @Parameter(names = "-debug", description = "Debug mode")
+ private boolean debug = false;
+
+ @Override
+ public String toString() {
+ return "Args{" +
+ "parameters=" + parameters +
+ ", verbose=" + verbose +
+ ", groups='" + groups + '\'' +
+ ", debug=" + debug +
+ '}';
+ }
+}
diff --git a/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/JCommanderCommands.java b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/JCommanderCommands.java
new file mode 100644
index 00000000..83c1334d
--- /dev/null
+++ b/spring-shell2-samples/src/main/java/org/springframework/shell2/samples/jcommander/JCommanderCommands.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.samples.jcommander;
+
+import org.springframework.shell2.standard.ShellComponent;
+import org.springframework.shell2.standard.ShellMethod;
+import org.springframework.shell2.standard.ShellOption;
+
+/**
+ * A class with JCommander commands.
+ *
+ * @author Eric Bottard
+ */
+@ShellComponent
+public class JCommanderCommands {
+
+ @ShellMethod(help = "bind parameters to JCommander POJO")
+ public String jcommander(@ShellOption(optOut = true) Args args) {
+ return "You said " + args;
+ }
+}
diff --git a/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java b/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java
index 17a1ac6e..c3da1053 100644
--- a/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java
+++ b/spring-shell2-shell1-adapter/src/main/java/org/springframework/shell2/legacy/LegacyParameterResolver.java
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
@@ -90,7 +91,7 @@ public class LegacyParameterResolver implements ParameterResolver {
}
@Override
- public ParameterDescription describe(MethodParameter parameter) {
+ public Stream describe(MethodParameter parameter) {
Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
CliOption option = jlrParameter.getAnnotation(CliOption.class);
ParameterDescription result = ParameterDescription.outOf(parameter);
@@ -108,7 +109,7 @@ public class LegacyParameterResolver implements ParameterResolver {
}
boolean containsEmptyKey = keys.contains("");
result.mandatoryKey(!containsEmptyKey);
- return result;
+ return Stream.of(result);
}
@Override
diff --git a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
index 50a83ea3..44cea25c 100644
--- a/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
+++ b/spring-shell2-shell1-adapter/src/test/java/org/springframework/shell2/legacy/LegacyParameterResolverTest.java
@@ -160,7 +160,7 @@ public class LegacyParameterResolverTest {
public void testDescribeBothDefaultsNotDeclared() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.REGISTER_METHOD, 1);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).containsExactly("--type");
assertThat(description.formal()).isEqualTo(Utils.unCamelify(ArtifactType.class.getSimpleName()));
@@ -175,7 +175,7 @@ public class LegacyParameterResolverTest {
public void testDescribeBothDefaultsDeclared() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SOME_METHOD, 1);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).containsExactly("--option");
assertThat(description.formal()).isEqualTo(boolean.class.getName());
@@ -191,7 +191,7 @@ public class LegacyParameterResolverTest {
public void testDescribeOnlySpecifiedDefaultDeclared() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 1);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).containsExactly("--v2");
assertThat(description.formal()).isEqualTo(int.class.getName());
@@ -207,7 +207,7 @@ public class LegacyParameterResolverTest {
public void testDescribeOnlyUnspecifiedDefaultDeclared() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).containsExactly("--v1");
assertThat(description.formal()).isEqualTo(int.class.getName());
@@ -222,7 +222,7 @@ public class LegacyParameterResolverTest {
public void testDescribeDefaultKey() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.LEGACY_ECHO_METHOD, 0);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).isEmpty();
assertThat(description.formal()).isEqualTo(Utils.unCamelify(String.class.getSimpleName()));
@@ -237,7 +237,7 @@ public class LegacyParameterResolverTest {
public void testDescribeNonMandatoryNoDefaults() {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SOME_METHOD, 0);
- ParameterDescription description = parameterResolver.describe(methodParameter);
+ ParameterDescription description = parameterResolver.describe(methodParameter).findFirst().get();
assertThat(description.keys()).containsExactly("--key");
assertThat(description.formal()).isEqualTo(Utils.unCamelify(String.class.getSimpleName()));
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 3615ae19..48bdc156 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
@@ -244,7 +244,7 @@ public class Help {
for (ParameterResolver parameterResolver : parameterResolvers) {
MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i);
if (parameterResolver.supports(methodParameter)) {
- parameterDescriptions.add(parameterResolver.describe(methodParameter));
+ parameterDescriptions.add(parameterResolver.describe(methodParameter).findFirst().get());
break;
}
}
diff --git a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/ShellOption.java b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/ShellOption.java
index 6978feeb..880d32a1 100644
--- a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/ShellOption.java
+++ b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/ShellOption.java
@@ -66,6 +66,14 @@ public @interface ShellOption {
Class extends ValueProvider> valueProvider() default NoValueProvider.class;
+ /**
+ * Used to indicate to the framework that the given parameter should NOT be resolved by
+ * {@link StandardParameterResolver}. This is useful if several implementations of
+ * {@link org.springframework.shell2.ParameterResolver} are present, given that the standard one can work with no
+ * annotation at all.
+ */
+ boolean optOut() default false;
+
interface NoValueProvider extends ValueProvider {
}
diff --git a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
index 9a2e9d19..13aba9f0 100644
--- a/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
+++ b/spring-shell2-standard/src/main/java/org/springframework/shell2/standard/StandardParameterResolver.java
@@ -100,7 +100,8 @@ public class StandardParameterResolver implements ParameterResolver {
@Override
public boolean supports(MethodParameter parameter) {
- return parameter.getMethodAnnotation(ShellMethod.class) != null;
+ boolean optOut = parameter.hasParameterAnnotation(ShellOption.class) && parameter.getParameterAnnotation(ShellOption.class).optOut();
+ return !optOut && parameter.getMethodAnnotation(ShellMethod.class) != null;
}
@Override
@@ -126,7 +127,7 @@ public class StandardParameterResolver implements ParameterResolver {
if (i + 1 + arity > words.size()) {
String input = words.subList(i, words.size()).stream().collect(Collectors.joining(" "));
- throw new UnfinishedParameterResolutionException(describe(Utils.createMethodParameter(parameter)), input);
+ throw new UnfinishedParameterResolutionException(describe(Utils.createMethodParameter(parameter)).findFirst().get(), input);
}
Assert.isTrue(i + 1 + arity <= words.size(), String.format("Not enough input for parameter '%s'", word));
String raw = words.subList(i + 1, i + 1 + arity).stream().collect(Collectors.joining(","));
@@ -178,7 +179,7 @@ public class StandardParameterResolver implements ParameterResolver {
Parameter param = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()];
if (!resolved.containsKey(param)) {
- throw new ParameterMissingResolutionException(describe(methodParameter));
+ throw new ParameterMissingResolutionException(describe(methodParameter).findFirst().get());
}
ParameterRawValue parameterRawValue = resolved.get(param);
return convertRawValue(parameterRawValue, methodParameter);
@@ -233,7 +234,7 @@ public class StandardParameterResolver implements ParameterResolver {
}
@Override
- public ParameterDescription describe(MethodParameter parameter) {
+ public Stream describe(MethodParameter parameter) {
Parameter jlrParameter = parameter.getMethod().getParameters()[parameter.getParameterIndex()];
int arity = getArity(jlrParameter);
Class> type = parameter.getParameterType();
@@ -259,7 +260,7 @@ public class StandardParameterResolver implements ParameterResolver {
.collect(Collectors.toList()))
.mandatoryKey(false);
- return result;
+ return Stream.of(result);
}
@Override
@@ -332,7 +333,7 @@ public class StandardParameterResolver implements ParameterResolver {
private List argumentKeysThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
- return describe(methodParameter).keys().stream()
+ return describe(methodParameter).flatMap(pd -> pd.keys().stream())
.filter(k -> k.startsWith(prefix))
.map(CompletionProposal::new)
.collect(Collectors.toList());