// 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;
}
}
Validate.notNull(cliOption,
"CliOption not found for parameter '%s'",
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 : converters) {
// 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()) {
if (translated.endsWith(" ")) {
results.add(new Completion(translated + "--"
+ value + " "));
}
else {
results.add(new Completion(translated + " --"
+ value + " "));
}
}
}
}
// 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
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 : converters) {
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(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());