StandardParameterResolver implements ParameterResolver { @Override public ValueResult resolve(MethodParameter methodParameter, List<String> wordsBuffer) { String prefix = prefixForMethod(methodParameter.getMethod()); List<String> words = wordsBuffer.stream().filter(w -> !w.isEmpty()).collect(Collectors.toList()); CacheKey cacheKey = new CacheKey(methodParameter.getMethod(), wordsBuffer); parameterCache.clear(); Map<Parameter, ParameterRawValue> resolved = parameterCache.computeIfAbsent(cacheKey, (k) -> { Map<Parameter, ParameterRawValue> result = new HashMap<>(); Map<String, String> namedParameters = new HashMap<>(); List<Integer> unusedWords = new ArrayList<>(); Set<String> possibleKeys = gatherAllPossibleKeys(methodParameter.getMethod()); for (int i = 0; i < words.size(); i++) { int from = i; String word = words.get(i); if (possibleKeys.contains(word)) { String key = word; Parameter parameter = lookupParameterForKey(methodParameter.getMethod(), key); int arity = getArity(parameter); 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); } 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)); namedParameters.put(key, raw); if (arity == 0) { boolean defaultValue = booleanDefaultValue(parameter); result.put(parameter, ParameterRawValue.explicit(String.valueOf(!defaultValue), key, from, from)); } else { i += arity; result.put(parameter, ParameterRawValue.explicit(raw, key, from, i)); } } else { unusedWords.add(i); } } int offset = 0; Parameter[] parameters = methodParameter.getMethod().getParameters(); for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) { Parameter parameter = parameters[i]; Collection<String> keys = getKeysForParameter(methodParameter.getMethod(), i) .collect(Collectors.toSet()); Collection<String> copy = new HashSet<>(keys); copy.retainAll(namedParameters.keySet()); if (copy.isEmpty()) { int arity = getArity(parameter); if (arity > 0 && (offset + arity) <= unusedWords.size()) { String raw = unusedWords.subList(offset, offset + arity).stream() .map(index -> words.get(index)) .collect(Collectors.joining(",")); int from = unusedWords.get(offset); int to = from + arity - 1; result.put(parameter, ParameterRawValue.explicit(raw, null, from, to)); offset += arity; } else { Optional<String> defaultValue = defaultValueFor(parameter); 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)); } } Assert.isTrue(offset == unusedWords.size(), "Too many arguments: the following could not be mapped to parameters: " + unusedWords.subList(offset, unusedWords.size()).stream() .map(index -> words.get(index)).collect(Collectors.joining(" ", "'", "'"))); return result; }); Parameter param = methodParameter.getMethod().getParameters()[methodParameter.getParameterIndex()]; if (!resolved.containsKey(param)) { throw new ParameterMissingResolutionException(describe(methodParameter).findFirst().get()); } ParameterRawValue parameterRawValue = resolved.get(param); Object value = convertRawValue(parameterRawValue, methodParameter); BitSet wordsUsed = getWordsUsed(parameterRawValue); BitSet wordsUsedForValue = getWordsUsedForValue(parameterRawValue); return new ValueResult(methodParameter, value, wordsUsed, wordsUsedForValue); } @Autowired  StandardParameterResolver(ConversionService conversionService); @Autowired(required = false) void setValueProviders(Collection<ValueProvider> valueProviders); @Autowired(required = false) void setValidatorFactory(ValidatorFactory validatorFactory); @Override boolean supports(MethodParameter parameter); @Override ValueResult resolve(MethodParameter methodParameter, List<String> wordsBuffer); @Override Stream<ParameterDescription> describe(MethodParameter parameter); @Override List<CompletionProposal> complete(MethodParameter methodParameter, CompletionContext context);  }
@Test public void testParses() throws Exception { Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class); List<String> words = asList("--force --name --foo y".split(" ")); ValueResult result0 = resolver.resolve(Utils.createMethodParameter(method, 0), words); assertThat(result0).hasValue(true).usesWords(0).notUsesWordsForValue(); assertThat(result0.wordsUsed(words)).containsExactly("--force"); ValueResult result1 = resolver.resolve(Utils.createMethodParameter(method, 1), words); assertThat(result1).hasValue("--foo").usesWords(1, 2).usesWordsForValue(2); assertThat(result1.wordsUsed(words)).containsExactly("--name", "--foo"); assertThat(result1.wordsUsedForValue(words)).containsExactly("--foo"); ValueResult result2 = resolver.resolve(Utils.createMethodParameter(method, 2), words); assertThat(result2).hasValue("y").usesWords(3).usesWordsForValue(3); assertThat(result2.wordsUsed(words)).containsExactly("y"); ValueResult result3 = resolver.resolve(Utils.createMethodParameter(method, 3), words); assertThat(result3).hasValue("last").notUsesWords().notUsesWordsForValue(); }
@Test public void testParsesWithMethodPrefix() throws Exception { Method method = findMethod(Remote.class, "prefixTest", String.class); ValueResult result = resolver.resolve(Utils.createMethodParameter(method, 0), asList("-message abc".split(" "))); assertThat(result).hasValue("abc").usesWords(0, 1).usesWordsForValue(1); }
@Test public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception { Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Named parameter has been specified multiple times via '--bar, --baz'"); resolver.resolve( Utils.createMethodParameter(method, 0), asList("--force --name --foo y --bar x --baz z".split(" ")) ); }
@Test public void testParameterSpecifiedTwiceViaSameKey() throws Exception { Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Parameter for '--baz' has already been specified"); resolver.resolve( Utils.createMethodParameter(method, 0), asList("--force --name --foo y --baz x --baz z".split(" ")) ); }
@Test public void testTooMuchInput() throws Exception { Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("the following could not be mapped to parameters: 'leftover'"); resolver.resolve( Utils.createMethodParameter(method, 0), asList("--foo hello --name bar --force --bar well leftover".split(" ")) ); }
@Test public void testIncompleteCommandResolution() throws Exception { Method method = findMethod(Remote.class, "shutdown", Remote.Delay.class); thrown.expect(UnfinishedParameterResolutionException.class); thrown.expectMessage("Error trying to resolve '--delay delay' using [--delay]"); resolver.resolve( Utils.createMethodParameter(method, 0), asList("--delay".split(" ")) ); }
@Test public void testIncompleteCommandResolutionBigArity() throws Exception { Method method = findMethod(Remote.class, "add", List.class); thrown.expect(UnfinishedParameterResolutionException.class); thrown.expectMessage("Error trying to resolve '--numbers list list list' using [--numbers 1 2]"); resolver.resolve( Utils.createMethodParameter(method, 0), asList("--numbers 1 2".split(" ")) ); }
@Test public void testUnresolvableArg() throws Exception { Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class); thrown.expect(ParameterMissingResolutionException.class); thrown.expectMessage("Parameter '--name string' should be specified"); resolver.resolve( Utils.createMethodParameter(method, 1), asList("--foo hello --force --bar well".split(" ")) ); }