Update from Google.

--
MOE_MIGRATED_REVID=85702957
diff --git a/src/main/java/com/google/devtools/common/options/Converter.java b/src/main/java/com/google/devtools/common/options/Converter.java
new file mode 100644
index 0000000..867ef82
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/Converter.java
@@ -0,0 +1,33 @@
+// 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;
+
+/**
+ * A converter is a little helper object that can take a String and
+ * turn it into an instance of type T (the type parameter to the converter).
+ */
+public interface Converter<T> {
+
+  /**
+   * Convert a string into type T.
+   */
+  T convert(String input) throws OptionsParsingException;
+
+  /**
+   * The type description appears in usage messages. E.g.: "a string",
+   * "a path", etc.
+   */
+  String getTypeDescription();
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/Converters.java b/src/main/java/com/google/devtools/common/options/Converters.java
new file mode 100644
index 0000000..e8c69ec
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/Converters.java
@@ -0,0 +1,326 @@
+// 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.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Some convenient converters used by blaze. Note: These are specific to
+ * blaze.
+ */
+public final class Converters {
+
+  /**
+   * Join a list of words as in English.  Examples:
+   * "nothing"
+   * "one"
+   * "one or two"
+   * "one and two"
+   * "one, two or three".
+   * "one, two and three".
+   * The toString method of each element is used.
+   */
+  static String joinEnglishList(Iterable<?> choices) {
+    StringBuilder buf = new StringBuilder();
+    for (Iterator<?> ii = choices.iterator(); ii.hasNext(); ) {
+      Object choice = ii.next();
+      if (buf.length() > 0) {
+        buf.append(ii.hasNext() ? ", " : " or ");
+      }
+      buf.append(choice);
+    }
+    return buf.length() == 0 ? "nothing" : buf.toString();
+  }
+
+  public static class SeparatedOptionListConverter
+      implements Converter<List<String>> {
+
+    private final String separatorDescription;
+    private final Splitter splitter;
+
+    protected SeparatedOptionListConverter(char separator,
+                                           String separatorDescription) {
+      this.separatorDescription = separatorDescription;
+      this.splitter = Splitter.on(separator);
+    }
+
+    @Override
+    public List<String> convert(String input) {
+      return input.equals("")
+          ? ImmutableList.<String>of()
+          : ImmutableList.copyOf(splitter.split(input));
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return separatorDescription + "-separated list of options";
+    }
+  }
+
+  public static class CommaSeparatedOptionListConverter
+      extends SeparatedOptionListConverter {
+    public CommaSeparatedOptionListConverter() {
+      super(',', "comma");
+    }
+  }
+
+  public static class ColonSeparatedOptionListConverter extends SeparatedOptionListConverter {
+    public ColonSeparatedOptionListConverter() {
+      super(':', "colon");
+    }
+  }
+
+  public static class LogLevelConverter implements Converter<Level> {
+
+    public static Level[] LEVELS = new Level[] {
+      Level.OFF, Level.SEVERE, Level.WARNING, Level.INFO, Level.FINE,
+      Level.FINER, Level.FINEST
+    };
+
+    @Override
+    public Level convert(String input) throws OptionsParsingException {
+      try {
+        int level = Integer.parseInt(input);
+        return LEVELS[level];
+      } catch (NumberFormatException e) {
+        throw new OptionsParsingException("Not a log level: " + input);
+      } catch (ArrayIndexOutOfBoundsException e) {
+        throw new OptionsParsingException("Not a log level: " + input);
+      }
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "0 <= an integer <= " + (LEVELS.length - 1);
+    }
+
+  }
+
+  /**
+   * Checks whether a string is part of a set of strings.
+   */
+  public static class StringSetConverter implements Converter<String> {
+
+    // TODO(bazel-team): if this class never actually contains duplicates, we could s/List/Set/
+    // here.
+    private final List<String> values;
+
+    public StringSetConverter(String... values) {
+      this.values = ImmutableList.copyOf(values);
+    }
+
+    @Override
+    public String convert(String input) throws OptionsParsingException {
+      if (values.contains(input)) {
+        return input;
+      }
+
+      throw new OptionsParsingException("Not one of " + values);
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return joinEnglishList(values);
+    }
+  }
+
+  /**
+   * Checks whether a string is a valid regex pattern and compiles it.
+   */
+  public static class RegexPatternConverter implements Converter<Pattern> {
+
+    @Override
+    public Pattern convert(String input) throws OptionsParsingException {
+      try {
+        return Pattern.compile(input);
+      } catch (PatternSyntaxException e) {
+        throw new OptionsParsingException("Not a valid regular expression: " + e.getMessage());
+      }
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a valid Java regular expression";
+    }
+  }
+
+  /**
+   * Limits the length of a string argument.
+   */
+  public static class LengthLimitingConverter implements Converter<String> {
+    private final int maxSize;
+
+    public LengthLimitingConverter(int maxSize) {
+      this.maxSize = maxSize;
+    }
+
+    @Override
+    public String convert(String input) throws OptionsParsingException {
+      if (input.length() > maxSize) {
+        throw new OptionsParsingException("Input must be " + getTypeDescription());
+      }
+      return input;
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a string <= " + maxSize + " characters";
+    }
+  }
+
+  /**
+   * Checks whether an integer is in the given range.
+   */
+  public static class RangeConverter implements Converter<Integer> {
+    final int minValue;
+    final int maxValue;
+
+    public RangeConverter(int minValue, int maxValue) {
+      this.minValue = minValue;
+      this.maxValue = maxValue;
+    }
+
+    @Override
+    public Integer convert(String input) throws OptionsParsingException {
+      try {
+        Integer value = Integer.parseInt(input);
+        if (value < minValue) {
+          throw new OptionsParsingException("'" + input + "' should be >= " + minValue);
+        } else if (value < minValue || value > maxValue) {
+          throw new OptionsParsingException("'" + input + "' should be <= " + maxValue);
+        }
+        return value;
+      } catch (NumberFormatException e) {
+        throw new OptionsParsingException("'" + input + "' is not an int");
+      }
+    }
+
+    @Override
+    public String getTypeDescription() {
+      if (minValue == Integer.MIN_VALUE) {
+        if (maxValue == Integer.MAX_VALUE) {
+          return "an integer";
+        } else {
+          return "an integer, <= " + maxValue;
+        }
+      } else if (maxValue == Integer.MAX_VALUE) {
+        return "an integer, >= " + minValue;
+      } else {
+        return "an integer in "
+            + (minValue < 0 ? "(" + minValue + ")" : minValue) + "-" + maxValue + " range";
+      }
+    }
+  }
+
+  /**
+   * A converter for variable assignments from the parameter list of a blaze
+   * command invocation. Assignments are expected to have the form "name=value",
+   * where names and values are defined to be as permissive as possible.
+   */
+  public static class AssignmentConverter implements Converter<Map.Entry<String, String>> {
+
+    @Override
+    public Map.Entry<String, String> convert(String input)
+        throws OptionsParsingException {
+      int pos = input.indexOf("=");
+      if (pos <= 0) {
+        throw new OptionsParsingException("Variable definitions must be in the form of a "
+            + "'name=value' assignment");
+      }
+      String name = input.substring(0, pos);
+      String value = input.substring(pos + 1);
+      return Maps.immutableEntry(name, value);
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a 'name=value' assignment";
+    }
+
+  }
+
+  /**
+   * A converter for variable assignments from the parameter list of a blaze
+   * command invocation. Assignments are expected to have the form "name[=value]",
+   * where names and values are defined to be as permissive as possible and value
+   * part can be optional (in which case it is considered to be null).
+   */
+  public static class OptionalAssignmentConverter implements Converter<Map.Entry<String, String>> {
+
+    @Override
+    public Map.Entry<String, String> convert(String input)
+        throws OptionsParsingException {
+      int pos = input.indexOf("=");
+      if (pos == 0 || input.length() == 0) {
+        throw new OptionsParsingException("Variable definitions must be in the form of a "
+            + "'name=value' or 'name' assignment");
+      } else if (pos < 0) {
+        return Maps.immutableEntry(input, null);
+      }
+      String name = input.substring(0, pos);
+      String value = input.substring(pos + 1);
+      return Maps.immutableEntry(name, value);
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a 'name=value' assignment with an optional value part";
+    }
+
+  }
+
+  public static class HelpVerbosityConverter extends EnumConverter<OptionsParser.HelpVerbosity> {
+    public HelpVerbosityConverter() {
+      super(OptionsParser.HelpVerbosity.class, "--help_verbosity setting");
+    }
+  }
+
+  /**
+   * A converter for boolean values. This is already one of the defaults, so clients
+   * should not typically need to add this.
+   */
+  public static class BooleanConverter implements Converter<Boolean> {
+    @Override
+    public Boolean convert(String input) throws OptionsParsingException {
+      if (input == null) {
+        return false;
+      }
+      input = input.toLowerCase();
+      if (input.equals("true") || input.equals("1") || input.equals("yes") ||
+          input.equals("t") || input.equals("y")) {
+        return true;
+      }
+      if (input.equals("false") || input.equals("0") || input.equals("no") ||
+          input.equals("f") || input.equals("n")) {
+        return false;
+      }
+      throw new OptionsParsingException("'" + input + "' is not a boolean");
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "a boolean";
+    }
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/DuplicateOptionDeclarationException.java b/src/main/java/com/google/devtools/common/options/DuplicateOptionDeclarationException.java
new file mode 100644
index 0000000..b4e572e
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/DuplicateOptionDeclarationException.java
@@ -0,0 +1,26 @@
+// 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;
+
+/**
+ * Indicates that an option is declared in more than one class.
+ */
+public class DuplicateOptionDeclarationException extends RuntimeException {
+
+  DuplicateOptionDeclarationException(String message) {
+    super(message);
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/EnumConverter.java b/src/main/java/com/google/devtools/common/options/EnumConverter.java
new file mode 100644
index 0000000..f65241a
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/EnumConverter.java
@@ -0,0 +1,74 @@
+// 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 java.util.Arrays;
+
+/**
+ * A converter superclass for converters that parse enums.
+ *
+ * Just subclass this class, creating a zero aro argument constructor that
+ * calls {@link #EnumConverter(Class, String)}.
+ *
+ * This class compares the input string to the string returned by the toString()
+ * method of each enum member in a case-insensitive way. Usually, this is the
+ * name of the symbol, but beware if you override toString()!
+ */
+public abstract class EnumConverter<T extends Enum<T>>
+    implements Converter<T> {
+
+  private final Class<T> enumType;
+  private final String typeName;
+
+  /**
+   * Creates a new enum converter. You *must* implement a zero-argument
+   * constructor that delegates to this constructor, passing in the appropriate
+   * parameters.
+   *
+   * @param enumType The type of your enumeration; usually a class literal
+   *                 like MyEnum.class
+   * @param typeName The intuitive name of your enumeration, for example, the
+   *                 type name for CompilationMode might be "compilation mode".
+   */
+  protected EnumConverter(Class<T> enumType, String typeName) {
+    this.enumType = enumType;
+    this.typeName = typeName;
+  }
+
+  /**
+   * Implements {@link #convert(String)}.
+   */
+  @Override
+  public final T convert(String input) throws OptionsParsingException {
+    for (T value : enumType.getEnumConstants()) {
+      if (value.toString().equalsIgnoreCase(input)) {
+        return value;
+      }
+    }
+    throw new OptionsParsingException("Not a valid " + typeName + ": '"
+                                      + input + "' (should be "
+                                      + getTypeDescription() + ")");
+  }
+
+  /**
+   * Implements {@link #getTypeDescription()}.
+   */
+  @Override
+  public final String getTypeDescription() {
+    return Converters.joinEnglishList(
+        Arrays.asList(enumType.getEnumConstants())).toLowerCase();
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/GenericTypeHelper.java b/src/main/java/com/google/devtools/common/options/GenericTypeHelper.java
new file mode 100644
index 0000000..2240860
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/GenericTypeHelper.java
@@ -0,0 +1,133 @@
+// 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.annotations.VisibleForTesting;
+import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+/**
+ * A helper class for {@link OptionsParserImpl} to help checking the return type
+ * of a {@link Converter} against the type of a field or the element type of a
+ * list.
+ *
+ * <p>This class has to go through considerable contortion to get the correct result
+ * from the Java reflection system, unfortunately. If the generic reflection part
+ * had been better designed, some of this would not be necessary.
+ */
+class GenericTypeHelper {
+
+  /**
+   * Returns the raw type of t, if t is either a raw or parameterized type.
+   * Otherwise, this method throws an {@link AssertionError}.
+   */
+  @VisibleForTesting
+  static Class<?> getRawType(Type t) {
+    if (t instanceof Class<?>) {
+      return (Class<?>) t;
+    } else if (t instanceof ParameterizedType) {
+      return (Class<?>) ((ParameterizedType) t).getRawType();
+    } else {
+      throw new AssertionError("A known concrete type is not concrete");
+    }
+  }
+
+  /**
+   * If type is a parameterized type, searches the given type variable in the list
+   * of declared type variables, and then returns the corresponding actual type.
+   * Returns null if the type variable is not defined by type.
+   */
+  private static Type matchTypeVariable(Type type, TypeVariable<?> variable) {
+    if (type instanceof ParameterizedType) {
+      Class<?> rawInterfaceType = getRawType(type);
+      TypeVariable<?>[] typeParameters = rawInterfaceType.getTypeParameters();
+      for (int i = 0; i < typeParameters.length; i++) {
+        if (variable.equals(typeParameters[i])) {
+          return ((ParameterizedType) type).getActualTypeArguments()[i];
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Resolves the return type of a method, in particular if the generic return
+   * type ({@link Method#getGenericReturnType()}) is a type variable
+   * ({@link TypeVariable}), by checking all super-classes and directly
+   * implemented interfaces.
+   *
+   * <p>The method m must be defined by the given type or by its raw class type.
+   *
+   * @throws AssertionError if the generic return type could not be resolved
+   */
+  // TODO(bazel-team): also check enclosing classes and indirectly implemented
+  // interfaces, which can also contribute type variables. This doesn't happen
+  // in the existing use cases.
+  public static Type getActualReturnType(Type type, Method method) {
+    Type returnType = method.getGenericReturnType();
+    if (returnType instanceof Class<?>) {
+      return returnType;
+    } else if (returnType instanceof ParameterizedType) {
+      return returnType;
+    } else if (returnType instanceof TypeVariable<?>) {
+      TypeVariable<?> variable = (TypeVariable<?>) returnType;
+      while (type != null) {
+        Type candidate = matchTypeVariable(type, variable);
+        if (candidate != null) {
+          return candidate;
+        }
+
+        Class<?> rawType = getRawType(type);
+        for (Type interfaceType : rawType.getGenericInterfaces()) {
+          candidate = matchTypeVariable(interfaceType, variable);
+          if (candidate != null) {
+            return candidate;
+          }
+        }
+
+        type = rawType.getGenericSuperclass();
+      }
+    }
+    throw new AssertionError("The type " + returnType
+        + " is not a Class, ParameterizedType, or TypeVariable");
+  }
+
+  /**
+   * Determines if a value of a particular type (from) is assignable to a field of
+   * a particular type (to). Also allows assigning wrapper types to primitive
+   * types.
+   *
+   * <p>The checks done here should be identical to the checks done by
+   * {@link java.lang.reflect.Field#set}. I.e., if this method returns true, a
+   * subsequent call to {@link java.lang.reflect.Field#set} should succeed.
+   */
+  public static boolean isAssignableFrom(Type to, Type from) {
+    if (to instanceof Class<?>) {
+      Class<?> toClass = (Class<?>) to;
+      if (toClass.isPrimitive()) {
+        return Primitives.wrap(toClass).equals(from);
+      }
+    }
+    return TypeToken.of(to).isAssignableFrom(from);
+  }
+
+  private GenericTypeHelper() {
+    // Prevents Java from creating a public constructor.
+  }
+}
diff --git a/src/main/java/com/google/devtools/common/options/Option.java b/src/main/java/com/google/devtools/common/options/Option.java
new file mode 100644
index 0000000..e244736
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/Option.java
@@ -0,0 +1,127 @@
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An interface for annotating fields in classes (derived from OptionsBase)
+ * that are options.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Option {
+
+  /**
+   * The name of the option ("--name").
+   */
+  String name();
+
+  /**
+   * The single-character abbreviation of the option ("-abbrev").
+   */
+  char abbrev() default '\0';
+
+  /**
+   * A help string for the usage information.
+   */
+  String help() default "";
+
+  /**
+   * The default value for the option. This method should only be invoked
+   * directly by the parser implementation. Any access to default values
+   * should go via the parser to allow for application specific defaults.
+   *
+   * <p>There are two reasons this is a string.  Firstly, it ensures that
+   * explicitly specifying this option at its default value (as printed in the
+   * usage message) has the same behavior as not specifying the option at all;
+   * this would be very hard to achieve if the default value was an instance of
+   * type T, since we'd need to ensure that {@link #toString()} and {@link
+   * #converter} were dual to each other.  The second reason is more mundane
+   * but also more restrictive: annotation values must be compile-time
+   * constants.
+   *
+   * <p>If an option's defaultValue() is the string "null", the option's
+   * converter will not be invoked to interpret it; a null reference will be
+   * used instead.  (It would be nice if defaultValue could simply return null,
+   * but bizarrely, the Java Language Specification does not consider null to
+   * be a compile-time constant.)  This special interpretation of the string
+   * "null" is only applicable when computing the default value; if specified
+   * on the command-line, this string will have its usual literal meaning.
+   */
+  String defaultValue();
+
+  /**
+   * A string describing the category of options that this belongs to. {@link
+   * OptionsParser#describeOptions} prints options of the same category grouped
+   * together.
+   */
+  String category() default "misc";
+
+  /**
+   * The converter that we'll use to convert this option into an object or
+   * a simple type. The default is to use the builtin converters.
+   * Custom converters must implement the {@link Converter} interface.
+   */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  // Can't figure out how to coerce Converter.class into Class<? extends Converter<?>>
+  Class<? extends Converter> converter() default Converter.class;
+
+  /**
+   * A flag indicating whether the option type should be allowed to occur
+   * multiple times in a single option list.
+   *
+   * <p>If the command can occur multiple times, then the attribute value
+   * <em>must</em> be a list type {@code List<T>}, and the result type of the
+   * converter for this option must either match the parameter {@code T} or
+   * {@code List<T>}. In the latter case the individual lists are concatenated
+   * to form the full options value.
+   */
+  boolean allowMultiple() default false;
+
+  /**
+   * If the option is actually an abbreviation for other options, this field will
+   * contain the strings to expand this option into. The original option is dropped
+   * and the replacement used in its stead. It is recommended that such an option be
+   * of type {@link Void}.
+   *
+   * An expanded option overrides previously specified options of the same name,
+   * even if it is explicitly specified. This is the original behavior and can
+   * be surprising if the user is not aware of it, which has led to several
+   * requests to change this behavior. This was discussed in the blaze team and
+   * it was decided that it is not a strong enough case to change the behavior.
+   */
+  String[] expansion() default {};
+
+  /**
+   * If the option requires that additional options be implicitly appended, this field
+   * will contain the additional options. Implicit dependencies are parsed at the end
+   * of each {@link OptionsParser#parse} invocation, and override options specified in
+   * the same call. However, they can be overridden by options specified in a later
+   * call or by options with a higher priority.
+   *
+   * @see OptionPriority
+   */
+  String[] implicitRequirements() default {};
+
+  /**
+   * If this field is a non-empty string, the option is deprecated, and a
+   * deprecation warning is added to the list of warnings when such an option
+   * is used.
+   */
+  String deprecationWarning() default "";
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionPriority.java b/src/main/java/com/google/devtools/common/options/OptionPriority.java
new file mode 100644
index 0000000..6e90008
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionPriority.java
@@ -0,0 +1,58 @@
+// 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;
+
+/**
+ * The priority of option values, in order of increasing priority.
+ *
+ * <p>In general, new values for options can only override values with a lower or
+ * equal priority. Option values provided in annotations in an options class are
+ * implicitly at the priority {@code DEFAULT}.
+ *
+ * <p>The ordering of the priorities is the source-code order. This is consistent
+ * with the automatically generated {@code compareTo} method as specified by the
+ * Java Language Specification. DO NOT change the source-code order of these
+ * values, or you will break code that relies on the ordering.
+ */
+public enum OptionPriority {
+
+  /**
+   * The priority of values specified in the {@link Option} annotation. This
+   * should never be specified in calls to {@link OptionsParser#parse}.
+   */
+  DEFAULT,
+
+  /**
+   * Overrides default options at runtime, while still allowing the values to be
+   * overridden manually.
+   */
+  COMPUTED_DEFAULT,
+
+  /**
+   * For options coming from a configuration file or rc file.
+   */
+  RC_FILE,
+
+  /**
+   * For options coming from the command line.
+   */
+  COMMAND_LINE,
+
+  /**
+   * This priority can be used to unconditionally override any user-provided options.
+   * This should be used rarely and with caution!
+   */
+  SOFTWARE_REQUIREMENT;
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/Options.java b/src/main/java/com/google/devtools/common/options/Options.java
new file mode 100644
index 0000000..171be2e
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/Options.java
@@ -0,0 +1,104 @@
+// 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 java.util.Arrays;
+import java.util.List;
+
+/**
+ * Interface for parsing options from a single options specification class.
+ *
+ * The {@link Options#parse(Class, String...)} method in this class has no clear
+ * use case. Instead, use the {@link OptionsParser} class directly, as in this
+ * code snippet:
+ *
+ * <pre>
+ * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class);
+ * try {
+ *   parser.parse(FooOptions.class, args);
+ * } catch (OptionsParsingException e) {
+ *   System.err.print("Error parsing options: " + e.getMessage());
+ *   System.err.print(options.getUsage());
+ *   System.exit(1);
+ * }
+ * FooOptions foo = parser.getOptions(FooOptions.class);
+ * List&lt;String&gt; otherArguments = parser.getResidue();
+ * </pre>
+ *
+ * Using this class in this case actually results in more code.
+ *
+ * @see OptionsParser for parsing options from multiple options specification classes.
+ */
+public class Options<O extends OptionsBase> {
+
+  /**
+   * Parse the options provided in args, given the specification in
+   * optionsClass.
+   */
+  public static <O extends OptionsBase> Options<O> parse(Class<O> optionsClass, String... args)
+      throws OptionsParsingException {
+    OptionsParser parser = OptionsParser.newOptionsParser(optionsClass);
+    parser.parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args));
+    List<String> remainingArgs = parser.getResidue();
+    return new Options<O>(parser.getOptions(optionsClass),
+                          remainingArgs.toArray(new String[0]));
+  }
+
+  /**
+   * Returns an options object at its default values.  The returned object may
+   * be freely modified by the caller, by assigning its fields.
+   */
+  public static <O extends OptionsBase> O getDefaults(Class<O> optionsClass) {
+    try {
+      return parse(optionsClass, new String[0]).getOptions();
+    } catch (OptionsParsingException e) {
+      String message = "Error while parsing defaults: " + e.getMessage();
+      throw new AssertionError(message);
+    }
+  }
+
+  /**
+   * Returns a usage string (renders the help information, the defaults, and
+   * of course the option names).
+   */
+  public static String getUsage(Class<? extends OptionsBase> optionsClass) {
+    StringBuilder usage = new StringBuilder();
+    OptionsUsage.getUsage(optionsClass, usage);
+    return usage.toString();
+  }
+
+  private O options;
+  private String[] remainingArgs;
+
+  private Options(O options, String[] remainingArgs) {
+    this.options = options;
+    this.remainingArgs = remainingArgs;
+  }
+
+  /**
+   * Returns an instance of options class O.
+   */
+  public O getOptions() {
+    return options;
+  }
+
+  /**
+   * Returns the arguments that we didn't parse.
+   */
+  public String[] getRemainingArgs() {
+    return remainingArgs;
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsBase.java b/src/main/java/com/google/devtools/common/options/OptionsBase.java
new file mode 100644
index 0000000..ed9f215
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsBase.java
@@ -0,0 +1,118 @@
+// 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.escape.CharEscaperBuilder;
+import com.google.common.escape.Escaper;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Base class for all options classes.  Extend this class, adding public
+ * instance fields annotated with @Option.  Then you can create instances
+ * either programmatically:
+ *
+ * <pre>
+ *   X x = Options.getDefaults(X.class);
+ *   x.host = "localhost";
+ *   x.port = 80;
+ * </pre>
+ *
+ * or from an array of command-line arguments:
+ *
+ * <pre>
+ *   OptionsParser parser = OptionsParser.newOptionsParser(X.class);
+ *   parser.parse("--host", "localhost", "--port", "80");
+ *   X x = parser.getOptions(X.class);
+ * </pre>
+ *
+ * <p>Subclasses of OptionsBase <b>must</b> be constructed reflectively,
+ * i.e. using not {@code new MyOptions}, but one of the two methods above
+ * instead.  (Direct construction creates an empty instance, not containing
+ * default values.  This leads to surprising behavior and often
+ * NullPointerExceptions, etc.)
+ */
+public abstract class OptionsBase {
+
+  private static final Escaper ESCAPER = new CharEscaperBuilder()
+      .addEscape('\\', "\\\\").addEscape('"', "\\\"").toEscaper();
+
+  /**
+   * Subclasses must provide a default (no argument) constructor.
+   */
+  protected OptionsBase() {
+    // There used to be a sanity check here that checks the stack trace of this constructor
+    // invocation; unfortunately, that makes the options construction about 10x slower. So be
+    // careful with how you construct options classes.
+  }
+
+  /**
+   * Returns this options object in the form of a (new) mapping from option
+   * names, including inherited ones, to option values.  If the public fields
+   * are mutated, this will be reflected in subsequent calls to {@code asMap}.
+   * Mutation of this map by the caller does not affect this options object.
+   */
+  public final Map<String, Object> asMap() {
+    return OptionsParserImpl.optionsAsMap(this);
+  }
+
+  @Override
+  public final String toString() {
+    return getClass().getName() + asMap();
+  }
+
+  /**
+   * Returns a string that uniquely identifies the options. This value is
+   * intended for analysis caching.
+   */
+  public final String cacheKey() {
+    StringBuilder result = new StringBuilder(getClass().getName()).append("{");
+
+    for (Entry<String, Object> entry : asMap().entrySet()) {
+      result.append(entry.getKey()).append("=");
+
+      Object value = entry.getValue();
+      // This special case is needed because List.toString() prints the same
+      // ("[]") for an empty list and for a list with a single empty string.
+      if (value instanceof List<?> && ((List<?>) value).isEmpty()) {
+        result.append("EMPTY");
+      } else if (value == null) {
+        result.append("NULL");
+      } else {
+        result
+            .append('"')
+            .append(ESCAPER.escape(value.toString()))
+            .append('"');
+      }
+      result.append(", ");
+    }
+
+    return result.append("}").toString();
+  }
+
+  @Override
+  public final boolean equals(Object that) {
+    return that != null &&
+        this.getClass() == that.getClass() &&
+        this.asMap().equals(((OptionsBase) that).asMap());
+  }
+
+  @Override
+  public final int hashCode() {
+    return this.getClass().hashCode() + asMap().hashCode();
+  }
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsClassProvider.java b/src/main/java/com/google/devtools/common/options/OptionsClassProvider.java
new file mode 100644
index 0000000..1868e23
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsClassProvider.java
@@ -0,0 +1,29 @@
+// 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;
+
+/**
+ * A read-only interface for options parser results, which only allows to query the options of
+ * a specific class, but not e.g. the residue any other information pertaining to the command line.
+ */
+public interface OptionsClassProvider {
+  /**
+   * Returns the options instance for the given {@code optionsClass}, that is,
+   * the parsed options, or null if it is not among those available.
+   *
+   * <p>The returned options should be treated by library code as immutable and
+   * a provider is permitted to return the same options instance multiple times.
+   */
+  <O extends OptionsBase> O getOptions(Class<O> optionsClass);
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsData.java b/src/main/java/com/google/devtools/common/options/OptionsData.java
new file mode 100644
index 0000000..e9b6574
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsData.java
@@ -0,0 +1,264 @@
+// 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.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * An immutable selection of options data corresponding to a set of options
+ * classes. The data is collected using reflection, which can be expensive.
+ * Therefore this class can be used internally to cache the results.
+ */
+@Immutable
+final class OptionsData {
+
+  /**
+   * These are the options-declaring classes which are annotated with
+   * {@link Option} annotations.
+   */
+  private final Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses;
+
+  /** Maps option name to Option-annotated Field. */
+  private final Map<String, Field> nameToField;
+
+  /** Maps option abbreviation to Option-annotated Field. */
+  private final Map<Character, Field> abbrevToField;
+
+  /**
+   * For each options class, contains a list of all Option-annotated fields in
+   * that class.
+   */
+  private final Map<Class<? extends OptionsBase>, List<Field>> allOptionsFields;
+
+  /**
+   * Mapping from each Option-annotated field to the default value for that
+   * field.
+   */
+  private final Map<Field, Object> optionDefaults;
+
+  /**
+   * Mapping from each Option-annotated field to the proper converter.
+   *
+   * @see OptionsParserImpl#findConverter
+   */
+  private final Map<Field, Converter<?>> converters;
+
+  private OptionsData(Map<Class<? extends OptionsBase>, Constructor<?>> optionsClasses,
+                      Map<String, Field> nameToField,
+                      Map<Character, Field> abbrevToField,
+                      Map<Class<? extends OptionsBase>, List<Field>> allOptionsFields,
+                      Map<Field, Object> optionDefaults,
+                      Map<Field, Converter<?>> converters) {
+    this.optionsClasses = ImmutableMap.copyOf(optionsClasses);
+    this.allOptionsFields = ImmutableMap.copyOf(allOptionsFields);
+    this.nameToField = ImmutableMap.copyOf(nameToField);
+    this.abbrevToField = ImmutableMap.copyOf(abbrevToField);
+    // Can't use an ImmutableMap here because of null values.
+    this.optionDefaults = Collections.unmodifiableMap(optionDefaults);
+    this.converters = ImmutableMap.copyOf(converters);
+  }
+
+  public Collection<Class<? extends OptionsBase>> getOptionsClasses() {
+    return optionsClasses.keySet();
+  }
+
+  @SuppressWarnings("unchecked") // The construction ensures that the case is always valid.
+  public <T extends OptionsBase> Constructor<T> getConstructor(Class<T> clazz) {
+    return (Constructor<T>) optionsClasses.get(clazz);
+  }
+
+  public Field getFieldFromName(String name) {
+    return nameToField.get(name);
+  }
+
+  public Iterable<Map.Entry<String, Field>> getAllNamedFields() {
+    return nameToField.entrySet();
+  }
+
+  public Field getFieldForAbbrev(char abbrev) {
+    return abbrevToField.get(abbrev);
+  }
+
+  public List<Field> getFieldsForClass(Class<? extends OptionsBase> optionsClass) {
+    return allOptionsFields.get(optionsClass);
+  }
+
+  public Object getDefaultValue(Field field) {
+    return optionDefaults.get(field);
+  }
+
+  public Converter<?> getConverter(Field field) {
+    return converters.get(field);
+  }
+
+  private static List<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) {
+    List<Field> allFields = Lists.newArrayList();
+    for (Field field : optionsClass.getFields()) {
+      if (field.isAnnotationPresent(Option.class)) {
+        allFields.add(field);
+      }
+    }
+    if (allFields.isEmpty()) {
+      throw new IllegalStateException(optionsClass + " has no public @Option-annotated fields");
+    }
+    return ImmutableList.copyOf(allFields);
+  }
+
+  private static Object retrieveDefaultFromAnnotation(Field optionField) {
+    Option annotation = optionField.getAnnotation(Option.class);
+    // If an option can be specified multiple times, its default value is a new empty list.
+    if (annotation.allowMultiple()) {
+      return Collections.emptyList();
+    }
+    String defaultValueString = OptionsParserImpl.getDefaultOptionString(optionField);
+    try {
+      return OptionsParserImpl.isSpecialNullDefault(defaultValueString, optionField)
+          ? null
+          : OptionsParserImpl.findConverter(optionField).convert(defaultValueString);
+    } catch (OptionsParsingException e) {
+      throw new IllegalStateException("OptionsParsingException while "
+          + "retrieving default for " + optionField.getName() + ": "
+          + e.getMessage());
+    }
+  }
+
+  static OptionsData of(Collection<Class<? extends OptionsBase>> classes) {
+    Map<Class<? extends OptionsBase>, Constructor<?>> constructorBuilder = Maps.newHashMap();
+    Map<Class<? extends OptionsBase>, List<Field>> allOptionsFieldsBuilder = Maps.newHashMap();
+    Map<String, Field> nameToFieldBuilder = Maps.newHashMap();
+    Map<Character, Field> abbrevToFieldBuilder = Maps.newHashMap();
+    Map<Field, Object> optionDefaultsBuilder = Maps.newHashMap();
+    Map<Field, Converter<?>> convertersBuilder = Maps.newHashMap();
+
+    // Read all Option annotations:
+    for (Class<? extends OptionsBase> parsedOptionsClass : classes) {
+      try {
+        Constructor<? extends OptionsBase> constructor =
+            parsedOptionsClass.getConstructor(new Class[0]);
+        constructorBuilder.put(parsedOptionsClass, constructor);
+      } catch (NoSuchMethodException e) {
+        throw new IllegalArgumentException(parsedOptionsClass
+            + " lacks an accessible default constructor");
+      }
+      List<Field> fields = getAllAnnotatedFields(parsedOptionsClass);
+      allOptionsFieldsBuilder.put(parsedOptionsClass, fields);
+
+      for (Field field : fields) {
+        Option annotation = field.getAnnotation(Option.class);
+
+        // Check that the field type is a List, and that the converter
+        // type matches the element type of the list.
+        Type fieldType = field.getGenericType();
+        if (annotation.allowMultiple()) {
+          if (!(fieldType instanceof ParameterizedType)) {
+            throw new AssertionError("Type of multiple occurrence option must be a List<...>");
+          }
+          ParameterizedType pfieldType = (ParameterizedType) fieldType;
+          if (pfieldType.getRawType() != List.class) {
+            // Throw an assertion, because this indicates an undetected type
+            // error in the code.
+            throw new AssertionError("Type of multiple occurrence option must be a List<...>");
+          }
+          fieldType = pfieldType.getActualTypeArguments()[0];
+        }
+
+        // Get the converter return type.
+        @SuppressWarnings("rawtypes")
+        Class<? extends Converter> converter = annotation.converter();
+        if (converter == Converter.class) {
+          Converter<?> actualConverter = OptionsParserImpl.DEFAULT_CONVERTERS.get(fieldType);
+          if (actualConverter == null) {
+            throw new AssertionError("Cannot find converter for field of type "
+                + field.getType() + " named " + field.getName()
+                + " in class " + field.getDeclaringClass().getName());
+          }
+          converter = actualConverter.getClass();
+        }
+        if (Modifier.isAbstract(converter.getModifiers())) {
+          throw new AssertionError("The converter type (" + converter
+              + ") must be a concrete type");
+        }
+        Type converterResultType;
+        try {
+          Method convertMethod = converter.getMethod("convert", String.class);
+          converterResultType = GenericTypeHelper.getActualReturnType(converter, convertMethod);
+        } catch (NoSuchMethodException e) {
+          throw new AssertionError("A known converter object doesn't implement the convert"
+              + " method");
+        }
+
+        if (annotation.allowMultiple()) {
+          if (GenericTypeHelper.getRawType(converterResultType) == List.class) {
+            Type elementType =
+                ((ParameterizedType) converterResultType).getActualTypeArguments()[0];
+            if (!GenericTypeHelper.isAssignableFrom(fieldType, elementType)) {
+              throw new AssertionError("If the converter return type of a multiple occurance " +
+                  "option is a list, then the type of list elements (" + fieldType + ") must be " +
+                  "assignable from the converter list element type (" + elementType + ")");
+            }
+          } else {
+            if (!GenericTypeHelper.isAssignableFrom(fieldType, converterResultType)) {
+              throw new AssertionError("Type of list elements (" + fieldType +
+                  ") for multiple occurrence option must be assignable from the converter " +
+                  "return type (" + converterResultType + ")");
+            }
+          }
+        } else {
+          if (!GenericTypeHelper.isAssignableFrom(fieldType, converterResultType)) {
+            throw new AssertionError("Type of field (" + fieldType +
+                ") must be assignable from the converter " +
+                "return type (" + converterResultType + ")");
+          }
+        }
+
+        if (annotation.name() == null) {
+          throw new AssertionError(
+              "Option cannot have a null name");
+        }
+        if (nameToFieldBuilder.put(annotation.name(), field) != null) {
+          throw new DuplicateOptionDeclarationException(
+              "Duplicate option name: --" + annotation.name());
+        }
+        if (annotation.abbrev() != '\0') {
+          if (abbrevToFieldBuilder.put(annotation.abbrev(), field) != null) {
+            throw new DuplicateOptionDeclarationException(
+                  "Duplicate option abbrev: -" + annotation.abbrev());
+          }
+        }
+        optionDefaultsBuilder.put(field, retrieveDefaultFromAnnotation(field));
+
+        convertersBuilder.put(field, OptionsParserImpl.findConverter(field));
+      }
+    }
+    return new OptionsData(constructorBuilder, nameToFieldBuilder, abbrevToFieldBuilder,
+        allOptionsFieldsBuilder, optionDefaultsBuilder, convertersBuilder);
+  }
+}
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&lt;String&gt; 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();
+  }
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
new file mode 100644
index 0000000..e339dcd
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -0,0 +1,722 @@
+// 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.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.devtools.common.options.OptionsParser.OptionValueDescription;
+import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The implementation of the options parser. This is intentionally package
+ * private for full flexibility. Use {@link OptionsParser} or {@link Options}
+ * if you're a consumer.
+ */
+class OptionsParserImpl {
+
+  /**
+   * A bunch of default converters in case the user doesn't specify a
+   * different one in the field annotation.
+   */
+  static final Map<Class<?>, Converter<?>> DEFAULT_CONVERTERS = Maps.newHashMap();
+
+  static {
+    DEFAULT_CONVERTERS.put(String.class, new Converter<String>() {
+      @Override
+      public String convert(String input) {
+        return input;
+      }
+      @Override
+      public String getTypeDescription() {
+        return "a string";
+      }});
+    DEFAULT_CONVERTERS.put(int.class, new Converter<Integer>() {
+      @Override
+      public Integer convert(String input) throws OptionsParsingException {
+        try {
+          return Integer.decode(input);
+        } catch (NumberFormatException e) {
+          throw new OptionsParsingException("'" + input + "' is not an int");
+        }
+      }
+      @Override
+      public String getTypeDescription() {
+        return "an integer";
+      }});
+    DEFAULT_CONVERTERS.put(double.class, new Converter<Double>() {
+      @Override
+      public Double convert(String input) throws OptionsParsingException {
+        try {
+          return Double.parseDouble(input);
+        } catch (NumberFormatException e) {
+          throw new OptionsParsingException("'" + input + "' is not a double");
+        }
+      }
+      @Override
+      public String getTypeDescription() {
+        return "a double";
+      }});
+    DEFAULT_CONVERTERS.put(boolean.class, new Converters.BooleanConverter());
+    DEFAULT_CONVERTERS.put(TriState.class, new Converter<TriState>() {
+      @Override
+      public TriState convert(String input) throws OptionsParsingException {
+        if (input == null) {
+          return TriState.AUTO;
+        }
+        input = input.toLowerCase();
+        if (input.equals("auto")) {
+          return TriState.AUTO;
+        }
+        if (input.equals("true") || input.equals("1") || input.equals("yes") ||
+            input.equals("t") || input.equals("y")) {
+          return TriState.YES;
+        }
+        if (input.equals("false") || input.equals("0") || input.equals("no") ||
+            input.equals("f") || input.equals("n")) {
+          return TriState.NO;
+        }
+        throw new OptionsParsingException("'" + input + "' is not a boolean");
+      }
+      @Override
+      public String getTypeDescription() {
+        return "a tri-state (auto, yes, no)";
+      }});
+    DEFAULT_CONVERTERS.put(Void.class, new Converter<Void>() {
+      @Override
+      public Void convert(String input) throws OptionsParsingException {
+        if (input == null) {
+          return null;  // expected input, return is unused so null is fine.
+        }
+        throw new OptionsParsingException("'" + input + "' unexpected");
+      }
+      @Override
+      public String getTypeDescription() {
+        return "";
+      }});
+    DEFAULT_CONVERTERS.put(long.class, new Converter<Long>() {
+      @Override
+      public Long convert(String input) throws OptionsParsingException {
+        try {
+          return Long.decode(input);
+        } catch (NumberFormatException e) {
+          throw new OptionsParsingException("'" + input + "' is not a long");
+        }
+      }
+      @Override
+      public String getTypeDescription() {
+        return "a long integer";
+      }});
+  }
+
+  /**
+   * For every value, this class keeps track of its priority, its free-form source
+   * description, whether it was set as an implicit dependency, and the value.
+   */
+  private static final class ParsedOptionEntry {
+    private final Object value;
+    private final OptionPriority priority;
+    private final String source;
+    private final String implicitDependant;
+    private final String expandedFrom;
+    private final boolean allowMultiple;
+
+    ParsedOptionEntry(Object value,
+        OptionPriority priority, String source, String implicitDependant, String expandedFrom,
+        boolean allowMultiple) {
+      this.value = value;
+      this.priority = priority;
+      this.source = source;
+      this.implicitDependant = implicitDependant;
+      this.expandedFrom = expandedFrom;
+      this.allowMultiple = allowMultiple;
+    }
+
+    // Need to suppress unchecked warnings, because the "multiple occurrence"
+    // options use unchecked ListMultimaps due to limitations of Java generics.
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    Object getValue() {
+      if (allowMultiple) {
+        // Sort the results by option priority and return them in a new list.
+        // The generic type of the list is not known at runtime, so we can't
+        // use it here. It was already checked in the constructor, so this is
+        // type-safe.
+        List result = Lists.newArrayList();
+        ListMultimap realValue = (ListMultimap) value;
+        for (OptionPriority priority : OptionPriority.values()) {
+          // If there is no mapping for this key, this check avoids object creation (because
+          // ListMultimap has to return a new object on get) and also an unnecessary addAll call.
+          if (realValue.containsKey(priority)) {
+            result.addAll(realValue.get(priority));
+          }
+        }
+        return result;
+      }
+      return value;
+    }
+
+    // Need to suppress unchecked warnings, because the "multiple occurrence"
+    // options use unchecked ListMultimaps due to limitations of Java generics.
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    void addValue(OptionPriority addedPriority, Object addedValue) {
+      Preconditions.checkState(allowMultiple);
+      ListMultimap optionValueList = (ListMultimap) value;
+      if (addedValue instanceof List<?>) {
+        for (Object element : (List<?>) addedValue) {
+          optionValueList.put(addedPriority, element);
+        }
+      } else {
+        optionValueList.put(addedPriority, addedValue);
+      }
+    }
+
+    OptionValueDescription asOptionValueDescription(String fieldName) {
+      return new OptionValueDescription(fieldName, getValue(), priority,
+          source, implicitDependant, expandedFrom);
+    }
+  }
+
+  private final OptionsData optionsData;
+
+  /**
+   * We store the results of parsing the arguments in here. It'll look like
+   * <pre>
+   *   Field("--host") -> "www.google.com"
+   *   Field("--port") -> 80
+   * </pre>
+   * This map is modified by repeated calls to
+   * {@link #parse(OptionPriority,Function,List)}.
+   */
+  private final Map<Field, ParsedOptionEntry> parsedValues = Maps.newHashMap();
+
+  /**
+   * We store the pre-parsed, explicit options for each priority in here.
+   * We use partially preparsed options, which can be different from the original
+   * representation, e.g. "--nofoo" becomes "--foo=0".
+   */
+  private final List<UnparsedOptionValueDescription> unparsedValues =
+      Lists.newArrayList();
+
+  private final List<String> warnings = Lists.newArrayList();
+  
+  private boolean allowSingleDashLongOptions = false;
+
+  /**
+   * Create a new parser object
+   */
+  OptionsParserImpl(OptionsData optionsData) {
+    this.optionsData = optionsData;
+  }
+
+  /**
+   * 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.
+   */
+  void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
+    this.allowSingleDashLongOptions = allowSingleDashLongOptions;
+  }
+  
+  /**
+   * The implementation of {@link OptionsBase#asMap}.
+   */
+  static Map<String, Object> optionsAsMap(OptionsBase optionsInstance) {
+    Map<String, Object> map = Maps.newHashMap();
+    for (Field field : OptionsParser.getAllAnnotatedFields(optionsInstance.getClass())) {
+      try {
+        String name = field.getAnnotation(Option.class).name();
+        Object value = field.get(optionsInstance);
+        map.put(name, value);
+      } catch (IllegalAccessException e) {
+        throw new IllegalStateException(e); // unreachable
+      }
+    }
+    return map;
+  }
+
+  List<Field> getAnnotatedFieldsFor(Class<? extends OptionsBase> clazz) {
+    return optionsData.getFieldsForClass(clazz);
+  }
+
+  /**
+   * Implements {@link OptionsParser#asListOfUnparsedOptions()}.
+   */
+  List<UnparsedOptionValueDescription> asListOfUnparsedOptions() {
+    List<UnparsedOptionValueDescription> result = Lists.newArrayList(unparsedValues);
+    // It is vital that this sort is stable so that options on the same priority are not reordered.
+    Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() {
+      @Override
+      public int compare(UnparsedOptionValueDescription o1,
+          UnparsedOptionValueDescription o2) {
+        return o1.getPriority().compareTo(o2.getPriority());
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Implements {@link OptionsParser#asListOfExplicitOptions()}.
+   */
+  List<UnparsedOptionValueDescription> asListOfExplicitOptions() {
+    List<UnparsedOptionValueDescription> result = Lists.newArrayList(Iterables.filter(
+      unparsedValues,
+      new Predicate<UnparsedOptionValueDescription>() {
+        @Override
+        public boolean apply(UnparsedOptionValueDescription input) {
+          return input.isExplicit();
+        }
+    }));
+    // It is vital that this sort is stable so that options on the same priority are not reordered.
+    Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() {
+      @Override
+      public int compare(UnparsedOptionValueDescription o1,
+          UnparsedOptionValueDescription o2) {
+        return o1.getPriority().compareTo(o2.getPriority());
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Implements {@link OptionsParser#canonicalize}.
+   */
+  List<String> asCanonicalizedList() {
+    List<UnparsedOptionValueDescription> processed = Lists.newArrayList(unparsedValues);
+    Collections.sort(processed, new Comparator<UnparsedOptionValueDescription>() {
+      // This Comparator sorts implicit requirement options to the end, keeping their existing
+      // order, and sorts the other options alphabetically.
+      @Override
+      public int compare(UnparsedOptionValueDescription o1,
+          UnparsedOptionValueDescription o2) {
+        if (o1.isImplicitRequirement()) {
+          return o2.isImplicitRequirement() ? 0 : 1;
+        }
+        if (o2.isImplicitRequirement()) {
+          return -1;
+        }
+        return o1.getName().compareTo(o2.getName());
+      }
+    });
+
+    List<String> result = Lists.newArrayList();
+    for (int i = 0; i < processed.size(); i++) {
+      UnparsedOptionValueDescription value = processed.get(i);
+      // Skip an option if the next option is the same, but only if the option does not allow
+      // multiple values.
+      if (!value.allowMultiple()) {
+        if ((i < processed.size() - 1) && value.getName().equals(processed.get(i + 1).getName())) {
+          continue;
+        }
+      }
+
+      // Ignore expansion options.
+      if (value.isExpansion()) {
+        continue;
+      }
+
+      result.add("--" + value.getName() + "=" + value.getUnparsedValue());
+    }
+    return result;
+  }
+
+  /**
+   * Implements {@link OptionsParser#asListOfEffectiveOptions()}.
+   */
+  List<OptionValueDescription> asListOfEffectiveOptions() {
+    List<OptionValueDescription> result = Lists.newArrayList();
+    for (Map.Entry<String,Field> mapEntry : optionsData.getAllNamedFields()) {
+      String fieldName = mapEntry.getKey();
+      Field field = mapEntry.getValue();
+      ParsedOptionEntry entry = parsedValues.get(field);
+      if (entry == null) {
+        Object value = optionsData.getDefaultValue(field);
+        result.add(new OptionValueDescription(fieldName, value, OptionPriority.DEFAULT,
+            null, null, null));
+      } else {
+        result.add(entry.asOptionValueDescription(fieldName));
+      }
+    }
+    return result;
+  }
+
+  Collection<Class<?  extends OptionsBase>> getOptionsClasses() {
+    return optionsData.getOptionsClasses();
+  }
+
+  private void maybeAddDeprecationWarning(Field field) {
+    Option option = field.getAnnotation(Option.class);
+    // Continue to support the old behavior for @Deprecated options.
+    String warning = option.deprecationWarning();
+    if (!warning.equals("") || (field.getAnnotation(Deprecated.class) != null)) {
+      warnings.add("Option '" + option.name() + "' is deprecated"
+          + (warning.equals("") ? "" : ": " + warning));
+    }
+  }
+
+  // Warnings should not end with a '.' because the internal reporter adds one automatically.
+  private void setValue(Field field, String name, Object value,
+      OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
+    ParsedOptionEntry entry = parsedValues.get(field);
+    if (entry != null) {
+      // Override existing option if the new value has higher or equal priority.
+      if (priority.compareTo(entry.priority) >= 0) {
+        // Output warnings:
+        if ((implicitDependant != null) && (entry.implicitDependant != null)) {
+          if (!implicitDependant.equals(entry.implicitDependant)) {
+            warnings.add("Option '" + name + "' is implicitly defined by both option '" +
+                entry.implicitDependant + "' and option '" + implicitDependant + "'");
+          }
+        } else if ((implicitDependant != null) && priority.equals(entry.priority)) {
+          warnings.add("Option '" + name + "' is implicitly defined by option '" +
+              implicitDependant + "'; the implicitly set value overrides the previous one");
+        } else if (entry.implicitDependant != null) {
+          warnings.add("A new value for option '" + name + "' overrides a previous " +
+              "implicit setting of that option by option '" + entry.implicitDependant + "'");
+        } else if ((priority == entry.priority) &&
+            ((entry.expandedFrom == null) && (expandedFrom != null))) {
+          // Create a warning if an expansion option overrides an explicit option:
+          warnings.add("The option '" + expandedFrom + "' was expanded and now overrides a "
+              + "previous explicitly specified option '" + name + "'");
+        }
+
+        // Record the new value:
+        parsedValues.put(field,
+            new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false));
+      }
+    } else {
+      parsedValues.put(field,
+          new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false));
+      maybeAddDeprecationWarning(field);
+    }
+  }
+
+  private void addListValue(Field field, String name, Object value,
+      OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
+    ParsedOptionEntry entry = parsedValues.get(field);
+    if (entry == null) {
+      entry = new ParsedOptionEntry(ArrayListMultimap.create(), priority, source,
+          implicitDependant, expandedFrom, true);
+      parsedValues.put(field, entry);
+      maybeAddDeprecationWarning(field);
+    }
+    entry.addValue(priority, value);
+  }
+
+  private Object getValue(Field field) {
+    ParsedOptionEntry entry = parsedValues.get(field);
+    return entry == null ? null : entry.getValue();
+  }
+
+  OptionValueDescription getOptionValueDescription(String name) {
+    Field field = optionsData.getFieldFromName(name);
+    if (field == null) {
+      throw new IllegalArgumentException("No such option '" + name + "'");
+    }
+    ParsedOptionEntry entry = parsedValues.get(field);
+    if (entry == null) {
+      return null;
+    }
+    return entry.asOptionValueDescription(name);
+  }
+
+  boolean containsExplicitOption(String name) {
+    Field field = optionsData.getFieldFromName(name);
+    if (field == null) {
+      throw new IllegalArgumentException("No such option '" + name + "'");
+    }
+    return parsedValues.get(field) != null;
+  }
+
+  /**
+   * Parses the args, and returns what it doesn't parse. May be called multiple
+   * times, and may be called recursively. In each call, there may be no
+   * duplicates, but separate calls may contain intersecting sets of options; in
+   * that case, the arg seen last takes precedence.
+   */
+  List<String> parse(OptionPriority priority, Function<? super String, String> sourceFunction,
+      List<String> args) throws OptionsParsingException {
+    return parse(priority, sourceFunction, null, null, args);
+  }
+
+  /**
+   * Parses the args, and returns what it doesn't parse. May be called multiple
+   * times, and may be called recursively. Calls may contain intersecting sets
+   * of options; in that case, the arg seen last takes precedence.
+   *
+   * <p>The method uses the invariant that if an option has neither an implicit
+   * dependant nor an expanded from value, then it must have been explicitly
+   * set.
+   */
+  private List<String> parse(OptionPriority priority,
+      final Function<? super String, String> sourceFunction, String implicitDependant,
+      String expandedFrom, List<String> args) throws OptionsParsingException {
+    List<String> unparsedArgs = Lists.newArrayList();
+    LinkedHashMap<String,List<String>> implicitRequirements = Maps.newLinkedHashMap();
+    for (int pos = 0; pos < args.size(); pos++) {
+      String arg = args.get(pos);
+      if (!arg.startsWith("-")) {
+        unparsedArgs.add(arg);
+        continue;  // not an option arg
+      }
+      if (arg.equals("--")) {  // "--" means all remaining args aren't options
+        while (++pos < args.size()) {
+          unparsedArgs.add(args.get(pos));
+        }
+        break;
+      }
+
+      String value = null;
+      Field field;
+      boolean booleanValue = true;
+
+      if (arg.length() == 2) { // -l  (may be nullary or unary)
+        field = optionsData.getFieldForAbbrev(arg.charAt(1));
+        booleanValue = true;
+
+      } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean)
+        field = optionsData.getFieldForAbbrev(arg.charAt(1));
+        booleanValue = false;
+
+      } else if (allowSingleDashLongOptions // -long_option
+          || arg.startsWith("--")) { // or --long_option
+        int equalsAt = arg.indexOf('=');
+        int nameStartsAt = arg.startsWith("--") ? 2 : 1;
+        String name =
+            equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt);
+        if (name.trim().equals("")) {
+          throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
+        }
+        value = equalsAt == -1 ? null : arg.substring(equalsAt + 1);
+        field = optionsData.getFieldFromName(name);
+
+        // look for a "no"-prefixed option name: "no<optionname>";
+        // (Undocumented: we also allow --no_foo.  We're generous like that.)
+        if (field == null && name.startsWith("no")) {
+          String realname = name.substring(name.startsWith("no_") ? 3 : 2);
+          field = optionsData.getFieldFromName(realname);
+          booleanValue = false;
+          if (field != null) {
+            // TODO(bazel-team): Add tests for these cases.
+            if (!OptionsParserImpl.isBooleanField(field)) {
+              throw new OptionsParsingException(
+                  "Illegal use of 'no' prefix on non-boolean option: " + arg, arg);
+            }
+            if (value != null) {
+              throw new OptionsParsingException(
+                  "Unexpected value after boolean option: " + arg, arg);
+            }
+            // "no<optionname>" signifies a boolean option w/ false value
+            value = "0";
+          }
+        }
+
+      } else {
+        throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
+      }
+
+      if (field == null) {
+        throw new OptionsParsingException("Unrecognized option: " + arg, arg);
+      }
+      
+      if (value == null) {
+        // special case boolean to supply value based on presence of "no" prefix
+        if (OptionsParserImpl.isBooleanField(field)) {
+          value = booleanValue ? "1" : "0";
+        } else if (field.getType().equals(Void.class)) {
+          // this is expected, Void type options have no args
+        } else if (pos != args.size() - 1) {
+          value = args.get(++pos);  // "--flag value" form
+        } else {
+          throw new OptionsParsingException("Expected value after " + arg);
+        }
+      }
+
+      Option option = field.getAnnotation(Option.class);
+      final String originalName = option.name();
+      if (implicitDependant == null) {
+        // Log explicit options and expanded options in the order they are parsed (can be sorted
+        // later). Also remember whether they were expanded or not. This information is needed to
+        // correctly canonicalize flags.
+        unparsedValues.add(new UnparsedOptionValueDescription(originalName, field, value,
+            priority, sourceFunction.apply(originalName), expandedFrom == null));
+      }
+
+      // Handle expansion options.
+      if (option.expansion().length > 0) {
+        Function<Object, String> expansionSourceFunction = Functions.<String>constant(
+            "expanded from option --" + originalName + " from " +
+            sourceFunction.apply(originalName));
+        maybeAddDeprecationWarning(field);
+        List<String> unparsed = parse(priority, expansionSourceFunction, null, originalName,
+            ImmutableList.copyOf(option.expansion()));
+        if (!unparsed.isEmpty()) {
+          // Throw an assertion, because this indicates an error in the code that specified the
+          // expansion for the current option.
+          throw new AssertionError("Unparsed options remain after parsing expansion of " +
+            arg + ":" + Joiner.on(' ').join(unparsed));
+        }
+      } else {
+        Converter<?> converter = optionsData.getConverter(field);
+        Object convertedValue;
+        try {
+          convertedValue = converter.convert(value);
+        } catch (OptionsParsingException e) {
+          // The converter doesn't know the option name, so we supply it here by
+          // re-throwing:
+          throw new OptionsParsingException("While parsing option " + arg
+                                            + ": " + e.getMessage(), e);
+        }
+
+        // ...but allow duplicates of single-use options across separate calls to
+        // parse(); latest wins:
+        if (!option.allowMultiple()) {
+          setValue(field, originalName, convertedValue,
+              priority, sourceFunction.apply(originalName), implicitDependant, expandedFrom);
+        } else {
+          // But if it's a multiple-use option, then just accumulate the
+          // values, in the order in which they were seen.
+          // Note: The type of the list member is not known; Java introspection
+          // only makes it available in String form via the signature string
+          // for the field declaration.
+          addListValue(field, originalName, convertedValue,
+              priority, sourceFunction.apply(originalName), implicitDependant, expandedFrom);
+        }
+      }
+
+      // Collect any implicit requirements.
+      if (option.implicitRequirements().length > 0) {
+        implicitRequirements.put(option.name(), Arrays.asList(option.implicitRequirements()));
+      }
+    }
+
+    // Now parse any implicit requirements that were collected.
+    // TODO(bazel-team): this should happen when the option is encountered.
+    if (!implicitRequirements.isEmpty()) {
+      for (Map.Entry<String,List<String>> entry : implicitRequirements.entrySet()) {
+        Function<Object, String> requirementSourceFunction = Functions.<String>constant(
+            "implicit requirement of option --" + entry.getKey() + " from " +
+            sourceFunction.apply(entry.getKey()));
+
+        List<String> unparsed = parse(priority, requirementSourceFunction, entry.getKey(), null,
+            entry.getValue());
+        if (!unparsed.isEmpty()) {
+          // Throw an assertion, because this indicates an error in the code that specified in the
+          // implicit requirements for the option(s).
+          throw new AssertionError("Unparsed options remain after parsing implicit options:"
+              + Joiner.on(' ').join(unparsed));
+        }
+      }
+    }
+
+    return unparsedArgs;
+  }
+
+  /**
+   * Gets the result of parsing the options.
+   */
+  <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) {
+    // Create the instance:
+    O optionsInstance;
+    try {
+      Constructor<O> constructor = optionsData.getConstructor(optionsClass);
+      if (constructor == null) {
+        return null;
+      }
+      optionsInstance = constructor.newInstance(new Object[0]);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
+
+    // Set the fields
+    for (Field field : optionsData.getFieldsForClass(optionsClass)) {
+      Object value = getValue(field);
+      if (value == null) {
+        value = optionsData.getDefaultValue(field);
+      }
+      try {
+        field.set(optionsInstance, value);
+      } catch (IllegalAccessException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+    return optionsInstance;
+  }
+
+  List<String> getWarnings() {
+    return ImmutableList.copyOf(warnings);
+  }
+
+  static String getDefaultOptionString(Field optionField) {
+    Option annotation = optionField.getAnnotation(Option.class);
+    return annotation.defaultValue();
+  }
+
+  static boolean isBooleanField(Field field) {
+    return field.getType().equals(boolean.class) || field.getType().equals(TriState.class);
+  }
+
+  static boolean isSpecialNullDefault(String defaultValueString, Field optionField) {
+    return defaultValueString.equals("null") && !optionField.getType().isPrimitive();
+  }
+
+  static Converter<?> findConverter(Field optionField) {
+    Option annotation = optionField.getAnnotation(Option.class);
+    if (annotation.converter() == Converter.class) {
+      Type type;
+      if (annotation.allowMultiple()) {
+        // The OptionParserImpl already checked that the type is List<T> for some T;
+        // here we extract the type T.
+        type = ((ParameterizedType) optionField.getGenericType()).getActualTypeArguments()[0];
+      } else {
+        type = optionField.getType();
+      }
+      Converter<?> converter = DEFAULT_CONVERTERS.get(type);
+      if (converter == null) {
+        throw new AssertionError("No converter found for "
+            + type + "; possible fix: add "
+            + "converter=... to @Option annotation for "
+            + optionField.getName());
+      }
+      return converter;
+    }
+    try {
+      Class<?> converter = annotation.converter();
+      Constructor<?> constructor = converter.getConstructor(new Class<?>[0]);
+      return (Converter<?>) constructor.newInstance(new Object[0]);
+    } catch (Exception e) {
+      throw new AssertionError(e);
+    }
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsParsingException.java b/src/main/java/com/google/devtools/common/options/OptionsParsingException.java
new file mode 100644
index 0000000..9d2916a
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsParsingException.java
@@ -0,0 +1,50 @@
+// 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;
+
+/**
+ * An exception that's thrown when the {@link OptionsParser} fails.
+ *
+ * @see OptionsParser#parse(OptionPriority,String,java.util.List)
+ */
+public class OptionsParsingException extends Exception {
+  private final String invalidArgument;
+
+  public OptionsParsingException(String message) {
+    this(message, (String) null);
+  }
+
+  public OptionsParsingException(String message, String argument) {
+    super(message);
+    this.invalidArgument = argument;
+  }
+
+  public OptionsParsingException(String message, Throwable throwable) {
+    this(message, null, throwable);
+  }
+
+  public OptionsParsingException(String message, String argument, Throwable throwable) {
+    super(message, throwable);
+    this.invalidArgument = argument;
+  }
+
+  /**
+   * Gets the name of the invalid argument or {@code null} if the exception
+   * can not determine the exact invalid arguments
+   */
+  public String getInvalidArgument() {
+    return invalidArgument;
+  }
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsProvider.java b/src/main/java/com/google/devtools/common/options/OptionsProvider.java
new file mode 100644
index 0000000..be399a7
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsProvider.java
@@ -0,0 +1,67 @@
+// 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.devtools.common.options.OptionsParser.OptionValueDescription;
+import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription;
+
+import java.util.List;
+
+/**
+ * A read-only interface for options parser results, which does not allow any
+ * further parsing of options.
+ */
+public interface OptionsProvider extends OptionsClassProvider {
+
+  /**
+   * Returns an immutable copy of the residue, that is, the arguments that
+   * have not been parsed.
+   */
+  List<String> getResidue();
+
+  /**
+   * Returns if the named option was specified explicitly in a call to parse.
+   */
+  boolean containsExplicitOption(String string);
+
+  /**
+   * Returns a mutable copy of the list of all options that were specified
+   * either explicitly or implicitly. These options are sorted by priority, and
+   * by the order in which they were specified. If an option was specified
+   * multiple times, it is included in the result multiple times. Does not
+   * include the residue.
+   *
+   * <p>The returned list can be filtered if undocumented, hidden or implicit
+   * options should not be displayed.
+   */
+  List<UnparsedOptionValueDescription> asListOfUnparsedOptions();
+
+  /**
+   * Returns a list of all explicitly specified options, suitable for logging
+   * or for displaying back to the user. These options are sorted by priority,
+   * and by the order in which they were specified. If an option was
+   * explicitly specified multiple times, it is included in the result
+   * multiple times. Does not include the residue.
+   *
+   * <p>The list includes undocumented options.
+   */
+  public List<UnparsedOptionValueDescription> asListOfExplicitOptions();
+
+  /**
+   * Returns a list of all options, including undocumented ones, and their
+   * effective values. There is no guaranteed ordering for the result.
+   */
+  public List<OptionValueDescription> asListOfEffectiveOptions();
+}
diff --git a/src/main/java/com/google/devtools/common/options/OptionsUsage.java b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
new file mode 100644
index 0000000..c48a532
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/OptionsUsage.java
@@ -0,0 +1,156 @@
+// 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 static com.google.devtools.common.options.OptionsParserImpl.findConverter;
+
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import java.lang.reflect.Field;
+import java.text.BreakIterator;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A renderer for usage messages. For now this is very simple.
+ */
+class OptionsUsage {
+
+  private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n');
+
+  /**
+   * Given an options class, render the usage string into the usage,
+   * which is passed in as an argument.
+   */
+  static void getUsage(Class<? extends OptionsBase> optionsClass, StringBuilder usage) {
+    List<Field> optionFields =
+        Lists.newArrayList(OptionsParser.getAllAnnotatedFields(optionsClass));
+    Collections.sort(optionFields, BY_NAME);
+    for (Field optionField : optionFields) {
+      getUsage(optionField, usage, OptionsParser.HelpVerbosity.LONG);
+    }
+  }
+
+  /**
+   * Paragraph-fill the specified input text, indenting lines to 'indent' and
+   * wrapping lines at 'width'.  Returns the formatted result.
+   */
+  static String paragraphFill(String in, int indent, int width) {
+    String indentString = Strings.repeat(" ", indent);
+    StringBuilder out = new StringBuilder();
+    String sep = "";
+    for (String paragraph : NEWLINE_SPLITTER.split(in)) {
+      BreakIterator boundary = BreakIterator.getLineInstance(); // (factory)
+      boundary.setText(paragraph);
+      out.append(sep).append(indentString);
+      int cursor = indent;
+      for (int start = boundary.first(), end = boundary.next();
+           end != BreakIterator.DONE;
+           start = end, end = boundary.next()) {
+        String word =
+            paragraph.substring(start, end); // (may include trailing space)
+        if (word.length() + cursor > width) {
+          out.append('\n').append(indentString);
+          cursor = indent;
+        }
+        out.append(word);
+        cursor += word.length();
+      }
+      sep = "\n";
+    }
+    return out.toString();
+  }
+
+  /**
+   * Append the usage message for a single option-field message to 'usage'.
+   */
+  static void getUsage(Field optionField, StringBuilder usage,
+                       OptionsParser.HelpVerbosity helpVerbosity) {
+    String flagName = getFlagName(optionField);
+    String typeDescription = getTypeDescription(optionField);
+    Option annotation = optionField.getAnnotation(Option.class);
+    usage.append("  --" + flagName);
+    if (helpVerbosity == OptionsParser.HelpVerbosity.SHORT) { // just the name
+      usage.append('\n');
+      return;
+    }
+    if (annotation.abbrev() != '\0') {
+      usage.append(" [-").append(annotation.abbrev()).append(']');
+    }
+    if (!typeDescription.equals("")) {
+      usage.append(" (" + typeDescription + "; ");
+      if (annotation.allowMultiple()) {
+        usage.append("may be used multiple times");
+      } else {
+        // Don't call the annotation directly (we must allow overrides to certain defaults)
+        String defaultValueString = OptionsParserImpl.getDefaultOptionString(optionField);
+        if (OptionsParserImpl.isSpecialNullDefault(defaultValueString, optionField)) {
+          usage.append("default: see description");
+        } else {
+          usage.append("default: \"" + defaultValueString + "\"");
+        }
+      }
+      usage.append(")");
+    }
+    usage.append("\n");
+    if (helpVerbosity == OptionsParser.HelpVerbosity.MEDIUM) { // just the name and type.
+      return;
+    }
+    if (!annotation.help().equals("")) {
+      usage.append(paragraphFill(annotation.help(), 4, 80)); // (indent, width)
+      usage.append('\n');
+    }
+    if (annotation.expansion().length > 0) {
+      StringBuilder expandsMsg = new StringBuilder("Expands to: ");
+      for (String exp : annotation.expansion()) {
+        expandsMsg.append(exp).append(" ");
+      }
+      usage.append(paragraphFill(expandsMsg.toString(), 4, 80)); // (indent, width)
+      usage.append('\n');
+    }
+  }
+
+  private static final Comparator<Field> BY_NAME = new Comparator<Field>() {
+    @Override
+    public int compare(Field left, Field right) {
+      return left.getName().compareTo(right.getName());
+    }
+  };
+
+  /**
+   * An ordering relation for option-field fields that first groups together
+   * options of the same category, then sorts by name within the category.
+   */
+  static final Comparator<Field> BY_CATEGORY = new Comparator<Field>() {
+    @Override
+    public int compare(Field left, Field right) {
+      int r = left.getAnnotation(Option.class).category().compareTo(
+              right.getAnnotation(Option.class).category());
+      return r == 0 ? BY_NAME.compare(left, right) : r;
+    }
+  };
+
+  private static String getTypeDescription(Field optionsField) {
+    return findConverter(optionsField).getTypeDescription();
+  }
+
+  static String getFlagName(Field field) {
+    String name = field.getAnnotation(Option.class).name();
+    return OptionsParserImpl.isBooleanField(field) ? "[no]" + name : name;
+  }
+
+}
diff --git a/src/main/java/com/google/devtools/common/options/TriState.java b/src/main/java/com/google/devtools/common/options/TriState.java
new file mode 100644
index 0000000..9e873ea
--- /dev/null
+++ b/src/main/java/com/google/devtools/common/options/TriState.java
@@ -0,0 +1,21 @@
+// 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;
+
+/**
+ * Enum used to represent tri-state options (yes/no/auto).
+ */
+public enum TriState {
+  YES, NO, AUTO
+}