blob: 8cbc728ee18a2e0b650cc48e3b214cc6e32e3656 [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.util;
import com.android.common.annotations.NonNull;
import com.android.common.annotations.Nullable;
import com.android.common.utils.ILogger;
import com.android.utils.LineUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
/**
* Parses the command-line and stores flags needed or requested.
* <p/>
* This is a base class. To be useful you want to:
* <ul>
* <li>override it.
* <li>pass an action array to the constructor.
* <li>define flags for your actions.
* </ul>
* <p/>
* To use, call {@link #parseArgs(String[])} and then
* call {@link #getValue(String, String, String)}.
*/
public class CommandLineParser {
/*
* Steps needed to add a new action:
* - Each action is defined as a "verb object" followed by parameters.
* - Either reuse a VERB_ constant or define a new one.
* - Either reuse an OBJECT_ constant or define a new one.
* - Add a new entry to mAction with a one-line help summary.
* - In the constructor, add a define() call for each parameter (either mandatory
* or optional) for the given action.
*/
/** Internal verb name for internally hidden flags. */
public final static String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$
/** String to use when the verb doesn't need any object. */
public final static String NO_VERB_OBJECT = ""; //$NON-NLS-1$
/** The global help flag. */
public static final String KEY_HELP = "help";
/** The global verbose flag. */
public static final String KEY_VERBOSE = "verbose";
/** The global silent flag. */
public static final String KEY_SILENT = "silent";
/** Verb requested by the user. Null if none specified, which will be an error. */
private String mVerbRequested;
/** Direct object requested by the user. Can be null. */
private String mDirectObjectRequested;
/**
* Action definitions.
* <p/>
* This list serves two purposes: first it is used to know which verb/object
* actions are acceptable on the command-line; second it provides a summary
* for each action that is printed in the help.
* <p/>
* Each entry is a string array with:
* <ul>
* <li> the verb.
* <li> a direct object (use {@link #NO_VERB_OBJECT} if there's no object).
* <li> a description.
* <li> an alternate form for the object (e.g. plural).
* </ul>
*/
private final String[][] mActions;
private static final int ACTION_VERB_INDEX = 0;
private static final int ACTION_OBJECT_INDEX = 1;
private static final int ACTION_DESC_INDEX = 2;
private static final int ACTION_ALT_OBJECT_INDEX = 3;
/**
* The map of all defined arguments.
* <p/>
* The key is a string "verb/directObject/longName".
*/
private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
/** Logger */
private final ILogger mLog;
/**
* Constructs a new command-line processor.
*
* @param logger An SDK logger object. Must not be null.
* @param actions The list of actions recognized on the command-line.
* See the javadoc of {@link #mActions} for more details.
*
* @see #mActions
*/
public CommandLineParser(ILogger logger, String[][] actions) {
mLog = logger;
mActions = actions;
/*
* usage should fit in 80 columns, including the space to print the options:
* " -v --verbose 7890123456789012345678901234567890123456789012345678901234567890"
*/
define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
"Verbose mode, shows errors, warnings and all messages.",
false);
define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
"Silent mode, shows errors only.",
false);
define(Mode.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
"Help on a specific command.",
false);
}
/**
* Indicates if this command-line can work when no verb is specified.
* The default is false, which generates an error when no verb/object is specified.
* Derived implementations can set this to true if they can deal with a lack
* of verb/action.
*/
public boolean acceptLackOfVerb() {
return false;
}
//------------------
// Helpers to get flags values
/** Helper that returns true if --verbose was requested. */
public boolean isVerbose() {
return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
}
/** Helper that returns true if --silent was requested. */
public boolean isSilent() {
return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
}
/** Helper that returns true if --help was requested. */
public boolean isHelpRequested() {
return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
}
/** Returns the verb name from the command-line. Can be null. */
public String getVerb() {
return mVerbRequested;
}
/** Returns the direct object name from the command-line. Can be null. */
public String getDirectObject() {
return mDirectObjectRequested;
}
//------------------
/**
* Raw access to parsed parameter values.
* <p/>
* The default is to scan all parameters. Parameters that have been explicitly set on the
* command line are returned first. Otherwise one with a non-null value is returned.
* <p/>
* Both a verb and a direct object filter can be specified. When they are non-null they limit
* the scope of the search.
* <p/>
* If nothing has been found, return the last default value seen matching the filter.
*
* @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
* verbs that match the direct object condition will be examined and the first
* value set will be used.
* @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
* all possible direct objects that match the verb condition will be examined and
* the first value set will be used.
* @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
* @return The current value object stored in the parameter, which depends on the argument mode.
*/
public Object getValue(String verb, String directObject, String longFlagName) {
if (verb != null && directObject != null) {
String key = verb + '/' + directObject + '/' + longFlagName;
Arg arg = mArguments.get(key);
return arg.getCurrentValue();
}
Object lastDefault = null;
for (Arg arg : mArguments.values()) {
if (arg.getLongArg().equals(longFlagName)) {
if (verb == null || arg.getVerb().equals(verb)) {
if (directObject == null || arg.getDirectObject().equals(directObject)) {
if (arg.isInCommandLine()) {
return arg.getCurrentValue();
}
if (arg.getCurrentValue() != null) {
lastDefault = arg.getCurrentValue();
}
}
}
}
}
return lastDefault;
}
/**
* Internal setter for raw parameter value.
* @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
* @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
* @param longFlagName The long flag name for the given action.
* @param value The new current value object stored in the parameter, which depends on the
* argument mode.
*/
protected void setValue(String verb, String directObject, String longFlagName, Object value) {
String key = verb + '/' + directObject + '/' + longFlagName;
Arg arg = mArguments.get(key);
arg.setCurrentValue(value);
}
/**
* Parses the command-line arguments.
* <p/>
* This method will exit and not return if a parsing error arise.
*
* @param args The arguments typically received by a main method.
*/
public void parseArgs(String[] args) {
String errorMsg = null;
String verb = null;
String directObject = null;
try {
int n = args.length;
for (int i = 0; i < n; i++) {
Arg arg = null;
String a = args[i];
if (a.startsWith("--")) { //$NON-NLS-1$
arg = findLongArg(verb, directObject, a.substring(2));
} else if (a.startsWith("-")) { //$NON-NLS-1$
arg = findShortArg(verb, directObject, a.substring(1));
}
// No matching argument name found
if (arg == null) {
// Does it looks like a dashed parameter?
if (a.startsWith("-")) { //$NON-NLS-1$
if (verb == null || directObject == null) {
// It looks like a dashed parameter and we don't have a a verb/object
// set yet, the parameter was just given too early.
errorMsg = String.format(
"Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
a);
return;
} else {
// It looks like a dashed parameter but it is unknown by this
// verb-object combination
errorMsg = String.format(
"Flag '%1$s' is not valid for '%2$s %3$s'.",
a, verb, directObject);
return;
}
}
if (verb == null) {
// Fill verb first. Find it.
for (String[] actionDesc : mActions) {
if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
verb = a;
break;
}
}
// Error if it was not a valid verb
if (verb == null) {
errorMsg = String.format(
"Expected verb after global parameters but found '%1$s' instead.",
a);
return;
}
} else if (directObject == null) {
// Then fill the direct object. Find it.
for (String[] actionDesc : mActions) {
if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
directObject = a;
break;
} else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
// if the alternate form exist and is used, we internally
// only memorize the default direct object form.
directObject = actionDesc[ACTION_OBJECT_INDEX];
break;
}
}
}
// Error if it was not a valid object for that verb
if (directObject == null) {
errorMsg = String.format(
"Expected verb after global parameters but found '%1$s' instead.",
a);
return;
}
} else {
// The argument is not a dashed parameter and we already
// have a verb/object. Must be some extra unknown argument.
errorMsg = String.format(
"Argument '%1$s' is not recognized.",
a);
}
} else {
// This argument was present on the command line
arg.setInCommandLine(true);
// Process keyword
Object error = null;
if (arg.getMode().needsExtra()) {
if (i+1 >= n) {
errorMsg = String.format("Missing argument for flag %1$s.", a);
return;
}
while (i+1 < n) {
String b = args[i+1];
if (arg.getMode() != Mode.STRING_ARRAY) {
// We never accept something that looks like a valid argument
// unless we see -- first
Arg dummyArg = null;
if (b.startsWith("--")) { //$NON-NLS-1$
dummyArg = findLongArg(verb, directObject, b.substring(2));
} else if (b.startsWith("-")) { //$NON-NLS-1$
dummyArg = findShortArg(verb, directObject, b.substring(1));
}
if (dummyArg != null) {
errorMsg = String.format(
"Oops, it looks like you didn't provide an argument for '%1$s'.\n'%2$s' was found instead.",
a, b);
return;
}
}
error = arg.getMode().process(arg, b);
if (error == Accept.CONTINUE) {
i++;
} else if (error == Accept.ACCEPT_AND_STOP) {
i++;
break;
} else if (error == Accept.REJECT_AND_STOP) {
break;
} else if (error instanceof String) {
// We stop because of an error
break;
}
}
} else {
error = arg.getMode().process(arg, null);
if (isHelpRequested()) {
// The --help flag was requested. We'll continue the usual processing
// so that we can find the optional verb/object words. Those will be
// used to print specific help.
// Setting a non-null error message triggers printing the help, however
// there is no specific error to print.
errorMsg = ""; //$NON-NLS-1$
}
}
if (error instanceof String) {
errorMsg = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
return;
}
}
}
if (errorMsg == null) {
if (verb == null && !acceptLackOfVerb()) {
errorMsg = "Missing verb name.";
} else if (verb != null) {
if (directObject == null) {
// Make sure this verb has an optional direct object
for (String[] actionDesc : mActions) {
if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
directObject = NO_VERB_OBJECT;
break;
}
}
if (directObject == null) {
errorMsg = String.format("Missing object name for verb '%1$s'.", verb);
return;
}
}
// Validate that all mandatory arguments are non-null for this action
String missing = null;
boolean plural = false;
for (Entry<String, Arg> entry : mArguments.entrySet()) {
Arg arg = entry.getValue();
if (arg.getVerb().equals(verb) &&
arg.getDirectObject().equals(directObject)) {
if (arg.isMandatory() && arg.getCurrentValue() == null) {
if (missing == null) {
missing = "--" + arg.getLongArg(); //$NON-NLS-1$
} else {
missing += ", --" + arg.getLongArg(); //$NON-NLS-1$
plural = true;
}
}
}
}
if (missing != null) {
errorMsg = String.format(
"The %1$s %2$s must be defined for action '%3$s %4$s'",
plural ? "parameters" : "parameter",
missing,
verb,
directObject);
}
mVerbRequested = verb;
mDirectObjectRequested = directObject;
}
}
} finally {
if (errorMsg != null) {
printHelpAndExitForAction(verb, directObject, errorMsg);
}
}
}
/**
* Finds an {@link com.android.sdklib.util.CommandLineParser.Arg} given an action name and a long flag name.
* @return The {@link com.android.sdklib.util.CommandLineParser.Arg} found or null.
*/
protected Arg findLongArg(String verb, String directObject, String longName) {
if (verb == null) {
verb = GLOBAL_FLAG_VERB;
}
if (directObject == null) {
directObject = NO_VERB_OBJECT;
}
String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$
return mArguments.get(key);
}
/**
* Finds an {@link com.android.sdklib.util.CommandLineParser.Arg} given an action name and a short flag name.
* @return The {@link com.android.sdklib.util.CommandLineParser.Arg} found or null.
*/
protected Arg findShortArg(String verb, String directObject, String shortName) {
if (verb == null) {
verb = GLOBAL_FLAG_VERB;
}
if (directObject == null) {
directObject = NO_VERB_OBJECT;
}
for (Entry<String, Arg> entry : mArguments.entrySet()) {
Arg arg = entry.getValue();
if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
if (shortName.equals(arg.getShortArg())) {
return arg;
}
}
}
return null;
}
/**
* Prints the help/usage and exits.
*
* @param errorFormat Optional error message to print prior to usage using String.format
* @param args Arguments for String.format
*/
public void printHelpAndExit(String errorFormat, Object... args) {
printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
}
/**
* Prints the help/usage and exits.
*
* @param verb If null, displays help for all verbs. If not null, display help only
* for that specific verb. In all cases also displays general usage and action list.
* @param directObject If null, displays help for all verb objects.
* If not null, displays help only for that specific action
* In all cases also display general usage and action list.
* @param errorFormat Optional error message to print prior to usage using String.format
* @param args Arguments for String.format
*/
public void printHelpAndExitForAction(String verb, String directObject,
String errorFormat, Object... args) {
if (errorFormat != null && errorFormat.length() > 0) {
stderr(errorFormat, args);
}
/*
* usage should fit in 80 columns
* 12345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
stdout("\n" +
"Usage:\n" +
" android [global options] %s [action options]\n" +
"\n" +
"Global options:",
verb == null ? "action" :
verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$
listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
if (verb == null || directObject == null) {
stdout("\nValid actions are composed of a verb and an optional direct object:");
for (String[] action : mActions) {
if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
stdout("- %1$6s %2$-13s: %3$s",
action[ACTION_VERB_INDEX],
action[ACTION_OBJECT_INDEX],
action[ACTION_DESC_INDEX]);
}
}
}
// Only print details if a verb/object is requested
if (verb != null) {
for (String[] action : mActions) {
if (verb.equals(action[ACTION_VERB_INDEX])) {
if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
stdout("\nAction \"%1$s %2$s\":",
action[ACTION_VERB_INDEX],
action[ACTION_OBJECT_INDEX]);
stdout(" %1$s", action[ACTION_DESC_INDEX]);
stdout("Options:");
listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
}
}
}
}
exit();
}
/**
* Internal helper to print all the option flags for a given action name.
*/
protected void listOptions(String verb, String directObject) {
int numOptions = 0;
int longArgLen = 8;
for (Entry<String, Arg> entry : mArguments.entrySet()) {
Arg arg = entry.getValue();
if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
int n = arg.getLongArg().length();
if (n > longArgLen) {
longArgLen = n;
}
}
}
for (Entry<String, Arg> entry : mArguments.entrySet()) {
Arg arg = entry.getValue();
if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
String value = ""; //$NON-NLS-1$
String required = ""; //$NON-NLS-1$
if (arg.isMandatory()) {
required = " [required]";
} else {
if (arg.getDefaultValue() instanceof String[]) {
for (String v : (String[]) arg.getDefaultValue()) {
if (value.length() > 0) {
value += ", ";
}
value += v;
}
} else if (arg.getDefaultValue() != null) {
Object v = arg.getDefaultValue();
if (arg.getMode() != Mode.BOOLEAN || v.equals(Boolean.TRUE)) {
value = v.toString();
}
}
if (value.length() > 0) {
value = " [Default: " + value + "]";
}
}
// Java doesn't support * for printf variable width, so we'll insert the long arg
// width "manually" in the printf format string.
String longArgWidth = Integer.toString(longArgLen + 2);
// Print a line in the form " -1_letter_arg --long_arg description"
// where either the 1-letter arg or the long arg are optional.
String output = String.format(
" %1$-2s %2$-" + longArgWidth + "s: %3$s%4$s%5$s", //$NON-NLS-1$ //$NON-NLS-2$
arg.getShortArg().length() > 0 ?
"-" + arg.getShortArg() : //$NON-NLS-1$
"", //$NON-NLS-1$
arg.getLongArg().length() > 0 ?
"--" + arg.getLongArg() : //$NON-NLS-1$
"", //$NON-NLS-1$
arg.getDescription(),
value,
required);
stdout(output);
numOptions++;
}
}
if (numOptions == 0) {
stdout(" No options");
}
}
//----
private static enum Accept {
CONTINUE,
ACCEPT_AND_STOP,
REJECT_AND_STOP,
}
/**
* The mode of an argument specifies the type of variable it represents,
* whether an extra parameter is required after the flag and how to parse it.
*/
public static enum Mode {
/** Argument value is a Boolean. Default value is a Boolean. */
BOOLEAN {
@Override
public boolean needsExtra() {
return false;
}
@Override
public Object process(Arg arg, String extra) {
// Toggle the current value
arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
return Accept.ACCEPT_AND_STOP;
}
},
/** Argument value is an Integer. Default value is an Integer. */
INTEGER {
@Override
public boolean needsExtra() {
return true;
}
@Override
public Object process(Arg arg, String extra) {
try {
arg.setCurrentValue(Integer.parseInt(extra));
return null;
} catch (NumberFormatException e) {
return String.format("Failed to parse '%1$s' as an integer: %2$s", extra,
e.getMessage());
}
}
},
/** Argument value is a String. Default value is a String[]. */
ENUM {
@Override
public boolean needsExtra() {
return true;
}
@Override
public Object process(Arg arg, String extra) {
StringBuilder desc = new StringBuilder();
String[] values = (String[]) arg.getDefaultValue();
for (String value : values) {
if (value.equals(extra)) {
arg.setCurrentValue(extra);
return Accept.ACCEPT_AND_STOP;
}
if (desc.length() != 0) {
desc.append(", ");
}
desc.append(value);
}
return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
}
},
/** Argument value is a String. Default value is a null. */
STRING {
@Override
public boolean needsExtra() {
return true;
}
@Override
public Object process(Arg arg, String extra) {
arg.setCurrentValue(extra);
return Accept.ACCEPT_AND_STOP;
}
},
/** Argument value is a {@link java.util.List}&lt;String&gt;. Default value is an empty list. */
STRING_ARRAY {
@Override
public boolean needsExtra() {
return true;
}
@Override
public Object process(Arg arg, String extra) {
// For simplification, a string array doesn't accept something that
// starts with a dash unless a pure -- was seen before.
if (extra != null) {
Object v = arg.getCurrentValue();
if (v == null) {
ArrayList<String> a = new ArrayList<String>();
arg.setCurrentValue(a);
v = a;
}
if (v instanceof List<?>) {
@SuppressWarnings("unchecked") List<String> a = (List<String>) v;
if (extra.equals("--") ||
!extra.startsWith("-") ||
(extra.startsWith("-") && a.contains("--"))) {
a.add(extra);
return Accept.CONTINUE;
} else if (a.isEmpty()) {
return "No values provided";
}
}
}
return Accept.REJECT_AND_STOP;
}
};
/**
* Returns true if this mode requires an extra parameter.
*/
public abstract boolean needsExtra();
/**
* Processes the flag for this argument.
*
* @param arg The argument being processed.
* @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
* @return {@link com.android.util.CommandLineParser.Accept#CONTINUE} if this argument can use multiple values and
* wishes to receive more.
* Or {@link com.android.util.CommandLineParser.Accept#ACCEPT_AND_STOP} if this was the last value accepted by the argument.
* Or {@link com.android.util.CommandLineParser.Accept#REJECT_AND_STOP} if this was value was reject and the argument
* stops accepting new values with no error.
* Or a string in case of error.
* Never returns null.
*/
public abstract Object process(Arg arg, String extra);
}
/**
* An argument accepted by the command-line, also called "a flag".
* Arguments must have a short version (one letter), a long version name and a description.
* They can have a default value, or it can be null.
* Depending on the {@link com.android.sdklib.util.CommandLineParser.Mode}, the default value can be a Boolean, an Integer, a String
* or a String array (in which case the first item is the current by default.)
*/
static class Arg {
/** Verb for that argument. Never null. */
private final String mVerb;
/** Direct Object for that argument. Never null, but can be empty string. */
private final String mDirectObject;
/** The 1-letter short name of the argument, e.g. -v. */
private final String mShortName;
/** The long name of the argument, e.g. --verbose. */
private final String mLongName;
/** A description. Never null. */
private final String mDescription;
/** A default value. Can be null. */
private final Object mDefaultValue;
/** The argument mode (type + process method). Never null. */
private final Mode mMode;
/** True if this argument is mandatory for this verb/directobject. */
private final boolean mMandatory;
/** Current value. Initially set to the default value. */
private Object mCurrentValue;
/** True if the argument has been used on the command line. */
private boolean mInCommandLine;
/**
* Creates a new argument flag description.
*
* @param mode The {@link com.android.util.CommandLineParser.Mode} for the argument.
* @param mandatory True if this argument is mandatory for this action.
* @param verb The verb name. Never null. Can be {@link com.android.util.CommandLineParser#GLOBAL_FLAG_VERB}.
* @param directObject The action name. Can be {@link com.android.util.CommandLineParser#NO_VERB_OBJECT}.
* @param shortName The one-letter short argument name. Can be empty but not null.
* @param longName The long argument name. Can be empty but not null.
* @param description The description. Cannot be null.
* @param defaultValue The default value (or values), which depends on the selected
* {@link com.android.util.CommandLineParser.Mode}. Can be null.
*/
public Arg(Mode mode,
boolean mandatory,
@NonNull String verb,
@NonNull String directObject,
@NonNull String shortName,
@NonNull String longName,
@NonNull String description,
@Nullable Object defaultValue) {
mMode = mode;
mMandatory = mandatory;
mVerb = verb;
mDirectObject = directObject;
mShortName = shortName;
mLongName = longName;
mDescription = description;
mDefaultValue = defaultValue;
mInCommandLine = false;
if (defaultValue instanceof String[]) {
mCurrentValue = ((String[])defaultValue)[0];
} else {
mCurrentValue = mDefaultValue;
}
}
/** Return true if this argument is mandatory for this verb/directobject. */
public boolean isMandatory() {
return mMandatory;
}
/** Returns the 1-letter short name of the argument, e.g. -v. */
public String getShortArg() {
return mShortName;
}
/** Returns the long name of the argument, e.g. --verbose. */
public String getLongArg() {
return mLongName;
}
/** Returns the description. Never null. */
public String getDescription() {
return mDescription;
}
/** Returns the verb for that argument. Never null. */
public String getVerb() {
return mVerb;
}
/** Returns the direct Object for that argument. Never null, but can be empty string. */
public String getDirectObject() {
return mDirectObject;
}
/** Returns the default value. Can be null. */
public Object getDefaultValue() {
return mDefaultValue;
}
/** Returns the current value. Initially set to the default value. Can be null. */
public Object getCurrentValue() {
return mCurrentValue;
}
/** Sets the current value. Can be null. */
public void setCurrentValue(Object currentValue) {
mCurrentValue = currentValue;
}
/** Returns the argument mode (type + process method). Never null. */
public Mode getMode() {
return mMode;
}
/** Returns true if the argument has been used on the command line. */
public boolean isInCommandLine() {
return mInCommandLine;
}
/** Sets if the argument has been used on the command line. */
public void setInCommandLine(boolean inCommandLine) {
mInCommandLine = inCommandLine;
}
}
/**
* Internal helper to define a new argument for a give action.
*
* @param mode The {@link com.android.sdklib.util.CommandLineParser.Mode} for the argument.
* @param mandatory The argument is required (never if {@link com.android.sdklib.util.CommandLineParser.Mode#BOOLEAN})
* @param verb The verb name. Never null. Can be {@link com.android.sdklib.util.CommandLineParser#GLOBAL_FLAG_VERB}.
* @param directObject The action name. Can be {@link com.android.sdklib.util.CommandLineParser#NO_VERB_OBJECT}.
* @param shortName The one-letter short argument name. Can be empty but not null.
* @param longName The long argument name. Can be empty but not null.
* @param description The description. Cannot be null.
* @param defaultValue The default value (or values), which depends on the selected
* {@link com.android.sdklib.util.CommandLineParser.Mode}.
*/
protected void define(Mode mode,
boolean mandatory,
@NonNull String verb,
@NonNull String directObject,
@NonNull String shortName,
@NonNull String longName,
@NonNull String description,
@Nullable Object defaultValue) {
assert verb != null;
assert(!(mandatory && mode == Mode.BOOLEAN)); // a boolean mode cannot be mandatory
// We should always have at least a short or long name, ideally both but never none.
assert shortName != null;
assert longName != null;
assert shortName.length() > 0 || longName.length() > 0;
if (directObject == null) {
directObject = NO_VERB_OBJECT;
}
String key = verb + '/' + directObject + '/' + longName;
mArguments.put(key, new Arg(mode, mandatory,
verb, directObject, shortName, longName, description, defaultValue));
}
/**
* Exits in case of error.
* This is protected so that it can be overridden in unit tests.
*/
protected void exit() {
System.exit(1);
}
/**
* Prints a line to stdout.
* This is protected so that it can be overridden in unit tests.
*
* @param format The string to be formatted. Cannot be null.
* @param args Format arguments.
*/
protected void stdout(String format, Object...args) {
String output = String.format(format, args);
output = LineUtil.reflowLine(output);
mLog.info("%s\n", output); //$NON-NLS-1$
}
/**
* Prints a line to stderr.
* This is protected so that it can be overridden in unit tests.
*
* @param format The string to be formatted. Cannot be null.
* @param args Format arguments.
*/
protected void stderr(String format, Object...args) {
mLog.error(null, format, args);
}
}