All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kohsuke.args4j.spi.SubCommandHandler Maven / Gradle / Ivy

package org.kohsuke.args4j.spi;

import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.OptionDef;

import java.util.AbstractList;
import java.util.ResourceBundle;

/**
 * {@link OptionHandler} used with {@link Argument} for parsing typical "sub-command" pattern.
 *
 * 

* The "sub-command" pattern refers to the design of the command line like git and svn, where * the first argument to the command designates a sub-command (say git checkout), then everything * that follows afterward are parsed by this sub-command (which is usually different depending on * which sub-command was selected.) * *

* This {@link OptionHandler} models this design pattern with the {@link SubCommands} annotation. * See the following example: * *

{@code
 * class Git {
 *      @Argument(handler={@link SubCommandHandler}.class)
 *      @SubCommands({
 *          @SubCommand(name="checkout", impl=CheckoutCommand.class),
 *          @SubCommand(name="commit", impl=CommitCommand.class),
 *          ...
 *      })
 *      Command cmd;
 *
 *      @Option(name="-r")
 *      boolean recursive;
 *
 *      public static void main(String[] args) {
 *          Git git = new Git();
 *          new CmdLineParser(git).parseArgument(args);
 *          git.cmd.execute();
 *      }
 * }
 *
 * class CheckoutCommand {
 *     @Option(name="-a")
 *     boolean all;
 *
 *     ...
 * }
 * }
* *

* An example of legal command line option for this is -r checkout -a. * *

    *
  • * {@link SubCommand} only works with {@link Argument} and not with {@link Option}. * *
  • * The same field/setter must be also annotated with {@link SubCommands} that specify possible sub-commands. * *
  • * Any {@link Option}s that you define in the Git class above can parse options that appear * prior to the sub-command name. This is useful for defining global options that work across sub-commands. * *
  • * The matching sub-command implementation gets instantiated with the default constructor, * then a new {@link CmdLineParser} will be created to parse its annotations. * *
  • * The rest of the arguments that follow the sub-command will be parsed with this new {@link CmdLineParser} * *
  • * The fully populated sub-command instance is set as the value. *
* *

* This class defines a number of protected methods that allow subtypes to customize various parts of the * behaviours. This should also serve as an example if you want to combine this with more sophisticated * sub-command lookup, such as through META-INF/services, sezpoz, * or annotation indexer. * * @author Kohsuke Kawaguchi */ public class SubCommandHandler extends OptionHandler { private final SubCommands commands; public SubCommandHandler(CmdLineParser parser, OptionDef option, Setter setter) { super(parser, option, setter); commands = setter.asAnnotatedElement().getAnnotation(SubCommands.class); if (commands==null) throw new IllegalStateException("SubCommandHandler must be used with @SubCommands annotation"); } @Override public int parseArguments(Parameters params) throws CmdLineException { String subCmd = params.getParameter(0); for (SubCommand c : commands.value()) { if (c.name().equals(subCmd)) { setter.addValue(subCommand(c,params)); return params.size(); // consume all the remaining tokens } } return fallback(subCmd); } protected int fallback(String subCmd) throws CmdLineException { throw new CmdLineException(owner, Messages.ILLEGAL_OPERAND, option.toString(), subCmd); } protected Object subCommand(SubCommand c, final Parameters params) throws CmdLineException { Object subCmd = instantiate(c); CmdLineParser p = configureParser(subCmd,c); p.parseArgument(new AbstractList() { @Override public String get(int index) { try { return params.getParameter(index+1); } catch (CmdLineException e) { // invalid index was accessed. throw new IndexOutOfBoundsException(); } } @Override public int size() { return params.size()-1; } }); return subCmd; } protected CmdLineParser configureParser(Object subCmd, SubCommand c) { return new CmdLineParser(subCmd); } protected Object instantiate(SubCommand c) { try { return c.impl().newInstance(); } catch (InstantiationException e) { throw new IllegalStateException("Failed to instantiate "+c,e); } catch (IllegalAccessException e) { throw new IllegalStateException("Failed to instantiate "+c,e); } } @Override public String getDefaultMetaVariable() { StringBuffer rv = new StringBuffer(); rv.append("["); for (SubCommand sc : commands.value()) { rv.append(sc.name()).append(" | "); } rv.delete(rv.length()-3, rv.length()); rv.append("]"); return rv.toString(); } @Override public String getMetaVariable(ResourceBundle rb) { return getDefaultMetaVariable(); } }