public boolean prepareDelegate(
      Object subject, CommandSession session, List<Object> params, CommandArguments args)
      throws Exception {
    args.subject = subject;
    // Introspect
    for (Class type = subject.getClass(); type != null; type = type.getSuperclass()) {
      for (Field field : type.getDeclaredFields()) {
        Option option = field.getAnnotation(Option.class);
        if (option != null) {
          args.options.put(option, field);
        }
        Argument argument = field.getAnnotation(Argument.class);
        if (argument != null) {
          if (Argument.DEFAULT.equals(argument.name())) {
            final Argument delegate = argument;
            final String name = field.getName();
            argument =
                new Argument() {
                  public String name() {
                    return name;
                  }

                  public String description() {
                    return delegate.description();
                  }

                  public boolean required() {
                    return delegate.required();
                  }

                  public int index() {
                    return delegate.index();
                  }

                  public boolean multiValued() {
                    return delegate.multiValued();
                  }

                  public String valueToShowInHelp() {
                    return delegate.valueToShowInHelp();
                  }

                  public Class<? extends Annotation> annotationType() {
                    return delegate.annotationType();
                  }
                };
          }
          args.arguments.put(argument, field);
          int index = argument.index();
          while (args.orderedArguments.size() <= index) {
            args.orderedArguments.add(null);
          }
          if (args.orderedArguments.get(index) != null) {
            throw new IllegalArgumentException("Duplicate argument index: " + index);
          }
          args.orderedArguments.set(index, argument);
        }
      }
    }
    // Check indexes are correct
    for (int i = 0; i < args.orderedArguments.size(); i++) {
      if (args.orderedArguments.get(i) == null) {
        throw new IllegalArgumentException("Missing argument for index: " + i);
      }
    }
    // Populate
    Map<Option, Object> optionValues =
        new TreeMap<Option, Object>(CommandArguments.OPTION_COMPARATOR);
    Map<Argument, Object> argumentValues = new HashMap<Argument, Object>();
    boolean processOptions = true;
    int argIndex = 0;
    for (Iterator<Object> it = params.iterator(); it.hasNext(); ) {
      Object param = it.next();
      // Check for help
      if (HELP.name().equals(param) || Arrays.asList(HELP.aliases()).contains(param)) {
        printUsageDelegate(session, subject, args.options, args.arguments, System.out);
        return false;
      }
      if (processOptions && param instanceof String && ((String) param).startsWith("-")) {
        boolean isKeyValuePair = ((String) param).indexOf('=') != -1;
        String name;
        Object value = null;
        if (isKeyValuePair) {
          name = ((String) param).substring(0, ((String) param).indexOf('='));
          value = ((String) param).substring(((String) param).indexOf('=') + 1);
        } else {
          name = (String) param;
        }
        Option option = null;
        for (Option opt : args.options.keySet()) {
          if (name.equals(opt.name()) || Arrays.asList(opt.aliases()).contains(name)) {
            option = opt;
            break;
          }
        }
        if (option == null) {
          throw new CommandException(
              Ansi.ansi()
                  .fg(Ansi.Color.RED)
                  .a("Error executing command ")
                  .a(scope)
                  .a(":")
                  .a(Ansi.Attribute.INTENSITY_BOLD)
                  .a(name)
                  .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                  .a(" undefined option ")
                  .a(Ansi.Attribute.INTENSITY_BOLD)
                  .a(param)
                  .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                  .fg(Ansi.Color.DEFAULT)
                  .toString(),
              "Undefined option: " + param);
        }
        Field field = args.options.get(option);
        if (value == null
            && (field.getType() == boolean.class || field.getType() == Boolean.class)) {
          value = Boolean.TRUE;
        }
        if (value == null && it.hasNext()) {
          value = it.next();
        }
        if (value == null) {
          throw new CommandException(
              Ansi.ansi()
                  .fg(Ansi.Color.RED)
                  .a("Error executing command ")
                  .a(scope)
                  .a(":")
                  .a(Ansi.Attribute.INTENSITY_BOLD)
                  .a(name)
                  .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                  .a(" missing value for option ")
                  .a(Ansi.Attribute.INTENSITY_BOLD)
                  .a(param)
                  .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                  .fg(Ansi.Color.DEFAULT)
                  .toString(),
              "Missing value for option: " + param);
        }
        if (option.multiValued()) {
          List<Object> l = (List<Object>) optionValues.get(option);
          if (l == null) {
            l = new ArrayList<Object>();
            optionValues.put(option, l);
          }
          l.add(value);
        } else {
          optionValues.put(option, value);
        }
      } else {
        processOptions = false;
        if (argIndex >= args.orderedArguments.size()) {
          throw new CommandException(
              Ansi.ansi()
                  .fg(Ansi.Color.RED)
                  .a("Error executing command ")
                  .a(scope)
                  .a(":")
                  .a(Ansi.Attribute.INTENSITY_BOLD)
                  .a(name)
                  .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                  .a(": too many arguments specified")
                  .fg(Ansi.Color.DEFAULT)
                  .toString(),
              "Too many arguments specified");
        }
        Argument argument = args.orderedArguments.get(argIndex);
        if (!argument.multiValued()) {
          argIndex++;
        }
        if (argument.multiValued()) {
          List<Object> l = (List<Object>) argumentValues.get(argument);
          if (l == null) {
            l = new ArrayList<Object>();
            argumentValues.put(argument, l);
          }
          l.add(param);
        } else {
          argumentValues.put(argument, param);
        }
      }
    }
    // Check required arguments / options
    for (Option option : args.options.keySet()) {
      if (option.required() && optionValues.get(option) == null) {
        throw new CommandException(
            Ansi.ansi()
                .fg(Ansi.Color.RED)
                .a("Error executing command ")
                .a(scope)
                .a(":")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(name)
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(": option ")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(option.name())
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(" is required")
                .fg(Ansi.Color.DEFAULT)
                .toString(),
            "Option " + option.name() + " is required");
      }
    }
    for (Argument argument : args.arguments.keySet()) {
      if (argument.required() && argumentValues.get(argument) == null) {
        throw new CommandException(
            Ansi.ansi()
                .fg(Ansi.Color.RED)
                .a("Error executing command ")
                .a(scope)
                .a(":")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(name)
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(": argument ")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(argument.name())
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(" is required")
                .fg(Ansi.Color.DEFAULT)
                .toString(),
            "Argument " + argument.name() + " is required");
      }
    }
    // Convert and inject values
    for (Map.Entry<Option, Object> entry : optionValues.entrySet()) {
      Field field = args.options.get(entry.getKey());
      Object value;
      try {
        value = convert(subject, entry.getValue(), field.getGenericType());
      } catch (Exception e) {
        throw new CommandException(
            Ansi.ansi()
                .fg(Ansi.Color.RED)
                .a("Error executing command ")
                .a(scope)
                .a(":")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(name)
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(": unable to convert option ")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(entry.getKey().name())
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(" with value '")
                .a(entry.getValue())
                .a("' to type ")
                .a(new GenericType(field.getGenericType()).toString())
                .fg(Ansi.Color.DEFAULT)
                .toString(),
            "Unable to convert option "
                + entry.getKey().name()
                + " with value '"
                + entry.getValue()
                + "' to type "
                + new GenericType(field.getGenericType()).toString(),
            e);
      }
      field.setAccessible(true);
      field.set(subject, value);
    }
    for (Map.Entry<Argument, Object> entry : argumentValues.entrySet()) {
      Field field = args.arguments.get(entry.getKey());
      Object value;
      try {
        value = convert(subject, entry.getValue(), field.getGenericType());
      } catch (Exception e) {
        throw new CommandException(
            Ansi.ansi()
                .fg(Ansi.Color.RED)
                .a("Error executing command ")
                .a(scope)
                .a(":")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(name)
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(": unable to convert argument ")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(entry.getKey().name())
                .a(Ansi.Attribute.INTENSITY_BOLD_OFF)
                .a(" with value '")
                .a(entry.getValue())
                .a("' to type ")
                .a(new GenericType(field.getGenericType()).toString())
                .fg(Ansi.Color.DEFAULT)
                .toString(),
            "Unable to convert argument "
                + entry.getKey().name()
                + " with value '"
                + entry.getValue()
                + "' to type "
                + new GenericType(field.getGenericType()).toString(),
            e);
      }
      field.setAccessible(true);
      field.set(subject, value);
    }
    return true;
  }
  protected void printUsageDelegate(
      CommandSession session,
      Object action,
      Map<Option, Field> optionsMap,
      Map<Argument, Field> argsMap,
      PrintStream out) {
    Command command = action.getClass().getAnnotation(Command.class);
    Terminal term = session != null ? (Terminal) session.get(".jline.terminal") : null;
    List<Argument> arguments = new ArrayList<Argument>(argsMap.keySet());
    Collections.sort(
        arguments,
        new Comparator<Argument>() {
          public int compare(Argument o1, Argument o2) {
            return Integer.valueOf(o1.index()).compareTo(Integer.valueOf(o2.index()));
          }
        });

    String commandName = command != null ? command.name() : name;
    String commandScope = command != null ? command.scope() : scope;
    String commandDescription =
        command != null ? command.description() : "No description available for " + name + ".";
    String commandDetailedDescription = command != null ? command.detailedDescription() : "";

    Set<Option> options = new TreeSet<Option>(CommandArguments.OPTION_COMPARATOR);
    options.addAll(optionsMap.keySet());
    options.add(HELP);
    boolean globalScope = NameScoping.isGlobalScope(session, scope);
    out.println(
        Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("DESCRIPTION").a(Ansi.Attribute.RESET));
    out.print("        ");
    if (commandName != null) {
      if (globalScope) {
        out.println(
            Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a(commandName).a(Ansi.Attribute.RESET));
      } else {
        out.println(
            Ansi.ansi()
                .a(commandScope)
                .a(":")
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(commandName)
                .a(Ansi.Attribute.RESET));
      }
      out.println();
    }
    String description = commandDescription;
    if (isBlank(description)) {
      description = "No description available for " + name + ".";
    }
    out.print("\t");
    out.println(description);
    out.println();
    out.println("\tLoaded from");
    out.println("\t" + scriptFile);
    out.println();

    StringBuffer syntax = new StringBuffer();
    if (globalScope) {
      syntax.append(commandName);
    } else {
      syntax.append(String.format("%s:%s", commandScope, commandName));
    }

    if (options.size() > 0) {
      syntax.append(" [options]");
    }
    if (arguments.size() > 0) {
      syntax.append(' ');
      for (Argument argument : arguments) {
        if (!argument.required()) {
          syntax.append(String.format("[%s] ", argument.name()));
        } else {
          syntax.append(String.format("%s ", argument.name()));
        }
      }
    }

    out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("SYNTAX").a(Ansi.Attribute.RESET));
    out.print("        ");
    out.println(syntax.toString());
    out.println();
    if (arguments.size() > 0) {
      out.println(
          Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("ARGUMENTS").a(Ansi.Attribute.RESET));
      for (Argument argument : arguments) {
        out.print("        ");
        out.println(
            Ansi.ansi()
                .a(Ansi.Attribute.INTENSITY_BOLD)
                .a(argument.name())
                .a(Ansi.Attribute.RESET));
        printFormatted(
            "                ", argument.description(), term != null ? term.getWidth() : 80, out);
        if (!argument.required()) {
          if (argument.valueToShowInHelp() != null && argument.valueToShowInHelp().length() != 0) {
            try {
              if (Argument.DEFAULT_STRING.equals(argument.valueToShowInHelp())) {
                argsMap.get(argument).setAccessible(true);
                Object o = argsMap.get(argument).get(action);
                printObjectDefaultsTo(out, o);
              } else {
                printDefaultsTo(out, argument.valueToShowInHelp());
              }
            } catch (Throwable t) {
              // Ignore
            }
          }
        }
      }
      out.println();
    }
    if (options.size() > 0) {
      out.println(
          Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("OPTIONS").a(Ansi.Attribute.RESET));
      for (Option option : options) {
        String opt = option.name();
        for (String alias : option.aliases()) {
          opt += ", " + alias;
        }
        out.print("        ");
        out.println(Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a(opt).a(Ansi.Attribute.RESET));
        printFormatted(
            "                ", option.description(), term != null ? term.getWidth() : 80, out);
        if (option.valueToShowInHelp() != null && option.valueToShowInHelp().length() != 0) {
          try {
            if (Option.DEFAULT_STRING.equals(option.valueToShowInHelp())) {
              optionsMap.get(option).setAccessible(true);
              Object o = optionsMap.get(option).get(action);
              printObjectDefaultsTo(out, o);
            } else {
              printDefaultsTo(out, option.valueToShowInHelp());
            }
          } catch (Throwable t) {
            // Ignore
          }
        }
      }
      out.println();
    }
    if (commandDetailedDescription.length() > 0) {
      out.println(
          Ansi.ansi().a(Ansi.Attribute.INTENSITY_BOLD).a("DETAILS").a(Ansi.Attribute.RESET));
      String desc = loadDescription(action.getClass(), commandDetailedDescription);
      printFormattedFixed("        ", desc, term != null ? term.getWidth() : 80, out);
    }
  }