Use JCommander resolver in combination with others.

Prepare for implementation of describe()
This commit is contained in:
Eric Bottard
2017-06-22 16:06:19 +02:00
parent 14b2401374
commit e49ee885ef
12 changed files with 137 additions and 20 deletions

View File

@@ -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.
* <p>Typical implementations will return a one element stream result, but some may return several (for
* example if binding several words to a POJO).</p>
*/
ParameterDescription describe(MethodParameter parameter);
Stream<ParameterDescription> describe(MethodParameter parameter);
/**
* Invoked during TAB completion. If the {@link CompletionContext} can be interpreted as the start

View File

@@ -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<Class<? extends Annotation>> JCOMMANDER_ANNOTATIONS =
@@ -80,7 +83,7 @@ public class JCommanderParameterResolver implements ParameterResolver {
}
@Override
public ParameterDescription describe(MethodParameter parameter) {
public Stream<ParameterDescription> describe(MethodParameter parameter) {
throw new UnsupportedOperationException();
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) {
}

View File

@@ -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<String> 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 +
'}';
}
}

View File

@@ -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;
}
}

View File

@@ -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<ParameterDescription> 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

View File

@@ -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()));

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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<ParameterDescription> 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<CompletionProposal> 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());