- * - named parameters (recognized because they start with some {@link ShellMethod#prefix()})
+ * Default ParameterResolver implementation that supports the following features:
+ *
+ * - named parameters (recognized because they start with some
+ * {@link ShellMethod#prefix()})
* - implicit named parameters (from the actual method parameter name)
- * - positional parameters (in order, for all parameter values that were not resolved via named
- * parameters)
+ * - positional parameters (in order, for all parameter values that were not resolved
+ * via named parameters)
* - default values (for all remaining parameters)
*
*
- * Method arguments can consume several words of input at once (driven by {@link ShellOption#arity()}, default 1).
- * If several words are consumed, they will be joined together as a comma separated value and passed to the {@link
- * ConversionService}
- * (which will typically return a List or array).
+ *
+ * Method arguments can consume several words of input at once (driven by
+ * {@link ShellOption#arity()}, default 1). If several words are consumed, they will be
+ * joined together as a comma separated value and passed to the {@link ConversionService}
+ * (which will typically return a List or array).
+ *
*
- * Boolean parameters are by default expected to have an arity of 0, allowing invocations in the form {@code rm
- * --force --dir /foo}:
- * the presence of {@code --force} passes {@code true} as a parameter value, while its absence passes {@code false}.
- * Both
- * the default arity of 0 and the default value of {@code false} can be overridden via {@link ShellOption}
- * if needed.
+ *
+ * Boolean parameters are by default expected to have an arity of 0, allowing invocations
+ * in the form {@code rm
+ * --force --dir /foo}: the presence of {@code --force} passes {@code true} as a parameter
+ * value, while its absence passes {@code false}. Both the default arity of 0 and the
+ * default value of {@code false} can be overridden via {@link ShellOption} if
+ * needed.
+ *
* @author Eric Bottard
* @author Florent Biville
* @author Camilo Gonzalez
@@ -86,9 +99,9 @@ public class StandardParameterResolver implements ParameterResolver {
private Collection valueProviders = new HashSet<>();
/**
- * A cache from method+input to String representation of actual parameter values.
- * Note that the converted result is not cached, to allow dynamic computation to happen at every invocation
- * if needed (e.g. if a remote service is involved).
+ * A cache from method+input to String representation of actual parameter values. Note
+ * that the converted result is not cached, to allow dynamic computation to happen at
+ * every invocation if needed (e.g. if a remote service is involved).
*/
private final Map> parameterCache = new ConcurrentReferenceHashMap<>();
@@ -102,9 +115,13 @@ public class StandardParameterResolver implements ParameterResolver {
this.valueProviders = valueProviders;
}
+ @Autowired(required = false)
+ private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
+
@Override
public boolean supports(MethodParameter parameter) {
- boolean optOut = parameter.hasParameterAnnotation(ShellOption.class) && parameter.getParameterAnnotation(ShellOption.class).optOut();
+ boolean optOut = parameter.hasParameterAnnotation(ShellOption.class)
+ && parameter.getParameterAnnotation(ShellOption.class).optOut();
return !optOut && parameter.getMethodAnnotation(ShellMethod.class) != null;
}
@@ -120,7 +137,7 @@ public class StandardParameterResolver implements ParameterResolver {
Map result = new HashMap<>();
Map namedParameters = new HashMap<>();
-
+
// index of words that haven't yet been used to resolve parameter values
List unusedWords = new ArrayList<>();
@@ -137,17 +154,22 @@ 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)).findFirst().get(), 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));
+ 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(","));
- Assert.isTrue(!namedParameters.containsKey(key), String.format("Parameter for '%s' has already been specified", word));
+ Assert.isTrue(!namedParameters.containsKey(key),
+ String.format("Parameter for '%s' has already been specified", word));
namedParameters.put(key, raw);
if (arity == 0) {
boolean defaultValue = booleanDefaultValue(parameter);
// Boolean parameter has been specified. Use the opposite of the default value
- result.put(parameter, ParameterRawValue.explicit(String.valueOf(!defaultValue), key, from, from));
- } else {
+ result.put(parameter,
+ ParameterRawValue.explicit(String.valueOf(!defaultValue), key, from, from));
+ }
+ else {
i += arity;
result.put(parameter, ParameterRawValue.explicit(raw, key, from, i));
}
@@ -162,8 +184,10 @@ public class StandardParameterResolver implements ParameterResolver {
Parameter[] parameters = methodParameter.getMethod().getParameters();
for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
Parameter parameter = parameters[i];
- // Compute the intersection between possible keys for the param and what we've already seen for named params
- Collection keys = getKeysForParameter(methodParameter.getMethod(), i).collect(Collectors.toSet());
+ // Compute the intersection between possible keys for the param and what we've already
+ // seen for named params
+ Collection keys = getKeysForParameter(methodParameter.getMethod(), i)
+ .collect(Collectors.toSet());
Collection copy = new HashSet<>(keys);
copy.retainAll(namedParameters.keySet());
if (copy.isEmpty()) { // Was not set via a key (including aliases), must be positional
@@ -179,11 +203,13 @@ public class StandardParameterResolver implements ParameterResolver {
} // No more input. Try defaultValues
else {
Optional defaultValue = defaultValueFor(parameter);
- defaultValue.ifPresent(value -> result.put(parameter, ParameterRawValue.implicit(value, null, null, null)));
+ defaultValue.ifPresent(
+ value -> result.put(parameter, ParameterRawValue.implicit(value, null, null, null)));
}
}
else if (copy.size() > 1) {
- throw new IllegalArgumentException("Named parameter has been specified multiple times via " + quote(copy));
+ throw new IllegalArgumentException(
+ "Named parameter has been specified multiple times via " + quote(copy));
}
}
@@ -213,7 +239,7 @@ public class StandardParameterResolver implements ParameterResolver {
}
return null;
}
-
+
private BitSet getWordsUsedForValue(ParameterRawValue parameterRawValue) {
if (parameterRawValue.from != null) {
BitSet wordsUsedForValue = new BitSet();
@@ -232,7 +258,8 @@ public class StandardParameterResolver implements ParameterResolver {
return null;
}
else {
- return conversionService.convert(s, TypeDescriptor.valueOf(String.class), new TypeDescriptor(methodParameter));
+ return conversionService.convert(s, TypeDescriptor.valueOf(String.class),
+ new TypeDescriptor(methodParameter));
}
}
@@ -276,7 +303,8 @@ public class StandardParameterResolver implements ParameterResolver {
if (i > 0) {
sb.append(" ");
}
- sb.append(arity > 1 ? unCamelify(removeMultiplicityFromType(parameter).getSimpleName()) : unCamelify(type.getSimpleName()));
+ sb.append(arity > 1 ? unCamelify(removeMultiplicityFromType(parameter).getSimpleName())
+ : unCamelify(type.getSimpleName()));
}
ParameterDescription result = ParameterDescription.outOf(parameter);
result.formal(sb.toString());
@@ -292,6 +320,14 @@ public class StandardParameterResolver implements ParameterResolver {
.collect(Collectors.toList()))
.mandatoryKey(false);
+ MethodDescriptor constraintsForMethod = validator.getConstraintsForClass(parameter.getDeclaringClass())
+ .getConstraintsForMethod(parameter.getMethod().getName(), parameter.getMethod().getParameterTypes());
+ if (constraintsForMethod != null) {
+ ParameterDescriptor constraintsDescriptor = constraintsForMethod
+ .getParameterDescriptors().get(parameter.getParameterIndex());
+ result.elementDescriptor(constraintsDescriptor);
+ }
+
return Stream.of(result);
}
@@ -309,16 +345,20 @@ public class StandardParameterResolver implements ParameterResolver {
arity = getArity(parameter);
parameterRawValue = parameterCache.get(cacheKey).get(parameter);
set = parameterRawValue.explicit;
- } catch (ParameterMissingResolutionException e) {
+ }
+ catch (ParameterMissingResolutionException e) {
set = false;
- } catch (UnfinishedParameterResolutionException e) {
+ }
+ catch (UnfinishedParameterResolutionException e) {
if (e.getParameterDescription().parameter().equals(methodParameter)) {
unfinished = e;
set = false;
- } else {
+ }
+ else {
return Collections.emptyList();
}
- } catch (Exception e) {
+ }
+ catch (Exception e) {
unfinished = e;
set = false;
// Most likely what is already typed would fail resolution (eg type conversion failure)
@@ -330,8 +370,10 @@ public class StandardParameterResolver implements ParameterResolver {
// 1) parameter not set at all
// 2) parameter set via its key, not enough input to consume a value
// 3) parameter set with multiple values, enough to cover arity. We're done
- // 4) parameter set, and some value bound. But maybe that value is just a prefix to what the user actually wants
- // 4.1) or maybe that value was resolved by position, but is a prefix of an actual valid key
+ // 4) parameter set, and some value bound. But maybe that value is just a prefix to what
+ // the user actually wants
+ // 4.1) or maybe that value was resolved by position, but is a prefix of an actual valid
+ // key
if (!set) {
if (unfinished == null) { // case 1 above
@@ -356,21 +398,24 @@ public class StandardParameterResolver implements ParameterResolver {
}
if (parameterRawValue.positional()) {
- // Case 4.1: There exists "--command foo" and user has typed "--comm" which (wrongly) got resolved as a positional param
+ // Case 4.1: There exists "--command foo" and user has typed "--comm" which (wrongly) got
+ // resolved as a positional param
result.addAll(argumentKeysThatStartWithContextPrefix(methodParameter, context));
}
return result;
}
}
- private List valueCompletions(MethodParameter methodParameter, CompletionContext completionContext) {
+ private List valueCompletions(MethodParameter methodParameter,
+ CompletionContext completionContext) {
return valueProviders.stream()
.filter(vp -> vp.supports(methodParameter, completionContext))
.map(vp -> vp.complete(methodParameter, completionContext, null))
.findFirst().orElseGet(() -> Collections.emptyList());
}
- private List argumentKeysThatStartWithContextPrefix(MethodParameter methodParameter, CompletionContext context) {
+ private List argumentKeysThatStartWithContextPrefix(MethodParameter methodParameter,
+ CompletionContext context) {
String prefix = context.currentWordUpToCursor() != null ? context.currentWordUpToCursor() : "";
return describe(methodParameter).flatMap(pd -> pd.keys().stream())
.filter(k -> k.startsWith(prefix))
@@ -402,8 +447,8 @@ public class StandardParameterResolver implements ParameterResolver {
}
/**
- * Return the arity of a given parameter. The default arity is 1, except for
- * booleans where arity is 0 (can be overridden back to 1 via an annotation)
+ * Return the arity of a given parameter. The default arity is 1, except for booleans
+ * where arity is 0 (can be overridden back to 1 via an annotation)
*/
private int getArity(Parameter parameter) {
ShellOption option = parameter.getAnnotation(ShellOption.class);
@@ -412,8 +457,8 @@ public class StandardParameterResolver implements ParameterResolver {
}
/**
- * Return the key(s) for the i-th parameter of the command method, resolved either from the {@link ShellOption}
- * annotation, or from the actual parameter name.
+ * Return the key(s) for the i-th parameter of the command method, resolved either from
+ * the {@link ShellOption} annotation, or from the actual parameter name.
*/
private Stream getKeysForParameter(Method method, int index) {
Parameter p = method.getParameters()[index];
@@ -459,8 +504,10 @@ public class StandardParameterResolver implements ParameterResolver {
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
CacheKey cacheKey = (CacheKey) o;
return Objects.equals(method, cacheKey.method) &&
Objects.equals(words, cacheKey.words);