Home | History | Annotate | Download | only in sdkmanager
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.sdkmanager;
     18 
     19 import com.android.sdklib.ISdkLog;
     20 import com.android.sdklib.util.LineUtil;
     21 
     22 import java.util.HashMap;
     23 import java.util.Map.Entry;
     24 
     25 /**
     26  * Parses the command-line and stores flags needed or requested.
     27  * <p/>
     28  * This is a base class. To be useful you want to:
     29  * <ul>
     30  * <li>override it.
     31  * <li>pass an action array to the constructor.
     32  * <li>define flags for your actions.
     33  * </ul>
     34  * <p/>
     35  * To use, call {@link #parseArgs(String[])} and then
     36  * call {@link #getValue(String, String, String)}.
     37  */
     38 class CommandLineProcessor {
     39 
     40     /*
     41      * Steps needed to add a new action:
     42      * - Each action is defined as a "verb object" followed by parameters.
     43      * - Either reuse a VERB_ constant or define a new one.
     44      * - Either reuse an OBJECT_ constant or define a new one.
     45      * - Add a new entry to mAction with a one-line help summary.
     46      * - In the constructor, add a define() call for each parameter (either mandatory
     47      *   or optional) for the given action.
     48      */
     49 
     50     /** Internal verb name for internally hidden flags. */
     51     public final static String GLOBAL_FLAG_VERB = "@@internal@@";   //$NON-NLS-1$
     52 
     53     /** String to use when the verb doesn't need any object. */
     54     public final static String NO_VERB_OBJECT = "";                 //$NON-NLS-1$
     55 
     56     /** The global help flag. */
     57     public static final String KEY_HELP = "help";
     58     /** The global verbose flag. */
     59     public static final String KEY_VERBOSE = "verbose";
     60     /** The global silent flag. */
     61     public static final String KEY_SILENT = "silent";
     62 
     63     /** Verb requested by the user. Null if none specified, which will be an error. */
     64     private String mVerbRequested;
     65     /** Direct object requested by the user. Can be null. */
     66     private String mDirectObjectRequested;
     67 
     68     /**
     69      * Action definitions.
     70      * <p/>
     71      * This list serves two purposes: first it is used to know which verb/object
     72      * actions are acceptable on the command-line; second it provides a summary
     73      * for each action that is printed in the help.
     74      * <p/>
     75      * Each entry is a string array with:
     76      * <ul>
     77      * <li> the verb.
     78      * <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
     79      * <li> a description.
     80      * <li> an alternate form for the object (e.g. plural).
     81      * </ul>
     82      */
     83     private final String[][] mActions;
     84 
     85     private static final int ACTION_VERB_INDEX = 0;
     86     private static final int ACTION_OBJECT_INDEX = 1;
     87     private static final int ACTION_DESC_INDEX = 2;
     88     private static final int ACTION_ALT_OBJECT_INDEX = 3;
     89 
     90     /**
     91      * The map of all defined arguments.
     92      * <p/>
     93      * The key is a string "verb/directObject/longName".
     94      */
     95     private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
     96     /** Logger */
     97     private final ISdkLog mLog;
     98 
     99     /**
    100      * Constructs a new command-line processor.
    101      *
    102      * @param logger An SDK logger object. Must not be null.
    103      * @param actions The list of actions recognized on the command-line.
    104      *                See the javadoc of {@link #mActions} for more details.
    105      *
    106      * @see #mActions
    107      */
    108     public CommandLineProcessor(ISdkLog logger, String[][] actions) {
    109         mLog = logger;
    110         mActions = actions;
    111 
    112         /*
    113          * usage should fit in 80 columns, including the space to print the options:
    114          * "  -v --verbose  7890123456789012345678901234567890123456789012345678901234567890"
    115          */
    116 
    117         define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
    118                            "Verbose mode, shows errors, warnings and all messages.",
    119                            false);
    120         define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
    121                            "Silent mode, shows errors only.",
    122                            false);
    123         define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
    124                            "Help on a specific command.",
    125                            false);
    126     }
    127 
    128     /**
    129      * Indicates if this command-line can work when no verb is specified.
    130      * The default is false, which generates an error when no verb/object is specified.
    131      * Derived implementations can set this to true if they can deal with a lack
    132      * of verb/action.
    133      */
    134     public boolean acceptLackOfVerb() {
    135         return false;
    136     }
    137 
    138 
    139     //------------------
    140     // Helpers to get flags values
    141 
    142     /** Helper that returns true if --verbose was requested. */
    143     public boolean isVerbose() {
    144         return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
    145     }
    146 
    147     /** Helper that returns true if --silent was requested. */
    148     public boolean isSilent() {
    149         return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
    150     }
    151 
    152     /** Helper that returns true if --help was requested. */
    153     public boolean isHelpRequested() {
    154         return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
    155     }
    156 
    157     /** Returns the verb name from the command-line. Can be null. */
    158     public String getVerb() {
    159         return mVerbRequested;
    160     }
    161 
    162     /** Returns the direct object name from the command-line. Can be null. */
    163     public String getDirectObject() {
    164         return mDirectObjectRequested;
    165     }
    166 
    167     //------------------
    168 
    169     /**
    170      * Raw access to parsed parameter values.
    171      * <p/>
    172      * The default is to scan all parameters. Parameters that have been explicitly set on the
    173      * command line are returned first. Otherwise one with a non-null value is returned.
    174      * <p/>
    175      * Both a verb and a direct object filter can be specified. When they are non-null they limit
    176      * the scope of the search.
    177      * <p/>
    178      * If nothing has been found, return the last default value seen matching the filter.
    179      *
    180      * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
    181      *             verbs that match the direct object condition will be examined and the first
    182      *             value set will be used.
    183      * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
    184      *             all possible direct objects that match the verb condition will be examined and
    185      *             the first value set will be used.
    186      * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
    187      * @return The current value object stored in the parameter, which depends on the argument mode.
    188      */
    189     public Object getValue(String verb, String directObject, String longFlagName) {
    190 
    191         if (verb != null && directObject != null) {
    192             String key = verb + '/' + directObject + '/' + longFlagName;
    193             Arg arg = mArguments.get(key);
    194             return arg.getCurrentValue();
    195         }
    196 
    197         Object lastDefault = null;
    198         for (Arg arg : mArguments.values()) {
    199             if (arg.getLongArg().equals(longFlagName)) {
    200                 if (verb == null || arg.getVerb().equals(verb)) {
    201                     if (directObject == null || arg.getDirectObject().equals(directObject)) {
    202                         if (arg.isInCommandLine()) {
    203                             return arg.getCurrentValue();
    204                         }
    205                         if (arg.getCurrentValue() != null) {
    206                             lastDefault = arg.getCurrentValue();
    207                         }
    208                     }
    209                 }
    210             }
    211         }
    212 
    213         return lastDefault;
    214     }
    215 
    216     /**
    217      * Internal setter for raw parameter value.
    218      * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
    219      * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
    220      * @param longFlagName The long flag name for the given action.
    221      * @param value The new current value object stored in the parameter, which depends on the
    222      *              argument mode.
    223      */
    224     protected void setValue(String verb, String directObject, String longFlagName, Object value) {
    225         String key = verb + '/' + directObject + '/' + longFlagName;
    226         Arg arg = mArguments.get(key);
    227         arg.setCurrentValue(value);
    228     }
    229 
    230     /**
    231      * Parses the command-line arguments.
    232      * <p/>
    233      * This method will exit and not return if a parsing error arise.
    234      *
    235      * @param args The arguments typically received by a main method.
    236      */
    237     public void parseArgs(String[] args) {
    238         String errorMsg = null;
    239         String verb = null;
    240         String directObject = null;
    241 
    242         try {
    243             int n = args.length;
    244             for (int i = 0; i < n; i++) {
    245                 Arg arg = null;
    246                 String a = args[i];
    247                 if (a.startsWith("--")) {                                       //$NON-NLS-1$
    248                     arg = findLongArg(verb, directObject, a.substring(2));
    249                 } else if (a.startsWith("-")) {                                 //$NON-NLS-1$
    250                     arg = findShortArg(verb, directObject, a.substring(1));
    251                 }
    252 
    253                 // No matching argument name found
    254                 if (arg == null) {
    255                     // Does it looks like a dashed parameter?
    256                     if (a.startsWith("-")) {                                    //$NON-NLS-1$
    257                         if (verb == null || directObject == null) {
    258                             // It looks like a dashed parameter and we don't have a a verb/object
    259                             // set yet, the parameter was just given too early.
    260 
    261                             errorMsg = String.format(
    262                                 "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
    263                                 a);
    264                             return;
    265                         } else {
    266                             // It looks like a dashed parameter but it is unknown by this
    267                             // verb-object combination
    268 
    269                             errorMsg = String.format(
    270                                     "Flag '%1$s' is not valid for '%2$s %3$s'.",
    271                                     a, verb, directObject);
    272                             return;
    273                         }
    274                     }
    275 
    276                     if (verb == null) {
    277                         // Fill verb first. Find it.
    278                         for (String[] actionDesc : mActions) {
    279                             if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
    280                                 verb = a;
    281                                 break;
    282                             }
    283                         }
    284 
    285                         // Error if it was not a valid verb
    286                         if (verb == null) {
    287                             errorMsg = String.format(
    288                                 "Expected verb after global parameters but found '%1$s' instead.",
    289                                 a);
    290                             return;
    291                         }
    292 
    293                     } else if (directObject == null) {
    294                         // Then fill the direct object. Find it.
    295                         for (String[] actionDesc : mActions) {
    296                             if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
    297                                 if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
    298                                     directObject = a;
    299                                     break;
    300                                 } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
    301                                         actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
    302                                     // if the alternate form exist and is used, we internally
    303                                     // only memorize the default direct object form.
    304                                     directObject = actionDesc[ACTION_OBJECT_INDEX];
    305                                     break;
    306                                 }
    307                             }
    308                         }
    309 
    310                         // Error if it was not a valid object for that verb
    311                         if (directObject == null) {
    312                             errorMsg = String.format(
    313                                 "Expected verb after global parameters but found '%1$s' instead.",
    314                                 a);
    315                             return;
    316 
    317                         }
    318                     } else {
    319                         // The argument is not a dashed parameter and we already
    320                         // have a verb/object. Must be some extra unknown argument.
    321                         errorMsg = String.format(
    322                                 "Argument '%1$s' is not recognized.",
    323                                 a);
    324                     }
    325                 } else if (arg != null) {
    326                     // This argument was present on the command line
    327                     arg.setInCommandLine(true);
    328 
    329                     // Process keyword
    330                     String error = null;
    331                     if (arg.getMode().needsExtra()) {
    332                         if (++i >= n) {
    333                             errorMsg = String.format("Missing argument for flag %1$s.", a);
    334                             return;
    335                         }
    336                         String b = args[i];
    337 
    338                         Arg dummyArg = null;
    339                         if (b.startsWith("--")) {                                   //$NON-NLS-1$
    340                             dummyArg = findLongArg(verb, directObject, b.substring(2));
    341                         } else if (b.startsWith("-")) {                             //$NON-NLS-1$
    342                             dummyArg = findShortArg(verb, directObject, b.substring(1));
    343                         }
    344                         if (dummyArg != null) {
    345                             errorMsg = String.format(
    346                                     "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.",
    347                                     a, b);
    348                             return;
    349                         }
    350 
    351                         error = arg.getMode().process(arg, b);
    352                     } else {
    353                         error = arg.getMode().process(arg, null);
    354 
    355                         if (isHelpRequested()) {
    356                             // The --help flag was requested. We'll continue the usual processing
    357                             // so that we can find the optional verb/object words. Those will be
    358                             // used to print specific help.
    359                             // Setting a non-null error message triggers printing the help, however
    360                             // there is no specific error to print.
    361                             errorMsg = "";                                          //$NON-NLS-1$
    362                         }
    363                     }
    364 
    365                     if (error != null) {
    366                         errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
    367                         return;
    368                     }
    369                 }
    370             }
    371 
    372             if (errorMsg == null) {
    373                 if (verb == null && !acceptLackOfVerb()) {
    374                     errorMsg = "Missing verb name.";
    375                 } else if (verb != null) {
    376                     if (directObject == null) {
    377                         // Make sure this verb has an optional direct object
    378                         for (String[] actionDesc : mActions) {
    379                             if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
    380                                     actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
    381                                 directObject = NO_VERB_OBJECT;
    382                                 break;
    383                             }
    384                         }
    385 
    386                         if (directObject == null) {
    387                             errorMsg = String.format("Missing object name for verb '%1$s'.", verb);
    388                             return;
    389                         }
    390                     }
    391 
    392                     // Validate that all mandatory arguments are non-null for this action
    393                     String missing = null;
    394                     boolean plural = false;
    395                     for (Entry<String, Arg> entry : mArguments.entrySet()) {
    396                         Arg arg = entry.getValue();
    397                         if (arg.getVerb().equals(verb) &&
    398                                 arg.getDirectObject().equals(directObject)) {
    399                             if (arg.isMandatory() && arg.getCurrentValue() == null) {
    400                                 if (missing == null) {
    401                                     missing = "--" + arg.getLongArg();              //$NON-NLS-1$
    402                                 } else {
    403                                     missing += ", --" + arg.getLongArg();           //$NON-NLS-1$
    404                                     plural = true;
    405                                 }
    406                             }
    407                         }
    408                     }
    409 
    410                     if (missing != null) {
    411                         errorMsg  = String.format(
    412                                 "The %1$s %2$s must be defined for action '%3$s %4$s'",
    413                                 plural ? "parameters" : "parameter",
    414                                 missing,
    415                                 verb,
    416                                 directObject);
    417                     }
    418 
    419                     mVerbRequested = verb;
    420                     mDirectObjectRequested = directObject;
    421                 }
    422             }
    423         } finally {
    424             if (errorMsg != null) {
    425                 printHelpAndExitForAction(verb, directObject, errorMsg);
    426             }
    427         }
    428     }
    429 
    430     /**
    431      * Finds an {@link Arg} given an action name and a long flag name.
    432      * @return The {@link Arg} found or null.
    433      */
    434     protected Arg findLongArg(String verb, String directObject, String longName) {
    435         if (verb == null) {
    436             verb = GLOBAL_FLAG_VERB;
    437         }
    438         if (directObject == null) {
    439             directObject = NO_VERB_OBJECT;
    440         }
    441         String key = verb + '/' + directObject + '/' + longName;                    //$NON-NLS-1$
    442         return mArguments.get(key);
    443     }
    444 
    445     /**
    446      * Finds an {@link Arg} given an action name and a short flag name.
    447      * @return The {@link Arg} found or null.
    448      */
    449     protected Arg findShortArg(String verb, String directObject, String shortName) {
    450         if (verb == null) {
    451             verb = GLOBAL_FLAG_VERB;
    452         }
    453         if (directObject == null) {
    454             directObject = NO_VERB_OBJECT;
    455         }
    456 
    457         for (Entry<String, Arg> entry : mArguments.entrySet()) {
    458             Arg arg = entry.getValue();
    459             if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
    460                 if (shortName.equals(arg.getShortArg())) {
    461                     return arg;
    462                 }
    463             }
    464         }
    465 
    466         return null;
    467     }
    468 
    469     /**
    470      * Prints the help/usage and exits.
    471      *
    472      * @param errorFormat Optional error message to print prior to usage using String.format
    473      * @param args Arguments for String.format
    474      */
    475     public void printHelpAndExit(String errorFormat, Object... args) {
    476         printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
    477     }
    478 
    479     /**
    480      * Prints the help/usage and exits.
    481      *
    482      * @param verb If null, displays help for all verbs. If not null, display help only
    483      *          for that specific verb. In all cases also displays general usage and action list.
    484      * @param directObject If null, displays help for all verb objects.
    485      *          If not null, displays help only for that specific action
    486      *          In all cases also display general usage and action list.
    487      * @param errorFormat Optional error message to print prior to usage using String.format
    488      * @param args Arguments for String.format
    489      */
    490     public void printHelpAndExitForAction(String verb, String directObject,
    491             String errorFormat, Object... args) {
    492         if (errorFormat != null && errorFormat.length() > 0) {
    493             stderr(errorFormat, args);
    494         }
    495 
    496         /*
    497          * usage should fit in 80 columns
    498          *   12345678901234567890123456789012345678901234567890123456789012345678901234567890
    499          */
    500         stdout("\n" +
    501             "Usage:\n" +
    502             "  android [global options] %s [action options]\n" +
    503             "\n" +
    504             "Global options:",
    505             verb == null ? "action" :
    506                 verb + (directObject == null ? "" : " " + directObject));           //$NON-NLS-1$
    507         listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
    508 
    509         if (verb == null || directObject == null) {
    510             stdout("\nValid actions are composed of a verb and an optional direct object:");
    511             for (String[] action : mActions) {
    512                 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
    513                     stdout("- %1$6s %2$-13s: %3$s",
    514                             action[ACTION_VERB_INDEX],
    515                             action[ACTION_OBJECT_INDEX],
    516                             action[ACTION_DESC_INDEX]);
    517                 }
    518             }
    519         }
    520 
    521         // Only print details if a verb/object is requested
    522         if (verb != null) {
    523             for (String[] action : mActions) {
    524                 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
    525                     if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
    526                         stdout("\nAction \"%1$s %2$s\":",
    527                                 action[ACTION_VERB_INDEX],
    528                                 action[ACTION_OBJECT_INDEX]);
    529                         stdout("  %1$s", action[ACTION_DESC_INDEX]);
    530                         stdout("Options:");
    531                         listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
    532                     }
    533                 }
    534             }
    535         }
    536 
    537         exit();
    538     }
    539 
    540     /**
    541      * Internal helper to print all the option flags for a given action name.
    542      */
    543     protected void listOptions(String verb, String directObject) {
    544         int numOptions = 0;
    545         int longArgLen = 8;
    546 
    547         for (Entry<String, Arg> entry : mArguments.entrySet()) {
    548             Arg arg = entry.getValue();
    549             if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
    550                 int n = arg.getLongArg().length();
    551                 if (n > longArgLen) {
    552                     longArgLen = n;
    553                 }
    554             }
    555         }
    556 
    557         for (Entry<String, Arg> entry : mArguments.entrySet()) {
    558             Arg arg = entry.getValue();
    559             if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
    560 
    561                 String value = "";                                              //$NON-NLS-1$
    562                 String required = "";                                           //$NON-NLS-1$
    563                 if (arg.isMandatory()) {
    564                     required = " [required]";
    565 
    566                 } else {
    567                     if (arg.getDefaultValue() instanceof String[]) {
    568                         for (String v : (String[]) arg.getDefaultValue()) {
    569                             if (value.length() > 0) {
    570                                 value += ", ";
    571                             }
    572                             value += v;
    573                         }
    574                     } else if (arg.getDefaultValue() != null) {
    575                         Object v = arg.getDefaultValue();
    576                         if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
    577                             value = v.toString();
    578                         }
    579                     }
    580                     if (value.length() > 0) {
    581                         value = " [Default: " + value + "]";
    582                     }
    583                 }
    584 
    585                 // Java doesn't support * for printf variable width, so we'll insert the long arg
    586                 // width "manually" in the printf format string.
    587                 String longArgWidth = Integer.toString(longArgLen + 2);
    588 
    589                 // Print a line in the form " -1_letter_arg --long_arg description"
    590                 // where either the 1-letter arg or the long arg are optional.
    591                 String output = String.format(
    592                         "  %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$
    593                         arg.getShortArg().length() > 0 ?
    594                                 "-" + arg.getShortArg() :                              //$NON-NLS-1$
    595                                 "",                                                    //$NON-NLS-1$
    596                         arg.getLongArg().length() > 0 ?
    597                                 "--" + arg.getLongArg() :                              //$NON-NLS-1$
    598                                 "",                                                    //$NON-NLS-1$
    599                         arg.getDescription(),
    600                         value,
    601                         required);
    602                 stdout(output);
    603                 numOptions++;
    604             }
    605         }
    606 
    607         if (numOptions == 0) {
    608             stdout("  No options");
    609         }
    610     }
    611 
    612     //----
    613 
    614 
    615     /**
    616      * The mode of an argument specifies the type of variable it represents,
    617      * whether an extra parameter is required after the flag and how to parse it.
    618      */
    619     static enum Mode {
    620         /** Argument value is a Boolean. Default value is a Boolean. */
    621         BOOLEAN {
    622             @Override
    623             public boolean needsExtra() {
    624                 return false;
    625             }
    626             @Override
    627             public String process(Arg arg, String extra) {
    628                 // Toggle the current value
    629                 arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
    630                 return null;
    631             }
    632         },
    633 
    634         /** Argument value is an Integer. Default value is an Integer. */
    635         INTEGER {
    636             @Override
    637             public boolean needsExtra() {
    638                 return true;
    639             }
    640             @Override
    641             public String process(Arg arg, String extra) {
    642                 try {
    643                     arg.setCurrentValue(Integer.parseInt(extra));
    644                     return null;
    645                 } catch (NumberFormatException e) {
    646                     return String.format("Failed to parse '%1$s' as an integer: %2$s", extra,
    647                             e.getMessage());
    648                 }
    649             }
    650         },
    651 
    652         /** Argument value is a String. Default value is a String[]. */
    653         ENUM {
    654             @Override
    655             public boolean needsExtra() {
    656                 return true;
    657             }
    658             @Override
    659             public String process(Arg arg, String extra) {
    660                 StringBuilder desc = new StringBuilder();
    661                 String[] values = (String[]) arg.getDefaultValue();
    662                 for (String value : values) {
    663                     if (value.equals(extra)) {
    664                         arg.setCurrentValue(extra);
    665                         return null;
    666                     }
    667 
    668                     if (desc.length() != 0) {
    669                         desc.append(", ");
    670                     }
    671                     desc.append(value);
    672                 }
    673 
    674                 return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
    675             }
    676         },
    677 
    678         /** Argument value is a String. Default value is a null. */
    679         STRING {
    680             @Override
    681             public boolean needsExtra() {
    682                 return true;
    683             }
    684             @Override
    685             public String process(Arg arg, String extra) {
    686                 arg.setCurrentValue(extra);
    687                 return null;
    688             }
    689         };
    690 
    691         /**
    692          * Returns true if this mode requires an extra parameter.
    693          */
    694         public abstract boolean needsExtra();
    695 
    696         /**
    697          * Processes the flag for this argument.
    698          *
    699          * @param arg The argument being processed.
    700          * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
    701          * @return An error string or null if there's no error.
    702          */
    703         public abstract String process(Arg arg, String extra);
    704     }
    705 
    706     /**
    707      * An argument accepted by the command-line, also called "a flag".
    708      * Arguments must have a short version (one letter), a long version name and a description.
    709      * They can have a default value, or it can be null.
    710      * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
    711      * or a String array (in which case the first item is the current by default.)
    712      */
    713     static class Arg {
    714         /** Verb for that argument. Never null. */
    715         private final String mVerb;
    716         /** Direct Object for that argument. Never null, but can be empty string. */
    717         private final String mDirectObject;
    718         /** The 1-letter short name of the argument, e.g. -v. */
    719         private final String mShortName;
    720         /** The long name of the argument, e.g. --verbose. */
    721         private final String mLongName;
    722         /** A description. Never null. */
    723         private final String mDescription;
    724         /** A default value. Can be null. */
    725         private final Object mDefaultValue;
    726         /** The argument mode (type + process method). Never null. */
    727         private final Mode mMode;
    728         /** True if this argument is mandatory for this verb/directobject. */
    729         private final boolean mMandatory;
    730         /** Current value. Initially set to the default value. */
    731         private Object mCurrentValue;
    732         /** True if the argument has been used on the command line. */
    733         private boolean mInCommandLine;
    734 
    735         /**
    736          * Creates a new argument flag description.
    737          *
    738          * @param mode The {@link Mode} for the argument.
    739          * @param mandatory True if this argument is mandatory for this action.
    740          * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
    741          * @param shortName The one-letter short argument name. Can be empty but not null.
    742          * @param longName The long argument name. Can be empty but not null.
    743          * @param description The description. Cannot be null.
    744          * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
    745          */
    746         public Arg(Mode mode,
    747                    boolean mandatory,
    748                    String verb,
    749                    String directObject,
    750                    String shortName,
    751                    String longName,
    752                    String description,
    753                    Object defaultValue) {
    754             mMode = mode;
    755             mMandatory = mandatory;
    756             mVerb = verb;
    757             mDirectObject = directObject;
    758             mShortName = shortName;
    759             mLongName = longName;
    760             mDescription = description;
    761             mDefaultValue = defaultValue;
    762             mInCommandLine = false;
    763             if (defaultValue instanceof String[]) {
    764                 mCurrentValue = ((String[])defaultValue)[0];
    765             } else {
    766                 mCurrentValue = mDefaultValue;
    767             }
    768         }
    769 
    770         /** Return true if this argument is mandatory for this verb/directobject. */
    771         public boolean isMandatory() {
    772             return mMandatory;
    773         }
    774 
    775         /** Returns the 1-letter short name of the argument, e.g. -v. */
    776         public String getShortArg() {
    777             return mShortName;
    778         }
    779 
    780         /** Returns the long name of the argument, e.g. --verbose. */
    781         public String getLongArg() {
    782             return mLongName;
    783         }
    784 
    785         /** Returns the description. Never null. */
    786         public String getDescription() {
    787             return mDescription;
    788         }
    789 
    790         /** Returns the verb for that argument. Never null. */
    791         public String getVerb() {
    792             return mVerb;
    793         }
    794 
    795         /** Returns the direct Object for that argument. Never null, but can be empty string. */
    796         public String getDirectObject() {
    797             return mDirectObject;
    798         }
    799 
    800         /** Returns the default value. Can be null. */
    801         public Object getDefaultValue() {
    802             return mDefaultValue;
    803         }
    804 
    805         /** Returns the current value. Initially set to the default value. Can be null. */
    806         public Object getCurrentValue() {
    807             return mCurrentValue;
    808         }
    809 
    810         /** Sets the current value. Can be null. */
    811         public void setCurrentValue(Object currentValue) {
    812             mCurrentValue = currentValue;
    813         }
    814 
    815         /** Returns the argument mode (type + process method). Never null. */
    816         public Mode getMode() {
    817             return mMode;
    818         }
    819 
    820         /** Returns true if the argument has been used on the command line. */
    821         public boolean isInCommandLine() {
    822             return mInCommandLine;
    823         }
    824 
    825         /** Sets if the argument has been used on the command line. */
    826         public void setInCommandLine(boolean inCommandLine) {
    827             mInCommandLine = inCommandLine;
    828         }
    829     }
    830 
    831     /**
    832      * Internal helper to define a new argument for a give action.
    833      *
    834      * @param mode The {@link Mode} for the argument.
    835      * @param mandatory The argument is required (never if {@link Mode#BOOLEAN})
    836      * @param verb The verb name. Can be #INTERNAL_VERB.
    837      * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
    838      * @param shortName The one-letter short argument name. Can be empty but not null.
    839      * @param longName The long argument name. Can be empty but not null.
    840      * @param description The description. Cannot be null.
    841      * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
    842      */
    843     protected void define(Mode mode,
    844             boolean mandatory,
    845             String verb,
    846             String directObject,
    847             String shortName, String longName,
    848             String description, Object defaultValue) {
    849         assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
    850 
    851         // We should always have at least a short or long name, ideally both but never none.
    852         assert shortName != null;
    853         assert longName != null;
    854         assert shortName.length() > 0 || longName.length()  > 0;
    855 
    856         if (directObject == null) {
    857             directObject = NO_VERB_OBJECT;
    858         }
    859 
    860         String key = verb + '/' + directObject + '/' + longName;
    861         mArguments.put(key, new Arg(mode, mandatory,
    862                 verb, directObject, shortName, longName, description, defaultValue));
    863     }
    864 
    865     /**
    866      * Exits in case of error.
    867      * This is protected so that it can be overridden in unit tests.
    868      */
    869     protected void exit() {
    870         System.exit(1);
    871     }
    872 
    873     /**
    874      * Prints a line to stdout.
    875      * This is protected so that it can be overridden in unit tests.
    876      *
    877      * @param format The string to be formatted. Cannot be null.
    878      * @param args Format arguments.
    879      */
    880     protected void stdout(String format, Object...args) {
    881         String output = String.format(format, args);
    882         output = LineUtil.reflowLine(output);
    883         mLog.printf("%s\n", output);    //$NON-NLS-1$
    884     }
    885 
    886     /**
    887      * Prints a line to stderr.
    888      * This is protected so that it can be overridden in unit tests.
    889      *
    890      * @param format The string to be formatted. Cannot be null.
    891      * @param args Format arguments.
    892      */
    893     protected void stderr(String format, Object...args) {
    894         mLog.error(null, format, args);
    895     }
    896 }
    897