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.collect.Lists;
     24 import com.google.common.collect.Maps;
     25 import com.google.common.escape.Escaper;
     26 import java.lang.reflect.Field;
     27 import java.nio.file.FileSystem;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Collection;
     31 import java.util.Collections;
     32 import java.util.Comparator;
     33 import java.util.List;
     34 import java.util.Map;
     35 
     36 /**
     37  * A parser for options. Typical use case in a main method:
     38  *
     39  * <pre>
     40  * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class);
     41  * parser.parseAndExitUponError(args);
     42  * FooOptions foo = parser.getOptions(FooOptions.class);
     43  * BarOptions bar = parser.getOptions(BarOptions.class);
     44  * List&lt;String&gt; otherArguments = parser.getResidue();
     45  * </pre>
     46  *
     47  * <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase,
     48  * that contain fields annotated with @Option(...).
     49  *
     50  * <p>Alternatively, rather than calling {@link #parseAndExitUponError(OptionPriority, String,
     51  * String[])}, client code may call {@link #parse(OptionPriority,String,List)}, and handle parser
     52  * exceptions usage messages themselves.
     53  *
     54  * <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz'
     55  * and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz'
     56  * in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch
     57  * options between void/boolean/tristate and everything else without breaking backwards
     58  * compatibility.
     59  *
     60  * @see Options a simpler class which you can use if you only have one options specification class
     61  */
     62 public class OptionsParser implements OptionsProvider {
     63 
     64   /**
     65    * A cache for the parsed options data. Both keys and values are immutable, so
     66    * this is always safe. Only access this field through the {@link
     67    * #getOptionsData} method for thread-safety! The cache is very unlikely to
     68    * grow to a significant amount of memory, because there's only a fixed set of
     69    * options classes on the classpath.
     70    */
     71   private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData =
     72       Maps.newHashMap();
     73 
     74   /**
     75    * Returns {@link OpaqueOptionsData} suitable for passing along to
     76    * {@link #newOptionsParser(OpaqueOptionsData optionsData)}.
     77    *
     78    * This is useful when you want to do the work of analyzing the given {@code optionsClasses}
     79    * exactly once, but you want to parse lots of different lists of strings (and thus need to
     80    * construct lots of different {@link OptionsParser} instances).
     81    */
     82   public static OpaqueOptionsData getOptionsData(
     83       ImmutableList<Class<? extends OptionsBase>> optionsClasses) {
     84     return getOptionsDataInternal(optionsClasses);
     85   }
     86 
     87   private static synchronized OptionsData getOptionsDataInternal(
     88       ImmutableList<Class<? extends OptionsBase>> optionsClasses) {
     89     OptionsData result = optionsData.get(optionsClasses);
     90     if (result == null) {
     91       result = OptionsData.from(optionsClasses);
     92       optionsData.put(optionsClasses, result);
     93     }
     94     return result;
     95   }
     96 
     97   /**
     98    * Returns all the annotated fields for the given class, including inherited
     99    * ones.
    100    */
    101   static Collection<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) {
    102     OptionsData data = getOptionsDataInternal(
    103         ImmutableList.<Class<? extends OptionsBase>>of(optionsClass));
    104     return data.getFieldsForClass(optionsClass);
    105   }
    106 
    107   /**
    108    * @see #newOptionsParser(Iterable)
    109    */
    110   public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1) {
    111     return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1));
    112   }
    113 
    114   /**
    115    * @see #newOptionsParser(Iterable)
    116    */
    117   public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1,
    118                                                Class<? extends OptionsBase> class2) {
    119     return newOptionsParser(ImmutableList.of(class1, class2));
    120   }
    121 
    122   /**
    123    * Create a new {@link OptionsParser}.
    124    */
    125   public static OptionsParser newOptionsParser(
    126       Iterable<? extends Class<? extends OptionsBase>> optionsClasses) {
    127     return newOptionsParser(
    128         getOptionsDataInternal(ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsClasses)));
    129   }
    130 
    131   /**
    132    * Create a new {@link OptionsParser}, using {@link OpaqueOptionsData} previously returned from
    133    * {@link #getOptionsData}.
    134    */
    135   public static OptionsParser newOptionsParser(OpaqueOptionsData optionsData) {
    136     return new OptionsParser((OptionsData) optionsData);
    137   }
    138 
    139   private final OptionsParserImpl impl;
    140   private final List<String> residue = new ArrayList<String>();
    141   private boolean allowResidue = true;
    142 
    143   OptionsParser(OptionsData optionsData) {
    144     impl = new OptionsParserImpl(optionsData);
    145   }
    146 
    147   /**
    148    * Indicates whether or not the parser will allow a non-empty residue; that
    149    * is, iff this value is true then a call to one of the {@code parse}
    150    * methods will throw {@link OptionsParsingException} unless
    151    * {@link #getResidue()} is empty after parsing.
    152    */
    153   public void setAllowResidue(boolean allowResidue) {
    154     this.allowResidue = allowResidue;
    155   }
    156 
    157   /**
    158    * Indicates whether or not the parser will allow long options with a
    159    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
    160    */
    161   public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
    162     this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions);
    163   }
    164 
    165   /** Enables the Parser to handle params files loacted insinde the provided {@link FileSystem}. */
    166   public void enableParamsFileSupport(FileSystem fs) {
    167     this.impl.setArgsPreProcessor(new ParamsFilePreProcessor(fs));
    168   }
    169 
    170   public void parseAndExitUponError(String[] args) {
    171     parseAndExitUponError(OptionPriority.COMMAND_LINE, "unknown", args);
    172   }
    173 
    174   /**
    175    * A convenience function for use in main methods. Parses the command line
    176    * parameters, and exits upon error. Also, prints out the usage message
    177    * if "--help" appears anywhere within {@code args}.
    178    */
    179   public void parseAndExitUponError(OptionPriority priority, String source, String[] args) {
    180     for (String arg : args) {
    181       if (arg.equals("--help")) {
    182         System.out.println(describeOptions(Collections.<String, String>emptyMap(),
    183                                            HelpVerbosity.LONG));
    184         System.exit(0);
    185       }
    186     }
    187     try {
    188       parse(priority, source, Arrays.asList(args));
    189     } catch (OptionsParsingException e) {
    190       System.err.println("Error parsing command line: " + e.getMessage());
    191       System.err.println("Try --help.");
    192       System.exit(2);
    193     }
    194   }
    195 
    196   /**
    197    * The metadata about an option.
    198    */
    199   public static final class OptionDescription {
    200 
    201     private final String name;
    202     private final Object defaultValue;
    203     private final Converter<?> converter;
    204     private final boolean allowMultiple;
    205 
    206     public OptionDescription(String name, Object defaultValue, Converter<?> converter,
    207         boolean allowMultiple) {
    208       this.name = name;
    209       this.defaultValue = defaultValue;
    210       this.converter = converter;
    211       this.allowMultiple = allowMultiple;
    212     }
    213 
    214     public String getName() {
    215       return name;
    216     }
    217 
    218     public Object getDefaultValue() {
    219       return defaultValue;
    220     }
    221 
    222     public Converter<?> getConverter() {
    223       return converter;
    224     }
    225 
    226     public boolean getAllowMultiple() {
    227       return allowMultiple;
    228     }
    229   }
    230 
    231   /**
    232    * The name and value of an option with additional metadata describing its
    233    * priority, source, whether it was set via an implicit dependency, and if so,
    234    * by which other option.
    235    */
    236   public static class OptionValueDescription {
    237     private final String name;
    238     private final Object value;
    239     private final OptionPriority priority;
    240     private final String source;
    241     private final String implicitDependant;
    242     private final String expandedFrom;
    243     private final boolean allowMultiple;
    244 
    245     public OptionValueDescription(
    246         String name,
    247         Object value,
    248         OptionPriority priority,
    249         String source,
    250         String implicitDependant,
    251         String expandedFrom,
    252         boolean allowMultiple) {
    253       this.name = name;
    254       this.value = value;
    255       this.priority = priority;
    256       this.source = source;
    257       this.implicitDependant = implicitDependant;
    258       this.expandedFrom = expandedFrom;
    259       this.allowMultiple = allowMultiple;
    260     }
    261 
    262     public String getName() {
    263       return name;
    264     }
    265 
    266     // Need to suppress unchecked warnings, because the "multiple occurrence"
    267     // options use unchecked ListMultimaps due to limitations of Java generics.
    268     @SuppressWarnings({"unchecked", "rawtypes"})
    269     public Object getValue() {
    270       if (allowMultiple) {
    271         // Sort the results by option priority and return them in a new list.
    272         // The generic type of the list is not known at runtime, so we can't
    273         // use it here. It was already checked in the constructor, so this is
    274         // type-safe.
    275         List result = Lists.newArrayList();
    276         ListMultimap realValue = (ListMultimap) value;
    277         for (OptionPriority priority : OptionPriority.values()) {
    278           // If there is no mapping for this key, this check avoids object creation (because
    279           // ListMultimap has to return a new object on get) and also an unnecessary addAll call.
    280           if (realValue.containsKey(priority)) {
    281             result.addAll(realValue.get(priority));
    282           }
    283         }
    284         return result;
    285       }
    286       return value;
    287     }
    288     /**
    289      * @return the priority of the thing that set this value for this flag
    290      */
    291     public OptionPriority getPriority() {
    292       return priority;
    293     }
    294 
    295     /**
    296      * @return the thing that set this value for this flag
    297      */
    298     public String getSource() {
    299       return source;
    300     }
    301 
    302     public String getImplicitDependant() {
    303       return implicitDependant;
    304     }
    305 
    306     public boolean isImplicitDependency() {
    307       return implicitDependant != null;
    308     }
    309 
    310     public String getExpansionParent() {
    311       return expandedFrom;
    312     }
    313 
    314     public boolean isExpansion() {
    315       return expandedFrom != null;
    316     }
    317 
    318     @Override
    319     public String toString() {
    320       StringBuilder result = new StringBuilder();
    321       result.append("option '").append(name).append("' ");
    322       result.append("set to '").append(value).append("' ");
    323       result.append("with priority ").append(priority);
    324       if (source != null) {
    325         result.append(" and source '").append(source).append("'");
    326       }
    327       if (implicitDependant != null) {
    328         result.append(" implicitly by ");
    329       }
    330       return result.toString();
    331     }
    332 
    333     // Need to suppress unchecked warnings, because the "multiple occurrence"
    334     // options use unchecked ListMultimaps due to limitations of Java generics.
    335     @SuppressWarnings({"unchecked", "rawtypes"})
    336     void addValue(OptionPriority addedPriority, Object addedValue) {
    337       Preconditions.checkState(allowMultiple);
    338       ListMultimap optionValueList = (ListMultimap) value;
    339       if (addedValue instanceof List<?>) {
    340         optionValueList.putAll(addedPriority, (List<?>) addedValue);
    341       } else {
    342         optionValueList.put(addedPriority, addedValue);
    343       }
    344     }
    345   }
    346 
    347   /**
    348    * The name and unparsed value of an option with additional metadata describing its
    349    * priority, source, whether it was set via an implicit dependency, and if so,
    350    * by which other option.
    351    *
    352    * <p>Note that the unparsed value and the source parameters can both be null.
    353    */
    354   public static class UnparsedOptionValueDescription {
    355     private final String name;
    356     private final Field field;
    357     private final String unparsedValue;
    358     private final OptionPriority priority;
    359     private final String source;
    360     private final boolean explicit;
    361 
    362     public UnparsedOptionValueDescription(String name, Field field, String unparsedValue,
    363         OptionPriority priority, String source, boolean explicit) {
    364       this.name = name;
    365       this.field = field;
    366       this.unparsedValue = unparsedValue;
    367       this.priority = priority;
    368       this.source = source;
    369       this.explicit = explicit;
    370     }
    371 
    372     public String getName() {
    373       return name;
    374     }
    375 
    376     Field getField() {
    377       return field;
    378     }
    379 
    380     public boolean isBooleanOption() {
    381       return field.getType().equals(boolean.class);
    382     }
    383 
    384     private DocumentationLevel documentationLevel() {
    385       Option option = field.getAnnotation(Option.class);
    386       return OptionsParser.documentationLevel(option.category());
    387     }
    388 
    389     public boolean isDocumented() {
    390       return documentationLevel() == DocumentationLevel.DOCUMENTED;
    391     }
    392 
    393     public boolean isHidden() {
    394       return documentationLevel() == DocumentationLevel.HIDDEN
    395           || documentationLevel() == DocumentationLevel.INTERNAL;
    396     }
    397 
    398     boolean isExpansion() {
    399       Option option = field.getAnnotation(Option.class);
    400       return (option.expansion().length > 0
    401           || OptionsData.usesExpansionFunction(option));
    402     }
    403 
    404     boolean isImplicitRequirement() {
    405       Option option = field.getAnnotation(Option.class);
    406       return option.implicitRequirements().length > 0;
    407     }
    408 
    409     boolean allowMultiple() {
    410       Option option = field.getAnnotation(Option.class);
    411       return option.allowMultiple();
    412     }
    413 
    414     public String getUnparsedValue() {
    415       return unparsedValue;
    416     }
    417 
    418     OptionPriority getPriority() {
    419       return priority;
    420     }
    421 
    422     public String getSource() {
    423       return source;
    424     }
    425 
    426     public boolean isExplicit() {
    427       return explicit;
    428     }
    429 
    430     @Override
    431     public String toString() {
    432       StringBuilder result = new StringBuilder();
    433       result.append("option '").append(name).append("' ");
    434       result.append("set to '").append(unparsedValue).append("' ");
    435       result.append("with priority ").append(priority);
    436       if (source != null) {
    437         result.append(" and source '").append(source).append("'");
    438       }
    439       return result.toString();
    440     }
    441   }
    442 
    443   /**
    444    * The verbosity with which option help messages are displayed: short (just
    445    * the name), medium (name, type, default, abbreviation), and long (full
    446    * description).
    447    */
    448   public enum HelpVerbosity { LONG, MEDIUM, SHORT }
    449 
    450   /**
    451    * The level of documentation. Only documented options are output as part of
    452    * the help.
    453    *
    454    * <p>We use 'hidden' so that options that form the protocol between the
    455    * client and the server are not logged.
    456    *
    457    * <p>Options which are 'internal' are not recognized by the parser at all.
    458    */
    459   enum DocumentationLevel {
    460     DOCUMENTED, UNDOCUMENTED, HIDDEN, INTERNAL
    461   }
    462 
    463   /**
    464    * Returns a description of all the options this parser can digest. In addition to {@link Option}
    465    * annotations, this method also interprets {@link OptionsUsage} annotations which give an
    466    * intuitive short description for the options. Options of the same category (see {@link
    467    * Option#category}) will be grouped together.
    468    *
    469    * @param categoryDescriptions a mapping from category names to category descriptions.
    470    *     Descriptions are optional; if omitted, a string based on the category name will be used.
    471    * @param helpVerbosity if {@code long}, the options will be described verbosely, including their
    472    *     types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if
    473    *     {@code short}, the options are just enumerated.
    474    */
    475   public String describeOptions(
    476       Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) {
    477     StringBuilder desc = new StringBuilder();
    478     if (!impl.getOptionsClasses().isEmpty()) {
    479       List<Field> allFields = Lists.newArrayList();
    480       for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
    481         allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
    482       }
    483       Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
    484       String prevCategory = null;
    485 
    486       for (Field optionField : allFields) {
    487         String category = optionField.getAnnotation(Option.class).category();
    488         if (!category.equals(prevCategory)) {
    489           prevCategory = category;
    490           String description = categoryDescriptions.get(category);
    491           if (description == null) {
    492             description = "Options category '" + category + "'";
    493           }
    494           if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) {
    495             desc.append("\n").append(description).append(":\n");
    496           }
    497         }
    498 
    499         if (documentationLevel(prevCategory) == DocumentationLevel.DOCUMENTED) {
    500           OptionsUsage.getUsage(optionField, desc, helpVerbosity, impl.getOptionsData());
    501         }
    502       }
    503     }
    504     return desc.toString().trim();
    505   }
    506 
    507   /**
    508    * Returns a description of all the options this parser can digest.
    509    * In addition to {@link Option} annotations, this method also
    510    * interprets {@link OptionsUsage} annotations which give an intuitive short
    511    * description for the options.
    512    *
    513    * @param categoryDescriptions a mapping from category names to category
    514    *   descriptions.  Options of the same category (see {@link
    515    *   Option#category}) will be grouped together, preceded by the description
    516    *   of the category.
    517    */
    518   public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) {
    519     StringBuilder desc = new StringBuilder();
    520     if (!impl.getOptionsClasses().isEmpty()) {
    521       List<Field> allFields = Lists.newArrayList();
    522       for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
    523         allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
    524       }
    525       Collections.sort(allFields, OptionsUsage.BY_CATEGORY);
    526       String prevCategory = null;
    527 
    528       for (Field optionField : allFields) {
    529         String category = optionField.getAnnotation(Option.class).category();
    530         DocumentationLevel level = documentationLevel(category);
    531         if (!category.equals(prevCategory) && level == DocumentationLevel.DOCUMENTED) {
    532           String description = categoryDescriptions.get(category);
    533           if (description == null) {
    534             description = "Options category '" + category + "'";
    535           }
    536           if (prevCategory != null) {
    537             desc.append("</dl>\n\n");
    538           }
    539           desc.append(escaper.escape(description)).append(":\n");
    540           desc.append("<dl>");
    541           prevCategory = category;
    542         }
    543 
    544         if (level == DocumentationLevel.DOCUMENTED) {
    545           OptionsUsage.getUsageHtml(optionField, desc, escaper, impl.getOptionsData());
    546         }
    547       }
    548       desc.append("</dl>\n");
    549     }
    550     return desc.toString();
    551   }
    552 
    553   /**
    554    * Returns a string listing the possible flag completion for this command along with the command
    555    * completion if any. See {@link OptionsUsage#getCompletion(Field, StringBuilder)} for more
    556    * details on the format for the flag completion.
    557    */
    558   public String getOptionsCompletion() {
    559     StringBuilder desc = new StringBuilder();
    560 
    561     // List all options
    562     List<Field> allFields = Lists.newArrayList();
    563     for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) {
    564       allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass));
    565     }
    566     // Sort field for deterministic ordering
    567     Collections.sort(allFields, new Comparator<Field>() {
    568       @Override
    569       public int compare(Field f1, Field f2) {
    570         String name1 = f1.getAnnotation(Option.class).name();
    571         String name2 = f2.getAnnotation(Option.class).name();
    572         return name1.compareTo(name2);
    573       }
    574     });
    575     for (Field optionField : allFields) {
    576       String category = optionField.getAnnotation(Option.class).category();
    577       if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) {
    578         OptionsUsage.getCompletion(optionField, desc);
    579       }
    580     }
    581 
    582     return desc.toString();
    583   }
    584 
    585   /**
    586    * Returns a description of the option.
    587    *
    588    * @return The {@link OptionValueDescription} for the option, or null if there is no option by
    589    *        the given name.
    590    */
    591   public OptionDescription getOptionDescription(String name) {
    592     return impl.getOptionDescription(name);
    593   }
    594 
    595   /**
    596    * Returns a description of the option value set by the last previous call to
    597    * {@link #parse(OptionPriority, String, List)} that successfully set the given
    598    * option. If the option is of type {@link List}, the description will
    599    * correspond to any one of the calls, but not necessarily the last.
    600    *
    601    * @return The {@link OptionValueDescription} for the option, or null if the value has not been
    602    *        set.
    603    * @throws IllegalArgumentException if there is no option by the given name.
    604    */
    605   public OptionValueDescription getOptionValueDescription(String name) {
    606     return impl.getOptionValueDescription(name);
    607   }
    608 
    609   static DocumentationLevel documentationLevel(String category) {
    610     if ("undocumented".equals(category)) {
    611       return DocumentationLevel.UNDOCUMENTED;
    612     } else if ("hidden".equals(category)) {
    613       return DocumentationLevel.HIDDEN;
    614     } else if ("internal".equals(category)) {
    615       return DocumentationLevel.INTERNAL;
    616     } else {
    617       return DocumentationLevel.DOCUMENTED;
    618     }
    619   }
    620 
    621   /**
    622    * A convenience method, equivalent to
    623    * {@code parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args))}.
    624    */
    625   public void parse(String... args) throws OptionsParsingException {
    626     parse(OptionPriority.COMMAND_LINE, (String) null, Arrays.asList(args));
    627   }
    628 
    629   /**
    630    * A convenience method, equivalent to
    631    * {@code parse(OptionPriority.COMMAND_LINE, null, args)}.
    632    */
    633   public void parse(List<String> args) throws OptionsParsingException {
    634     parse(OptionPriority.COMMAND_LINE, (String) null, args);
    635   }
    636 
    637   /**
    638    * Parses {@code args}, using the classes registered with this parser.
    639    * {@link #getOptions(Class)} and {@link #getResidue()} return the results.
    640    * May be called multiple times; later options override existing ones if they
    641    * have equal or higher priority. The source of options is a free-form string
    642    * that can be used for debugging. Strings that cannot be parsed as options
    643    * accumulates as residue, if this parser allows it.
    644    *
    645    * @see OptionPriority
    646    */
    647   public void parse(OptionPriority priority, String source,
    648       List<String> args) throws OptionsParsingException {
    649     parseWithSourceFunction(priority, Functions.constant(source), args);
    650   }
    651 
    652   /**
    653    * Parses {@code args}, using the classes registered with this parser.
    654    * {@link #getOptions(Class)} and {@link #getResidue()} return the results. May be called
    655    * multiple times; later options override existing ones if they have equal or higher priority.
    656    * The source of options is given as a function that maps option names to the source of the
    657    * option. Strings that cannot be parsed as options accumulates as* residue, if this parser
    658    * allows it.
    659    */
    660   public void parseWithSourceFunction(OptionPriority priority,
    661       Function<? super String, String> sourceFunction, List<String> args)
    662       throws OptionsParsingException {
    663     Preconditions.checkNotNull(priority);
    664     Preconditions.checkArgument(priority != OptionPriority.DEFAULT);
    665     residue.addAll(impl.parse(priority, sourceFunction, args));
    666     if (!allowResidue && !residue.isEmpty()) {
    667       String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue);
    668       throw new OptionsParsingException(errorMsg);
    669     }
    670   }
    671 
    672   /**
    673    * Clears the given option. Also clears expansion arguments and implicit requirements for that
    674    * option.
    675    *
    676    * <p>This will not affect options objects that have already been retrieved from this parser
    677    * through {@link #getOptions(Class)}.
    678    *
    679    * @param optionName The full name of the option to clear.
    680    * @return A map of an option name to the old value of the options that were cleared.
    681    * @throws IllegalArgumentException If the flag does not exist.
    682    */
    683   public Map<String, OptionValueDescription> clearValue(String optionName)
    684       throws OptionsParsingException {
    685     Map<String, OptionValueDescription> clearedValues = Maps.newHashMap();
    686     impl.clearValue(optionName, clearedValues);
    687     return clearedValues;
    688   }
    689 
    690   @Override
    691   public List<String> getResidue() {
    692     return ImmutableList.copyOf(residue);
    693   }
    694 
    695   /**
    696    * Returns a list of warnings about problems encountered by previous parse calls.
    697    */
    698   public List<String> getWarnings() {
    699     return impl.getWarnings();
    700   }
    701 
    702   @Override
    703   public <O extends OptionsBase> O getOptions(Class<O> optionsClass) {
    704     return impl.getParsedOptions(optionsClass);
    705   }
    706 
    707   @Override
    708   public boolean containsExplicitOption(String name) {
    709     return impl.containsExplicitOption(name);
    710   }
    711 
    712   @Override
    713   public List<UnparsedOptionValueDescription> asListOfUnparsedOptions() {
    714     return impl.asListOfUnparsedOptions();
    715   }
    716 
    717   @Override
    718   public List<UnparsedOptionValueDescription> asListOfExplicitOptions() {
    719     return impl.asListOfExplicitOptions();
    720   }
    721 
    722   @Override
    723   public List<OptionValueDescription> asListOfEffectiveOptions() {
    724     return impl.asListOfEffectiveOptions();
    725   }
    726 
    727   @Override
    728   public List<String> canonicalize() {
    729     return impl.asCanonicalizedList();
    730   }
    731 }
    732