package vnet.sms.common.shell.springshell.internal.parser;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Required;
import vnet.sms.common.shell.springshell.AbstractShell;
import vnet.sms.common.shell.springshell.Completion;
import vnet.sms.common.shell.springshell.Converter;
import vnet.sms.common.shell.springshell.MethodTarget;
import vnet.sms.common.shell.springshell.ParseResult;
import vnet.sms.common.shell.springshell.Parser;
import vnet.sms.common.shell.springshell.command.CliCommand;
import vnet.sms.common.shell.springshell.command.CliOption;
import vnet.sms.common.shell.springshell.command.CliOptionContext;
import vnet.sms.common.shell.springshell.command.CliSimpleParserContext;
import vnet.sms.common.shell.springshell.internal.NaturalOrderComparator;
import vnet.sms.common.shell.springshell.internal.commands.CommandsRegistry;
import vnet.sms.common.shell.springshell.internal.converters.ConvertersRegistry;
import vnet.sms.common.shell.springshell.internal.logging.HandlerUtils;
import vnet.sms.common.shell.springshell.internal.util.Assert;
import vnet.sms.common.shell.springshell.internal.util.CollectionUtils;
import vnet.sms.common.shell.springshell.internal.util.ExceptionUtils;
import vnet.sms.common.shell.springshell.internal.util.StringUtils;
/**
* Default implementation of {@link Parser}.
*
* @author Ben Alex
* @since 1.0
*/
public class SimpleParser implements Parser {
// Constants
private static final Logger LOGGER = HandlerUtils
.getLogger(SimpleParser.class);
private static final Comparator<Object> COMPARATOR = new NaturalOrderComparator<Object>();
// Fields
private final Object mutex = new Object();
private ConvertersRegistry convertersRegistry;
private CommandsRegistry commandsRegistry;
@Required
public void setConvertersRegistry(final ConvertersRegistry converters) {
this.convertersRegistry = converters;
}
@Required
public void setCommandRegistry(final CommandsRegistry commands) {
this.commandsRegistry = commands;
}
/**
* get all options key.
*
* @param cliOptions
* @param includeOptionalOptions
* @return options keys
*/
private List<List<String>> getOptionsKeys(
final Collection<CliOption> cliOptions,
final boolean includeOptionalOptions) {
final List<List<String>> optionsKeys = new ArrayList<List<String>>();
for (final CliOption option : cliOptions) {
if (includeOptionalOptions) {
final List<String> keys = new ArrayList<String>();
keys.addAll(Arrays.asList(option.key()));
optionsKeys.add(keys);
} else if (option.mandatory()) {
final List<String> keys = new ArrayList<String>();
keys.addAll(Arrays.asList(option.key()));
optionsKeys.add(keys);
}
}
return optionsKeys;
}
@Override
public ParseResult parse(final String rawInput) {
synchronized (this.mutex) {
Assert.notNull(rawInput, "Raw input required");
final String input = normalise(rawInput);
// Locate the applicable targets which match this buffer
final Collection<MethodTarget> matchingTargets = this.commandsRegistry
.findMatchingCommands(input, true, true);
if (matchingTargets.isEmpty()) {
// Before we just give up, let's see if we can offer a more
// informative message to the user
// by seeing the command is simply unavailable at this point in
// time
CollectionUtils.populate(matchingTargets, this.commandsRegistry
.findMatchingCommands(input, true, false));
if (matchingTargets.isEmpty()) {
commandNotFound(LOGGER, input);
} else {
LOGGER.warning("Command '"
+ input
+ "' was found but is not currently available (type 'help' then ENTER to learn about this command)");
}
return null;
}
if (matchingTargets.size() > 1) {
LOGGER.warning("Ambigious command '" + input
+ "' (for assistance press "
+ AbstractShell.completionKeys
+ " or type \"hint\" then hit ENTER)");
return null;
}
final MethodTarget methodTarget = matchingTargets.iterator().next();
// Argument conversion time
final Annotation[][] parameterAnnotations = methodTarget
.getMethod().getParameterAnnotations();
if (parameterAnnotations.length == 0) {
// No args
return new ParseResult(methodTarget.getMethod(),
methodTarget.getTarget(), null);
}
// Oh well, we need to convert some arguments
final List<Object> arguments = new ArrayList<Object>(methodTarget
.getMethod().getParameterTypes().length);
// Attempt to parse
Map<String, String> options = null;
try {
options = ParserUtils.tokenize(methodTarget
.getRemainingBuffer());
} catch (final IllegalArgumentException e) {
LOGGER.warning(ExceptionUtils.extractRootCause(e).getMessage());
return null;
}
final Set<CliOption> cliOptions = getCliOptions(parameterAnnotations);
for (final CliOption cliOption : cliOptions) {
final Class<?> requiredType = methodTarget.getMethod()
.getParameterTypes()[arguments.size()];
if (cliOption.systemProvided()) {
Object result;
if (SimpleParser.class.isAssignableFrom(requiredType)) {
result = this;
} else {
LOGGER.warning("Parameter type '" + requiredType
+ "' is not system provided");
return null;
}
arguments.add(result);
continue;
}
// Obtain the value the user specified, taking care to ensure
// they only specified it via a single alias
String value = null;
String sourcedFrom = null;
for (final String possibleKey : cliOption.key()) {
if (options.containsKey(possibleKey)) {
if (sourcedFrom != null) {
LOGGER.warning("You cannot specify option '"
+ possibleKey
+ "' when you have also specified '"
+ sourcedFrom + "' in the same command");
return null;
}
sourcedFrom = possibleKey;
value = options.get(possibleKey);
}
}
// Ensure the user specified a value if the value is mandatory
// or
// key and value must appear in pair
final boolean mandatory = StringUtils.isBlank(value)
&& cliOption.mandatory();
final boolean specifiedKey = StringUtils.isBlank(value)
&& options.containsKey(sourcedFrom);
boolean specifiedKeyWithoutValue = false;
if (specifiedKey) {
value = cliOption.specifiedDefaultValue();
if ("__NULL__".equals(value)) {
specifiedKeyWithoutValue = true;
}
}
if (mandatory || specifiedKeyWithoutValue) {
if ("".equals(cliOption.key()[0])) {
final StringBuilder message = new StringBuilder(
"You should specify a default option ");
if (cliOption.key().length > 1) {
message.append("(otherwise known as option '")
.append(cliOption.key()[1]).append("') ");
}
message.append("for this command");
LOGGER.warning(message.toString());
} else {
printHintMessage(cliOptions, options);
}
return null;
}
// Accept a default if the user specified the option, but didn't
// provide a value
if ("".equals(value)) {
value = cliOption.specifiedDefaultValue();
}
// Accept a default if the user didn't specify the option at all
if (value == null) {
value = cliOption.unspecifiedDefaultValue();
}
// Special token that denotes a null value is sought (useful for
// default values)
if ("__NULL__".equals(value)) {
if (requiredType.isPrimitive()) {
LOGGER.warning("Nulls cannot be presented to primitive type "
+ requiredType.getSimpleName()
+ " for option '"
+ StringUtils
.arrayToCommaDelimitedString(cliOption
.key()) + "'");
return null;
}
arguments.add(null);
continue;
}
// Now we're ready to perform a conversion
try {
CliOptionContext
.setOptionContext(cliOption.optionContext());
CliSimpleParserContext.setSimpleParserContext(this);
Object result;
Converter<?> c = null;
for (final Converter<?> candidate : this.convertersRegistry) {
if (candidate.supports(requiredType,
cliOption.optionContext())) {
// Found a usable converter
c = candidate;
break;
}
}
if (c == null) {
throw new IllegalStateException(
"TODO: Add basic type conversion");
// TODO Fall back to a normal SimpleTypeConverter and
// attempt conversion
// SimpleTypeConverter simpleTypeConverter = new
// SimpleTypeConverter();
// result =
// simpleTypeConverter.convertIfNecessary(value,
// requiredType, mp);
}
// Use the converter
result = c.convertFromText(value, requiredType,
cliOption.optionContext());
// If the option has been specified to be mandatory then the
// result should never be null
if ((result == null) && cliOption.mandatory()) {
throw new IllegalStateException();
}
arguments.add(result);
} catch (final RuntimeException e) {
LOGGER.warning(e.getClass().getName()
+ ": Failed to convert '"
+ value
+ "' to type "
+ requiredType.getSimpleName()
+ " for option '"
+ StringUtils.arrayToCommaDelimitedString(cliOption
.key()) + "'");
if (StringUtils.hasText(e.getMessage())) {
LOGGER.warning(e.getMessage());
}
return null;
} finally {
CliOptionContext.resetOptionContext();
CliSimpleParserContext.resetSimpleParserContext();
}
}
// Check for options specified by the user but are unavailable for
// the command
final Set<String> unavailableOptions = getSpecifiedUnavailableOptions(
cliOptions, options);
if (!unavailableOptions.isEmpty()) {
final StringBuilder message = new StringBuilder();
if (unavailableOptions.size() == 1) {
message.append("Option '")
.append(unavailableOptions.iterator().next())
.append("' is not available for this command. ");
} else {
message.append("Options ")
.append(StringUtils.collectionToDelimitedString(
unavailableOptions, ", ", "'", "'"))
.append(" are not available for this command. ");
}
message.append("Use tab assist or the \"help\" command to see the legal options");
LOGGER.warning(message.toString());
return null;
}
return new ParseResult(methodTarget.getMethod(),
methodTarget.getTarget(), arguments.toArray());
}
}
/**
* @param cliOptions
* @param options
*/
private void printHintMessage(final Set<CliOption> cliOptions,
final Map<String, String> options) {
boolean hintForOptions = true;
final StringBuilder optionBuilder = new StringBuilder();
optionBuilder.append("You should specify option (");
final StringBuilder valueBuilder = new StringBuilder();
valueBuilder.append("You should specify value for option '");
final List<List<String>> optionsKeys = getOptionsKeys(cliOptions, true);
for (final List<String> keys : optionsKeys) {
boolean found = false;
for (final String key : keys) {
if (options.containsKey(key)) {
if (StringUtils.isBlank(options.get(key))) {
valueBuilder.append(key);
valueBuilder.append("' for this command");
hintForOptions = false;
}
found = true;
break;
}
}
if (!found) {
optionBuilder.append("--");
optionBuilder.append(keys.get(0));
optionBuilder.append(", ");
}
}
// remove the ", " in the end.
String hintForOption = optionBuilder.toString();
hintForOption = hintForOption.substring(0, hintForOption.length() - 2);
if (hintForOptions) {
LOGGER.warning(hintForOption + ") for this command");
} else {
LOGGER.warning(valueBuilder.toString());
}
}
/**
* Normalises the given raw user input string ready for parsing
*
* @param rawInput
* the string to normalise; can't be <code>null</code>
* @return a non-<code>null</code> string
*/
String normalise(final String rawInput) {
// Replace all multiple spaces with a single space and then trim
return rawInput.replaceAll(" +", " ").trim();
}
private Set<String> getSpecifiedUnavailableOptions(
final Set<CliOption> cliOptions, final Map<String, String> options) {
final Set<String> cliOptionKeySet = new LinkedHashSet<String>();
for (final CliOption cliOption : cliOptions) {
for (final String key : cliOption.key()) {
cliOptionKeySet.add(key.toLowerCase());
}
}
final Set<String> unavailableOptions = new LinkedHashSet<String>();
for (final String suppliedOption : options.keySet()) {
if (!cliOptionKeySet.contains(suppliedOption.toLowerCase())) {
unavailableOptions.add(suppliedOption);
}
}
return unavailableOptions;
}
private Set<CliOption> getCliOptions(
final Annotation[][] parameterAnnotations) {
final Set<CliOption> cliOptions = new LinkedHashSet<CliOption>();
for (final Annotation[] annotations : parameterAnnotations) {
for (final Annotation annotation : annotations) {
if (annotation instanceof CliOption) {
final CliOption cliOption = (CliOption) annotation;
cliOptions.add(cliOption);
}
}
}
return cliOptions;
}
private void commandNotFound(final Logger logger, final String buffer) {
logger.warning("Command '" + buffer
+ "' not found (for assistance press "
+ AbstractShell.completionKeys + ")");
}
@Override
public int complete(final String buffer, final int cursor,
final List<String> candidates) {
final List<Completion> completions = new ArrayList<Completion>();
final int result = completeAdvanced(buffer, cursor, completions);
for (final Completion completion : completions) {
candidates.add(completion.getValue());
}
return result;
}
@Override
public int completeAdvanced(String buffer, int cursor,
final List<Completion> candidates) {
synchronized (this.mutex) {
Assert.notNull(buffer, "Buffer required");
Assert.notNull(candidates, "Candidates list required");
// Remove all spaces from beginning of command
while (buffer.startsWith(" ")) {
buffer = buffer.replaceFirst("^ ", "");
cursor--;
}
// Replace all multiple spaces with a single space
while (buffer.contains(" ")) {
buffer = StringUtils.replaceFirst(buffer, " ", " ");
cursor--;
}
// Begin by only including the portion of the buffer represented to
// the present cursor position
final String translated = buffer.substring(0, cursor);
// Start by locating a method that matches
final Collection<MethodTarget> targets = this.commandsRegistry
.findMatchingCommands(translated, false, true);
final SortedSet<Completion> results = new TreeSet<Completion>(
COMPARATOR);
if (targets.isEmpty()) {
// Nothing matches the buffer they've presented
return cursor;
}
if (targets.size() > 1) {
// Assist them locate a particular target
for (final MethodTarget target : targets) {
// Calculate the correct starting position
final int startAt = translated.length();
// Only add the first word of each target
int stopAt = target.getKey().indexOf(" ", startAt);
if (stopAt == -1) {
stopAt = target.getKey().length();
}
results.add(new Completion(target.getKey().substring(0,
stopAt)
+ " "));
}
candidates.addAll(results);
return 0;
}
// There is a single target of this method, so provide completion
// services for it
final MethodTarget methodTarget = targets.iterator().next();
// Identify the command we're working with
final CliCommand cmd = methodTarget.getMethod().getAnnotation(
CliCommand.class);
Assert.notNull(cmd, "CliCommand unavailable for '"
+ methodTarget.getMethod().toGenericString() + "'");
// Make a reasonable attempt at parsing the remainingBuffer
Map<String, String> options;
try {
options = ParserUtils.tokenize(methodTarget
.getRemainingBuffer());
} catch (final IllegalArgumentException ex) {
// Assume any IllegalArgumentException is due to a quotation
// mark mismatch
candidates.add(new Completion(translated + "\""));
return 0;
}
// Lookup arguments for this target
final Annotation[][] parameterAnnotations = methodTarget
.getMethod().getParameterAnnotations();
// If there aren't any parameters for the method, at least ensure
// they have typed the command properly
if (parameterAnnotations.length == 0) {
for (final String value : cmd.value()) {
if (buffer.startsWith(value) || value.startsWith(buffer)) {
results.add(new Completion(value)); // no space at the
// end, as there's
// no need to
// continue the
// command further
}
}
candidates.addAll(results);
return 0;
}
// If they haven't specified any parameters yet, at least verify the
// command name is fully completed
if (options.isEmpty()) {
for (final String value : cmd.value()) {
if (value.startsWith(buffer)) {
// They are potentially trying to type this command
// We only need provide completion, though, if they
// failed to specify it fully
if (!buffer.startsWith(value)) {
// They failed to specify the command fully
results.add(new Completion(value + " "));
}
}
}
// Only quit right now if they have to finish specifying the
// command name
if (results.size() > 0) {
candidates.addAll(results);
return 0;
}
}
// To get this far, we know there are arguments required for this
// CliCommand, and they specified a valid command name
// Record all the CliOptions applicable to this command
final List<CliOption> cliOptions = new ArrayList<CliOption>();
for (final Annotation[] annotations : parameterAnnotations) {
CliOption cliOption = null;
for (final 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
final List<CliOption> alreadySpecified = new ArrayList<CliOption>();
for (final CliOption option : cliOptions) {
for (final 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
final 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("--")) {
boolean showAllRemaining = true;
for (final CliOption include : unspecified) {
if (include.mandatory()) {
showAllRemaining = false;
break;
}
}
for (final CliOption include : unspecified) {
for (final 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(" "))) {
// 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 (final CliOption include : unspecified) {
for (final 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 (final Converter<?> candidate : this.convertersRegistry) {
// Find the target parameter
Class<?> paramType = null;
int index = -1;
for (final Annotation[] a : methodTarget
.getMethod()
.getParameterAnnotations()) {
index++;
for (final 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 (final 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 (((lastOptionValue == null) || "".equals(lastOptionValue))
&& !translated.endsWith(" ")) {
// Given we haven't got an option value of any form, and there's
// no space at the buffer end, we must still be typing an option
// key
// System.out.println("completing an option");
for (final CliOption option : cliOptions) {
for (final String value : option.key()) {
if ((value != null)
&& (lastOptionKey != null)
&& value.regionMatches(true, 0, lastOptionKey,
0, lastOptionKey.length())) {
final 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
final Class<?>[] parameterTypes = methodTarget.getMethod()
.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
final CliOption option = cliOptions.get(i);
final Class<?> parameterType = parameterTypes[i];
for (final String key : option.key()) {
if (key.equals(lastOptionKey)) {
final List<Completion> allValues = new ArrayList<Completion>();
String suffix = " ";
// Let's use a Converter if one is available
for (final Converter<?> candidate : this.convertersRegistry) {
if (candidate.supports(parameterType,
option.optionContext())) {
// Found a usable converter
final boolean addSpace = candidate
.getAllPossibleValues(allValues,
parameterType,
lastOptionValue,
option.optionContext(),
methodTarget);
if (!addSpace) {
suffix = "";
}
break;
}
}
if (allValues.isEmpty()) {
// Doesn't appear to be a custom Converter, so
// let's go and provide defaults for simple
// types
// Provide some simple options for common types
if (Boolean.class
.isAssignableFrom(parameterType)
|| Boolean.TYPE
.isAssignableFrom(parameterType)) {
allValues.add(new Completion("true"));
allValues.add(new Completion("false"));
}
if (Number.class
.isAssignableFrom(parameterType)) {
allValues.add(new Completion("0"));
allValues.add(new Completion("1"));
allValues.add(new Completion("2"));
allValues.add(new Completion("3"));
allValues.add(new Completion("4"));
allValues.add(new Completion("5"));
allValues.add(new Completion("6"));
allValues.add(new Completion("7"));
allValues.add(new Completion("8"));
allValues.add(new Completion("9"));
}
}
String prefix = "";
if (!translated.endsWith(" ")) {
prefix = " ";
}
// Only include in the candidates those results
// which are compatible with the present buffer
for (final Completion currentValue : allValues) {
// We only provide a suggestion if the
// lastOptionValue == ""
if (StringUtils.isBlank(lastOptionValue)) {
// We should add the result, as they haven't
// typed anything yet
results.add(new Completion(prefix
+ currentValue.getValue() + suffix,
currentValue.getFormattedValue(),
currentValue.getHeading(),
currentValue.getOrder()));
} else {
// Only add the result **if** what they've
// typed is compatible *AND* they haven't
// already typed it in full
if (currentValue
.getValue()
.toLowerCase()
.startsWith(
lastOptionValue
.toLowerCase())
&& !lastOptionValue
.equalsIgnoreCase(currentValue
.getValue())
&& (lastOptionValue.length() < currentValue
.getValue().length())) {
results.add(new Completion(prefix
+ currentValue.getValue()
+ suffix, currentValue
.getFormattedValue(),
currentValue.getHeading(),
currentValue.getOrder()));
}
}
}
// ROO-389: give inline options given there's
// multiple choices available and we want to help
// the user
final StringBuilder help = new StringBuilder();
help.append(StringUtils.LINE_SEPARATOR);
help.append(option.mandatory() ? "required --"
: "optional --");
if ("".equals(option.help())) {
help.append(lastOptionKey).append(": ")
.append("No help available");
} else {
help.append(lastOptionKey).append(": ")
.append(option.help());
}
if (option.specifiedDefaultValue().equals(
option.unspecifiedDefaultValue())) {
if (option.specifiedDefaultValue().equals(
"__NULL__")) {
help.append("; no default value");
} else {
help.append("; default: '")
.append(option
.specifiedDefaultValue())
.append("'");
}
} else {
if (!"".equals(option.specifiedDefaultValue())
&& !"__NULL__".equals(option
.specifiedDefaultValue())) {
help.append(
"; default if option present: '")
.append(option
.specifiedDefaultValue())
.append("'");
}
if (!"".equals(option.unspecifiedDefaultValue())
&& !"__NULL__".equals(option
.unspecifiedDefaultValue())) {
help.append(
"; default if option not present: '")
.append(option
.unspecifiedDefaultValue())
.append("'");
}
}
LOGGER.info(help.toString());
if (results.size() == 1) {
final String suggestion = results.iterator()
.next().getValue().trim();
if (suggestion.equals(lastOptionValue)) {
// They have pressed TAB in the default
// value, and the default value has already
// been provided as an explicit option
return 0;
}
}
if (results.size() > 0) {
candidates.addAll(results);
// Values presented from the last space onwards
if (translated.endsWith(" ")) {
return translated.lastIndexOf(" ") + 1;
}
return translated.trim().lastIndexOf(" ");
}
return 0;
}
}
}
}
return 0;
}
}
/**
* populate completion for mandatory options
*
* @param translated
* user's input
* @param unspecified
* unspecified options
* @param value
* the option key
* @param results
* completion list
*/
private void handleMandatoryCompletion(final String translated,
final List<CliOption> unspecified, final String value,
final SortedSet<Completion> results) {
final StringBuilder strBuilder = new StringBuilder(translated);
if (!translated.endsWith(" ")) {
strBuilder.append(" ");
}
strBuilder.append("--");
strBuilder.append(value);
strBuilder.append(" ");
results.add(new Completion(strBuilder.toString()));
}
public Set<String> getEveryCommand() {
return this.commandsRegistry.getAllCommandNames();
}
}