Update from Google.
--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParser.java b/src/main/java/com/google/devtools/common/options/OptionsParser.java
new file mode 100644
index 0000000..9564daa
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsParser.java
@@ -0,0 +1,526 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.google.devtools.common.options;
+
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A parser for options. Typical use case in a main method:
+ *
+ * <pre>
+ * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class);
+ * parser.parseAndExitUponError(args);
+ * FooOptions foo = parser.getOptions(FooOptions.class);
+ * BarOptions bar = parser.getOptions(BarOptions.class);
+ * List<String> otherArguments = parser.getResidue();
+ * </pre>
+ *
+ * <p>FooOptions and BarOptions would be options specification classes, derived
+ * from OptionsBase, that contain fields annotated with @Option(...).
+ *
+ * <p>Alternatively, rather than calling
+ * {@link #parseAndExitUponError(OptionPriority, String, String[])},
+ * client code may call {@link #parse(OptionPriority,String,List)}, and handle
+ * parser exceptions usage messages themselves.
+ *
+ * <p>This options parsing implementation has (at least) one design flaw. It
+ * allows both '--foo=baz' and '--foo baz' for all options except void, boolean
+ * and tristate options. For these, the 'baz' in '--foo baz' is not treated as
+ * a parameter to the option, making it is impossible to switch options between
+ * void/boolean/tristate and everything else without breaking backwards
+ * compatibility.
+ *
+ * @see Options a simpler class which you can use if you only have one options
+ * specification class
+ */
+public class OptionsParser implements OptionsProvider {
+
+ /**
+ * A cache for the parsed options data. Both keys and values are immutable, so
+ * this is always safe. Only access this field through the {@link
+ * #getOptionsData} method for thread-safety! The cache is very unlikely to
+ * grow to a significant amount of memory, because there's only a fixed set of
+ * options classes on the classpath.
+ */
+ private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData =
+ Maps.newHashMap();
+
+ private static synchronized OptionsData getOptionsData(
+ ImmutableList<Class<? extends OptionsBase>> optionsClasses) {
+ OptionsData result = optionsData.get(optionsClasses);
+ if (result == null) {
+ result = OptionsData.of(optionsClasses);
+ optionsData.put(optionsClasses, result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns all the annotated fields for the given class, including inherited
+ * ones.
+ */
+ static Collection<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) {
+ OptionsData data = getOptionsData(ImmutableList.<Class<? extends OptionsBase>>of(optionsClass));
+ return data.getFieldsForClass(optionsClass);
+ }
+
+ /**
+ * @see #newOptionsParser(Iterable)
+ */
+ public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1) {
+ return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1));
+ }
+
+ /**
+ * @see #newOptionsParser(Iterable)
+ */
+ public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1,
+ Class<? extends OptionsBase> class2) {
+ return newOptionsParser(ImmutableList.of(class1, class2));
+ }
+
+ /**
+ * Create a new {@link OptionsParser}.
+ */
+ public static OptionsParser newOptionsParser(
+ Iterable<Class<? extends OptionsBase>> optionsClasses) {
+ return new OptionsParser(getOptionsData(ImmutableList.copyOf(optionsClasses)));
+ }
+
+ /**
+ * Canonicalizes a list of options using the given option classes. The
+ * contract is that if the returned set of options is passed to an options
+ * parser with the same options classes, then that will have the same effect
+ * as using the original args (which are passed in here), except for cosmetic
+ * differences.
+ */
+ public static List<String> canonicalize(
+ Collection<Class<? extends OptionsBase>> optionsClasses, List<String> args)
+ throws OptionsParsingException {
+ OptionsParser parser = new OptionsParser(optionsClasses);
+ parser.setAllowResidue(false);
+ parser.parse(args);
+ return parser.impl.asCanonicalizedList();
+ }
+
+ private final OptionsParserImpl impl;
+ private final List<String> residue = new ArrayList<String>();
+ private boolean allowResidue = true;
+
+ OptionsParser(Collection<Class<? extends OptionsBase>> optionsClasses) {
+ this(OptionsData.of(optionsClasses));
+ }
+
+ OptionsParser(OptionsData optionsData) {
+ impl = new OptionsParserImpl(optionsData);
+ }
+
+ /**
+ * Indicates whether or not the parser will allow a non-empty residue; that
+ * is, iff this value is true then a call to one of the {@code parse}
+ * methods will throw {@link OptionsParsingException} unless
+ * {@link #getResidue()} is empty after parsing.
+ */
+ public void setAllowResidue(boolean allowResidue) {
+ this.allowResidue = allowResidue;
+ }
+
+ /**
+ * Indicates whether or not the parser will allow long options with a
+ * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
+ */
+ public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
+ this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions);
+ }
+
+ public void parseAndExitUponError(String[] args) {
+ parseAndExitUponError(OptionPriority.COMMAND_LINE, "unknown", args);
+ }
+
+ /**
+ * A convenience function for use in main methods. Parses the command line
+ * parameters, and exits upon error. Also, prints out the usage message
+ * if "--help" appears anywhere within {@code args}.
+ */
+ public void parseAndExitUponError(OptionPriority priority, String source, String[] args) {
+ try {
+ parse(priority, source, Arrays.asList(args));
+ } catch (OptionsParsingException e) {
+ System.err.println("Error parsing command line: " + e.getMessage());
+ System.err.println("Try --help.");
+ System.exit(2);
+ }
+ for (String arg : args) {
+ if (arg.equals("--help")) {
+ System.out.println(describeOptions(Collections.<String, String>emptyMap(),
+ HelpVerbosity.LONG));
+ System.exit(0);
+ }
+ }
+ }
+
+ /**
+ * The name and value of an option with additional metadata describing its
+ * priority, source, whether it was set via an implicit dependency, and if so,
+ * by which other option.
+ */
+ public static class OptionValueDescription {
+ private final String name;
+ private final Object value;
+ private final OptionPriority priority;
+ private final String source;
+ private final String implicitDependant;
+ private final String expandedFrom;
+
+ public OptionValueDescription(String name, Object value,
+ OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
+ this.name = name;
+ this.value = value;
+ this.priority = priority;
+ this.source = source;
+ this.implicitDependant = implicitDependant;
+ this.expandedFrom = expandedFrom;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public OptionPriority getPriority() {
+ return priority;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getImplicitDependant() {
+ return implicitDependant;
+ }
+
+ public boolean isImplicitDependency() {
+ return implicitDependant != null;
+ }
+
+ public String getExpansionParent() {
+ return expandedFrom;
+ }
+
+ public boolean isExpansion() {
+ return expandedFrom != null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("option '").append(name).append("' ");
+ result.append("set to '").append(value).append("' ");
+ result.append("with priority ").append(priority);
+ if (source != null) {
+ result.append(" and source '").append(source).append("'");
+ }
+ if (implicitDependant != null) {
+ result.append(" implicitly by ");
+ }
+ return result.toString();
+ }
+ }
+
+ /**
+ * The name and unparsed value of an option with additional metadata describing its
+ * priority, source, whether it was set via an implicit dependency, and if so,
+ * by which other option.
+ *
+ * <p>Note that the unparsed value and the source parameters can both be null.
+ */
+ public static class UnparsedOptionValueDescription {
+ private final String name;
+ private final Field field;
+ private final String unparsedValue;
+ private final OptionPriority priority;
+ private final String source;
+ private final boolean explicit;
+
+ public UnparsedOptionValueDescription(String name, Field field, String unparsedValue,
+ OptionPriority priority, String source, boolean explicit) {
+ this.name = name;
+ this.field = field;
+ this.unparsedValue = unparsedValue;
+ this.priority = priority;
+ this.source = source;
+ this.explicit = explicit;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ Field getField() {
+ return field;
+ }
+
+ public boolean isBooleanOption() {
+ return field.getType().equals(boolean.class);
+ }
+
+ private DocumentationLevel documentationLevel() {
+ Option option = field.getAnnotation(Option.class);
+ return OptionsParser.documentationLevel(option.category());
+ }
+
+ public boolean isDocumented() {
+ return documentationLevel() == DocumentationLevel.DOCUMENTED;
+ }
+
+ public boolean isHidden() {
+ return documentationLevel() == DocumentationLevel.HIDDEN;
+ }
+
+ boolean isExpansion() {
+ Option option = field.getAnnotation(Option.class);
+ return option.expansion().length > 0;
+ }
+
+ boolean isImplicitRequirement() {
+ Option option = field.getAnnotation(Option.class);
+ return option.implicitRequirements().length > 0;
+ }
+
+ boolean allowMultiple() {
+ Option option = field.getAnnotation(Option.class);
+ return option.allowMultiple();
+ }
+
+ public String getUnparsedValue() {
+ return unparsedValue;
+ }
+
+ OptionPriority getPriority() {
+ return priority;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public boolean isExplicit() {
+ return explicit;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("option '").append(name).append("' ");
+ result.append("set to '").append(unparsedValue).append("' ");
+ result.append("with priority ").append(priority);
+ if (source != null) {
+ result.append(" and source '").append(source).append("'");
+ }
+ return result.toString();
+ }
+ }
+
+ /**
+ * The verbosity with which option help messages are displayed: short (just
+ * the name), medium (name, type, default, abbreviation), and long (full
+ * description).
+ */
+ public enum HelpVerbosity { LONG, MEDIUM, SHORT }
+
+ /**
+ * The level of documentation. Only documented options are output as part of
+ * the help.
+ *
+ * <p>We use 'hidden' so that options that form the protocol between the
+ * client and the server are not logged.
+ */
+ enum DocumentationLevel {
+ DOCUMENTED, UNDOCUMENTED, HIDDEN
+ }
+
+ /**
+ * Returns a description of all the options this parser can digest.
+ * In addition to {@link Option} annotations, this method also
+ * interprets {@link OptionsUsage} annotations which give an intuitive short
+ * description for the options.
+ *
+ * @param categoryDescriptions a mapping from category names to category
+ * descriptions. Options of the same category (see {@link
+ * Option#category}) will be grouped together, preceded by the description
+ * of the category.
+ * @param helpVerbosity if {@code long}, the options will be described
+ * verbosely, including their types, defaults and descriptions. If {@code
+ * medium}, the descriptions are omitted, and if {@code short}, the options
+ * are just enumerated.
+ */
+ public String describeOptions(Map<String, String> categoryDescriptions,
+ HelpVerbosity helpVerbosity) {
+ StringBuilder desc = new StringBuilder();
+ if (!impl.getOptionsClasses().isEmpty()) {
+
+ List<Field> allFields = Lists.newArrayList();
+ for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
+ allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
+ }
+ Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
+ String prevCategory = null;
+
+ for (Field optionField : allFields) {
+ String category = optionField.getAnnotation(Option.class).category();
+ if (!category.equals(prevCategory)) {
+ prevCategory = category;
+ String description = categoryDescriptions.get(category);
+ if (description == null) {
+ description = "Options category '" + category + "'";
+ }
+ if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) {
+ desc.append("\n").append(description).append(":\n");
+ }
+ }
+
+ if (documentationLevel(prevCategory) == DocumentationLevel.DOCUMENTED) {
+ OptionsUsage.getUsage(optionField, desc, helpVerbosity);
+ }
+ }
+ }
+ return desc.toString().trim();
+ }
+
+ /**
+ * Returns a description of the option value set by the last previous call to
+ * {@link #parse(OptionPriority, String, List)} that successfully set the given
+ * option. If the option is of type {@link List}, the description will
+ * correspond to any one of the calls, but not necessarily the last.
+ */
+ public OptionValueDescription getOptionValueDescription(String name) {
+ return impl.getOptionValueDescription(name);
+ }
+
+ static DocumentationLevel documentationLevel(String category) {
+ if ("undocumented".equals(category)) {
+ return DocumentationLevel.UNDOCUMENTED;
+ } else if ("hidden".equals(category)) {
+ return DocumentationLevel.HIDDEN;
+ } else {
+ return DocumentationLevel.DOCUMENTED;
+ }
+ }
+
+ /**
+ * A convenience method, equivalent to
+ * {@code parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args))}.
+ */
+ public void parse(String... args) throws OptionsParsingException {
+ parse(OptionPriority.COMMAND_LINE, (String) null, Arrays.asList(args));
+ }
+
+ /**
+ * A convenience method, equivalent to
+ * {@code parse(OptionPriority.COMMAND_LINE, null, args)}.
+ */
+ public void parse(List<String> args) throws OptionsParsingException {
+ parse(OptionPriority.COMMAND_LINE, (String) null, args);
+ }
+
+ /**
+ * Parses {@code args}, using the classes registered with this parser.
+ * {@link #getOptions(Class)} and {@link #getResidue()} return the results.
+ * May be called multiple times; later options override existing ones if they
+ * have equal or higher priority. The source of options is a free-form string
+ * that can be used for debugging. Strings that cannot be parsed as options
+ * accumulates as residue, if this parser allows it.
+ *
+ * @see OptionPriority
+ */
+ public void parse(OptionPriority priority, String source,
+ List<String> args) throws OptionsParsingException {
+ parseWithSourceFunction(priority, Functions.constant(source), args);
+ }
+
+ /**
+ * Parses {@code args}, using the classes registered with this parser.
+ * {@link #getOptions(Class)} and {@link #getResidue()} return the results. May be called
+ * multiple times; later options override existing ones if they have equal or higher priority.
+ * The source of options is given as a function that maps option names to the source of the
+ * option. Strings that cannot be parsed as options accumulates as* residue, if this parser
+ * allows it.
+ */
+ public void parseWithSourceFunction(OptionPriority priority,
+ Function<? super String, String> sourceFunction, List<String> args)
+ throws OptionsParsingException {
+ Preconditions.checkNotNull(priority);
+ Preconditions.checkArgument(priority != OptionPriority.DEFAULT);
+ residue.addAll(impl.parse(priority, sourceFunction, args));
+ if (!allowResidue && !residue.isEmpty()) {
+ String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
+ throw new OptionsParsingException(errorMsg);
+ }
+ }
+
+ @Override
+ public List<String> getResidue() {
+ return ImmutableList.copyOf(residue);
+ }
+
+ /**
+ * Returns a list of warnings about problems encountered by previous parse calls.
+ */
+ public List<String> getWarnings() {
+ return impl.getWarnings();
+ }
+
+ @Override
+ public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
+ return impl.getParsedOptions(optionsClass);
+ }
+
+ @Override
+ public boolean containsExplicitOption(String name) {
+ return impl.containsExplicitOption(name);
+ }
+
+ @Override
+ public List<UnparsedOptionValueDescription> asListOfUnparsedOptions() {
+ return impl.asListOfUnparsedOptions();
+ }
+
+ @Override
+ public List<UnparsedOptionValueDescription> asListOfExplicitOptions() {
+ return impl.asListOfExplicitOptions();
+ }
+
+ @Override
+ public List<OptionValueDescription> asListOfEffectiveOptions() {
+ return impl.asListOfEffectiveOptions();
+ }
+}