OSDN Git Service

SDK Setup fixes.
[android-x86/sdk.git] / sdkmanager / app / src / com / android / sdkmanager / CommandLineProcessor.java
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 }