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