• 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                 "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      */
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 errorMsg = 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                             errorMsg = 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 but it is unknown by this
261                             // verb-object combination
262 
263                             errorMsg = 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                             errorMsg = 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                             errorMsg = String.format(
307                                 "Expected verb after global parameters but found '%1$s' instead.",
308                                 a);
309                             return;
310 
311                         }
312                     } else {
313                         // The argument is not a dashed parameter and we already
314                         // have a verb/object. Must be some extra unknown argument.
315                         errorMsg = String.format(
316                                 "Argument '%1$s' is not recognized.",
317                                 a);
318                     }
319                 } else if (arg != null) {
320                     // This argument was present on the command line
321                     arg.setInCommandLine(true);
322 
323                     // Process keyword
324                     String error = null;
325                     if (arg.getMode().needsExtra()) {
326                         if (++i >= n) {
327                             errorMsg = String.format("Missing argument for flag %1$s.", a);
328                             return;
329                         }
330                         String b = args[i];
331 
332                         Arg dummyArg = null;
333                         if (b.startsWith("--")) {
334                             dummyArg = findLongArg(verb, directObject, b.substring(2));
335                         } else if (b.startsWith("-")) {
336                             dummyArg = findShortArg(verb, directObject, b.substring(1));
337                         }
338                         if (dummyArg != null) {
339                             errorMsg = String.format(
340                                     "Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.",
341                                     a, b);
342                             return;
343                         }
344 
345                         error = arg.getMode().process(arg, b);
346                     } else {
347                         error = arg.getMode().process(arg, null);
348 
349                         if (isHelpRequested()) {
350                             // The --help flag was requested. We'll continue the usual processing
351                             // so that we can find the optional verb/object words. Those will be
352                             // used to print specific help.
353                             // Setting a non-null error message triggers printing the help, however
354                             // there is no specific error to print.
355                             errorMsg = "";
356                         }
357                     }
358 
359                     if (error != null) {
360                         errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
361                         return;
362                     }
363                 }
364             }
365 
366             if (errorMsg == null) {
367                 if (verb == null && !acceptLackOfVerb()) {
368                     errorMsg = "Missing verb name.";
369                 } else if (verb != null) {
370                     if (directObject == null) {
371                         // Make sure this verb has an optional direct object
372                         for (String[] actionDesc : mActions) {
373                             if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
374                                     actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
375                                 directObject = NO_VERB_OBJECT;
376                                 break;
377                             }
378                         }
379 
380                         if (directObject == null) {
381                             errorMsg = String.format("Missing object name for verb '%1$s'.", verb);
382                             return;
383                         }
384                     }
385 
386                     // Validate that all mandatory arguments are non-null for this action
387                     String missing = null;
388                     boolean plural = false;
389                     for (Entry<String, Arg> entry : mArguments.entrySet()) {
390                         Arg arg = entry.getValue();
391                         if (arg.getVerb().equals(verb) &&
392                                 arg.getDirectObject().equals(directObject)) {
393                             if (arg.isMandatory() && arg.getCurrentValue() == null) {
394                                 if (missing == null) {
395                                     missing = "--" + arg.getLongArg();
396                                 } else {
397                                     missing += ", --" + arg.getLongArg();
398                                     plural = true;
399                                 }
400                             }
401                         }
402                     }
403 
404                     if (missing != null) {
405                         errorMsg  = String.format(
406                                 "The %1$s %2$s must be defined for action '%3$s %4$s'",
407                                 plural ? "parameters" : "parameter",
408                                 missing,
409                                 verb,
410                                 directObject);
411                     }
412 
413                     mVerbRequested = verb;
414                     mDirectObjectRequested = directObject;
415                 }
416             }
417         } finally {
418             if (errorMsg != null) {
419                 printHelpAndExitForAction(verb, directObject, errorMsg);
420             }
421         }
422     }
423 
424     /**
425      * Finds an {@link Arg} given an action name and a long flag name.
426      * @return The {@link Arg} found or null.
427      */
findLongArg(String verb, String directObject, String longName)428     protected Arg findLongArg(String verb, String directObject, String longName) {
429         if (verb == null) {
430             verb = GLOBAL_FLAG_VERB;
431         }
432         if (directObject == null) {
433             directObject = NO_VERB_OBJECT;
434         }
435         String key = verb + "/" + directObject + "/" + longName;
436         return mArguments.get(key);
437     }
438 
439     /**
440      * Finds an {@link Arg} given an action name and a short flag name.
441      * @return The {@link Arg} found or null.
442      */
findShortArg(String verb, String directObject, String shortName)443     protected Arg findShortArg(String verb, String directObject, String shortName) {
444         if (verb == null) {
445             verb = GLOBAL_FLAG_VERB;
446         }
447         if (directObject == null) {
448             directObject = NO_VERB_OBJECT;
449         }
450 
451         for (Entry<String, Arg> entry : mArguments.entrySet()) {
452             Arg arg = entry.getValue();
453             if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
454                 if (shortName.equals(arg.getShortArg())) {
455                     return arg;
456                 }
457             }
458         }
459 
460         return null;
461     }
462 
463     /**
464      * Prints the help/usage and exits.
465      *
466      * @param errorFormat Optional error message to print prior to usage using String.format
467      * @param args Arguments for String.format
468      */
printHelpAndExit(String errorFormat, Object... args)469     public void printHelpAndExit(String errorFormat, Object... args) {
470         printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
471     }
472 
473     /**
474      * Prints the help/usage and exits.
475      *
476      * @param verb If null, displays help for all verbs. If not null, display help only
477      *          for that specific verb. In all cases also displays general usage and action list.
478      * @param directObject If null, displays help for all verb objects.
479      *          If not null, displays help only for that specific action
480      *          In all cases also display general usage and action list.
481      * @param errorFormat Optional error message to print prior to usage using String.format
482      * @param args Arguments for String.format
483      */
printHelpAndExitForAction(String verb, String directObject, String errorFormat, Object... args)484     public void printHelpAndExitForAction(String verb, String directObject,
485             String errorFormat, Object... args) {
486         if (errorFormat != null && errorFormat.length() > 0) {
487             stderr(errorFormat, args);
488         }
489 
490         /*
491          * usage should fit in 80 columns
492          *   12345678901234567890123456789012345678901234567890123456789012345678901234567890
493          */
494         stdout("\n" +
495             "Usage:\n" +
496             "  android [global options] %s [action options]\n" +
497             "\n" +
498             "Global options:",
499             verb == null ? "action" :
500                 verb + (directObject == null ? "" : " " + directObject));
501         listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
502 
503         if (verb == null || directObject == null) {
504             stdout("\nValid actions are composed of a verb and an optional direct object:");
505             for (String[] action : mActions) {
506                 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
507                     stdout("- %1$6s %2$-14s: %3$s",
508                             action[ACTION_VERB_INDEX],
509                             action[ACTION_OBJECT_INDEX],
510                             action[ACTION_DESC_INDEX]);
511                 }
512             }
513         }
514 
515         // Only print details if a verb/object is requested
516         if (verb != null) {
517             for (String[] action : mActions) {
518                 if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
519                     if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
520                         stdout("\nAction \"%1$s %2$s\":",
521                                 action[ACTION_VERB_INDEX],
522                                 action[ACTION_OBJECT_INDEX]);
523                         stdout("  %1$s", action[ACTION_DESC_INDEX]);
524                         stdout("Options:");
525                         listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
526                     }
527                 }
528             }
529         }
530 
531         exit();
532     }
533 
534     /**
535      * Internal helper to print all the option flags for a given action name.
536      */
listOptions(String verb, String directObject)537     protected void listOptions(String verb, String directObject) {
538         int numOptions = 0;
539         for (Entry<String, Arg> entry : mArguments.entrySet()) {
540             Arg arg = entry.getValue();
541             if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
542 
543                 String value = "";
544                 String required = "";
545                 if (arg.isMandatory()) {
546                     required = " [required]";
547 
548                 } else {
549                     if (arg.getDefaultValue() instanceof String[]) {
550                         for (String v : (String[]) arg.getDefaultValue()) {
551                             if (value.length() > 0) {
552                                 value += ", ";
553                             }
554                             value += v;
555                         }
556                     } else if (arg.getDefaultValue() != null) {
557                         Object v = arg.getDefaultValue();
558                         if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
559                             value = v.toString();
560                         }
561                     }
562                     if (value.length() > 0) {
563                         value = " [Default: " + value + "]";
564                     }
565                 }
566 
567                 stdout("  -%1$s %2$-10s %3$s%4$s%5$s",
568                         arg.getShortArg(),
569                         "--" + arg.getLongArg(),
570                         arg.getDescription(),
571                         value,
572                         required);
573                 numOptions++;
574             }
575         }
576 
577         if (numOptions == 0) {
578             stdout("  No options");
579         }
580     }
581 
582     //----
583 
584     /**
585      * The mode of an argument specifies the type of variable it represents,
586      * whether an extra parameter is required after the flag and how to parse it.
587      */
588     static enum Mode {
589         /** Argument value is a Boolean. Default value is a Boolean. */
590         BOOLEAN {
591             @Override
needsExtra()592             public boolean needsExtra() {
593                 return false;
594             }
595             @Override
process(Arg arg, String extra)596             public String process(Arg arg, String extra) {
597                 // Toggle the current value
598                 arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
599                 return null;
600             }
601         },
602 
603         /** Argument value is an Integer. Default value is an Integer. */
604         INTEGER {
605             @Override
needsExtra()606             public boolean needsExtra() {
607                 return true;
608             }
609             @Override
process(Arg arg, String extra)610             public String process(Arg arg, String extra) {
611                 try {
612                     arg.setCurrentValue(Integer.parseInt(extra));
613                     return null;
614                 } catch (NumberFormatException e) {
615                     return String.format("Failed to parse '%1$s' as an integer: %2%s",
616                             extra, e.getMessage());
617                 }
618             }
619         },
620 
621         /** Argument value is a String. Default value is a String[]. */
622         ENUM {
623             @Override
needsExtra()624             public boolean needsExtra() {
625                 return true;
626             }
627             @Override
process(Arg arg, String extra)628             public String process(Arg arg, String extra) {
629                 StringBuilder desc = new StringBuilder();
630                 String[] values = (String[]) arg.getDefaultValue();
631                 for (String value : values) {
632                     if (value.equals(extra)) {
633                         arg.setCurrentValue(extra);
634                         return null;
635                     }
636 
637                     if (desc.length() != 0) {
638                         desc.append(", ");
639                     }
640                     desc.append(value);
641                 }
642 
643                 return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
644             }
645         },
646 
647         /** Argument value is a String. Default value is a null. */
648         STRING {
649             @Override
needsExtra()650             public boolean needsExtra() {
651                 return true;
652             }
653             @Override
process(Arg arg, String extra)654             public String process(Arg arg, String extra) {
655                 arg.setCurrentValue(extra);
656                 return null;
657             }
658         };
659 
660         /**
661          * Returns true if this mode requires an extra parameter.
662          */
needsExtra()663         public abstract boolean needsExtra();
664 
665         /**
666          * Processes the flag for this argument.
667          *
668          * @param arg The argument being processed.
669          * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
670          * @return An error string or null if there's no error.
671          */
process(Arg arg, String extra)672         public abstract String process(Arg arg, String extra);
673     }
674 
675     /**
676      * An argument accepted by the command-line, also called "a flag".
677      * Arguments must have a short version (one letter), a long version name and a description.
678      * They can have a default value, or it can be null.
679      * Depending on the {@link Mode}, the default value can be a Boolean, an Integer, a String
680      * or a String array (in which case the first item is the current by default.)
681      */
682     static class Arg {
683         /** Verb for that argument. Never null. */
684         private final String mVerb;
685         /** Direct Object for that argument. Never null, but can be empty string. */
686         private final String mDirectObject;
687         /** The 1-letter short name of the argument, e.g. -v. */
688         private final String mShortName;
689         /** The long name of the argument, e.g. --verbose. */
690         private final String mLongName;
691         /** A description. Never null. */
692         private final String mDescription;
693         /** A default value. Can be null. */
694         private final Object mDefaultValue;
695         /** The argument mode (type + process method). Never null. */
696         private final Mode mMode;
697         /** True if this argument is mandatory for this verb/directobject. */
698         private final boolean mMandatory;
699         /** Current value. Initially set to the default value. */
700         private Object mCurrentValue;
701         /** True if the argument has been used on the command line. */
702         private boolean mInCommandLine;
703 
704         /**
705          * Creates a new argument flag description.
706          *
707          * @param mode The {@link Mode} for the argument.
708          * @param mandatory True if this argument is mandatory for this action.
709          * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
710          * @param shortName The one-letter short argument name. Cannot be empty nor null.
711          * @param longName The long argument name. Cannot be empty nor null.
712          * @param description The description. Cannot be null.
713          * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
714          */
Arg(Mode mode, boolean mandatory, String verb, String directObject, String shortName, String longName, String description, Object defaultValue)715         public Arg(Mode mode,
716                    boolean mandatory,
717                    String verb,
718                    String directObject,
719                    String shortName,
720                    String longName,
721                    String description,
722                    Object defaultValue) {
723             mMode = mode;
724             mMandatory = mandatory;
725             mVerb = verb;
726             mDirectObject = directObject;
727             mShortName = shortName;
728             mLongName = longName;
729             mDescription = description;
730             mDefaultValue = defaultValue;
731             mInCommandLine = false;
732             if (defaultValue instanceof String[]) {
733                 mCurrentValue = ((String[])defaultValue)[0];
734             } else {
735                 mCurrentValue = mDefaultValue;
736             }
737         }
738 
739         /** Return true if this argument is mandatory for this verb/directobject. */
isMandatory()740         public boolean isMandatory() {
741             return mMandatory;
742         }
743 
744         /** Returns the 1-letter short name of the argument, e.g. -v. */
getShortArg()745         public String getShortArg() {
746             return mShortName;
747         }
748 
749         /** Returns the long name of the argument, e.g. --verbose. */
getLongArg()750         public String getLongArg() {
751             return mLongName;
752         }
753 
754         /** Returns the description. Never null. */
getDescription()755         public String getDescription() {
756             return mDescription;
757         }
758 
759         /** Returns the verb for that argument. Never null. */
getVerb()760         public String getVerb() {
761             return mVerb;
762         }
763 
764         /** Returns the direct Object for that argument. Never null, but can be empty string. */
getDirectObject()765         public String getDirectObject() {
766             return mDirectObject;
767         }
768 
769         /** Returns the default value. Can be null. */
getDefaultValue()770         public Object getDefaultValue() {
771             return mDefaultValue;
772         }
773 
774         /** Returns the current value. Initially set to the default value. Can be null. */
getCurrentValue()775         public Object getCurrentValue() {
776             return mCurrentValue;
777         }
778 
779         /** Sets the current value. Can be null. */
setCurrentValue(Object currentValue)780         public void setCurrentValue(Object currentValue) {
781             mCurrentValue = currentValue;
782         }
783 
784         /** Returns the argument mode (type + process method). Never null. */
getMode()785         public Mode getMode() {
786             return mMode;
787         }
788 
789         /** Returns true if the argument has been used on the command line. */
isInCommandLine()790         public boolean isInCommandLine() {
791             return mInCommandLine;
792         }
793 
794         /** Sets if the argument has been used on the command line. */
setInCommandLine(boolean inCommandLine)795         public void setInCommandLine(boolean inCommandLine) {
796             mInCommandLine = inCommandLine;
797         }
798     }
799 
800     /**
801      * Internal helper to define a new argument for a give action.
802      *
803      * @param mode The {@link Mode} for the argument.
804      * @param verb The verb name. Can be #INTERNAL_VERB.
805      * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
806      * @param shortName The one-letter short argument name. Cannot be empty nor null.
807      * @param longName The long argument name. Cannot be empty nor null.
808      * @param description The description. Cannot be null.
809      * @param defaultValue The default value (or values), which depends on the selected {@link Mode}.
810      */
define(Mode mode, boolean mandatory, String verb, String directObject, String shortName, String longName, String description, Object defaultValue)811     protected void define(Mode mode,
812             boolean mandatory,
813             String verb,
814             String directObject,
815             String shortName, String longName,
816             String description, Object defaultValue) {
817         assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
818 
819         if (directObject == null) {
820             directObject = NO_VERB_OBJECT;
821         }
822 
823         String key = verb + "/" + directObject + "/" + longName;
824         mArguments.put(key, new Arg(mode, mandatory,
825                 verb, directObject, shortName, longName, description, defaultValue));
826     }
827 
828     /**
829      * Exits in case of error.
830      * This is protected so that it can be overridden in unit tests.
831      */
exit()832     protected void exit() {
833         System.exit(1);
834     }
835 
836     /**
837      * Prints a line to stdout.
838      * This is protected so that it can be overridden in unit tests.
839      *
840      * @param format The string to be formatted. Cannot be null.
841      * @param args Format arguments.
842      */
stdout(String format, Object...args)843     protected void stdout(String format, Object...args) {
844         mLog.printf(format + "\n", args);
845     }
846 
847     /**
848      * Prints a line to stderr.
849      * This is protected so that it can be overridden in unit tests.
850      *
851      * @param format The string to be formatted. Cannot be null.
852      * @param args Format arguments.
853      */
stderr(String format, Object...args)854     protected void stderr(String format, Object...args) {
855         mLog.error(null, format, args);
856     }
857 }
858