// command name
// Record all the CliOptions applicable to this command
List<CliOption> cliOptions = new ArrayList<CliOption>();
for (Annotation[] annotations : parameterAnnotations) {
CliOption cliOption = null;
for (Annotation a : annotations) {
if (a instanceof CliOption) {
cliOption = (CliOption) a;
}
}
Assert.notNull(cliOption, "CliOption not found for parameter '" + Arrays.toString(annotations) + "'");
cliOptions.add(cliOption);
}
// Make a list of all CliOptions they've already included or are system-provided
List<CliOption> alreadySpecified = new ArrayList<CliOption>();
for (CliOption option : cliOptions) {
for (String value : option.key()) {
if (options.containsKey(value)) {
alreadySpecified.add(option);
break;
}
}
if (option.systemProvided()) {
alreadySpecified.add(option);
}
}
// Make a list of all CliOptions they have not provided
List<CliOption> unspecified = new ArrayList<CliOption>(cliOptions);
unspecified.removeAll(alreadySpecified);
// Determine whether they're presently editing an option key or an option value
// (and if possible, the full or partial name of the said option key being edited)
String lastOptionKey = null;
String lastOptionValue = null;
// The last item in the options map is *always* the option key they're editing (will never be null)
if (options.size() > 0) {
lastOptionKey = new ArrayList<String>(options.keySet()).get(options.keySet().size() - 1);
lastOptionValue = options.get(lastOptionKey);
}
// Handle if they are trying to find out the available option keys; always present option keys in order
// of their declaration on the method signature, thus we can stop when mandatory options are filled in
if (methodTarget.getRemainingBuffer().endsWith("--") && !tokenizer.openingQuotesHaveNotBeenClosed()) {
boolean showAllRemaining = true;
for (CliOption include : unspecified) {
if (include.mandatory()) {
showAllRemaining = false;
break;
}
}
for (CliOption include : unspecified) {
for (String value : include.key()) {
if (!"".equals(value)) {
results.add(new Completion(translated + value + " "));
}
}
if (!showAllRemaining) {
break;
}
}
candidates.addAll(results);
return 0;
}
// Handle suggesting an option key if they haven't got one presently specified (or they've completed a full
// option key/value pair)
if (lastOptionKey == null
|| (!"".equals(lastOptionKey) && !"".equals(lastOptionValue) && translated.endsWith(" ") && !tokenizer
.openingQuotesHaveNotBeenClosed())) {
// We have either NEVER specified an option key/value pair
// OR we have specified a full option key/value pair
// Let's list some other options the user might want to try (naturally skip the "" option, as that's the
// default)
for (CliOption include : unspecified) {
for (String value : include.key()) {
// Manually determine if this non-mandatory but unspecifiedDefaultValue=* requiring option is
// able to be bound
if (!include.mandatory() && "*".equals(include.unspecifiedDefaultValue()) && !"".equals(value)) {
try {
for (Converter<?> candidate : converters) {
// Find the target parameter
Class<?> paramType = null;
int index = -1;
for (Annotation[] a : methodTarget.getMethod().getParameterAnnotations()) {
index++;
for (Annotation an : a) {
if (an instanceof CliOption) {
if (an.equals(include)) {
// Found the parameter, so store it
paramType = methodTarget.getMethod().getParameterTypes()[index];
break;
}
}
}
}
if (paramType != null && candidate.supports(paramType, include.optionContext())) {
// Try to invoke this usable converter
candidate.convertFromText("*", paramType, include.optionContext());
// If we got this far, the converter is happy with "*" so we need not bother the
// user with entering the data in themselves
break;
}
}
}
catch (RuntimeException notYetReady) {
if (translated.endsWith(" ")) {
results.add(new Completion(translated + "--" + value + " "));
}
else {
results.add(new Completion(translated + " --" + value + " "));
}
continue;
}
}
// Handle normal mandatory options
if (!"".equals(value) && include.mandatory()) {
handleMandatoryCompletion(translated, unspecified, value, results);
}
}
}
// Only abort at this point if we have some suggestions;
// otherwise we might want to try to complete the "" option
if (results.size() > 0) {
candidates.addAll(results);
return 0;
}
}
// Handle completing the option key they're presently typing
if (lastOptionKey != null && "".equals(lastOptionValue)
&& !translated.endsWith("" + tokenizer.getLastValueDelimiter())) {
// Given we haven't got an option value of any form, we must
// still be typing an option key.
for (CliOption option : unspecified) {
for (String value : option.key()) {
if (value != null && value.regionMatches(true, 0, lastOptionKey, 0, lastOptionKey.length())) {
String completionValue = translated.substring(0,
(translated.length() - lastOptionKey.length()))
+ value + " ";
results.add(new Completion(completionValue));
}
}
}
candidates.addAll(results);
return 0;
}
// To be here, we are NOT typing an option key (or we might be, and there are no further option keys left)
if (lastOptionKey != null && !"".equals(lastOptionKey)) {
// Lookup the relevant CliOption that applies to this lastOptionKey
// We do this via the parameter type
Class<?>[] parameterTypes = methodTarget.getMethod().getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
CliOption option = cliOptions.get(i);
Class<?> parameterType = parameterTypes[i];
for (String key : option.key()) {
if (key.equals(lastOptionKey)) {
List<Completion> allValues = new ArrayList<Completion>();
// We'll append the closing delimiter to proposals
String suffix = "" + tokenizer.getLastValueDelimiter();
if (!suffix.endsWith(" ")) {
suffix += " ";
}
// Let's use a Converter if one is available
for (Converter<?> candidate : converters) {
String optionContext = successiveInvocationContext + " " + option.optionContext();
if (candidate.supports(parameterType, optionContext)) {
// Found a usable converter
boolean allComplete = candidate.getAllPossibleValues(allValues, parameterType,
lastOptionValue, optionContext, methodTarget);
if (!allComplete) {