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.Joiner;
     18 import com.google.common.base.Preconditions;
     19 import com.google.common.base.Throwables;
     20 import com.google.common.collect.ArrayListMultimap;
     21 import com.google.common.collect.ImmutableList;
     22 import com.google.common.collect.ImmutableMap;
     23 import com.google.common.collect.ListMultimap;
     24 import com.google.common.escape.Escaper;
     25 import com.google.devtools.common.options.OptionDefinition.NotAnOptionException;
     26 import java.lang.reflect.Constructor;
     27 import java.lang.reflect.Field;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Collections;
     31 import java.util.HashMap;
     32 import java.util.LinkedHashMap;
     33 import java.util.LinkedHashSet;
     34 import java.util.List;
     35 import java.util.Map;
     36 import java.util.Map.Entry;
     37 import java.util.Set;
     38 import java.util.function.Consumer;
     39 import java.util.function.Function;
     40 import java.util.function.Predicate;
     41 import java.util.stream.Collectors;
     42 
     43 /**
     44  * A parser for options. Typical use case in a main method:
     45  *
     46  * <pre>
     47  * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class);
     48  * parser.parseAndExitUponError(args);
     49  * FooOptions foo = parser.getOptions(FooOptions.class);
     50  * BarOptions bar = parser.getOptions(BarOptions.class);
     51  * List&lt;String&gt; otherArguments = parser.getResidue();
     52  * </pre>
     53  *
     54  * <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase,
     55  * that contain fields annotated with @Option(...).
     56  *
     57  * <p>Alternatively, rather than calling {@link
     58  * #parseAndExitUponError(OptionPriority.PriorityCategory, String, String[])}, client code may call
     59  * {@link #parse(OptionPriority.PriorityCategory,String,List)}, and handle parser exceptions usage
     60  * messages themselves.
     61  *
     62  * <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz'
     63  * and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz'
     64  * in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch
     65  * options between void/boolean/tristate and everything else without breaking backwards
     66  * compatibility.
     67  *
     68  * @see Options a simpler class which you can use if you only have one options specification class
     69  */
     70 public class OptionsParser implements OptionsProvider {
     71 
     72   // TODO(b/65049598) make ConstructionException checked.
     73   /**
     74    * An unchecked exception thrown when there is a problem constructing a parser, e.g. an error
     75    * while validating an {@link OptionDefinition} in one of its {@link OptionsBase} subclasses.
     76    *
     77    * <p>This exception is unchecked because it generally indicates an internal error affecting all
     78    * invocations of the program. I.e., any such error should be immediately obvious to the
     79    * developer. Although unchecked, we explicitly mark some methods as throwing it as a reminder in
     80    * the API.
     81    */
     82   public static class ConstructionException extends RuntimeException {
     83     public ConstructionException(String message) {
     84       super(message);
     85     }
     86 
     87     public ConstructionException(Throwable cause) {
     88       super(cause);
     89     }
     90 
     91     public ConstructionException(String message, Throwable cause) {
     92       super(message, cause);
     93     }
     94   }
     95 
     96   /**
     97    * A cache for the parsed options data. Both keys and values are immutable, so
     98    * this is always safe. Only access this field through the {@link
     99    * #getOptionsData} method for thread-safety! The cache is very unlikely to
    100    * grow to a significant amount of memory, because there's only a fixed set of
    101    * options classes on the classpath.
    102    */
    103   private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData =
    104       new HashMap<>();
    105 
    106   /**
    107    * Returns {@link OpaqueOptionsData} suitable for passing along to {@link
    108    * #newOptionsParser(OpaqueOptionsData optionsData)}.
    109    *
    110    * <p>This is useful when you want to do the work of analyzing the given {@code optionsClasses}
    111    * exactly once, but you want to parse lots of different lists of strings (and thus need to
    112    * construct lots of different {@link OptionsParser} instances).
    113    */
    114   public static OpaqueOptionsData getOptionsData(
    115       List<Class<? extends OptionsBase>> optionsClasses) throws ConstructionException {
    116     return getOptionsDataInternal(optionsClasses);
    117   }
    118 
    119   /**
    120    * Returns the {@link OptionsData} associated with the given list of options classes.
    121    */
    122   static synchronized OptionsData getOptionsDataInternal(
    123       List<Class<? extends OptionsBase>> optionsClasses) throws ConstructionException {
    124     ImmutableList<Class<? extends OptionsBase>> immutableOptionsClasses =
    125         ImmutableList.copyOf(optionsClasses);
    126     OptionsData result = optionsData.get(immutableOptionsClasses);
    127     if (result == null) {
    128       try {
    129         result = OptionsData.from(immutableOptionsClasses);
    130       } catch (Exception e) {
    131         Throwables.throwIfInstanceOf(e, ConstructionException.class);
    132         throw new ConstructionException(e.getMessage(), e);
    133       }
    134       optionsData.put(immutableOptionsClasses, result);
    135     }
    136     return result;
    137   }
    138 
    139   /**
    140    * Returns the {@link OptionsData} associated with the given options class.
    141    */
    142   static OptionsData getOptionsDataInternal(Class<? extends OptionsBase> optionsClass)
    143       throws ConstructionException {
    144     return getOptionsDataInternal(ImmutableList.of(optionsClass));
    145   }
    146 
    147   /**
    148    * @see #newOptionsParser(Iterable)
    149    */
    150   public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1)
    151       throws ConstructionException {
    152     return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1));
    153   }
    154 
    155   /** @see #newOptionsParser(Iterable) */
    156   public static OptionsParser newOptionsParser(
    157       Class<? extends OptionsBase> class1, Class<? extends OptionsBase> class2)
    158       throws ConstructionException {
    159     return newOptionsParser(ImmutableList.of(class1, class2));
    160   }
    161 
    162   /** Create a new {@link OptionsParser}. */
    163   public static OptionsParser newOptionsParser(
    164       Iterable<? extends Class<? extends OptionsBase>> optionsClasses)
    165       throws ConstructionException {
    166     return newOptionsParser(getOptionsDataInternal(ImmutableList.copyOf(optionsClasses)));
    167   }
    168 
    169   /**
    170    * Create a new {@link OptionsParser}, using {@link OpaqueOptionsData} previously returned from
    171    * {@link #getOptionsData}.
    172    */
    173   public static OptionsParser newOptionsParser(OpaqueOptionsData optionsData) {
    174     return new OptionsParser((OptionsData) optionsData);
    175   }
    176 
    177   private final OptionsParserImpl impl;
    178   private final List<String> residue = new ArrayList<String>();
    179   private boolean allowResidue = true;
    180 
    181   OptionsParser(OptionsData optionsData) {
    182     impl = new OptionsParserImpl(optionsData);
    183   }
    184 
    185   /**
    186    * Indicates whether or not the parser will allow a non-empty residue; that
    187    * is, iff this value is true then a call to one of the {@code parse}
    188    * methods will throw {@link OptionsParsingException} unless
    189    * {@link #getResidue()} is empty after parsing.
    190    */
    191   public void setAllowResidue(boolean allowResidue) {
    192     this.allowResidue = allowResidue;
    193   }
    194 
    195   /**
    196    * Indicates whether or not the parser will allow long options with a
    197    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
    198    */
    199   public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
    200     this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions);
    201   }
    202 
    203   /**
    204    * Enables the Parser to handle params files using the provided {@link ParamsFilePreProcessor}.
    205    */
    206   public void enableParamsFileSupport(ParamsFilePreProcessor preProcessor) {
    207     this.impl.setArgsPreProcessor(preProcessor);
    208   }
    209 
    210   public void parseAndExitUponError(String[] args) {
    211     parseAndExitUponError(OptionPriority.PriorityCategory.COMMAND_LINE, "unknown", args);
    212   }
    213 
    214   /**
    215    * A convenience function for use in main methods. Parses the command line parameters, and exits
    216    * upon error. Also, prints out the usage message if "--help" appears anywhere within {@code
    217    * args}.
    218    */
    219   public void parseAndExitUponError(
    220       OptionPriority.PriorityCategory priority, String source, String[] args) {
    221     for (String arg : args) {
    222       if (arg.equals("--help")) {
    223         System.out.println(
    224             describeOptionsWithDeprecatedCategories(ImmutableMap.of(), HelpVerbosity.LONG));
    225 
    226         System.exit(0);
    227       }
    228     }
    229     try {
    230       parse(priority, source, Arrays.asList(args));
    231     } catch (OptionsParsingException e) {
    232       System.err.println("Error parsing command line: " + e.getMessage());
    233       System.err.println("Try --help.");
    234       System.exit(2);
    235     }
    236   }
    237 
    238   /** The metadata about an option, in the context of this options parser. */
    239   public static final class OptionDescription {
    240     private final OptionDefinition optionDefinition;
    241     private final ImmutableList<String> evaluatedExpansion;
    242 
    243     OptionDescription(OptionDefinition definition, OptionsData optionsData) {
    244       this.optionDefinition = definition;
    245       this.evaluatedExpansion = optionsData.getEvaluatedExpansion(optionDefinition);
    246     }
    247 
    248     public OptionDefinition getOptionDefinition() {
    249       return optionDefinition;
    250     }
    251 
    252     public boolean isExpansion() {
    253       return optionDefinition.isExpansionOption();
    254     }
    255 
    256     /** Return a list of flags that this option expands to. */
    257     public ImmutableList<String> getExpansion() throws OptionsParsingException {
    258       return evaluatedExpansion;
    259     }
    260 
    261     @Override
    262     public boolean equals(Object obj) {
    263       if (obj instanceof OptionDescription) {
    264         OptionDescription other = (OptionDescription) obj;
    265         // Check that the option is the same, with the same expansion.
    266         return other.optionDefinition.equals(optionDefinition)
    267             && other.evaluatedExpansion.equals(evaluatedExpansion);
    268       }
    269       return false;
    270     }
    271 
    272     @Override
    273     public int hashCode() {
    274       return optionDefinition.hashCode() + evaluatedExpansion.hashCode();
    275     }
    276   }
    277 
    278   /**
    279    * The verbosity with which option help messages are displayed: short (just
    280    * the name), medium (name, type, default, abbreviation), and long (full
    281    * description).
    282    */
    283   public enum HelpVerbosity { LONG, MEDIUM, SHORT }
    284 
    285   /**
    286    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    287    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    288    * intuitive short description for the options. Options of the same category (see {@link
    289    * OptionDocumentationCategory}) will be grouped together.
    290    *
    291    * @param productName the name of this product (blaze, bazel)
    292    * @param helpVerbosity if {@code long}, the options will be described verbosely, including their
    293    *     types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
    294    *     {@code short}, the options are just enumerated.
    295    */
    296   public String describeOptions(String productName, HelpVerbosity helpVerbosity) {
    297     StringBuilder desc = new StringBuilder();
    298     LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
    299         getOptionsSortedByCategory();
    300     ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
    301         OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
    302     for (Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
    303         optionsByCategory.entrySet()) {
    304       String categoryDescription = optionCategoryDescriptions.get(e.getKey());
    305       List<OptionDefinition> categorizedOptionList = e.getValue();
    306 
    307       // Describe the category if we're going to end up using it at all.
    308       if (!categorizedOptionList.isEmpty()) {
    309         desc.append("\n").append(categoryDescription).append(":\n");
    310       }
    311       // Describe the options in this category.
    312       for (OptionDefinition optionDef : categorizedOptionList) {
    313         OptionsUsage.getUsage(optionDef, desc, helpVerbosity, impl.getOptionsData(), true);
    314       }
    315     }
    316 
    317     return desc.toString().trim();
    318   }
    319 
    320   /**
    321    * @return all documented options loaded in this parser, grouped by categories in display order.
    322    */
    323   private LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>>
    324       getOptionsSortedByCategory() {
    325     OptionsData data = impl.getOptionsData();
    326     if (data.getOptionsClasses().isEmpty()) {
    327       return new LinkedHashMap<>();
    328     }
    329 
    330     // Get the documented options grouped by category.
    331     ListMultimap<OptionDocumentationCategory, OptionDefinition> optionsByCategories =
    332         ArrayListMultimap.create();
    333     for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    334       for (OptionDefinition optionDefinition :
    335           OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
    336         // Only track documented options.
    337         if (optionDefinition.getDocumentationCategory()
    338             != OptionDocumentationCategory.UNDOCUMENTED) {
    339           optionsByCategories.put(optionDefinition.getDocumentationCategory(), optionDefinition);
    340         }
    341       }
    342     }
    343 
    344     // Put the categories into display order and sort the options in each category.
    345     LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> sortedCategoriesToOptions =
    346         new LinkedHashMap<>(OptionFilterDescriptions.documentationOrder.length, 1);
    347     for (OptionDocumentationCategory category : OptionFilterDescriptions.documentationOrder) {
    348       List<OptionDefinition> optionList = optionsByCategories.get(category);
    349       if (optionList != null) {
    350         optionList.sort(OptionDefinition.BY_OPTION_NAME);
    351         sortedCategoriesToOptions.put(category, optionList);
    352       }
    353     }
    354     return sortedCategoriesToOptions;
    355   }
    356 
    357   /**
    358    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    359    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    360    * intuitive short description for the options. Options of the same category (see {@link
    361    * Option#category}) will be grouped together.
    362    *
    363    * @param categoryDescriptions a mapping from category names to category descriptions.
    364    *     Descriptions are optional; if omitted, a string based on the category name will be used.
    365    * @param helpVerbosity if {@code long}, the options will be described verbosely, including their
    366    *     types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
    367    *     {@code short}, the options are just enumerated.
    368    */
    369   @Deprecated
    370   public String describeOptionsWithDeprecatedCategories(
    371       Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) {
    372     OptionsData data = impl.getOptionsData();
    373     StringBuilder desc = new StringBuilder();
    374     if (!data.getOptionsClasses().isEmpty()) {
    375       List<OptionDefinition> allFields = new ArrayList<>();
    376       for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    377         allFields.addAll(OptionsData.getAllOptionDefinitionsForClass(optionsClass));
    378       }
    379       Collections.sort(allFields, OptionDefinition.BY_CATEGORY);
    380       String prevCategory = null;
    381 
    382       for (OptionDefinition optionDefinition : allFields) {
    383         String category = optionDefinition.getOptionCategory();
    384         if (!category.equals(prevCategory)
    385             && optionDefinition.getDocumentationCategory()
    386                 != OptionDocumentationCategory.UNDOCUMENTED) {
    387           String description = categoryDescriptions.get(category);
    388           if (description == null) {
    389             description = "Options category '" + category + "'";
    390           }
    391           desc.append("\n").append(description).append(":\n");
    392           prevCategory = category;
    393         }
    394 
    395         if (optionDefinition.getDocumentationCategory()
    396             != OptionDocumentationCategory.UNDOCUMENTED) {
    397           OptionsUsage.getUsage(
    398               optionDefinition, desc, helpVerbosity, impl.getOptionsData(), false);
    399         }
    400       }
    401     }
    402     return desc.toString().trim();
    403   }
    404 
    405   /**
    406    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    407    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    408    * intuitive short description for the options.
    409    *
    410    * @param categoryDescriptions a mapping from category names to category descriptions. Options of
    411    *     the same category (see {@link Option#category}) will be grouped together, preceded by the
    412    *     description of the category.
    413    */
    414   @Deprecated
    415   public String describeOptionsHtmlWithDeprecatedCategories(
    416       Map<String, String> categoryDescriptions, Escaper escaper) {
    417     OptionsData data = impl.getOptionsData();
    418     StringBuilder desc = new StringBuilder();
    419     if (!data.getOptionsClasses().isEmpty()) {
    420       List<OptionDefinition> allFields = new ArrayList<>();
    421       for (Class<? extends OptionsBase> optionsClass : data.getOptionsClasses()) {
    422         allFields.addAll(OptionsData.getAllOptionDefinitionsForClass(optionsClass));
    423       }
    424       Collections.sort(allFields, OptionDefinition.BY_CATEGORY);
    425       String prevCategory = null;
    426 
    427       for (OptionDefinition optionDefinition : allFields) {
    428         String category = optionDefinition.getOptionCategory();
    429         if (!category.equals(prevCategory)
    430             && optionDefinition.getDocumentationCategory()
    431                 != OptionDocumentationCategory.UNDOCUMENTED) {
    432           String description = categoryDescriptions.get(category);
    433           if (description == null) {
    434             description = "Options category '" + category + "'";
    435           }
    436           if (prevCategory != null) {
    437             desc.append("</dl>\n\n");
    438           }
    439           desc.append(escaper.escape(description)).append(":\n");
    440           desc.append("<dl>");
    441           prevCategory = category;
    442         }
    443 
    444         if (optionDefinition.getDocumentationCategory()
    445             != OptionDocumentationCategory.UNDOCUMENTED) {
    446           OptionsUsage.getUsageHtml(optionDefinition, desc, escaper, impl.getOptionsData(), false);
    447         }
    448       }
    449       desc.append("</dl>\n");
    450     }
    451     return desc.toString();
    452   }
    453 
    454   /**
    455    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    456    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    457    * intuitive short description for the options.
    458    */
    459   public String describeOptionsHtml(Escaper escaper, String productName) {
    460     StringBuilder desc = new StringBuilder();
    461     LinkedHashMap<OptionDocumentationCategory, List<OptionDefinition>> optionsByCategory =
    462         getOptionsSortedByCategory();
    463     ImmutableMap<OptionDocumentationCategory, String> optionCategoryDescriptions =
    464         OptionFilterDescriptions.getOptionCategoriesEnumDescription(productName);
    465 
    466     for (Entry<OptionDocumentationCategory, List<OptionDefinition>> e :
    467         optionsByCategory.entrySet()) {
    468       desc.append("<dl>");
    469       String categoryDescription = optionCategoryDescriptions.get(e.getKey());
    470       List<OptionDefinition> categorizedOptionsList = e.getValue();
    471 
    472       // Describe the category if we're going to end up using it at all.
    473       if (!categorizedOptionsList.isEmpty()) {
    474         desc.append(escaper.escape(categoryDescription)).append(":\n");
    475       }
    476       // Describe the options in this category.
    477       for (OptionDefinition optionDef : categorizedOptionsList) {
    478         OptionsUsage.getUsageHtml(optionDef, desc, escaper, impl.getOptionsData(), true);
    479       }
    480       desc.append("</dl>\n");
    481     }
    482     return desc.toString();
    483   }
    484 
    485   /**
    486    * Returns a string listing the possible flag completion for this command along with the command
    487    * completion if any. See {@link OptionsUsage#getCompletion(OptionDefinition, StringBuilder)} for
    488    * more details on the format for the flag completion.
    489    */
    490   public String getOptionsCompletion() {
    491     StringBuilder desc = new StringBuilder();
    492 
    493     visitOptions(
    494         optionDefinition ->
    495             optionDefinition.getDocumentationCategory() != OptionDocumentationCategory.UNDOCUMENTED,
    496         optionDefinition -> OptionsUsage.getCompletion(optionDefinition, desc));
    497 
    498     return desc.toString();
    499   }
    500 
    501   public void visitOptions(
    502       Predicate<OptionDefinition> predicate, Consumer<OptionDefinition> visitor) {
    503     Preconditions.checkNotNull(predicate, "Missing predicate.");
    504     Preconditions.checkNotNull(visitor, "Missing visitor.");
    505 
    506     OptionsData data = impl.getOptionsData();
    507     data.getOptionsClasses()
    508         // List all options
    509         .stream()
    510         .flatMap(optionsClass -> OptionsData.getAllOptionDefinitionsForClass(optionsClass).stream())
    511         // Sort field for deterministic ordering
    512         .sorted(OptionDefinition.BY_OPTION_NAME)
    513         .filter(predicate)
    514         .forEach(visitor);
    515   }
    516 
    517   /**
    518    * Returns a description of the option.
    519    *
    520    * @return The {@link OptionDescription} for the option, or null if there is no option by the
    521    *     given name.
    522    */
    523   OptionDescription getOptionDescription(String name) throws OptionsParsingException {
    524     return impl.getOptionDescription(name);
    525   }
    526 
    527   /**
    528    * Returns the parsed options that get expanded from this option, whether it expands due to an
    529    * implicit requirement or expansion.
    530    *
    531    * @param expansionOption the option that might need to be expanded. If this option does not
    532    *     expand to other options, the empty list will be returned.
    533    * @param originOfExpansionOption the origin of the option that's being expanded. This function
    534    *     will take care of adjusting the source messages as necessary.
    535    */
    536   ImmutableList<ParsedOptionDescription> getExpansionValueDescriptions(
    537       OptionDefinition expansionOption, OptionInstanceOrigin originOfExpansionOption)
    538       throws OptionsParsingException {
    539     return impl.getExpansionValueDescriptions(expansionOption, originOfExpansionOption);
    540   }
    541 
    542   /**
    543    * Returns a description of the option value set by the last previous call to {@link
    544    * #parse(OptionPriority.PriorityCategory, String, List)} that successfully set the given option.
    545    * If the option is of type {@link List}, the description will correspond to any one of the calls,
    546    * but not necessarily the last.
    547    *
    548    * @return The {@link com.google.devtools.common.options.OptionValueDescription} for the option,
    549    *     or null if the value has not been set.
    550    * @throws IllegalArgumentException if there is no option by the given name.
    551    */
    552   public OptionValueDescription getOptionValueDescription(String name) {
    553     return impl.getOptionValueDescription(name);
    554   }
    555 
    556   /**
    557    * A convenience method, equivalent to {@code parse(PriorityCategory.COMMAND_LINE, null,
    558    * Arrays.asList(args))}.
    559    */
    560   public void parse(String... args) throws OptionsParsingException {
    561     parse(OptionPriority.PriorityCategory.COMMAND_LINE, null, Arrays.asList(args));
    562   }
    563 
    564   /**
    565    * A convenience method, equivalent to {@code parse(PriorityCategory.COMMAND_LINE, null, args)}.
    566    */
    567   public void parse(List<String> args) throws OptionsParsingException {
    568     parse(OptionPriority.PriorityCategory.COMMAND_LINE, null, args);
    569   }
    570 
    571   /**
    572    * Parses {@code args}, using the classes registered with this parser, at the given priority.
    573    *
    574    * <p>May be called multiple times; later options override existing ones if they have equal or
    575    * higher priority. Strings that cannot be parsed as options are accumulated as residue, if this
    576    * parser allows it.
    577    *
    578    * <p>{@link #getOptions(Class)} and {@link #getResidue()} will return the results.
    579    *
    580    * @param priority the priority at which to parse these options. Within this priority category,
    581    *     each option will be given an index to track its position. If parse() has already been
    582    *     called at this priority, the indexing will continue where it left off, to keep ordering.
    583    * @param source the source to track for each option parsed.
    584    * @param args the arg list to parse. Each element might be an option, a value linked to an
    585    *     option, or residue.
    586    */
    587   public void parse(OptionPriority.PriorityCategory priority, String source, List<String> args)
    588       throws OptionsParsingException {
    589     parseWithSourceFunction(priority, o -> source, args);
    590   }
    591 
    592   /**
    593    * Parses {@code args}, using the classes registered with this parser, at the given priority.
    594    *
    595    * <p>May be called multiple times; later options override existing ones if they have equal or
    596    * higher priority. Strings that cannot be parsed as options are accumulated as residue, if this
    597    * parser allows it.
    598    *
    599    * <p>{@link #getOptions(Class)} and {@link #getResidue()} will return the results.
    600    *
    601    * @param priority the priority at which to parse these options. Within this priority category,
    602    *     each option will be given an index to track its position. If parse() has already been
    603    *     called at this priority, the indexing will continue where it left off, to keep ordering.
    604    * @param sourceFunction a function that maps option names to the source of the option.
    605    * @param args the arg list to parse. Each element might be an option, a value linked to an
    606    *     option, or residue.
    607    */
    608   public void parseWithSourceFunction(
    609       OptionPriority.PriorityCategory priority,
    610       Function<OptionDefinition, String> sourceFunction,
    611       List<String> args)
    612       throws OptionsParsingException {
    613     Preconditions.checkNotNull(priority);
    614     Preconditions.checkArgument(priority != OptionPriority.PriorityCategory.DEFAULT);
    615     residue.addAll(impl.parse(priority, sourceFunction, args));
    616     if (!allowResidue && !residue.isEmpty()) {
    617       String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
    618       throw new OptionsParsingException(errorMsg);
    619     }
    620   }
    621 
    622   public void parseOptionsFixedAtSpecificPriority(
    623       OptionPriority priority, String source, List<String> args) throws OptionsParsingException {
    624     Preconditions.checkNotNull(priority, "Priority not specified for arglist " + args);
    625     Preconditions.checkArgument(
    626         priority.getPriorityCategory() != OptionPriority.PriorityCategory.DEFAULT,
    627         "Priority cannot be default, which was specified for arglist " + args);
    628     residue.addAll(impl.parseOptionsFixedAtSpecificPriority(priority, o -> source, args));
    629     if (!allowResidue && !residue.isEmpty()) {
    630       String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
    631       throw new OptionsParsingException(errorMsg);
    632     }
    633   }
    634 
    635   /**
    636    * @param origin the origin of this option instance, it includes the priority of the value. If
    637    *     other values have already been or will be parsed at a higher priority, they might override
    638    *     the provided value. If this option already has a value at this priority, this value will
    639    *     have precedence, but this should be avoided, as it breaks order tracking.
    640    * @param option the option to add the value for.
    641    * @param value the value to add at the given priority.
    642    */
    643   void addOptionValueAtSpecificPriority(
    644       OptionInstanceOrigin origin, OptionDefinition option, String value)
    645       throws OptionsParsingException {
    646     impl.addOptionValueAtSpecificPriority(origin, option, value);
    647   }
    648 
    649   /**
    650    * Clears the given option.
    651    *
    652    * <p>This will not affect options objects that have already been retrieved from this parser
    653    * through {@link #getOptions(Class)}.
    654    *
    655    * @param option The option to clear.
    656    * @return The old value of the option that was cleared.
    657    * @throws IllegalArgumentException If the flag does not exist.
    658    */
    659   public OptionValueDescription clearValue(OptionDefinition option) throws OptionsParsingException {
    660     return impl.clearValue(option);
    661   }
    662 
    663   @Override
    664   public List<String> getResidue() {
    665     return ImmutableList.copyOf(residue);
    666   }
    667 
    668   /** Returns a list of warnings about problems encountered by previous parse calls. */
    669   public List<String> getWarnings() {
    670     return impl.getWarnings();
    671   }
    672 
    673   @Override
    674   public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
    675     return impl.getParsedOptions(optionsClass);
    676   }
    677 
    678   @Override
    679   public boolean containsExplicitOption(String name) {
    680     return impl.containsExplicitOption(name);
    681   }
    682 
    683   @Override
    684   public List<ParsedOptionDescription> asCompleteListOfParsedOptions() {
    685     return impl.asCompleteListOfParsedOptions();
    686   }
    687 
    688   @Override
    689   public List<ParsedOptionDescription> asListOfExplicitOptions() {
    690     return impl.asListOfExplicitOptions();
    691   }
    692 
    693   @Override
    694   public List<ParsedOptionDescription> asListOfCanonicalOptions() {
    695     return impl.asCanonicalizedListOfParsedOptions();
    696   }
    697 
    698   @Override
    699   public List<OptionValueDescription> asListOfOptionValues() {
    700     return impl.asListOfEffectiveOptions();
    701   }
    702 
    703   @Override
    704   public List<String> canonicalize() {
    705     return impl.asCanonicalizedList();
    706   }
    707 
    708   /** Returns all options fields of the given options class, in alphabetic order. */
    709   public static ImmutableList<OptionDefinition> getOptionDefinitions(
    710       Class<? extends OptionsBase> optionsClass) {
    711     return OptionsData.getAllOptionDefinitionsForClass(optionsClass);
    712   }
    713 
    714   /**
    715    * Returns whether the given options class uses only the core types listed in {@link
    716    * UsesOnlyCoreTypes#CORE_TYPES}. These are guaranteed to be deeply immutable and serializable.
    717    */
    718   public static boolean getUsesOnlyCoreTypes(Class<? extends OptionsBase> optionsClass) {
    719     OptionsData data = OptionsParser.getOptionsDataInternal(optionsClass);
    720     return data.getUsesOnlyCoreTypes(optionsClass);
    721   }
    722 
    723   /**
    724    * Returns a mapping from each option {@link Field} in {@code optionsClass} (including inherited
    725    * ones) to its value in {@code options}.
    726    *
    727    * <p>To save space, the map directly stores {@code Fields} instead of the {@code
    728    * OptionDefinitions}.
    729    *
    730    * <p>The map is a mutable copy; changing the map won't affect {@code options} and vice versa. The
    731    * map entries appear sorted alphabetically by option name.
    732    *
    733    * <p>If {@code options} is an instance of a subclass of {@link OptionsBase}, any options defined
    734    * by the subclass are not included in the map, only the options declared in the provided class
    735    * are included.
    736    *
    737    * @throws IllegalArgumentException if {@code options} is not an instance of {@link OptionsBase}
    738    */
    739   public static <O extends OptionsBase> Map<Field, Object> toMap(Class<O> optionsClass, O options) {
    740     // Alphabetized due to getAllOptionDefinitionsForClass()'s order.
    741     Map<Field, Object> map = new LinkedHashMap<>();
    742     for (OptionDefinition optionDefinition :
    743         OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
    744       try {
    745         // Get the object value of the optionDefinition and place in map.
    746         map.put(optionDefinition.getField(), optionDefinition.getField().get(options));
    747       } catch (IllegalAccessException e) {
    748         // All options fields of options classes should be public.
    749         throw new IllegalStateException(e);
    750       } catch (IllegalArgumentException e) {
    751         // This would indicate an inconsistency in the cached OptionsData.
    752         throw new IllegalStateException(e);
    753       }
    754     }
    755     return map;
    756   }
    757 
    758   /**
    759    * Given a mapping as returned by {@link #toMap}, and the options class it that its entries
    760    * correspond to, this constructs the corresponding instance of the options class.
    761    *
    762    * @param map Field to Object, expecting an entry for each field in the optionsClass. This
    763    *     directly refers to the Field, without wrapping it in an OptionDefinition, see {@link
    764    *     #toMap}.
    765    * @throws IllegalArgumentException if {@code map} does not contain exactly the fields of {@code
    766    *     optionsClass}, with values of the appropriate type
    767    */
    768   public static <O extends OptionsBase> O fromMap(Class<O> optionsClass, Map<Field, Object> map) {
    769     // Instantiate the options class.
    770     OptionsData data = getOptionsDataInternal(optionsClass);
    771     O optionsInstance;
    772     try {
    773       Constructor<O> constructor = data.getConstructor(optionsClass);
    774       Preconditions.checkNotNull(constructor, "No options class constructor available");
    775       optionsInstance = constructor.newInstance();
    776     } catch (ReflectiveOperationException e) {
    777       throw new IllegalStateException("Error while instantiating options class", e);
    778     }
    779 
    780     List<OptionDefinition> optionDefinitions =
    781         OptionsData.getAllOptionDefinitionsForClass(optionsClass);
    782     // Ensure all fields are covered, no extraneous fields.
    783     validateFieldsSets(optionsClass, new LinkedHashSet<Field>(map.keySet()));
    784     // Populate the instance.
    785     for (OptionDefinition optionDefinition : optionDefinitions) {
    786       // Non-null as per above check.
    787       Object value = map.get(optionDefinition.getField());
    788       try {
    789         optionDefinition.getField().set(optionsInstance, value);
    790       } catch (IllegalAccessException e) {
    791         throw new IllegalStateException(e);
    792       }
    793       // May also throw IllegalArgumentException if map value is ill typed.
    794     }
    795     return optionsInstance;
    796   }
    797 
    798   /**
    799    * Raises a pretty {@link IllegalArgumentException} if the provided set of fields is a complete
    800    * set for the optionsClass.
    801    *
    802    * <p>The entries in {@code fieldsFromMap} may be ill formed by being null or lacking an {@link
    803    * Option} annotation.
    804    */
    805   private static void validateFieldsSets(
    806       Class<? extends OptionsBase> optionsClass, LinkedHashSet<Field> fieldsFromMap) {
    807     ImmutableList<OptionDefinition> optionDefsFromClasses =
    808         OptionsData.getAllOptionDefinitionsForClass(optionsClass);
    809     Set<Field> fieldsFromClass =
    810         optionDefsFromClasses.stream().map(OptionDefinition::getField).collect(Collectors.toSet());
    811 
    812     if (fieldsFromClass.equals(fieldsFromMap)) {
    813       // They are already equal, avoid additional checks.
    814       return;
    815     }
    816 
    817     List<String> extraNamesFromClass = new ArrayList<>();
    818     List<String> extraNamesFromMap = new ArrayList<>();
    819     for (OptionDefinition optionDefinition : optionDefsFromClasses) {
    820       if (!fieldsFromMap.contains(optionDefinition.getField())) {
    821         extraNamesFromClass.add("'" + optionDefinition.getOptionName() + "'");
    822       }
    823     }
    824     for (Field field : fieldsFromMap) {
    825       // Extra validation on the map keys since they don't come from OptionsData.
    826       if (!fieldsFromClass.contains(field)) {
    827         if (field == null) {
    828           extraNamesFromMap.add("<null field>");
    829         } else {
    830           OptionDefinition optionDefinition = null;
    831           try {
    832             // TODO(ccalvarin) This shouldn't be necessary, no option definitions should be found in
    833             // this optionsClass that weren't in the cache.
    834             optionDefinition = OptionDefinition.extractOptionDefinition(field);
    835             extraNamesFromMap.add("'" + optionDefinition.getOptionName() + "'");
    836           } catch (NotAnOptionException e) {
    837             extraNamesFromMap.add("<non-Option field>");
    838           }
    839         }
    840       }
    841     }
    842     throw new IllegalArgumentException(
    843         "Map keys do not match fields of options class; extra map keys: {"
    844             + Joiner.on(", ").join(extraNamesFromMap)
    845             + "}; extra options class options: {"
    846             + Joiner.on(", ").join(extraNamesFromClass)
    847             + "}");
    848   }
    849 }
    850