package org.springframework.roo.shell;
import static org.apache.commons.io.IOUtils.LINE_SEPARATOR;
import static org.springframework.roo.shell.CliOption.EMPTY;
import static org.springframework.roo.shell.CliOption.NULL;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.AnsiEscapeCode;
import org.springframework.roo.support.util.CollectionUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Default implementation of {@link Parser}.
*
* @author Ben Alex
* @author Juan Carlos GarcÃa
* @since 1.0
*/
public class SimpleParser implements Parser {
// ------------ OSGi component attributes ----------------
public BundleContext context;
private RooBundleActivator rooBundleActivator;
private Long lastTimeUpdateComponents;
private static final Comparator<Object> COMPARATOR = new NaturalOrderComparator<Object>();
private static final Logger LOGGER = HandlerUtils.getLogger(SimpleParser.class);
static String isMatch(final String buffer, final String command, final boolean strictMatching) {
if ("".equals(buffer.trim())) {
return "";
}
final String[] commandWords = StringUtils.split(command, " ");
int lastCommandWordUsed = 0;
Validate.notEmpty(commandWords, "Command required");
String bufferToReturn = null;
String lastWord = null;
next_buffer_loop: for (int bufferIndex = 0; bufferIndex < buffer.length(); bufferIndex++) {
final String bufferSoFarIncludingThis = buffer.substring(0, bufferIndex + 1);
final String bufferRemaining = buffer.substring(bufferIndex + 1);
final int bufferLastIndexOfWord = bufferSoFarIncludingThis.lastIndexOf(" ");
String wordSoFarIncludingThis = bufferSoFarIncludingThis;
if (bufferLastIndexOfWord != -1) {
wordSoFarIncludingThis = bufferSoFarIncludingThis.substring(bufferLastIndexOfWord);
}
if (wordSoFarIncludingThis.equals(" ") || bufferIndex == buffer.length() - 1) {
if (bufferIndex == buffer.length() - 1 && !"".equals(wordSoFarIncludingThis.trim())) {
lastWord = wordSoFarIncludingThis.trim();
}
// At end of word or buffer. Let's see if a word matched or not
for (int candidate = lastCommandWordUsed; candidate < commandWords.length; candidate++) {
if (lastWord != null && lastWord.length() > 0
&& commandWords[candidate].startsWith(lastWord)) {
if (bufferToReturn == null) {
// This is the first match, so ensure the intended
// match really represents the start of a command
// and not a later word within it
if (lastCommandWordUsed == 0 && candidate > 0) {
// This is not a valid match
break next_buffer_loop;
}
}
if (bufferToReturn != null) {
// We already matched something earlier, so ensure
// we didn't skip any word
if (candidate != lastCommandWordUsed + 1) {
// User has skipped a word
bufferToReturn = null;
break next_buffer_loop;
}
}
bufferToReturn = bufferRemaining;
lastCommandWordUsed = candidate;
if (candidate + 1 == commandWords.length) {
// This was a match for the final word in the
// command, so abort
break next_buffer_loop;
}
// There are more words left to potentially match, so
// continue
continue next_buffer_loop;
}
}
// This word is unrecognised as part of a command, so abort
bufferToReturn = null;
break next_buffer_loop;
}
lastWord = wordSoFarIncludingThis.trim();
}
// We only consider it a match if ALL words were actually used
if (bufferToReturn != null) {
if (!strictMatching || lastCommandWordUsed + 1 == commandWords.length) {
return bufferToReturn;
}
}
return null; // Not a match
}
private final Map<String, MethodTarget> availabilityIndicators =
new HashMap<String, MethodTarget>();
private final Map<String, MethodTarget> dynamicMandatoryIndicators =
new HashMap<String, MethodTarget>();
private final Map<String, MethodTarget> optionVisibilityIndicators =
new HashMap<String, MethodTarget>();
private final Map<String, MethodTarget> optionAutocompleteIndicators =
new HashMap<String, MethodTarget>();
private final Set<CommandMarker> commands = new HashSet<CommandMarker>();
private final Set<Converter<?>> converters = new HashSet<Converter<?>>();
// ROO-3697: Include global parameters in all Spring Roo commands.
private final List<String> globalParameters = new ArrayList<String>();
private final Object mutex = new Object();
public final void add(final CommandMarker command) {
synchronized (mutex) {
commands.add(command);
for (final Method method : command.getClass().getMethods()) {
// Getting method availability indicators
final CliAvailabilityIndicator availability =
method.getAnnotation(CliAvailabilityIndicator.class);
if (availability != null) {
Validate.isTrue(method.getParameterTypes().length == 0,
"CliAvailabilityIndicator is only legal for 0 parameter methods ('%s')",
method.toGenericString());
Validate.isTrue(method.getReturnType().equals(Boolean.TYPE),
"CliAvailabilityIndicator is only legal for primitive boolean return types (%s)",
method.toGenericString());
for (final String cmd : availability.value()) {
Validate.isTrue(!availabilityIndicators.containsKey(cmd),
"Cannot specify an availability indicator for '%s' more than once", cmd);
availabilityIndicators.put(cmd, new MethodTarget(method, command));
}
}
// Getting method dynamicMandatory indicators
final CliOptionMandatoryIndicator dynamicMandatoryIndicator =
method.getAnnotation(CliOptionMandatoryIndicator.class);
if (dynamicMandatoryIndicator != null) {
Validate.isTrue(method.getParameterTypes().length < 2,
"CliDynamicMandatoryIndicator is only legal for 0 or 1 parameter methods ('%s')",
method.toGenericString());
if (method.getParameterTypes().length == 1) {
Validate.isTrue(method.getParameterTypes()[0].equals(ShellContext.class),
"CliDynamicMandatoryIndicator could only receive a ShellContext parameter ('%s')",
method.toGenericString());
}
Validate.isTrue(method.getReturnType().equals(Boolean.TYPE),
"CliDynamicMandatoryIndicator is only legal for primitive boolean return types (%s)",
method.toGenericString());
for (String param : dynamicMandatoryIndicator.params()) {
dynamicMandatoryIndicators.put(
dynamicMandatoryIndicator.command().concat("|").concat(param), new MethodTarget(
method, command));
}
}
// Getting method options visibility indicators
final CliOptionVisibilityIndicator optionVisibilityIndicator =
method.getAnnotation(CliOptionVisibilityIndicator.class);
if (optionVisibilityIndicator != null) {
Validate.isTrue(method.getParameterTypes().length == 1,
"CliOptionVisibilityIndicator must receive a ShellContext parameter ('%s')",
method.toGenericString());
Validate.isTrue(method.getParameterTypes()[0].equals(ShellContext.class),
"CliOptionVisibilityIndicator must receive a ShellContext parameter ('%s')",
method.toGenericString());
Validate.isTrue(method.getReturnType().equals(Boolean.TYPE),
"CliOptionVisibilityIndicator is only legal for primitive boolean return types (%s)",
method.toGenericString());
for (String param : optionVisibilityIndicator.params()) {
optionVisibilityIndicators.put(
optionVisibilityIndicator.command().concat("|").concat(param), new MethodTarget(
method, command));
}
}
// Getting method option autocomplete indicators
final CliOptionAutocompleteIndicator optionAutocompleteIndicator =
method.getAnnotation(CliOptionAutocompleteIndicator.class);
if (optionAutocompleteIndicator != null) {
Validate.isTrue(
(method.getParameterTypes().length == 0 || method.getParameterTypes().length == 1),
"CliOptionAutocompleteIndicator must receive 0 or 1 ShellContext ('%s')",
method.toGenericString());
if (method.getParameterTypes().length == 1) {
// Validate first parameter is ShellContext if any
Validate
.isTrue(
method.getParameterTypes()[0].equals(ShellContext.class),
"CliOptionAutocompleteIndicator must receive a ShellContext as first parameter ('%s')",
method.toGenericString());
}
// Checking the return type is a List<String>
Validate
.isTrue(method.getReturnType().isAssignableFrom(List.class),
"CliOptionAutocompleteIndicator must return a List<String>",
method.toGenericString());
// Add method to option autocomplete indicators
optionAutocompleteIndicators.put(optionAutocompleteIndicator.command().concat("|")
.concat(optionAutocompleteIndicator.param()), new MethodTarget(method, command));
}
// ROO-3697: Including global parameters.
globalParameters.add("force");
globalParameters.add("profile");
}
}
}
public final void add(final Converter<?> converter) {
synchronized (mutex) {
converters.add(converter);
}
}
protected void commandNotFound(final Logger logger, final String buffer) {
logger.warning("Command '" + buffer + "' not found (for assistance press "
+ AbstractShell.completionKeys + " or type \"hint\" then hit ENTER)");
}
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;
}
public int completeAdvanced(String buffer, int cursor, final List<Completion> candidates) {
synchronized (mutex) {
// Create ShellContext
ShellContextImpl shellContext = new ShellContextImpl();
// Set command written input until now
shellContext.setExecutedCommand(buffer);
// ROO-3622: Validate if version change
if (isDifferentVersion()) {
return 0;
}
// Loading converters and commands if needed
loadConvertersAndCommands();
Validate.notNull(buffer, "Buffer required");
Validate.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.replace(buffer, " ", " ", 1);
cursor--;
}
// Replace extra "-" by only "--"
while (buffer.contains("---")) {
buffer = StringUtils.replace(buffer, "---", "--", 1);
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 = locateTargets(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);
Validate.notNull(cmd, "CliCommand unavailable for '%s'", 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;
}
// Set options as ShellContext parameters
for (Entry<String, String> entry : options.entrySet()) {
shellContext.setParameter(entry.getKey(), entry.getValue());
}
// 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)) {
// No space at the end, as there's no need to continue
// the command further
results.add(new Completion(value));
}
}
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;
}
}
// ROO-3697: Since Spring Roo 2.0, you could define a parameter
// in methods without @CliOption annotation. This parameter will
// not be
// included on cliOptions list
if (cliOption != null) {
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 ((translated.endsWith(" ") || (translated.endsWith(" -") || translated.endsWith(" --")))
&& (!"".equals(lastOptionValue) || lastOptionKey == null)) {
suggestOptionKey(shellContext, translated, results, methodTarget, options, unspecified,
lastOptionValue, alreadySpecified);
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) {
// First check visibility
if (isVisibleParam(methodTarget.getKey(), include, shellContext)) {
for (final String value : include.key()) {
// Manually determine if this non-mandatory but
// unspecifiedDefaultValue=* requiring option is
// able to
// be bound
if (!isMandatoryParam(methodTarget.getKey(), include, shellContext)
&& "*".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)
&& isMandatoryParam(methodTarget.getKey(), include, shellContext)) {
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 supposing the last value was
// wanted as null ("")
if ((methodTarget.getRemainingBuffer().endsWith("--") || methodTarget.getRemainingBuffer()
.endsWith(" -")) && "".equals(lastOptionValue)) {
suggestOptionKey(shellContext, translated, results, methodTarget, options, unspecified,
lastOptionValue, alreadySpecified);
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
// Check if there are still dynamic mandatory options
List<CliOption> remainingMandatories = new ArrayList<CliOption>();
for (final CliOption include : unspecified) {
if (isMandatoryParam(methodTarget.getKey(), include, shellContext)) {
remainingMandatories.add(include);
}
}
// Check if user is still writing the last mandatory key
// which will already be in the last position of
// "alreadySpecified"
if (alreadySpecified.size() > 0) {
List<String> paramsKeys =
Arrays.asList(alreadySpecified.get(alreadySpecified.size() - 1).key());
if (paramsKeys.contains(lastOptionKey)) {
// User is still writing mandatory
remainingMandatories.add(alreadySpecified.get(alreadySpecified.size() - 1));
}
}
if (remainingMandatories.size() != 0) {
// Complete with remaining mandatory keys
for (final CliOption option : remainingMandatories) {
// Check option's visibility
if (isVisibleParam(methodTarget.getKey(), option, shellContext)) {
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));
}
}
}
}
} else {
// Complete with optional keys or global parameters
for (final CliOption option : unspecified) {
// Check option's visibility
if (isVisibleParam(methodTarget.getKey(), option, shellContext)) {
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));
}
}
}
}
// ROO-3697: check if current key completion is a
// globalParameter
if (hasShellContextParameter(methodTarget.getMethod())) {
for (final String parameter : globalParameters) {
if (parameter != null && lastOptionKey != null
&& parameter.regionMatches(true, 0, lastOptionKey, 0, lastOptionKey.length())) {
final String completionValue =
translated.substring(0, translated.length() - lastOptionKey.length())
+ parameter + " ";
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 Class<?> parameterType = parameterTypes[i];
// ROO-3697: Check if parameter type is ShellContext
if (parameterType.isAssignableFrom(ShellContext.class)) {
break;
}
final CliOption option = cliOptions.get(i);
for (final String key : option.key()) {
if (key.equals(lastOptionKey)) {
// Check if CliOption is boolean mandatory and has specific value
if (!option.specifiedDefaultValue().equals(CliOption.NULL)
&& parameterType.equals(boolean.class)) {
// Complete with next option keys and let current key with default value
suggestOptionKey(shellContext, translated, results, methodTarget, options,
unspecified, lastOptionValue, alreadySpecified);
candidates.addAll(results);
return 0;
}
final List<Completion> allValues = new ArrayList<Completion>();
String suffix = " ";
// First, use an autocomplete indicator if any
List<String> values =
getPossibleValuesByIndicator(methodTarget.getKey(), option, shellContext);
if (values != null) {
if (!hasToIncludeSpaceOnFinish(methodTarget.getKey(), option)) {
suffix = "";
}
for (String value : values) {
allValues.add(getCompletion(value));
}
} else {
// 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(isMandatoryParam(methodTarget.getKey(), option, shellContext) ? "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;
}
}
/**
* This method obtains the Completion by the provided value.
*
* If the provided value contains ":" means that is necessary to include
* heading with the module.
*
* @param value possible value to show on auto-completion
*
* @return Completion with all necessary elements
*/
private Completion getCompletion(String value) {
// Obtaining module that always be before :
if (value.split(":").length > 1 && AnsiEscapeCode.isAnsiEnabled()) {
String module = value.split(":")[0];
return new Completion(value, value.replace(module.concat(":"), ""), module, 0);
} else {
return new Completion(value);
}
}
/**
* Suggests command option keys when the user try to autocomplete.
*
* @param shellContext
* @param translated
* @param results
* @param methodTarget
* @param options
* @param unspecified
*/
private void suggestOptionKey(ShellContextImpl shellContext, final String translated,
final SortedSet<Completion> results, final MethodTarget methodTarget,
Map<String, String> options, final List<CliOption> unspecified, String lastOptionValue,
List<CliOption> alreadySpecified) {
boolean showAllRemaining = true;
// Check if there are still dynamic mandatory options
for (final CliOption include : unspecified) {
if (isMandatoryParam(methodTarget.getKey(), include, shellContext)) {
showAllRemaining = false;
break;
}
}
// If there is a CliOption with void key and user only write a "-", last
// value will be "-" so is needed to complete with the secondary value of the key
if (lastOptionValue != null && lastOptionValue.equals("-") && showAllRemaining == true) {
for (CliOption option : alreadySpecified) {
if (option.key().length > 1) {
for (String key : option.key()) {
if (!("").equals(key)) {
results.add(new Completion(translated.concat("-").concat(key).concat(" ")));
showAllRemaining = false;
}
}
}
}
}
// Exists some mandatory param, so we need to show it first if
// it is visible
if (!showAllRemaining) {
for (final CliOption include : unspecified) {
if (isMandatoryParam(methodTarget.getKey(), include, shellContext)
&& isVisibleParam(methodTarget.getKey(), include, shellContext)) {
// Auto complete with that mandatory key
for (final String value : include.key()) {
if (!"".equals(value)) {
// Check if should complete with "--" prefix
if (methodTarget.getRemainingBuffer().endsWith("--")) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat(value)
.concat(" ")));
} else if (methodTarget.getRemainingBuffer().endsWith(" -")
|| methodTarget.getRemainingBuffer().length() == 1) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat("-")
.concat(value).concat(" ")));
} else {
results.add(new Completion(translated.concat("--").concat(value).concat(" ")));
}
}
}
break;
}
}
} else {
// No more mandatory options remaining
for (final CliOption include : unspecified) {
if (isVisibleParam(methodTarget.getKey(), include, shellContext)) {
// Auto complete with that key
for (final String value : include.key()) {
if (!"".equals(value)) {
// Check if should complete with "--" prefix
if (methodTarget.getRemainingBuffer().endsWith("--")) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat(value)
.concat(" ")));
} else if (methodTarget.getRemainingBuffer().endsWith(" -")
|| methodTarget.getRemainingBuffer().length() == 1) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat("-")
.concat(value).concat(" ")));
} else {
results.add(new Completion(translated.concat("--").concat(value).concat(" ")));
}
}
}
}
}
// ROO-3697: Including global parameters in all Spring Roo
// commands
// if all mandatory parameters have been defined and exists
// a ShellContext
// parameter defined on last position of current method.
if (hasShellContextParameter(methodTarget.getMethod())) {
for (String parameter : globalParameters) {
// Check if this global parameter is already defined
if (!options.containsKey(parameter)) {
if (!"".equals(parameter)) {
// Check if should complete with "--" prefix
if (methodTarget.getRemainingBuffer().endsWith("--")) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat(parameter)
.concat(" ")));
} else if (methodTarget.getRemainingBuffer().endsWith(" -")
|| methodTarget.getRemainingBuffer().length() == 1) {
results.add(new Completion(StringUtils.stripEnd(translated, null).concat("-")
.concat(parameter).concat(" ")));
} else {
results.add(new Completion(translated.concat("--").concat(parameter).concat(" ")));
}
}
}
}
}
}
}
private MethodTarget getAvailabilityIndicator(final String command) {
return availabilityIndicators.get(command);
}
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;
}
/**
* This method loads converters and commands if needed
*/
public void loadConvertersAndCommands() {
synchronized (mutex) {
boolean someComponentChanges = false;
if (commands.isEmpty() || hasToReloadComponents()) {
// Cleaning commands and indicators
commands.clear();
availabilityIndicators.clear();
// Get all Services implement CommandMarker interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(CommandMarker.class.getName(), null);
for (ServiceReference<?> ref : references) {
CommandMarker command = (CommandMarker) this.context.getService(ref);
add(command);
}
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load CommandMarker on SimpleParser.");
}
someComponentChanges = true;
}
if (converters.isEmpty() || hasToReloadComponents()) {
// Cleaning converters
converters.clear();
// Get all Services implement Converter interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(Converter.class.getName(), null);
for (ServiceReference<?> ref : references) {
Converter<?> converter = (Converter<?>) this.context.getService(ref);
add(converter);
}
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load Converter on SimpleParser.");
}
someComponentChanges = true;
}
if (someComponentChanges) {
setLasTimeUpdateComponents(System.currentTimeMillis());
}
}
}
public Set<String> getEveryCommand() {
synchronized (mutex) {
loadConvertersAndCommands();
// Return commands list
final SortedSet<String> result = new TreeSet<String>(COMPARATOR);
for (final Object o : commands) {
final Method[] methods = o.getClass().getMethods();
for (final Method m : methods) {
final CliCommand cmd = m.getAnnotation(CliCommand.class);
if (cmd != null) {
result.addAll(Arrays.asList(cmd.value()));
}
}
}
return result;
}
}
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);
}
}
final Set<String> unavailableOptions = new LinkedHashSet<String>();
for (final String suppliedOption : options.keySet()) {
// ROO-3697: Check if current parameter is a global parameter.
boolean isGlobalParameter = globalParameters.contains(suppliedOption);
if (!cliOptionKeySet.contains(suppliedOption) && !isGlobalParameter) {
unavailableOptions.add(suppliedOption);
}
}
return unavailableOptions;
}
private Collection<MethodTarget> locateTargets(final String buffer, final boolean strictMatching,
final boolean checkAvailabilityIndicators) {
if (commands.isEmpty() || hasToReloadComponents()) {
// Cleaning commands
commands.clear();
availabilityIndicators.clear();
// Get all Services implement CommandMarker interface
try {
ServiceReference<?>[] references =
this.context.getAllServiceReferences(CommandMarker.class.getName(), null);
for (ServiceReference<?> ref : references) {
CommandMarker command = (CommandMarker) this.context.getService(ref);
add(command);
}
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load CommandMarker on SimpleParser.");
}
setLasTimeUpdateComponents(System.currentTimeMillis());
}
Validate.notNull(buffer, "Buffer required");
final Collection<MethodTarget> result = new HashSet<MethodTarget>();
// The reflection could certainly be optimised, but it's good enough for
// now (and cached reflection
// is unlikely to be noticeable to a human being using the CLI)
for (final CommandMarker command : commands) {
for (final Method method : command.getClass().getMethods()) {
final CliCommand cmd = method.getAnnotation(CliCommand.class);
if (cmd != null) {
// We have a @CliCommand.
if (checkAvailabilityIndicators) {
// Decide if this @CliCommand is available at this
// moment
Boolean available = null;
for (final String value : cmd.value()) {
final MethodTarget mt = getAvailabilityIndicator(value);
if (mt != null) {
Validate.isTrue(
available == null,
"More than one availability indicator is defined for '"
+ method.toGenericString() + "'");
try {
available = (Boolean) mt.getMethod().invoke(mt.getTarget());
// We should "break" here, but we loop over
// all to ensure no conflicting availability
// indicators are defined
} catch (final Exception e) {
available = false;
}
}
}
// Skip this @CliCommand if it's not available
if (available != null && !available) {
continue;
}
}
for (final String value : cmd.value()) {
final String remainingBuffer = isMatch(buffer, value, strictMatching);
if (remainingBuffer != null) {
result.add(new MethodTarget(method, command, remainingBuffer, value));
}
}
}
}
}
return result;
}
/**
* 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();
}
public ParseResult parse(final String rawInput) {
synchronized (mutex) {
// Load converters and commands if needed
loadConvertersAndCommands();
Validate.notNull(rawInput, "Raw input required");
final String input = normalise(rawInput);
// Locate the applicable targets which match this buffer
final Collection<MethodTarget> matchingTargets = locateTargets(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, locateTargets(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(StringUtils.defaultIfBlank(ExceptionUtils.getRootCauseMessage(e),
e.getMessage()));
return null;
}
// Create ShellContext for checking visibility
ShellContextImpl shellContext = new ShellContextImpl();
// Save typed parameters
for (Entry<String, String> option : options.entrySet()) {
String parameter = option.getKey();
String value = option.getValue();
shellContext.setParameter(parameter, value);
}
final Set<CliOption> cliOptions = getCliOptions(parameterAnnotations);
for (final CliOption cliOption : cliOptions) {
final Class<?> requiredType =
methodTarget.getMethod().getParameterTypes()[arguments.size()];
// Validate visibility and values
if (options.keySet().contains(cliOption.key()[0])) {
checkVisibilityAndValues(methodTarget, options, shellContext, cliOption);
}
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
if (StringUtils.isBlank(value)
&& isMandatoryParam(methodTarget.getKey(), cliOption, shellContext)) {
if ("".equals(cliOption.key()[0])) {
final StringBuilder message = new StringBuilder("You must 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());
return null;
} else if (!NULL.equals(cliOption.specifiedDefaultValue()) && value != null) {
// Just continue execution
} else {
LOGGER.warning("You must specify option '" + cliOption.key()[0] + "' for this command");
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.join(cliOption.key(), ",") + "'");
return null;
}
arguments.add(null);
continue;
}
// Change the empty string marker back into an empty string now
// that we are passed the default and null value checks.
if (EMPTY.equals(value)) {
value = "";
}
// 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 : converters) {
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 && isMandatoryParam(methodTarget.getKey(), cliOption, shellContext)) {
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.join(cliOption.key(), ",") + "'");
if (StringUtils.isNotBlank(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(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;
}
// ROO-3697: Use shellContext to save current shell parameters if
// method contains ShellContext parameter
if (hasShellContextParameter(methodTarget.getMethod())) {
// Save executed command
shellContext.setExecutedCommand(input);
// Adding shellContext to command arguments
arguments.add(shellContext);
}
return new ParseResult(methodTarget.getMethod(), methodTarget.getTarget(),
arguments.toArray());
}
}
/**
* Check if executed command has compatible options and values based on its
* indicators
*
* @param methodTarget
* @param options
* @param shellContext
* @param cliOption
*/
private void checkVisibilityAndValues(final MethodTarget methodTarget,
Map<String, String> options, ShellContextImpl shellContext, final CliOption cliOption) {
// Ensure the user didn't specified a not visible value
if (!this.isVisibleParam(methodTarget.getKey(), cliOption, shellContext)) {
MethodTarget optionVisibilityIndicator =
optionVisibilityIndicators.get(methodTarget.getKey().concat("|")
.concat(cliOption.key()[0]));
// The user specified incompatible options
if (optionVisibilityIndicator != null && optionVisibilityIndicator.getMethod() != null) {
CliOptionVisibilityIndicator visibilityIndicator =
optionVisibilityIndicator.getMethod().getAnnotation(CliOptionVisibilityIndicator.class);
// Get visibility indicator help message
if (visibilityIndicator != null) {
LOGGER.warning(visibilityIndicator.help());
throw new RuntimeException();
}
}
}
// Ensure the user didn't specified an incorrect value if necessary
if (autocompleteNeedsValidation(methodTarget.getKey(), cliOption)) {
String value = options.get(cliOption.key()[0]);
List<String> possibleValues =
getPossibleValuesByIndicator(methodTarget.getKey(), cliOption, shellContext);
if (possibleValues != null && !possibleValues.contains(value)) {
// The user specified an incorrect value
MethodTarget optionAutocompleteIndicator =
optionAutocompleteIndicators.get(methodTarget.getKey().concat("|")
.concat(cliOption.key()[0]));
if (optionAutocompleteIndicator != null && optionAutocompleteIndicator.getMethod() != null) {
CliOptionAutocompleteIndicator autocompleteIndicator =
optionAutocompleteIndicator.getMethod().getAnnotation(
CliOptionAutocompleteIndicator.class);
// Get autocomplete indicator help message
if (autocompleteIndicator != null) {
LOGGER.warning(autocompleteIndicator.help());
throw new RuntimeException();
}
}
}
}
}
/**
* Checks if some method has the ShellContext parameter
*
* @param method
* @return
*/
private boolean hasShellContextParameter(Method method) {
int paramNumbers = method.getParameterTypes().length;
int shellContextPosition = 1;
for (Class<?> methodParameters : method.getParameterTypes()) {
if (methodParameters.isAssignableFrom(ShellContext.class)) {
if (shellContextPosition != paramNumbers) {
String msg =
String
.format(
"ShellContext parameter is on position '%s' but should be defined after all '@CliOption' parameters.",
shellContextPosition);
throw new RuntimeException(msg);
}
return true;
}
shellContextPosition++;
}
return false;
}
/**
* Method that checks if given method attribute is mandatory. Takes in count
* if this option could be dynamicMandatory.
*
* @param command
* @param cliOption
* @param shellContext
* @return
*/
private boolean isMandatoryParam(String command, CliOption cliOption, ShellContext shellContext) {
if (cliOption.mandatory()) {
String[] option = cliOption.key();
try {
MethodTarget dynamicMandatoryIndicator =
dynamicMandatoryIndicators.get(command.concat("|").concat(option[0]));
if (dynamicMandatoryIndicator == null) {
return cliOption.mandatory();
} else {
if (dynamicMandatoryIndicator.getMethod().getParameterTypes().length == 1) {
return (Boolean) dynamicMandatoryIndicator.getMethod().invoke(
dynamicMandatoryIndicator.getTarget(), shellContext);
}
return (Boolean) dynamicMandatoryIndicator.getMethod().invoke(
dynamicMandatoryIndicator.getTarget());
}
} catch (Exception e) {
throw new RuntimeException(
String
.format(
"ERROR: Error trying to get mandatory value for '%s' option on '%s' command. Please, fix errors on indicator method.",
option[0], command));
}
} else {
return cliOption.mandatory();
}
}
/**
* Method that checks if given method attribute is visible in the shell.
* Takes in count the dependencies with other parameters in same command.
*
* @param command
* @param cliOption
* @param shellContext
* @return
*/
private boolean isVisibleParam(String command, CliOption cliOption, ShellContextImpl shellContext) {
String[] option = cliOption.key();
try {
MethodTarget optionVisibilityIndicator =
optionVisibilityIndicators.get(command.concat("|").concat(option[0]));
if (optionVisibilityIndicator == null) {
return true;
} else {
return (Boolean) optionVisibilityIndicator.getMethod().invoke(
optionVisibilityIndicator.getTarget(), shellContext);
}
} catch (Exception e) {
throw new RuntimeException(
String
.format(
"ERROR: Error trying to get option visibility value for '%s' option on '%s' command. Please, fix errors on indicator method.",
option[0], command));
}
}
/**
* Method that returns the autocompletion list of values for a command's
* param, if any
*
* @param command
* @param cliOption
* @param shellContext
* @return List<String> with the possible values, or null if that parameter
* hasn't an autocompletion indicator
*/
@SuppressWarnings("unchecked")
private List<String> getPossibleValuesByIndicator(String command, CliOption cliOption,
ShellContextImpl shellContext) {
String[] option = cliOption.key();
try {
MethodTarget optionAutocompleteIndicator =
optionAutocompleteIndicators.get(command.concat("|").concat(option[0]));
if (optionAutocompleteIndicator == null) {
return null;
} else {
if (optionAutocompleteIndicator.getMethod().getParameterTypes().length == 0) {
return (List<String>) optionAutocompleteIndicator.getMethod().invoke(
optionAutocompleteIndicator.getTarget());
} else {
return (List<String>) optionAutocompleteIndicator.getMethod().invoke(
optionAutocompleteIndicator.getTarget(), shellContext);
}
}
} catch (Exception e) {
throw new RuntimeException(
String
.format(
"ERROR: Error trying to get autocomplete values for '%s' option on '%s' command. Please, fix errors on indicator method and also be sure return type is a List<String>",
option[0], command));
}
}
private boolean hasToIncludeSpaceOnFinish(String command, CliOption cliOption) {
String[] option = cliOption.key();
try {
MethodTarget optionAutocompleteIndicator =
optionAutocompleteIndicators.get(command.concat("|").concat(option[0]));
if (optionAutocompleteIndicator != null) {
CliOptionAutocompleteIndicator autocompleteIndicator =
optionAutocompleteIndicator.getMethod().getAnnotation(
CliOptionAutocompleteIndicator.class);
return autocompleteIndicator.includeSpaceOnFinish();
}
return true;
} catch (Exception e) {
throw new RuntimeException(
String
.format(
"ERROR: Error trying to get autocomplete values for '%s' option on '%s' command. Please, fix errors on indicator method and also be sure return type is a List<String>",
option[0], command));
}
}
/**
* Checks if exists {@link CliOptionAutocompleteIndicator} for a {@link CliOption} and
* returns if value specified for the option must be validated with autocomplete results
*
* @param command
* @param cliOption
* @return <code>true</code> if value specified shoud be validated with indicator return values
*/
private boolean autocompleteNeedsValidation(String command, CliOption cliOption) {
String[] option = cliOption.key();
try {
MethodTarget optionAutocompleteIndicator =
optionAutocompleteIndicators.get(command.concat("|").concat(option[0]));
if (optionAutocompleteIndicator != null) {
CliOptionAutocompleteIndicator autocompleteIndicator =
optionAutocompleteIndicator.getMethod().getAnnotation(
CliOptionAutocompleteIndicator.class);
return autocompleteIndicator.validate();
}
return true;
} catch (Exception e) {
throw new RuntimeException(
String
.format(
"ERROR: Error trying to get autocomplete values for '%s' option on '%s' command. Please, fix errors on indicator method and also be sure return type is a List<String>",
option[0], command));
}
}
private String collectionToDelimitedString(final Collection<?> coll, final String delim,
final String prefix, final String suffix) {
if (CollectionUtils.isEmpty(coll)) {
return "";
}
final StringBuilder sb = new StringBuilder();
final Iterator<?> it = coll.iterator();
while (it.hasNext()) {
sb.append(prefix).append(it.next()).append(suffix);
if (it.hasNext() && delim != null) {
sb.append(delim);
}
}
return sb.toString();
}
public final void remove(final CommandMarker command) {
synchronized (mutex) {
commands.remove(command);
for (final Method m : command.getClass().getMethods()) {
final CliAvailabilityIndicator availability =
m.getAnnotation(CliAvailabilityIndicator.class);
if (availability != null) {
for (final String cmd : availability.value()) {
availabilityIndicators.remove(cmd);
}
}
}
}
}
public final void remove(final Converter<?> converter) {
synchronized (mutex) {
converters.remove(converter);
}
}
/**
* This method compares RooBundleActivator lastTimeBundleChange with
* SimpleParser lasTimeUpdateCommands
*
* @return
*/
private boolean hasToReloadComponents() {
if (getRooBundleActivator() != null) {
return getRooBundleActivator().getLastTimeBundleChange() > getLasTimeUpdateComponents();
}
return true;
}
/**
* @return the lasTimeUpdateCommands
*/
public Long getLasTimeUpdateComponents() {
return lastTimeUpdateComponents == null ? Long.MIN_VALUE : lastTimeUpdateComponents;
}
/**
* @param lasTimeUpdateCommands the lasTimeUpdateCommands to set
*/
public void setLasTimeUpdateComponents(Long lastTimeUpdateComponents) {
this.lastTimeUpdateComponents = lastTimeUpdateComponents;
}
/**
* Obtains all Services that implements RooBundleActivator
*
* @return
*/
public RooBundleActivator getRooBundleActivator() {
if (rooBundleActivator == null) {
// Get all Services implement RooBundleActivator interface
try {
ServiceReference<?>[] references =
context.getAllServiceReferences(RooBundleActivator.class.getName(), null);
for (ServiceReference<?> ref : references) {
rooBundleActivator = (RooBundleActivator) context.getService(ref);
return rooBundleActivator;
}
return null;
} catch (InvalidSyntaxException e) {
LOGGER.warning("Cannot load RooBundleActivator on SimpleParser.");
return null;
}
} else {
return rooBundleActivator;
}
}
private boolean isDifferentVersion() {
String rooVersion = getRooProjectVersion();
if ("UNKNOWN".equals(rooVersion)) {
return false;
}
return !rooVersion.equals(versionInfoWithoutGit());
}
private String getRooProjectVersion() {
String homePath = new File(".").getPath();
String pomPath = homePath + "/pom.xml";
File pom = new File(pomPath);
try {
if (pom.exists()) {
InputStream is = new FileInputStream(pom);
Document docXml = XmlUtils.readXml(is);
Element document = docXml.getDocumentElement();
Element rooVersionElement = XmlUtils.findFirstElement("properties/roo.version", document);
String rooVersion = rooVersionElement.getTextContent();
return rooVersion;
}
return "UNKNOWN";
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return "";
}
public static String versionInfoWithoutGit() {
// Try to determine the bundle version
String bundleVersion = null;
JarFile jarFile = null;
try {
final URL classContainer =
AbstractShell.class.getProtectionDomain().getCodeSource().getLocation();
if (classContainer.toString().endsWith(".jar")) {
// Attempt to obtain the "Bundle-Version" version from the
// manifest
jarFile = new JarFile(new File(classContainer.toURI()), false);
final ZipEntry manifestEntry = jarFile.getEntry("META-INF/MANIFEST.MF");
final Manifest manifest = new Manifest(jarFile.getInputStream(manifestEntry));
bundleVersion = manifest.getMainAttributes().getValue("Bundle-Version");
}
} catch (final IOException ignoreAndMoveOn) {
} catch (final URISyntaxException ignoreAndMoveOn) {
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (final IOException ignored) {
}
}
}
final StringBuilder sb = new StringBuilder();
if (bundleVersion != null) {
sb.append(bundleVersion);
}
if (sb.length() == 0) {
sb.append("UNKNOWN VERSION");
}
return sb.toString();
}
}