Provide ability to call parameters during parsing

This allows option handlers to simulate parameters as if they had been
passed from the command line. To call other parameters, Option handlers
need to instantiate a CmdLineParser.Parameters class, and then call the
callPrameters(String...) method on it to parse the new parameters as if
they were added to the command line before the remaining parameters.
OptionHandlers may optionally pass the parameters instances to other
methods which may then both parse/consume more parameters and call
additional parameters.

Change-Id: Ia9e5a4a4463731d43a238cf514b7fb5c4cb83fd6
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index d5ffda2..9662d83 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -801,6 +801,53 @@
   }
 ----
 
+=== Calling Command Options ===
+
+Within an OptionHandler, during the processing of an option, plugins can
+provide and call extra parameters on the current command during parsing
+simulating as if they had been passed from the command line originally.
+
+To call additional parameters from within an option handler, instantiate
+the com.google.gerrit.util.cli.CmdLineParser.Parameters class with the
+existing parameters, and then call callParameters() with the additional
+parameters to be parsed. OptionHandlers may optionally pass this class to
+other methods which may then both parse/consume more parameters and call
+additional parameters.
+
+The example below shows a plugin that adds a "--special" option (perhaps
+for use with the Query command) that calls the "--format json" option.
+
+[source, java]
+----
+public class JsonOutputOptionHandler<T> extends OptionHandler<T> {
+  protected com.google.gerrit.util.cli.CmdLineParser.MyParser myParser;
+
+  public JsonOutputOptionHandler(CmdLineParser parser, OptionDef option, Setter<? super T> setter) {
+    super(parser, option, setter);
+    myParser = (com.google.gerrit.util.cli.CmdLineParser.MyParser) owner;
+  }
+
+  @Override
+  public int parseArguments(org.kohsuke.args4j.spi.Parameters params) throws CmdLineException {
+    new Parameters(params, myParser).callParameters("--format", "json");
+    setter.addValue(true);
+    return 0; // we didn't consume any additional args
+  }
+
+  @Override
+  public String getDefaultMetaVariable() {
+   ...
+  }
+}
+
+@Option(
+  name = "--special",
+  usage = "ouptut results using json",
+  handler = JsonOutputOptionHandler.class
+)
+boolean json;
+----
+
 [[query_attributes]]
 === Query Attributes ===
 
diff --git a/java/com/google/gerrit/util/cli/CmdLineParser.java b/java/com/google/gerrit/util/cli/CmdLineParser.java
index 5b7ea3f..79dba65 100644
--- a/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -50,6 +50,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -85,6 +86,90 @@
     CmdLineParser create(Object bean);
   }
 
+  /**
+   * This may be used by an option handler during parsing to "call" additional parameters simulating
+   * as if they had been passed from the command line originally.
+   *
+   * <p>To call additional parameters from within an option handler, instantiate this class with the
+   * parameters and then call callParameters() with the additional parameters to be parsed.
+   * OptionHandlers may optionally pass this class to other methods which may then both
+   * parse/consume more parameters and call additional parameters.
+   */
+  public static class Parameters implements org.kohsuke.args4j.spi.Parameters {
+    protected final String[] args;
+    protected MyParser parser;
+    protected int consumed = 0;
+
+    public Parameters(org.kohsuke.args4j.spi.Parameters args, MyParser parser)
+        throws CmdLineException {
+      this.args = new String[args.size()];
+      for (int i = 0; i < args.size(); i++) {
+        this.args[i] = args.getParameter(i);
+      }
+      this.parser = parser;
+    }
+
+    public Parameters(String[] args, MyParser parser) {
+      this.args = args;
+      this.parser = parser;
+    }
+
+    @Override
+    public String getParameter(int idx) throws CmdLineException {
+      return args[idx];
+    }
+
+    /**
+     * get and consume (consider parsed) a parameter
+     *
+     * @param name name
+     * @return the consumed parameter
+     */
+    public String consumeParameter() throws CmdLineException {
+      return getParameter(consumed++);
+    }
+
+    @Override
+    public int size() {
+      return args.length;
+    }
+
+    /**
+     * Add 'count' to the value of parsed parameters. May be called more than once.
+     *
+     * @param count How many parameters were just parsed.
+     */
+    public void consume(int count) {
+      consumed += count;
+    }
+
+    /**
+     * Reports handlers how many parameters were parsed
+     *
+     * @return the count of parsed parameters
+     */
+    public int getConsumed() {
+      return consumed;
+    }
+
+    /**
+     * Use during parsing to call additional parameters simulating as if they had been passed from
+     * the command line originally.
+     *
+     * @param String... args A variable amount of parameters to call immediately
+     *     <p>The parameters will be parsed immediately, before the remaining parameter will be
+     *     parsed.
+     *     <p>Note: Since this is done outside of the arg4j parsing loop, it will not match exactly
+     *     what would happen if they were actually passed from the command line, but it will be
+     *     pretty close. If this were moved to args4j, the interface could be the same and it could
+     *     match exactly the behavior as if passed from the command line originally.
+     */
+    public void callParameters(String... args) throws CmdLineException {
+      Parameters impl = new Parameters(Arrays.copyOfRange(args, 1, args.length), parser);
+      parser.findOptionByName(args[0]).parseArguments(impl);
+    }
+  }
+
   private final OptionHandlers handlers;
   private final MyParser parser;
 
@@ -404,7 +489,7 @@
     }
   }
 
-  private class MyParser extends org.kohsuke.args4j.CmdLineParser {
+  public class MyParser extends org.kohsuke.args4j.CmdLineParser {
     @SuppressWarnings("rawtypes")
     private List<OptionHandler> optionsList;
 
@@ -480,6 +565,29 @@
       return add(super.createOptionHandler(option, setter));
     }
 
+    /**
+     * Finds a registered {@code OptionHandler} by its name or its alias.
+     *
+     * @param name name
+     * @return the {@code OptionHandler} or {@code null}
+     *     <p>Note: this is cut & pasted from the parent class in arg4j, it was private and it
+     *     needed to be exposed.
+     */
+    public OptionHandler findOptionByName(String name) {
+      for (OptionHandler h : optionsList) {
+        NamedOptionDef option = (NamedOptionDef) h.option;
+        if (name.equals(option.name())) {
+          return h;
+        }
+        for (String alias : option.aliases()) {
+          if (name.equals(alias)) {
+            return h;
+          }
+        }
+      }
+      return null;
+    }
+
     @SuppressWarnings("rawtypes")
     private OptionHandler add(OptionHandler handler) {
       ensureOptionsInitialized();