• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
CommandLineProcessor(ISdkLog logger, String[][] actions)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                 "This help.",
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      */
acceptLackOfVerb()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. */
isVerbose()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. */
isSilent()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. */
isHelpRequested()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. */
getVerb()152     public String getVerb() {
153         return mVerbRequested;
154     }
155 
156     /** Returns the direct object name from the command-line. Can be null. */
getDirectObject()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      */
getValue(String verb, String directObject, String longFlagName)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      */
setValue(String verb, String directObject, String longFlagName, Object value)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      */
parseArgs(String[] args)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      */
findLongArg(String verb, String directObject, String longName)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      */
findShortArg(String verb, String directObject, String shortName)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      */
printHelpAndExit(String errorFormat, Object... args)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      */
printHelpAndExitForAction(String verb, String directObject, String errorFormat, Object... args)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      */
listOptions(String verb, String directObject)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
needsExtra()567             public boolean needsExtra() {
568                 return false;
569             }
570             @Override
process(Arg arg, String extra)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
needsExtra()581             public boolean needsExtra() {
582                 return true;
583             }
584             @Override
process(Arg arg, String extra)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
needsExtra()599             public boolean needsExtra() {
600                 return true;
601             }
602             @Override
process(Arg arg, String extra)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
needsExtra()625             public boolean needsExtra() {
626                 return true;
627             }
628             @Override
process(Arg arg, String extra)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          */
needsExtra()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          */
process(Arg arg, String extra)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          */
Arg(Mode mode, boolean mandatory, String verb, String directObject, String shortName, String longName, String description, Object defaultValue)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. */
isMandatory()715         public boolean isMandatory() {
716             return mMandatory;
717         }
718 
719         /** Returns the 1-letter short name of the argument, e.g. -v. */
getShortArg()720         public String getShortArg() {
721             return mShortName;
722         }
723 
724         /** Returns the long name of the argument, e.g. --verbose. */
getLongArg()725         public String getLongArg() {
726             return mLongName;
727         }
728 
729         /** Returns the description. Never null. */
getDescription()730         public String getDescription() {
731             return mDescription;
732         }
733 
734         /** Returns the verb for that argument. Never null. */
getVerb()735         public String getVerb() {
736             return mVerb;
737         }
738 
739         /** Returns the direct Object for that argument. Never null, but can be empty string. */
getDirectObject()740         public String getDirectObject() {
741             return mDirectObject;
742         }
743 
744         /** Returns the default value. Can be null. */
getDefaultValue()745         public Object getDefaultValue() {
746             return mDefaultValue;
747         }
748 
749         /** Returns the current value. Initially set to the default value. Can be null. */
getCurrentValue()750         public Object getCurrentValue() {
751             return mCurrentValue;
752         }
753 
754         /** Sets the current value. Can be null. */
setCurrentValue(Object currentValue)755         public void setCurrentValue(Object currentValue) {
756             mCurrentValue = currentValue;
757         }
758 
759         /** Returns the argument mode (type + process method). Never null. */
getMode()760         public Mode getMode() {
761             return mMode;
762         }
763 
764         /** Returns true if the argument has been used on the command line. */
isInCommandLine()765         public boolean isInCommandLine() {
766             return mInCommandLine;
767         }
768 
769         /** Sets if the argument has been used on the command line. */
setInCommandLine(boolean inCommandLine)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      */
define(Mode mode, boolean mandatory, String verb, String directObject, String shortName, String longName, String description, Object defaultValue)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      */
exit()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      */
stdout(String format, Object...args)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      */
stderr(String format, Object...args)829     protected void stderr(String format, Object...args) {
830         mLog.error(null, format, args);
831     }
832 }
833