/** * Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception * in case of overloaded method. */ public static MethodTarget of(String name, Object bean, Help help, Supplier<Availability> availabilityIndicator) { Set<Method> found = new HashSet<>(); ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name)); if (found.size() != 1) { throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s", name, bean.getClass(), found)); } return new MethodTarget(found.iterator().next(), bean, help, availabilityIndicator); }
private boolean isAvailable(MethodTarget methodTarget) { return methodTarget.getAvailability().isAvailable(); }
private List<ParameterDescription> getParameterDescriptions(MethodTarget methodTarget) { return Utils.createMethodParameters(methodTarget.getMethod()) .flatMap(mp -> parameterResolvers.stream().filter(pr -> pr.supports(mp)).limit(1L) .flatMap(pr -> pr.describe(mp))) .collect(Collectors.toList()); }
private void validateArgs(Object[] args, MethodTarget methodTarget) { for (int i = 0; i < args.length; i++) { if (args[i] == UNRESOLVED) { MethodParameter methodParameter = Utils.createMethodParameter(methodTarget.getMethod(), i); throw new IllegalStateException("Could not resolve " + methodParameter); } } Set<ConstraintViolation<Object>> constraintViolations = validator.forExecutables().validateParameters( methodTarget.getBean(), methodTarget.getMethod(), args); if (constraintViolations.size() > 0) { throw new ParameterValidationException(constraintViolations, methodTarget); } }
if (command != null) { MethodTarget methodTarget = methodTargets.get(command); Availability availability = methodTarget.getAvailability(); if (availability.isAvailable()) { List<String> wordsForArgs = wordsForArguments(command, words); Method method = methodTarget.getMethod(); validateArgs(args, methodTarget); return ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
private CharSequence listCommands() { Map<String, MethodTarget> commandsByName = commandRegistry.listCommands(); SortedMap<String, Map<String, MethodTarget>> commandsByGroupAndName = commandsByName.entrySet().stream() .collect(groupingBy(e -> e.getValue().getGroup(), TreeMap::new, // group by and sort by command group toMap(Entry::getKey, Entry::getValue))); AttributedStringBuilder result = new AttributedStringBuilder(); result.append("AVAILABLE COMMANDS\n\n", AttributedStyle.BOLD); // display groups, sorted alphabetically, "Default" first commandsByGroupAndName.forEach((group, commandsInGroup) -> { result.append("".equals(group) ? "Default" : group, AttributedStyle.BOLD).append('\n'); Map<MethodTarget, SortedSet<String>> commandNamesByMethod = commandsInGroup.entrySet().stream() .collect(groupingBy(Entry::getValue, // group by command method mapping(Entry::getKey, toCollection(TreeSet::new)))); // sort command names // display commands, sorted alphabetically by their first alias commandNamesByMethod.entrySet().stream().sorted(sortByFirstCommandName()).forEach(e -> { result .append(isAvailable(e.getKey()) ? " " : " * ") .append(String.join(", ", e.getValue()), AttributedStyle.BOLD) .append(": ") .append(e.getKey().getHelp()) .append('\n'); }); result.append('\n'); }); if (commandsByName.values().stream().distinct().anyMatch(m -> !isAvailable(m))) { result.append("Commands marked with (*) are currently unavailable.\nType `help <command>` to learn more.\n\n"); } return result; }
private CompletionProposal toCommandProposal(String command, MethodTarget methodTarget) { return new CompletionProposal(command) .dontQuote(true) .category("Available commands") .description(methodTarget.getHelp()); }
/** * Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception * in case of overloaded method. */ public static MethodTarget of(String name, Object bean, Help help) { return of(name, bean, help, null); }
private void documentAliases(AttributedStringBuilder result, String command, MethodTarget methodTarget) { Set<String> aliases = commandRegistry.listCommands().entrySet().stream() .filter(e -> e.getValue().equals(methodTarget)) .map(Map.Entry::getKey) .filter(c -> !command.equals(c)) .collect(toCollection(TreeSet::new)); if (!aliases.isEmpty()) { result.append("ALSO KNOWN AS", AttributedStyle.BOLD).append("\n"); for (String alias : aliases) { result.append('\t').append(alias).append('\n'); } } }
/** * Return a description of a specific command. Uses a layout inspired by *nix man pages. */ private CharSequence documentCommand(String command) { MethodTarget methodTarget = commandRegistry.listCommands().get(command); if (methodTarget == null) { throw new IllegalArgumentException("Unknown command '" + command + "'"); } AttributedStringBuilder result = new AttributedStringBuilder().append("\n\n"); List<ParameterDescription> parameterDescriptions = getParameterDescriptions(methodTarget); // NAME documentCommandName(result, command, methodTarget.getHelp()); // SYNOPSYS documentSynopsys(result, command, parameterDescriptions); // OPTIONS documentOptions(result, parameterDescriptions); // ALSO KNOWN AS documentAliases(result, command, methodTarget); // AVAILABILITY documentAvailability(result, methodTarget); result.append("\n"); return result; }
/** * Verifies that we have at least one {@link ParameterResolver} that supports each of the * {@link MethodParameter}s in the method. */ private void validateParameterResolvers(MethodTarget methodTarget) { Utils.createMethodParameters(methodTarget.getMethod()) .forEach(parameter -> { parameterResolvers.stream() .filter(resolver -> resolver.supports(parameter)) .findFirst() .orElseThrow(() -> new ParameterResolverMissingException(parameter)); }); }
private void documentAvailability(AttributedStringBuilder result, MethodTarget methodTarget) { Availability availability = methodTarget.getAvailability(); if (!availability.isAvailable()) { result.append("CURRENTLY UNAVAILABLE", AttributedStyle.BOLD).append("\n"); result.append('\t').append("This command is currently not available because ") .append(availability.getReason()) .append(".\n"); } }
@Override public void register(ConfigurableCommandRegistry registry) { Map<String, CommandMarker> beans = applicationContext.getBeansOfType(CommandMarker.class); for (Object bean : beans.values()) { Class<?> clazz = bean.getClass(); ReflectionUtils.doWithMethods(clazz, method -> { CliCommand cliCommand = method.getAnnotation(CliCommand.class); for (String key : cliCommand.value()) { Supplier<Availability> availabilityIndicator = bridgeAvailabilityIndicator(key, bean); MethodTarget target = new MethodTarget(method, bean, cliCommand.help(), availabilityIndicator); registry.register(key, target); commands.put(key, target); } }, method -> method.getAnnotation(CliCommand.class) != null); } }
/** * Gather completion proposals given some (incomplete) input the user has already typed * in. When and how this method is invoked is implementation specific and decided by the * actual user interface. */ public List<CompletionProposal> complete(CompletionContext context) { String prefix = context.upToCursor(); List<CompletionProposal> candidates = new ArrayList<>(); candidates.addAll(commandsStartingWith(prefix)); String best = findLongestCommand(prefix); if (best != null) { CompletionContext argsContext = context.drop(best.split(" ").length); // Try to complete arguments MethodTarget methodTarget = methodTargets.get(best); Method method = methodTarget.getMethod(); List<MethodParameter> parameters = Utils.createMethodParameters(method).collect(Collectors.toList()); for (ParameterResolver resolver : parameterResolvers) { for (int index = 0; index < parameters.size(); index++) { MethodParameter parameter = parameters.get(index); if (resolver.supports(parameter)) { resolver.complete(parameter, argsContext).stream().forEach(candidates::add); } } } } return candidates; }
.findFirst(); MethodParameter methodParameter = Utils.createMethodParameter(result.getMethodTarget().getMethod(), parameterIndex.get()); List<ParameterDescription> descriptions = findParameterResolver(methodParameter)