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