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
15package com.google.devtools.common.options;
16
17import com.google.common.base.Function;
18import com.google.common.base.Functions;
19import com.google.common.base.Joiner;
20import com.google.common.base.Preconditions;
21import com.google.common.base.Predicate;
22import com.google.common.collect.ArrayListMultimap;
23import com.google.common.collect.ImmutableList;
24import com.google.common.collect.Iterables;
25import com.google.common.collect.Iterators;
26import com.google.common.collect.LinkedHashMultimap;
27import com.google.common.collect.Lists;
28import com.google.common.collect.Multimap;
29import com.google.devtools.common.options.OptionsParser.OptionDescription;
30import com.google.devtools.common.options.OptionsParser.OptionUsageRestrictions;
31import com.google.devtools.common.options.OptionsParser.OptionValueDescription;
32import com.google.devtools.common.options.OptionsParser.UnparsedOptionValueDescription;
33import java.lang.reflect.Constructor;
34import java.lang.reflect.Field;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.Collections;
38import java.util.Comparator;
39import java.util.HashMap;
40import java.util.Iterator;
41import java.util.LinkedHashMap;
42import java.util.List;
43import 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 */
50class 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