Home | History | Annotate | Download | only in options
      1 // Copyright 2014 The Bazel Authors. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //    http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package com.google.devtools.common.options;
     16 
     17 import com.google.common.base.Function;
     18 import com.google.common.base.Functions;
     19 import com.google.common.base.Joiner;
     20 import com.google.common.base.Preconditions;
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.common.collect.ListMultimap;
     23 import com.google.common.escape.Escaper;
     24 import java.lang.reflect.Constructor;
     25 import java.lang.reflect.Field;
     26 import java.nio.file.FileSystem;
     27 import java.util.ArrayList;
     28 import java.util.Arrays;
     29 import java.util.Collection;
     30 import java.util.Collections;
     31 import java.util.Comparator;
     32 import java.util.HashMap;
     33 import java.util.LinkedHashMap;
     34 import java.util.LinkedHashSet;
     35 import java.util.List;
     36 import java.util.Map;
     37 import javax.annotation.Nullable;
     38 
     39 /**
     40  * A parser for options. Typical use case in a main method:
     41  *
     42  * <pre>
     43  * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class);
     44  * parser.parseAndExitUponError(args);
     45  * FooOptions foo = parser.getOptions(FooOptions.class);
     46  * BarOptions bar = parser.getOptions(BarOptions.class);
     47  * List&lt;String&gt; otherArguments = parser.getResidue();
     48  * </pre>
     49  *
     50  * <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase,
     51  * that contain fields annotated with @Option(...).
     52  *
     53  * <p>Alternatively, rather than calling {@link #parseAndExitUponError(OptionPriority, String,
     54  * String[])}, client code may call {@link #parse(OptionPriority,String,List)}, and handle parser
     55  * exceptions usage messages themselves.
     56  *
     57  * <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz'
     58  * and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz'
     59  * in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch
     60  * options between void/boolean/tristate and everything else without breaking backwards
     61  * compatibility.
     62  *
     63  * @see Options a simpler class which you can use if you only have one options specification class
     64  */
     65 public class OptionsParser implements OptionsProvider {
     66 
     67   /**
     68    * An unchecked exception thrown when there is a problem constructing a parser, e.g. an error
     69    * while validating an {@link Option} field in one of its {@link OptionsBase} subclasses.
     70    *
     71    * <p>This exception is unchecked because it generally indicates an internal error affecting all
     72    * invocations of the program. I.e., any such error should be immediately obvious to the
     73    * developer. Although unchecked, we explicitly mark some methods as throwing it as a reminder in
     74    * the API.
     75    */
     76   public static class ConstructionException extends RuntimeException {
     77     public ConstructionException(String message) {
     78       super(message);
     79     }
     80 
     81     public ConstructionException(Throwable cause) {
     82       super(cause);
     83     }
     84 
     85     public ConstructionException(String message, Throwable cause) {
     86       super(message, cause);
     87     }
     88   }
     89 
     90   /**
     91    * A cache for the parsed options data. Both keys and values are immutable, so
     92    * this is always safe. Only access this field through the {@link
     93    * #getOptionsData} method for thread-safety! The cache is very unlikely to
     94    * grow to a significant amount of memory, because there's only a fixed set of
     95    * options classes on the classpath.
     96    */
     97   private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData =
     98       new HashMap<>();
     99 
    100   /**
    101    * Returns {@link OpaqueOptionsData} suitable for passing along to {@link
    102    * #newOptionsParser(OpaqueOptionsData optionsData)}.
    103    *
    104    * <p>This is useful when you want to do the work of analyzing the given {@code optionsClasses}
    105    * exactly once, but you want to parse lots of different lists of strings (and thus need to
    106    * construct lots of different {@link OptionsParser} instances).
    107    */
    108   public static OpaqueOptionsData getOptionsData(
    109       List<Class<? extends OptionsBase>> optionsClasses) throws ConstructionException {
    110     return getOptionsDataInternal(optionsClasses);
    111   }
    112 
    113   /**
    114    * Returns the {@link OptionsData} associated with the given list of options classes.
    115    */
    116   static synchronized OptionsData getOptionsDataInternal(
    117       List<Class<? extends OptionsBase>> optionsClasses) throws ConstructionException {
    118     ImmutableList<Class<? extends OptionsBase>> immutableOptionsClasses =
    119         ImmutableList.copyOf(optionsClasses);
    120     OptionsData result = optionsData.get(immutableOptionsClasses);
    121     if (result == null) {
    122       try {
    123         result = OptionsData.from(immutableOptionsClasses);
    124       } catch (Exception e) {
    125         throw new ConstructionException(e.getMessage(), e);
    126       }
    127       optionsData.put(immutableOptionsClasses, result);
    128     }
    129     return result;
    130   }
    131 
    132   /**
    133    * Returns the {@link OptionsData} associated with the given options class.
    134    */
    135   static OptionsData getOptionsDataInternal(Class<? extends OptionsBase> optionsClass)
    136       throws ConstructionException {
    137     return getOptionsDataInternal(ImmutableList.<Class<? extends OptionsBase>>of(optionsClass));
    138   }
    139 
    140   /**
    141    * @see #newOptionsParser(Iterable)
    142    */
    143   public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1)
    144       throws ConstructionException {
    145     return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1));
    146   }
    147 
    148   /**
    149    * @see #newOptionsParser(Iterable)
    150    */
    151   public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1,
    152                                                Class<? extends OptionsBase> class2)
    153       throws ConstructionException {
    154     return newOptionsParser(ImmutableList.of(class1, class2));
    155   }
    156 
    157   /** Create a new {@link OptionsParser}. */
    158   public static OptionsParser newOptionsParser(
    159       Iterable<? extends Class<? extends OptionsBase>> optionsClasses)
    160       throws ConstructionException {
    161     return newOptionsParser(
    162         getOptionsDataInternal(ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsClasses)));
    163   }
    164 
    165   /**
    166    * Create a new {@link OptionsParser}, using {@link OpaqueOptionsData} previously returned from
    167    * {@link #getOptionsData}.
    168    */
    169   public static OptionsParser newOptionsParser(OpaqueOptionsData optionsData) {
    170     return new OptionsParser((OptionsData) optionsData);
    171   }
    172 
    173   private final OptionsParserImpl impl;
    174   private final List<String> residue = new ArrayList<String>();
    175   private boolean allowResidue = true;
    176 
    177   OptionsParser(OptionsData optionsData) {
    178     impl = new OptionsParserImpl(optionsData);
    179   }
    180 
    181   /**
    182    * Indicates whether or not the parser will allow a non-empty residue; that
    183    * is, iff this value is true then a call to one of the {@code parse}
    184    * methods will throw {@link OptionsParsingException} unless
    185    * {@link #getResidue()} is empty after parsing.
    186    */
    187   public void setAllowResidue(boolean allowResidue) {
    188     this.allowResidue = allowResidue;
    189   }
    190 
    191   /**
    192    * Indicates whether or not the parser will allow long options with a
    193    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
    194    */
    195   public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
    196     this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions);
    197   }
    198 
    199   /** Enables the Parser to handle params files loacted insinde the provided {@link FileSystem}. */
    200   public void enableParamsFileSupport(FileSystem fs) {
    201     this.impl.setArgsPreProcessor(new ParamsFilePreProcessor(fs));
    202   }
    203 
    204   public void parseAndExitUponError(String[] args) {
    205     parseAndExitUponError(OptionPriority.COMMAND_LINE, "unknown", args);
    206   }
    207 
    208   /**
    209    * A convenience function for use in main methods. Parses the command line
    210    * parameters, and exits upon error. Also, prints out the usage message
    211    * if "--help" appears anywhere within {@code args}.
    212    */
    213   public void parseAndExitUponError(OptionPriority priority, String source, String[] args) {
    214     for (String arg : args) {
    215       if (arg.equals("--help")) {
    216         System.out.println(describeOptions(Collections.<String, String>emptyMap(),
    217                                            HelpVerbosity.LONG));
    218         System.exit(0);
    219       }
    220     }
    221     try {
    222       parse(priority, source, Arrays.asList(args));
    223     } catch (OptionsParsingException e) {
    224       System.err.println("Error parsing command line: " + e.getMessage());
    225       System.err.println("Try --help.");
    226       System.exit(2);
    227     }
    228   }
    229 
    230   /**
    231    * The metadata about an option.
    232    */
    233   public static final class OptionDescription {
    234 
    235     private final String name;
    236 
    237     // For valued flags
    238     private final Object defaultValue;
    239     private final Converter<?> converter;
    240     private final boolean allowMultiple;
    241 
    242     private final ImmutableList<OptionValueDescription> expansions;
    243     private final ImmutableList<OptionValueDescription> implicitRequirements;
    244 
    245     OptionDescription(
    246         String name,
    247         Object defaultValue,
    248         Converter<?> converter,
    249         boolean allowMultiple,
    250         ImmutableList<OptionValueDescription> expansions,
    251         ImmutableList<OptionValueDescription> implicitRequirements) {
    252       this.name = name;
    253       this.defaultValue = defaultValue;
    254       this.converter = converter;
    255       this.allowMultiple = allowMultiple;
    256       this.expansions = expansions;
    257       this.implicitRequirements = implicitRequirements;
    258     }
    259 
    260     public String getName() {
    261       return name;
    262     }
    263 
    264     public Object getDefaultValue() {
    265       return defaultValue;
    266     }
    267 
    268     public Converter<?> getConverter() {
    269       return converter;
    270     }
    271 
    272     public boolean getAllowMultiple() {
    273       return allowMultiple;
    274     }
    275 
    276     public ImmutableList<OptionValueDescription> getImplicitRequirements() {
    277       return implicitRequirements;
    278     }
    279 
    280     public ImmutableList<OptionValueDescription> getExpansions() {
    281       return expansions;
    282     }
    283   }
    284 
    285   /**
    286    * The name and value of an option with additional metadata describing its
    287    * priority, source, whether it was set via an implicit dependency, and if so,
    288    * by which other option.
    289    */
    290   public static class OptionValueDescription {
    291     private final String name;
    292     @Nullable private final String originalValueString;
    293     @Nullable private final Object value;
    294     @Nullable private final OptionPriority priority;
    295     @Nullable private final String source;
    296     @Nullable private final String implicitDependant;
    297     @Nullable private final String expandedFrom;
    298     private final boolean allowMultiple;
    299 
    300     public OptionValueDescription(
    301         String name,
    302         @Nullable String originalValueString,
    303         @Nullable Object value,
    304         @Nullable OptionPriority priority,
    305         @Nullable String source,
    306         @Nullable String implicitDependant,
    307         @Nullable String expandedFrom,
    308         boolean allowMultiple) {
    309       this.name = name;
    310       this.originalValueString = originalValueString;
    311       this.value = value;
    312       this.priority = priority;
    313       this.source = source;
    314       this.implicitDependant = implicitDependant;
    315       this.expandedFrom = expandedFrom;
    316       this.allowMultiple = allowMultiple;
    317     }
    318 
    319     public String getName() {
    320       return name;
    321     }
    322 
    323     public String getOriginalValueString() {
    324       return originalValueString;
    325     }
    326 
    327     // Need to suppress unchecked warnings, because the "multiple occurrence"
    328     // options use unchecked ListMultimaps due to limitations of Java generics.
    329     @SuppressWarnings({"unchecked", "rawtypes"})
    330     public Object getValue() {
    331       if (allowMultiple) {
    332         // Sort the results by option priority and return them in a new list.
    333         // The generic type of the list is not known at runtime, so we can't
    334         // use it here. It was already checked in the constructor, so this is
    335         // type-safe.
    336         List result = new ArrayList<>();
    337         ListMultimap realValue = (ListMultimap) value;
    338         for (OptionPriority priority : OptionPriority.values()) {
    339           // If there is no mapping for this key, this check avoids object creation (because
    340           // ListMultimap has to return a new object on get) and also an unnecessary addAll call.
    341           if (realValue.containsKey(priority)) {
    342             result.addAll(realValue.get(priority));
    343           }
    344         }
    345         return result;
    346       }
    347       return value;
    348     }
    349 
    350     /**
    351      * @return the priority of the thing that set this value for this flag
    352      */
    353     public OptionPriority getPriority() {
    354       return priority;
    355     }
    356 
    357     /**
    358      * @return the thing that set this value for this flag
    359      */
    360     public String getSource() {
    361       return source;
    362     }
    363 
    364     public String getImplicitDependant() {
    365       return implicitDependant;
    366     }
    367 
    368     public boolean isImplicitDependency() {
    369       return implicitDependant != null;
    370     }
    371 
    372     public String getExpansionParent() {
    373       return expandedFrom;
    374     }
    375 
    376     public boolean isExpansion() {
    377       return expandedFrom != null;
    378     }
    379 
    380     public boolean getAllowMultiple() {
    381       return allowMultiple;
    382     }
    383 
    384     @Override
    385     public String toString() {
    386       StringBuilder result = new StringBuilder();
    387       result.append("option '").append(name).append("' ");
    388       result.append("set to '").append(value).append("' ");
    389       result.append("with priority ").append(priority);
    390       if (source != null) {
    391         result.append(" and source '").append(source).append("'");
    392       }
    393       if (implicitDependant != null) {
    394         result.append(" implicitly by ");
    395       }
    396       return result.toString();
    397     }
    398 
    399     // Need to suppress unchecked warnings, because the "multiple occurrence"
    400     // options use unchecked ListMultimaps due to limitations of Java generics.
    401     @SuppressWarnings({"unchecked", "rawtypes"})
    402     void addValue(OptionPriority addedPriority, Object addedValue) {
    403       Preconditions.checkState(allowMultiple);
    404       ListMultimap optionValueList = (ListMultimap) value;
    405       if (addedValue instanceof List<?>) {
    406         optionValueList.putAll(addedPriority, (List<?>) addedValue);
    407       } else {
    408         optionValueList.put(addedPriority, addedValue);
    409       }
    410     }
    411   }
    412 
    413   /**
    414    * The name and unparsed value of an option with additional metadata describing its
    415    * priority, source, whether it was set via an implicit dependency, and if so,
    416    * by which other option.
    417    *
    418    * <p>Note that the unparsed value and the source parameters can both be null.
    419    */
    420   public static class UnparsedOptionValueDescription {
    421     private final String name;
    422     private final Field field;
    423     private final String unparsedValue;
    424     private final OptionPriority priority;
    425     private final String source;
    426     private final boolean explicit;
    427 
    428     public UnparsedOptionValueDescription(String name, Field field, String unparsedValue,
    429         OptionPriority priority, String source, boolean explicit) {
    430       this.name = name;
    431       this.field = field;
    432       this.unparsedValue = unparsedValue;
    433       this.priority = priority;
    434       this.source = source;
    435       this.explicit = explicit;
    436     }
    437 
    438     public String getName() {
    439       return name;
    440     }
    441 
    442     Field getField() {
    443       return field;
    444     }
    445 
    446     public boolean isBooleanOption() {
    447       return field.getType().equals(boolean.class);
    448     }
    449 
    450     private OptionUsageRestrictions optionUsageRestrictions() {
    451       return field.getAnnotation(Option.class).optionUsageRestrictions();
    452     }
    453 
    454     public boolean isDocumented() {
    455       return optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED;
    456     }
    457 
    458     public boolean isHidden() {
    459       return optionUsageRestrictions() == OptionUsageRestrictions.HIDDEN
    460           || optionUsageRestrictions() == OptionUsageRestrictions.INTERNAL;
    461     }
    462 
    463     boolean isExpansion() {
    464       return OptionsData.isExpansionOption(field.getAnnotation(Option.class));
    465     }
    466 
    467     boolean isImplicitRequirement() {
    468       Option option = field.getAnnotation(Option.class);
    469       return option.implicitRequirements().length > 0;
    470     }
    471 
    472     boolean allowMultiple() {
    473       Option option = field.getAnnotation(Option.class);
    474       return option.allowMultiple();
    475     }
    476 
    477     public String getUnparsedValue() {
    478       return unparsedValue;
    479     }
    480 
    481     OptionPriority getPriority() {
    482       return priority;
    483     }
    484 
    485     public String getSource() {
    486       return source;
    487     }
    488 
    489     public boolean isExplicit() {
    490       return explicit;
    491     }
    492 
    493     @Override
    494     public String toString() {
    495       StringBuilder result = new StringBuilder();
    496       result.append("option '").append(name).append("' ");
    497       result.append("set to '").append(unparsedValue).append("' ");
    498       result.append("with priority ").append(priority);
    499       if (source != null) {
    500         result.append(" and source '").append(source).append("'");
    501       }
    502       return result.toString();
    503     }
    504   }
    505 
    506   /**
    507    * The verbosity with which option help messages are displayed: short (just
    508    * the name), medium (name, type, default, abbreviation), and long (full
    509    * description).
    510    */
    511   public enum HelpVerbosity { LONG, MEDIUM, SHORT }
    512 
    513   /**
    514    * The restrictions on an option. Only documented options are output as part of the help and are
    515    * intended for general user use. Undocumented options can be used by any user but aren't
    516    * advertised and in practice should be used by bazel developers or early adopters helping to test
    517    * a feature.
    518    *
    519    * <p>We use HIDDEN so that options that form the protocol between the client and the server are
    520    * not logged. These are flags, but should never be set by a user.
    521    *
    522    * <p>Options which are INTERNAL are not recognized by the parser at all, and so cannot be used as
    523    * flags.
    524    */
    525   public enum OptionUsageRestrictions {
    526     DOCUMENTED, UNDOCUMENTED, HIDDEN, INTERNAL
    527   }
    528 
    529   /**
    530    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    531    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    532    * intuitive short description for the options. Options of the same category (see {@link
    533    * Option#category}) will be grouped together.
    534    *
    535    * @param categoryDescriptions a mapping from category names to category descriptions.
    536    *     Descriptions are optional; if omitted, a string based on the category name will be used.
    537    * @param helpVerbosity if {@code long}, the options will be described verbosely, including their
    538    *     types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
    539    *     {@code short}, the options are just enumerated.
    540    */
    541   public String describeOptions(
    542       Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) {
    543     OptionsData data = impl.getOptionsData();
    544     StringBuilder desc = new StringBuilder();
    545     if (!data.getOptionsClasses().isEmpty()) {
    546       List<Field> allFields = new ArrayList<>();
    547       for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    548         allFields.addAll(data.getFieldsForClass(optionsClass));
    549       }
    550       Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
    551       String prevCategory = null;
    552 
    553       for (Field optionField : allFields) {
    554         Option option = optionField.getAnnotation(Option.class);
    555         String category = option.category();
    556         if (!category.equals(prevCategory)
    557             && option.optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED) {
    558           String description = categoryDescriptions.get(category);
    559           if (description == null) {
    560             description = "Options category '" + category + "'";
    561           }
    562           desc.append("\n").append(description).append(":\n");
    563           prevCategory = category;
    564         }
    565 
    566         if (option.optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED) {
    567           OptionsUsage.getUsage(optionField, desc, helpVerbosity, impl.getOptionsData());
    568         }
    569       }
    570     }
    571     return desc.toString().trim();
    572   }
    573 
    574   /**
    575    * Returns a description of all the options this parser can digest.
    576    * In addition to {@link Option} annotations, this method also
    577    * interprets {@link OptionsUsage} annotations which give an intuitive short
    578    * description for the options.
    579    *
    580    * @param categoryDescriptions a mapping from category names to category
    581    *   descriptions.  Options of the same category (see {@link
    582    *   Option#category}) will be grouped together, preceded by the description
    583    *   of the category.
    584    */
    585   public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) {
    586     OptionsData data = impl.getOptionsData();
    587     StringBuilder desc = new StringBuilder();
    588     if (!data.getOptionsClasses().isEmpty()) {
    589       List<Field> allFields = new ArrayList<>();
    590       for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    591         allFields.addAll(data.getFieldsForClass(optionsClass));
    592       }
    593       Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
    594       String prevCategory = null;
    595 
    596       for (Field optionField : allFields) {
    597         Option option = optionField.getAnnotation(Option.class);
    598         String category = option.category();
    599         if (!category.equals(prevCategory)
    600             && option.optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED) {
    601           String description = categoryDescriptions.get(category);
    602           if (description == null) {
    603             description = "Options category '" + category + "'";
    604           }
    605           if (prevCategory != null) {
    606             desc.append("</dl>\n\n");
    607           }
    608           desc.append(escaper.escape(description)).append(":\n");
    609           desc.append("<dl>");
    610           prevCategory = category;
    611         }
    612 
    613         if (option.optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED) {
    614           OptionsUsage.getUsageHtml(optionField, desc, escaper, impl.getOptionsData());
    615         }
    616       }
    617       desc.append("</dl>\n");
    618     }
    619     return desc.toString();
    620   }
    621 
    622   /**
    623    * Returns a string listing the possible flag completion for this command along with the command
    624    * completion if any. See {@link OptionsUsage#getCompletion(Field, StringBuilder)} for more
    625    * details on the format for the flag completion.
    626    */
    627   public String getOptionsCompletion() {
    628     OptionsData data = impl.getOptionsData();
    629     StringBuilder desc = new StringBuilder();
    630 
    631     // List all options
    632     List<Field> allFields = new ArrayList<>();
    633     for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    634       allFields.addAll(data.getFieldsForClass(optionsClass));
    635     }
    636     // Sort field for deterministic ordering
    637     Collections.sort(allFields, new Comparator<Field>() {
    638       @Override
    639       public int compare(Field f1, Field f2) {
    640         String name1 = f1.getAnnotation(Option.class).name();
    641         String name2 = f2.getAnnotation(Option.class).name();
    642         return name1.compareTo(name2);
    643       }
    644     });
    645     for (Field optionField : allFields) {
    646       Option option = optionField.getAnnotation(Option.class);
    647       if (option.optionUsageRestrictions() == OptionUsageRestrictions.DOCUMENTED) {
    648         OptionsUsage.getCompletion(optionField, desc);
    649       }
    650     }
    651 
    652     return desc.toString();
    653   }
    654 
    655   /**
    656    * Returns a description of the option.
    657    *
    658    * @return The {@link OptionDescription} for the option, or null if there is no option by the
    659    *     given name.
    660    */
    661   public OptionDescription getOptionDescription(String name) throws OptionsParsingException {
    662     return impl.getOptionDescription(name);
    663   }
    664 
    665   /**
    666    * Returns a description of the option value set by the last previous call to
    667    * {@link #parse(OptionPriority, String, List)} that successfully set the given
    668    * option. If the option is of type {@link List}, the description will
    669    * correspond to any one of the calls, but not necessarily the last.
    670    *
    671    * @return The {@link OptionValueDescription} for the option, or null if the value has not been
    672    *        set.
    673    * @throws IllegalArgumentException if there is no option by the given name.
    674    */
    675   public OptionValueDescription getOptionValueDescription(String name) {
    676     return impl.getOptionValueDescription(name);
    677   }
    678 
    679   /**
    680    * A convenience method, equivalent to
    681    * {@code parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args))}.
    682    */
    683   public void parse(String... args) throws OptionsParsingException {
    684     parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args));
    685   }
    686 
    687   /**
    688    * A convenience method, equivalent to
    689    * {@code parse(OptionPriority.COMMAND_LINE, null, args)}.
    690    */
    691   public void parse(List<String> args) throws OptionsParsingException {
    692     parse(OptionPriority.COMMAND_LINE, null, args);
    693   }
    694 
    695   /**
    696    * Parses {@code args}, using the classes registered with this parser.
    697    * {@link #getOptions(Class)} and {@link #getResidue()} return the results.
    698    * May be called multiple times; later options override existing ones if they
    699    * have equal or higher priority. The source of options is a free-form string
    700    * that can be used for debugging. Strings that cannot be parsed as options
    701    * accumulates as residue, if this parser allows it.
    702    *
    703    * @see OptionPriority
    704    */
    705   public void parse(OptionPriority priority, String source,
    706       List<String> args) throws OptionsParsingException {
    707     parseWithSourceFunction(priority, Functions.constant(source), args);
    708   }
    709 
    710   /**
    711    * Parses {@code args}, using the classes registered with this parser.
    712    * {@link #getOptions(Class)} and {@link #getResidue()} return the results. May be called
    713    * multiple times; later options override existing ones if they have equal or higher priority.
    714    * The source of options is given as a function that maps option names to the source of the
    715    * option. Strings that cannot be parsed as options accumulates as* residue, if this parser
    716    * allows it.
    717    */
    718   public void parseWithSourceFunction(OptionPriority priority,
    719       Function<? super String, String> sourceFunction, List<String> args)
    720       throws OptionsParsingException {
    721     Preconditions.checkNotNull(priority);
    722     Preconditions.checkArgument(priority != OptionPriority.DEFAULT);
    723     residue.addAll(impl.parse(priority, sourceFunction, args));
    724     if (!allowResidue && !residue.isEmpty()) {
    725       String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
    726       throw new OptionsParsingException(errorMsg);
    727     }
    728   }
    729 
    730   /**
    731    * Clears the given option.
    732    *
    733    * <p>This will not affect options objects that have already been retrieved from this parser
    734    * through {@link #getOptions(Class)}.
    735    *
    736    * @param optionName The full name of the option to clear.
    737    * @return A map of an option name to the old value of the options that were cleared.
    738    * @throws IllegalArgumentException If the flag does not exist.
    739    */
    740   public OptionValueDescription clearValue(String optionName)
    741       throws OptionsParsingException {
    742     OptionValueDescription clearedValue = impl.clearValue(optionName);
    743     return clearedValue;
    744   }
    745 
    746   @Override
    747   public List<String> getResidue() {
    748     return ImmutableList.copyOf(residue);
    749   }
    750 
    751   /**
    752    * Returns a list of warnings about problems encountered by previous parse calls.
    753    */
    754   public List<String> getWarnings() {
    755     return impl.getWarnings();
    756   }
    757 
    758   @Override
    759   public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
    760     return impl.getParsedOptions(optionsClass);
    761   }
    762 
    763   @Override
    764   public boolean containsExplicitOption(String name) {
    765     return impl.containsExplicitOption(name);
    766   }
    767 
    768   @Override
    769   public List<UnparsedOptionValueDescription> asListOfUnparsedOptions() {
    770     return impl.asListOfUnparsedOptions();
    771   }
    772 
    773   @Override
    774   public List<UnparsedOptionValueDescription> asListOfExplicitOptions() {
    775     return impl.asListOfExplicitOptions();
    776   }
    777 
    778   @Override
    779   public List<OptionValueDescription> asListOfEffectiveOptions() {
    780     return impl.asListOfEffectiveOptions();
    781   }
    782 
    783   @Override
    784   public List<String> canonicalize() {
    785     return impl.asCanonicalizedList();
    786   }
    787 
    788   /** Returns all options fields of the given options class, in alphabetic order. */
    789   public static Collection<Field> getFields(Class<? extends OptionsBase> optionsClass) {
    790     OptionsData data = OptionsParser.getOptionsDataInternal(optionsClass);
    791     return data.getFieldsForClass(optionsClass);
    792   }
    793 
    794   /**
    795    * Returns whether the given options class uses only the core types listed in {@link
    796    * OptionsBase#coreTypes}. These are guaranteed to be deeply immutable and serializable.
    797    */
    798   public static boolean getUsesOnlyCoreTypes(Class<? extends OptionsBase> optionsClass) {
    799     OptionsData data = OptionsParser.getOptionsDataInternal(optionsClass);
    800     return data.getUsesOnlyCoreTypes(optionsClass);
    801   }
    802 
    803   /**
    804    * Returns a mapping from each option {@link Field} in {@code optionsClass} (including inherited
    805    * ones) to its value in {@code options}.
    806    *
    807    * <p>The map is a mutable copy; changing the map won't affect {@code options} and vice versa.
    808    * The map entries appear sorted alphabetically by option name.
    809    *
    810    * If {@code options} is an instance of a subclass of {@code optionsClass}, any options defined
    811    * by the subclass are not included in the map.
    812    *
    813    * @throws IllegalArgumentException if {@code options} is not an instance of {@code optionsClass}
    814    */
    815   public static <O extends OptionsBase> Map<Field, Object> toMap(Class<O> optionsClass, O options) {
    816     OptionsData data = getOptionsDataInternal(optionsClass);
    817     // Alphabetized due to getFieldsForClass()'s order.
    818     Map<Field, Object> map = new LinkedHashMap<>();
    819     for (Field field : data.getFieldsForClass(optionsClass)) {
    820       try {
    821         map.put(field, field.get(options));
    822       } catch (IllegalAccessException e) {
    823         // All options fields of options classes should be public.
    824         throw new IllegalStateException(e);
    825       } catch (IllegalArgumentException e) {
    826         // This would indicate an inconsistency in the cached OptionsData.
    827         throw new IllegalStateException(e);
    828       }
    829     }
    830     return map;
    831   }
    832 
    833   /**
    834    * Given a mapping as returned by {@link #toMap}, and the options class it that its entries
    835    * correspond to, this constructs the corresponding instance of the options class.
    836    *
    837    * @throws IllegalArgumentException if {@code map} does not contain exactly the fields of {@code
    838    *     optionsClass}, with values of the appropriate type
    839    */
    840   public static <O extends OptionsBase> O fromMap(Class<O> optionsClass, Map<Field, Object> map) {
    841     // Instantiate the options class.
    842     OptionsData data = getOptionsDataInternal(optionsClass);
    843     O optionsInstance;
    844     try {
    845       Constructor<O> constructor = data.getConstructor(optionsClass);
    846       Preconditions.checkNotNull(constructor, "No options class constructor available");
    847       optionsInstance = constructor.newInstance();
    848     } catch (ReflectiveOperationException e) {
    849       throw new IllegalStateException("Error while instantiating options class", e);
    850     }
    851 
    852     List<Field> fields = data.getFieldsForClass(optionsClass);
    853     // Ensure all fields are covered, no extraneous fields.
    854     validateFieldsSets(new LinkedHashSet<>(fields), new LinkedHashSet<>(map.keySet()));
    855     // Populate the instance.
    856     for (Field field : fields) {
    857       // Non-null as per above check.
    858       Object value = map.get(field);
    859       try {
    860         field.set(optionsInstance, value);
    861       } catch (IllegalAccessException e) {
    862         throw new IllegalStateException(e);
    863       }
    864       // May also throw IllegalArgumentException if map value is ill typed.
    865     }
    866     return optionsInstance;
    867   }
    868 
    869   /**
    870    * Raises a pretty {@link IllegalArgumentException} if the two sets of fields are not equal.
    871    *
    872    * <p>The entries in {@code fieldsFromMap} may be ill formed by being null or lacking an {@link
    873    * Option} annotation. (This isn't done for {@code fieldsFromClass} because they come from an
    874    * {@link OptionsData} object.)
    875    */
    876   private static void validateFieldsSets(
    877       LinkedHashSet<Field> fieldsFromClass, LinkedHashSet<Field> fieldsFromMap) {
    878     if (!fieldsFromClass.equals(fieldsFromMap)) {
    879       List<String> extraNamesFromClass = new ArrayList<>();
    880       List<String> extraNamesFromMap = new ArrayList<>();
    881       for (Field field : fieldsFromClass) {
    882         if (!fieldsFromMap.contains(field)) {
    883           extraNamesFromClass.add("'" + field.getAnnotation(Option.class).name() + "'");
    884         }
    885       }
    886       for (Field field : fieldsFromMap) {
    887         // Extra validation on the map keys since they don't come from OptionsData.
    888         if (!fieldsFromClass.contains(field)) {
    889           if (field == null) {
    890             extraNamesFromMap.add("<null field>");
    891           } else {
    892             Option annotation = field.getAnnotation(Option.class);
    893             if (annotation == null) {
    894               extraNamesFromMap.add("<non-Option field>");
    895             } else {
    896               extraNamesFromMap.add("'" + annotation.name() + "'");
    897             }
    898           }
    899         }
    900       }
    901       throw new IllegalArgumentException(
    902           "Map keys do not match fields of options class; extra map keys: {"
    903           + Joiner.on(", ").join(extraNamesFromMap) + "}; extra options class options: {"
    904           + Joiner.on(", ").join(extraNamesFromClass) + "}");
    905     }
    906   }
    907 }
    908