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.base.Predicate;
     22 import com.google.common.collect.ArrayListMultimap;
     23 import com.google.common.collect.ImmutableList;
     24 import com.google.common.collect.Iterables;
     25 import com.google.common.collect.Iterators;
     26 import com.google.common.collect.LinkedHashMultimap;
     27 import com.google.common.collect.Lists;
     28 import com.google.common.collect.Maps;
     29 import com.google.common.collect.Multimap;
     30 import com.google.devtools.common.options.OptionsParser.OptionDescription;
     31 import com.google.devtools.common.options.OptionsParser.OptionValueDescription;
     32 import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription;
     33 import java.lang.reflect.Constructor;
     34 import java.lang.reflect.Field;
     35 import java.util.Arrays;
     36 import java.util.Collection;
     37 import java.util.Collections;
     38 import java.util.Comparator;
     39 import java.util.Iterator;
     40 import java.util.LinkedHashMap;
     41 import java.util.List;
     42 import java.util.Map;
     43 
     44 /**
     45  * The implementation of the options parser. This is intentionally package
     46  * private for full flexibility. Use {@link OptionsParser} or {@link Options}
     47  * if you're a consumer.
     48  */
     49 class OptionsParserImpl {
     50 
     51   private final OptionsData optionsData;
     52 
     53   /**
     54    * We store the results of parsing the arguments in here. It'll look like
     55    *
     56    * <pre>
     57    *   Field("--host") -> "www.google.com"
     58    *   Field("--port") -> 80
     59    * </pre>
     60    *
     61    * This map is modified by repeated calls to {@link #parse(OptionPriority,Function,List)}.
     62    */
     63   private final Map<Field, OptionValueDescription> parsedValues = Maps.newHashMap();
     64 
     65   /**
     66    * We store the pre-parsed, explicit options for each priority in here.
     67    * We use partially preparsed options, which can be different from the original
     68    * representation, e.g. "--nofoo" becomes "--foo=0".
     69    */
     70   private final List<UnparsedOptionValueDescription> unparsedValues = Lists.newArrayList();
     71 
     72   /**
     73    * Unparsed values for use with the canonicalize command are stored separately from
     74    * unparsedValues so that invocation policy can modify the values for canonicalization (e.g.
     75    * override user-specified values with default values) without corrupting the data used to
     76    * represent the user's original invocation for {@link #asListOfExplicitOptions()} and
     77    * {@link #asListOfUnparsedOptions()}. A LinkedHashMultimap is used so that canonicalization
     78    * happens in the correct order and multiple values can be stored for flags that allow multiple
     79    * values.
     80    */
     81   private final Multimap<Field, UnparsedOptionValueDescription> canonicalizeValues
     82       = LinkedHashMultimap.create();
     83 
     84   private final List<String> warnings = Lists.newArrayList();
     85 
     86   private boolean allowSingleDashLongOptions = false;
     87 
     88   private ArgsPreProcessor argsPreProcessor =
     89       new ArgsPreProcessor() {
     90         @Override
     91         public List<String> preProcess(List<String> args) throws OptionsParsingException {
     92           return args;
     93         }
     94       };
     95 
     96   /**
     97    * Create a new parser object
     98    */
     99   OptionsParserImpl(OptionsData optionsData) {
    100     this.optionsData = optionsData;
    101   }
    102 
    103   OptionsData getOptionsData() {
    104     return optionsData;
    105   }
    106 
    107   /**
    108    * Indicates whether or not the parser will allow long options with a
    109    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
    110    */
    111   void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
    112     this.allowSingleDashLongOptions = allowSingleDashLongOptions;
    113   }
    114 
    115   /** Sets the ArgsPreProcessor for manipulations of the options before parsing. */
    116   void setArgsPreProcessor(ArgsPreProcessor preProcessor) {
    117     this.argsPreProcessor = Preconditions.checkNotNull(preProcessor);
    118   }
    119 
    120   /**
    121    * The implementation of {@link OptionsBase#asMap}.
    122    */
    123   static Map<String, Object> optionsAsMap(OptionsBase optionsInstance) {
    124     Map<String, Object> map = Maps.newHashMap();
    125     for (Field field : OptionsParser.getAllAnnotatedFields(optionsInstance.getClass())) {
    126       try {
    127         String name = field.getAnnotation(Option.class).name();
    128         Object value = field.get(optionsInstance);
    129         map.put(name, value);
    130       } catch (IllegalAccessException e) {
    131         throw new IllegalStateException(e); // unreachable
    132       }
    133     }
    134     return map;
    135   }
    136 
    137   List<Field> getAnnotatedFieldsFor(Class<? extends OptionsBase> clazz) {
    138     return optionsData.getFieldsForClass(clazz);
    139   }
    140 
    141   /**
    142    * Implements {@link OptionsParser#asListOfUnparsedOptions()}.
    143    */
    144   List<UnparsedOptionValueDescription> asListOfUnparsedOptions() {
    145     List<UnparsedOptionValueDescription> result = Lists.newArrayList(unparsedValues);
    146     // It is vital that this sort is stable so that options on the same priority are not reordered.
    147     Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() {
    148       @Override
    149       public int compare(UnparsedOptionValueDescription o1,
    150           UnparsedOptionValueDescription o2) {
    151         return o1.getPriority().compareTo(o2.getPriority());
    152       }
    153     });
    154     return result;
    155   }
    156 
    157   /**
    158    * Implements {@link OptionsParser#asListOfExplicitOptions()}.
    159    */
    160   List<UnparsedOptionValueDescription> asListOfExplicitOptions() {
    161     List<UnparsedOptionValueDescription> result = Lists.newArrayList(Iterables.filter(
    162       unparsedValues,
    163       new Predicate<UnparsedOptionValueDescription>() {
    164         @Override
    165         public boolean apply(UnparsedOptionValueDescription input) {
    166           return input.isExplicit();
    167         }
    168     }));
    169     // It is vital that this sort is stable so that options on the same priority are not reordered.
    170     Collections.sort(result, new Comparator<UnparsedOptionValueDescription>() {
    171       @Override
    172       public int compare(UnparsedOptionValueDescription o1,
    173           UnparsedOptionValueDescription o2) {
    174         return o1.getPriority().compareTo(o2.getPriority());
    175       }
    176     });
    177     return result;
    178   }
    179 
    180   /**
    181    * Implements {@link OptionsParser#canonicalize}.
    182    */
    183   List<String> asCanonicalizedList() {
    184 
    185     List<UnparsedOptionValueDescription> processed = Lists.newArrayList(
    186         canonicalizeValues.values());
    187     // Sort implicit requirement options to the end, keeping their existing order, and sort the
    188     // other options alphabetically.
    189     Collections.sort(processed, new Comparator<UnparsedOptionValueDescription>() {
    190       @Override
    191       public int compare(UnparsedOptionValueDescription o1, UnparsedOptionValueDescription o2) {
    192         if (o1.isImplicitRequirement()) {
    193           return o2.isImplicitRequirement() ? 0 : 1;
    194         }
    195         if (o2.isImplicitRequirement()) {
    196           return -1;
    197         }
    198         return o1.getName().compareTo(o2.getName());
    199       }
    200     });
    201 
    202     List<String> result = Lists.newArrayList();
    203     for (UnparsedOptionValueDescription value : processed) {
    204 
    205       // Ignore expansion options.
    206       if (value.isExpansion()) {
    207         continue;
    208       }
    209 
    210       result.add("--" + value.getName() + "=" + value.getUnparsedValue());
    211     }
    212     return result;
    213   }
    214 
    215   /**
    216    * Implements {@link OptionsParser#asListOfEffectiveOptions()}.
    217    */
    218   List<OptionValueDescription> asListOfEffectiveOptions() {
    219     List<OptionValueDescription> result = Lists.newArrayList();
    220     for (Map.Entry<String, Field> mapEntry : optionsData.getAllNamedFields()) {
    221       String fieldName = mapEntry.getKey();
    222       Field field = mapEntry.getValue();
    223       OptionValueDescription entry = parsedValues.get(field);
    224       if (entry == null) {
    225         Object value = optionsData.getDefaultValue(field);
    226         result.add(
    227             new OptionValueDescription(
    228                 fieldName, value, OptionPriority.DEFAULT, null, null, null, false));
    229       } else {
    230         result.add(entry);
    231       }
    232     }
    233     return result;
    234   }
    235 
    236   Collection<Class<?  extends OptionsBase>> getOptionsClasses() {
    237     return optionsData.getOptionsClasses();
    238   }
    239 
    240   private void maybeAddDeprecationWarning(Field field) {
    241     Option option = field.getAnnotation(Option.class);
    242     // Continue to support the old behavior for @Deprecated options.
    243     String warning = option.deprecationWarning();
    244     if (!warning.isEmpty() || (field.getAnnotation(Deprecated.class) != null)) {
    245       addDeprecationWarning(option.name(), warning);
    246     }
    247   }
    248 
    249   private void addDeprecationWarning(String optionName, String warning) {
    250     warnings.add("Option '" + optionName + "' is deprecated"
    251         + (warning.isEmpty() ? "" : ": " + warning));
    252   }
    253 
    254   // Warnings should not end with a '.' because the internal reporter adds one automatically.
    255   private void setValue(Field field, String name, Object value,
    256       OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
    257     OptionValueDescription entry = parsedValues.get(field);
    258     if (entry != null) {
    259       // Override existing option if the new value has higher or equal priority.
    260       if (priority.compareTo(entry.getPriority()) >= 0) {
    261         // Output warnings:
    262         if ((implicitDependant != null) && (entry.getImplicitDependant() != null)) {
    263           if (!implicitDependant.equals(entry.getImplicitDependant())) {
    264             warnings.add(
    265                 "Option '"
    266                     + name
    267                     + "' is implicitly defined by both option '"
    268                     + entry.getImplicitDependant()
    269                     + "' and option '"
    270                     + implicitDependant
    271                     + "'");
    272           }
    273         } else if ((implicitDependant != null) && priority.equals(entry.getPriority())) {
    274           warnings.add(
    275               "Option '"
    276                   + name
    277                   + "' is implicitly defined by option '"
    278                   + implicitDependant
    279                   + "'; the implicitly set value overrides the previous one");
    280         } else if (entry.getImplicitDependant() != null) {
    281           warnings.add(
    282               "A new value for option '"
    283                   + name
    284                   + "' overrides a previous implicit setting of that option by option '"
    285                   + entry.getImplicitDependant()
    286                   + "'");
    287         } else if ((priority == entry.getPriority())
    288             && ((entry.getExpansionParent() == null) && (expandedFrom != null))) {
    289           // Create a warning if an expansion option overrides an explicit option:
    290           warnings.add("The option '" + expandedFrom + "' was expanded and now overrides a "
    291               + "previous explicitly specified option '" + name + "'");
    292         }
    293 
    294         // Record the new value:
    295         parsedValues.put(
    296             field,
    297             new OptionValueDescription(
    298                 name, value, priority, source, implicitDependant, expandedFrom, false));
    299       }
    300     } else {
    301       parsedValues.put(
    302           field,
    303           new OptionValueDescription(
    304               name, value, priority, source, implicitDependant, expandedFrom, false));
    305       maybeAddDeprecationWarning(field);
    306     }
    307   }
    308 
    309   private void addListValue(Field field, Object value, OptionPriority priority, String source,
    310       String implicitDependant, String expandedFrom) {
    311     OptionValueDescription entry = parsedValues.get(field);
    312     if (entry == null) {
    313       entry =
    314           new OptionValueDescription(
    315               field.getName(),
    316               ArrayListMultimap.create(),
    317               priority,
    318               source,
    319               implicitDependant,
    320               expandedFrom,
    321               true);
    322       parsedValues.put(field, entry);
    323       maybeAddDeprecationWarning(field);
    324     }
    325     entry.addValue(priority, value);
    326   }
    327 
    328   void clearValue(String optionName, Map<String, OptionValueDescription> clearedValues)
    329       throws OptionsParsingException {
    330     Field field = optionsData.getFieldFromName(optionName);
    331     if (field == null) {
    332       throw new IllegalArgumentException("No such option '" + optionName + "'");
    333     }
    334     Option option = field.getAnnotation(Option.class);
    335     clearValue(field, option, clearedValues);
    336   }
    337 
    338   private void clearValue(
    339       Field field, Option option, Map<String, OptionValueDescription> clearedValues)
    340       throws OptionsParsingException {
    341 
    342     OptionValueDescription removed = parsedValues.remove(field);
    343     if (removed != null) {
    344       clearedValues.put(option.name(), removed);
    345     }
    346 
    347     canonicalizeValues.removeAll(field);
    348 
    349     // Recurse to remove any implicit or expansion flags that this flag may have added when
    350     // originally parsed.
    351     String[] expansion = optionsData.getEvaluatedExpansion(field);
    352     for (String[] args : new String[][] {option.implicitRequirements(), expansion}) {
    353       Iterator<String> argsIterator = Iterators.forArray(args);
    354       while (argsIterator.hasNext()) {
    355         String arg = argsIterator.next();
    356         ParseOptionResult parseOptionResult = parseOption(arg, argsIterator);
    357         clearValue(parseOptionResult.field, parseOptionResult.option, clearedValues);
    358       }
    359     }
    360   }
    361 
    362   OptionValueDescription getOptionValueDescription(String name) {
    363     Field field = optionsData.getFieldFromName(name);
    364     if (field == null) {
    365       throw new IllegalArgumentException("No such option '" + name + "'");
    366     }
    367     return parsedValues.get(field);
    368   }
    369 
    370   OptionDescription getOptionDescription(String name) {
    371     Field field = optionsData.getFieldFromName(name);
    372     if (field == null) {
    373       return null;
    374     }
    375 
    376     Option optionAnnotation = field.getAnnotation(Option.class);
    377     return new OptionDescription(
    378         name,
    379         optionsData.getDefaultValue(field),
    380         optionsData.getConverter(field),
    381         optionAnnotation.allowMultiple());
    382   }
    383 
    384   boolean containsExplicitOption(String name) {
    385     Field field = optionsData.getFieldFromName(name);
    386     if (field == null) {
    387       throw new IllegalArgumentException("No such option '" + name + "'");
    388     }
    389     return parsedValues.get(field) != null;
    390   }
    391 
    392   /**
    393    * Parses the args, and returns what it doesn't parse. May be called multiple
    394    * times, and may be called recursively. In each call, there may be no
    395    * duplicates, but separate calls may contain intersecting sets of options; in
    396    * that case, the arg seen last takes precedence.
    397    */
    398   List<String> parse(OptionPriority priority, Function<? super String, String> sourceFunction,
    399       List<String> args) throws OptionsParsingException {
    400     return parse(priority, sourceFunction, null, null, args);
    401   }
    402 
    403   /**
    404    * Parses the args, and returns what it doesn't parse. May be called multiple
    405    * times, and may be called recursively. Calls may contain intersecting sets
    406    * of options; in that case, the arg seen last takes precedence.
    407    *
    408    * <p>The method uses the invariant that if an option has neither an implicit
    409    * dependent nor an expanded from value, then it must have been explicitly
    410    * set.
    411    */
    412   private List<String> parse(
    413       OptionPriority priority,
    414       Function<? super String, String> sourceFunction,
    415       String implicitDependent,
    416       String expandedFrom,
    417       List<String> args) throws OptionsParsingException {
    418 
    419     List<String> unparsedArgs = Lists.newArrayList();
    420     LinkedHashMap<String,List<String>> implicitRequirements = Maps.newLinkedHashMap();
    421 
    422     Iterator<String> argsIterator = argsPreProcessor.preProcess(args).iterator();
    423     while (argsIterator.hasNext()) {
    424       String arg = argsIterator.next();
    425 
    426       if (!arg.startsWith("-")) {
    427         unparsedArgs.add(arg);
    428         continue;  // not an option arg
    429       }
    430 
    431       if (arg.equals("--")) {  // "--" means all remaining args aren't options
    432         Iterators.addAll(unparsedArgs, argsIterator);
    433         break;
    434       }
    435 
    436       ParseOptionResult optionParseResult = parseOption(arg, argsIterator);
    437       Field field = optionParseResult.field;
    438       Option option = optionParseResult.option;
    439       String value = optionParseResult.value;
    440 
    441       final String originalName = option.name();
    442 
    443       if (option.wrapperOption()) {
    444         if (value.startsWith("-")) {
    445 
    446           List<String> unparsed = parse(
    447               priority,
    448               Functions.constant("Unwrapped from wrapper option --" + originalName),
    449               null, // implicitDependent
    450               null, // expandedFrom
    451               ImmutableList.of(value));
    452 
    453           if (!unparsed.isEmpty()) {
    454             throw new OptionsParsingException("Unparsed options remain after unwrapping " +
    455               arg + ": " + Joiner.on(' ').join(unparsed));
    456           }
    457 
    458           // Don't process implicitRequirements or expansions for wrapper options. In particular,
    459           // don't record this option in unparsedValues, so that only the wrapped option shows
    460           // up in canonicalized options.
    461           continue;
    462 
    463         } else {
    464           throw new OptionsParsingException("Invalid --" + originalName + " value format. "
    465               + "You may have meant --" + originalName + "=--" + value);
    466         }
    467       }
    468 
    469       if (implicitDependent == null) {
    470         // Log explicit options and expanded options in the order they are parsed (can be sorted
    471         // later). Also remember whether they were expanded or not. This information is needed to
    472         // correctly canonicalize flags.
    473         UnparsedOptionValueDescription unparsedOptionValueDescription =
    474             new UnparsedOptionValueDescription(
    475                 originalName,
    476                 field,
    477                 value,
    478                 priority,
    479                 sourceFunction.apply(originalName),
    480                 expandedFrom == null);
    481         unparsedValues.add(unparsedOptionValueDescription);
    482         if (option.allowMultiple()) {
    483           canonicalizeValues.put(field, unparsedOptionValueDescription);
    484         } else {
    485           canonicalizeValues.replaceValues(field, ImmutableList.of(unparsedOptionValueDescription));
    486         }
    487       }
    488 
    489       // Handle expansion options.
    490       String[] expansion = optionsData.getEvaluatedExpansion(field);
    491       if (expansion.length > 0) {
    492         Function<Object, String> expansionSourceFunction =
    493             Functions.constant(
    494                 "expanded from option --"
    495                     + originalName
    496                     + " from "
    497                     + sourceFunction.apply(originalName));
    498         maybeAddDeprecationWarning(field);
    499         List<String> unparsed = parse(priority, expansionSourceFunction, null, originalName,
    500             ImmutableList.copyOf(expansion));
    501         if (!unparsed.isEmpty()) {
    502           // Throw an assertion, because this indicates an error in the code that specified the
    503           // expansion for the current option.
    504           throw new AssertionError("Unparsed options remain after parsing expansion of " +
    505             arg + ": " + Joiner.on(' ').join(unparsed));
    506         }
    507       } else {
    508         Converter<?> converter = optionsData.getConverter(field);
    509         Object convertedValue;
    510         try {
    511           convertedValue = converter.convert(value);
    512         } catch (OptionsParsingException e) {
    513           // The converter doesn't know the option name, so we supply it here by
    514           // re-throwing:
    515           throw new OptionsParsingException("While parsing option " + arg
    516                                             + ": " + e.getMessage(), e);
    517         }
    518 
    519         // ...but allow duplicates of single-use options across separate calls to
    520         // parse(); latest wins:
    521         if (!option.allowMultiple()) {
    522           setValue(field, originalName, convertedValue,
    523               priority, sourceFunction.apply(originalName), implicitDependent, expandedFrom);
    524         } else {
    525           // But if it's a multiple-use option, then just accumulate the
    526           // values, in the order in which they were seen.
    527           // Note: The type of the list member is not known; Java introspection
    528           // only makes it available in String form via the signature string
    529           // for the field declaration.
    530           addListValue(field, convertedValue, priority, sourceFunction.apply(originalName),
    531               implicitDependent, expandedFrom);
    532         }
    533       }
    534 
    535       // Collect any implicit requirements.
    536       if (option.implicitRequirements().length > 0) {
    537         implicitRequirements.put(option.name(), Arrays.asList(option.implicitRequirements()));
    538       }
    539     }
    540 
    541     // Now parse any implicit requirements that were collected.
    542     // TODO(bazel-team): this should happen when the option is encountered.
    543     if (!implicitRequirements.isEmpty()) {
    544       for (Map.Entry<String, List<String>> entry : implicitRequirements.entrySet()) {
    545         Function<Object, String> requirementSourceFunction =
    546             Functions.constant(
    547                 "implicit requirement of option --"
    548                     + entry.getKey()
    549                     + " from "
    550                     + sourceFunction.apply(entry.getKey()));
    551 
    552         List<String> unparsed = parse(priority, requirementSourceFunction, entry.getKey(), null,
    553             entry.getValue());
    554         if (!unparsed.isEmpty()) {
    555           // Throw an assertion, because this indicates an error in the code that specified in the
    556           // implicit requirements for the option(s).
    557           throw new AssertionError("Unparsed options remain after parsing implicit options: "
    558               + Joiner.on(' ').join(unparsed));
    559         }
    560       }
    561     }
    562 
    563     return unparsedArgs;
    564   }
    565 
    566   private static final class ParseOptionResult {
    567     final Field field;
    568     final Option option;
    569     final String value;
    570 
    571     ParseOptionResult(Field field, Option option, String value) {
    572       this.field = field;
    573       this.option = option;
    574       this.value = value;
    575     }
    576   }
    577 
    578   private ParseOptionResult parseOption(String arg, Iterator<String> nextArgs)
    579       throws OptionsParsingException {
    580 
    581     String value = null;
    582     Field field;
    583     boolean booleanValue = true;
    584 
    585     if (arg.length() == 2) { // -l  (may be nullary or unary)
    586       field = optionsData.getFieldForAbbrev(arg.charAt(1));
    587       booleanValue = true;
    588 
    589     } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean)
    590       field = optionsData.getFieldForAbbrev(arg.charAt(1));
    591       booleanValue = false;
    592 
    593     } else if (allowSingleDashLongOptions // -long_option
    594         || arg.startsWith("--")) { // or --long_option
    595 
    596       int equalsAt = arg.indexOf('=');
    597       int nameStartsAt = arg.startsWith("--") ? 2 : 1;
    598       String name =
    599           equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt);
    600       if (name.trim().isEmpty()) {
    601         throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
    602       }
    603       value = equalsAt == -1 ? null : arg.substring(equalsAt + 1);
    604       field = optionsData.getFieldFromName(name);
    605 
    606       // look for a "no"-prefixed option name: "no<optionname>";
    607       // (Undocumented: we also allow --no_foo.  We're generous like that.)
    608       if (field == null && name.startsWith("no")) {
    609         name = name.substring(name.startsWith("no_") ? 3 : 2);
    610         field = optionsData.getFieldFromName(name);
    611         booleanValue = false;
    612         if (field != null) {
    613           // TODO(bazel-team): Add tests for these cases.
    614           if (!OptionsData.isBooleanField(field)) {
    615             throw new OptionsParsingException(
    616                 "Illegal use of 'no' prefix on non-boolean option: " + arg, arg);
    617           }
    618           if (value != null) {
    619             throw new OptionsParsingException(
    620                 "Unexpected value after boolean option: " + arg, arg);
    621           }
    622           // "no<optionname>" signifies a boolean option w/ false value
    623           value = "0";
    624         }
    625       }
    626     } else {
    627       throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
    628     }
    629 
    630     Option option = field == null ? null : field.getAnnotation(Option.class);
    631 
    632     if (option == null
    633         || OptionsParser.documentationLevel(option.category())
    634             == OptionsParser.DocumentationLevel.INTERNAL) {
    635       // This also covers internal options, which are treated as if they did not exist.
    636       throw new OptionsParsingException("Unrecognized option: " + arg, arg);
    637     }
    638 
    639     if (value == null) {
    640       // Special-case boolean to supply value based on presence of "no" prefix.
    641       if (OptionsData.isBooleanField(field)) {
    642         value = booleanValue ? "1" : "0";
    643       } else if (field.getType().equals(Void.class) && !option.wrapperOption()) {
    644         // This is expected, Void type options have no args (unless they're wrapper options).
    645       } else if (nextArgs.hasNext()) {
    646         value = nextArgs.next();  // "--flag value" form
    647       } else {
    648         throw new OptionsParsingException("Expected value after " + arg);
    649       }
    650     }
    651 
    652     return new ParseOptionResult(field, option, value);
    653   }
    654 
    655   /**
    656    * Gets the result of parsing the options.
    657    */
    658   <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) {
    659     // Create the instance:
    660     O optionsInstance;
    661     try {
    662       Constructor<O> constructor = optionsData.getConstructor(optionsClass);
    663       if (constructor == null) {
    664         return null;
    665       }
    666       optionsInstance = constructor.newInstance(new Object[0]);
    667     } catch (Exception e) {
    668       throw new IllegalStateException(e);
    669     }
    670 
    671     // Set the fields
    672     for (Field field : optionsData.getFieldsForClass(optionsClass)) {
    673       Object value;
    674       OptionValueDescription entry = parsedValues.get(field);
    675       if (entry == null) {
    676         value = optionsData.getDefaultValue(field);
    677       } else {
    678         value = entry.getValue();
    679       }
    680       try {
    681         field.set(optionsInstance, value);
    682       } catch (IllegalAccessException e) {
    683         throw new IllegalStateException(e);
    684       }
    685     }
    686     return optionsInstance;
    687   }
    688 
    689   List<String> getWarnings() {
    690     return ImmutableList.copyOf(warnings);
    691   }
    692 
    693   static String getDefaultOptionString(Field optionField) {
    694     Option annotation = optionField.getAnnotation(Option.class);
    695     return annotation.defaultValue();
    696   }
    697 
    698   static boolean isSpecialNullDefault(String defaultValueString, Field optionField) {
    699     return defaultValueString.equals("null") && !optionField.getType().isPrimitive();
    700   }
    701 }
    702