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