• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static java.util.Comparator.comparing;
18 import static java.util.stream.Collectors.toCollection;
19 
20 import com.google.common.base.Joiner;
21 import com.google.common.base.Preconditions;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.Iterators;
24 import com.google.devtools.common.options.OptionPriority.PriorityCategory;
25 import com.google.devtools.common.options.OptionValueDescription.ExpansionBundle;
26 import com.google.devtools.common.options.OptionsParser.OptionDescription;
27 import java.lang.reflect.Constructor;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.function.Function;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 import javax.annotation.Nullable;
38 
39 /**
40  * The implementation of the options parser. This is intentionally package
41  * private for full flexibility. Use {@link OptionsParser} or {@link Options}
42  * if you're a consumer.
43  */
44 class OptionsParserImpl {
45 
46   private final OptionsData optionsData;
47 
48   /**
49    * We store the results of option parsing in here - since there can only be one value per option
50    * field, this is where the different instances of an option have been combined and the final
51    * value is tracked. It'll look like
52    *
53    * <pre>
54    *   OptionDefinition("--host") -> "www.google.com"
55    *   OptionDefinition("--port") -> 80
56    * </pre>
57    *
58    * This map is modified by repeated calls to {@link #parse(OptionPriority.PriorityCategory,
59    * Function,List)}.
60    */
61   private final Map<OptionDefinition, OptionValueDescription> optionValues = new HashMap<>();
62 
63   /**
64    * Explicit option tracking, tracking each option as it was provided, after they have been parsed.
65    *
66    * <p>The value is unconverted, still the string as it was read from the input, or partially
67    * altered in cases where the flag was set by non {@code --flag=value} forms; e.g. {@code --nofoo}
68    * becomes {@code --foo=0}.
69    */
70   private final List<ParsedOptionDescription> parsedOptions = new ArrayList<>();
71 
72   private final List<String> warnings = new ArrayList<>();
73 
74   /**
75    * Since parse() expects multiple calls to it with the same {@link PriorityCategory} to be treated
76    * as though the args in the later call have higher priority over the earlier calls, we need to
77    * track the high water mark of option priority at each category. Each call to parse will start at
78    * this level.
79    */
80   private final Map<PriorityCategory, OptionPriority> nextPriorityPerPriorityCategory =
81       Stream.of(PriorityCategory.values())
82           .collect(Collectors.toMap(p -> p, OptionPriority::lowestOptionPriorityAtCategory));
83 
84   private boolean allowSingleDashLongOptions = false;
85 
86   private ArgsPreProcessor argsPreProcessor = args -> args;
87 
88   /** Create a new parser object. Do not accept a null OptionsData object. */
OptionsParserImpl(OptionsData optionsData)89   OptionsParserImpl(OptionsData optionsData) {
90     Preconditions.checkNotNull(optionsData);
91     this.optionsData = optionsData;
92   }
93 
getOptionsData()94   OptionsData getOptionsData() {
95     return optionsData;
96   }
97 
98   /**
99    * Indicates whether or not the parser will allow long options with a
100    * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example.
101    */
setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions)102   void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) {
103     this.allowSingleDashLongOptions = allowSingleDashLongOptions;
104   }
105 
106   /** Sets the ArgsPreProcessor for manipulations of the options before parsing. */
setArgsPreProcessor(ArgsPreProcessor preProcessor)107   void setArgsPreProcessor(ArgsPreProcessor preProcessor) {
108     this.argsPreProcessor = Preconditions.checkNotNull(preProcessor);
109   }
110 
111   /** Implements {@link OptionsParser#asCompleteListOfParsedOptions()}. */
asCompleteListOfParsedOptions()112   List<ParsedOptionDescription> asCompleteListOfParsedOptions() {
113     return parsedOptions
114         .stream()
115         // It is vital that this sort is stable so that options on the same priority are not
116         // reordered.
117         .sorted(comparing(ParsedOptionDescription::getPriority))
118         .collect(toCollection(ArrayList::new));
119   }
120 
121   /** Implements {@link OptionsParser#asListOfExplicitOptions()}. */
asListOfExplicitOptions()122   List<ParsedOptionDescription> asListOfExplicitOptions() {
123     return parsedOptions
124         .stream()
125         .filter(ParsedOptionDescription::isExplicit)
126         // It is vital that this sort is stable so that options on the same priority are not
127         // reordered.
128         .sorted(comparing(ParsedOptionDescription::getPriority))
129         .collect(toCollection(ArrayList::new));
130   }
131 
132   /** Implements {@link OptionsParser#canonicalize}. */
asCanonicalizedList()133   List<String> asCanonicalizedList() {
134     return asCanonicalizedListOfParsedOptions()
135         .stream()
136         .map(ParsedOptionDescription::getDeprecatedCanonicalForm)
137         .collect(ImmutableList.toImmutableList());
138   }
139 
140   /** Implements {@link OptionsParser#canonicalize}. */
asCanonicalizedListOfParsedOptions()141   List<ParsedOptionDescription> asCanonicalizedListOfParsedOptions() {
142     return optionValues
143         .keySet()
144         .stream()
145         .map(optionDefinition -> optionValues.get(optionDefinition).getCanonicalInstances())
146         .flatMap(Collection::stream)
147         // Return the effective (canonical) options in the order they were applied.
148         .sorted(comparing(ParsedOptionDescription::getPriority))
149         .collect(ImmutableList.toImmutableList());
150   }
151 
152   /** Implements {@link OptionsParser#asListOfOptionValues()}. */
asListOfEffectiveOptions()153   List<OptionValueDescription> asListOfEffectiveOptions() {
154     List<OptionValueDescription> result = new ArrayList<>();
155     for (Map.Entry<String, OptionDefinition> mapEntry : optionsData.getAllOptionDefinitions()) {
156       OptionDefinition optionDefinition = mapEntry.getValue();
157       OptionValueDescription optionValue = optionValues.get(optionDefinition);
158       if (optionValue == null) {
159         result.add(OptionValueDescription.getDefaultOptionValue(optionDefinition));
160       } else {
161         result.add(optionValue);
162       }
163     }
164     return result;
165   }
166 
maybeAddDeprecationWarning(OptionDefinition optionDefinition)167   private void maybeAddDeprecationWarning(OptionDefinition optionDefinition) {
168     // Continue to support the old behavior for @Deprecated options.
169     String warning = optionDefinition.getDeprecationWarning();
170     if (!warning.isEmpty() || (optionDefinition.getField().isAnnotationPresent(Deprecated.class))) {
171       addDeprecationWarning(optionDefinition.getOptionName(), warning);
172     }
173   }
174 
addDeprecationWarning(String optionName, String warning)175   private void addDeprecationWarning(String optionName, String warning) {
176     warnings.add(
177         String.format(
178             "Option '%s' is deprecated%s", optionName, (warning.isEmpty() ? "" : ": " + warning)));
179   }
180 
181 
clearValue(OptionDefinition optionDefinition)182   OptionValueDescription clearValue(OptionDefinition optionDefinition)
183       throws OptionsParsingException {
184     return optionValues.remove(optionDefinition);
185   }
186 
getOptionValueDescription(String name)187   OptionValueDescription getOptionValueDescription(String name) {
188     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
189     if (optionDefinition == null) {
190       throw new IllegalArgumentException("No such option '" + name + "'");
191     }
192     return optionValues.get(optionDefinition);
193   }
194 
getOptionDescription(String name)195   OptionDescription getOptionDescription(String name) throws OptionsParsingException {
196     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
197     if (optionDefinition == null) {
198       return null;
199     }
200     return new OptionDescription(optionDefinition, optionsData);
201   }
202 
203   /**
204    * Implementation of {@link OptionsParser#getExpansionValueDescriptions(OptionDefinition,
205    * OptionInstanceOrigin)}
206    */
getExpansionValueDescriptions( OptionDefinition expansionFlagDef, OptionInstanceOrigin originOfExpansionFlag)207   ImmutableList<ParsedOptionDescription> getExpansionValueDescriptions(
208       OptionDefinition expansionFlagDef, OptionInstanceOrigin originOfExpansionFlag)
209       throws OptionsParsingException {
210     ImmutableList.Builder<ParsedOptionDescription> builder = ImmutableList.builder();
211 
212     // Values needed to correctly track the origin of the expanded options.
213     OptionPriority nextOptionPriority =
214         OptionPriority.getChildPriority(originOfExpansionFlag.getPriority());
215     String source;
216     ParsedOptionDescription implicitDependent = null;
217     ParsedOptionDescription expandedFrom = null;
218 
219     ImmutableList<String> options;
220     ParsedOptionDescription expansionFlagParsedDummy =
221         ParsedOptionDescription.newDummyInstance(expansionFlagDef, originOfExpansionFlag);
222     if (expansionFlagDef.hasImplicitRequirements()) {
223       options = ImmutableList.copyOf(expansionFlagDef.getImplicitRequirements());
224       source =
225           String.format(
226               "implicitly required by %s (source: %s)",
227               expansionFlagDef, originOfExpansionFlag.getSource());
228       implicitDependent = expansionFlagParsedDummy;
229     } else if (expansionFlagDef.isExpansionOption()) {
230       options = optionsData.getEvaluatedExpansion(expansionFlagDef);
231       source =
232           String.format(
233               "expanded by %s (source: %s)", expansionFlagDef, originOfExpansionFlag.getSource());
234       expandedFrom = expansionFlagParsedDummy;
235     } else {
236       return ImmutableList.of();
237     }
238 
239     Iterator<String> optionsIterator = options.iterator();
240     while (optionsIterator.hasNext()) {
241       String unparsedFlagExpression = optionsIterator.next();
242       ParsedOptionDescription parsedOption =
243           identifyOptionAndPossibleArgument(
244               unparsedFlagExpression,
245               optionsIterator,
246               nextOptionPriority,
247               o -> source,
248               implicitDependent,
249               expandedFrom);
250       builder.add(parsedOption);
251       nextOptionPriority = OptionPriority.nextOptionPriority(nextOptionPriority);
252     }
253     return builder.build();
254   }
255 
containsExplicitOption(String name)256   boolean containsExplicitOption(String name) {
257     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
258     if (optionDefinition == null) {
259       throw new IllegalArgumentException("No such option '" + name + "'");
260     }
261     return optionValues.get(optionDefinition) != null;
262   }
263 
264   /**
265    * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be
266    * called recursively. The option's definition dictates how it reacts to multiple settings. By
267    * default, the arg seen last at the highest priority takes precedence, overriding the early
268    * values. Options that accumulate multiple values will track them in priority and appearance
269    * order.
270    */
parse( PriorityCategory priorityCat, Function<OptionDefinition, String> sourceFunction, List<String> args)271   List<String> parse(
272       PriorityCategory priorityCat,
273       Function<OptionDefinition, String> sourceFunction,
274       List<String> args)
275       throws OptionsParsingException {
276     ResidueAndPriority residueAndPriority =
277         parse(nextPriorityPerPriorityCategory.get(priorityCat), sourceFunction, null, null, args);
278     nextPriorityPerPriorityCategory.put(priorityCat, residueAndPriority.nextPriority);
279     return residueAndPriority.residue;
280   }
281 
282   private static final class ResidueAndPriority {
283     List<String> residue;
284     OptionPriority nextPriority;
285 
ResidueAndPriority(List<String> residue, OptionPriority nextPriority)286     public ResidueAndPriority(List<String> residue, OptionPriority nextPriority) {
287       this.residue = residue;
288       this.nextPriority = nextPriority;
289     }
290   }
291 
292   /** Implements {@link OptionsParser#parseArgsAsExpansionOfOption} */
parseArgsAsExpansionOfOption( ParsedOptionDescription optionToExpand, Function<OptionDefinition, String> sourceFunction, List<String> args)293   List<String> parseArgsAsExpansionOfOption(
294       ParsedOptionDescription optionToExpand,
295       Function<OptionDefinition, String> sourceFunction,
296       List<String> args)
297       throws OptionsParsingException {
298     ResidueAndPriority residueAndPriority =
299         parse(
300             OptionPriority.getChildPriority(optionToExpand.getPriority()),
301             sourceFunction,
302             null,
303             optionToExpand,
304             args);
305     return residueAndPriority.residue;
306   }
307 
308   /**
309    * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be
310    * called recursively. Calls may contain intersecting sets of options; in that case, the arg seen
311    * last takes precedence.
312    *
313    * <p>The method treats options that have neither an implicitDependent nor an expandedFrom value
314    * as explicitly set.
315    */
parse( OptionPriority priority, Function<OptionDefinition, String> sourceFunction, ParsedOptionDescription implicitDependent, ParsedOptionDescription expandedFrom, List<String> args)316   private ResidueAndPriority parse(
317       OptionPriority priority,
318       Function<OptionDefinition, String> sourceFunction,
319       ParsedOptionDescription implicitDependent,
320       ParsedOptionDescription expandedFrom,
321       List<String> args)
322       throws OptionsParsingException {
323     List<String> unparsedArgs = new ArrayList<>();
324 
325     Iterator<String> argsIterator = argsPreProcessor.preProcess(args).iterator();
326     while (argsIterator.hasNext()) {
327       String arg = argsIterator.next();
328 
329       if (!arg.startsWith("-")) {
330         unparsedArgs.add(arg);
331         continue;  // not an option arg
332       }
333 
334       if (arg.equals("--")) {  // "--" means all remaining args aren't options
335         Iterators.addAll(unparsedArgs, argsIterator);
336         break;
337       }
338 
339       ParsedOptionDescription parsedOption =
340           identifyOptionAndPossibleArgument(
341               arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom);
342       handleNewParsedOption(parsedOption);
343       priority = OptionPriority.nextOptionPriority(priority);
344     }
345 
346     // Go through the final values and make sure they are valid values for their option. Unlike any
347     // checks that happened above, this also checks that flags that were not set have a valid
348     // default value. getValue() will throw if the value is invalid.
349     for (OptionValueDescription valueDescription : asListOfEffectiveOptions()) {
350       valueDescription.getValue();
351     }
352 
353     return new ResidueAndPriority(unparsedArgs, priority);
354   }
355 
356   /**
357    * Implementation of {@link OptionsParser#addOptionValueAtSpecificPriority(OptionInstanceOrigin,
358    * OptionDefinition, String)}
359    */
addOptionValueAtSpecificPriority( OptionInstanceOrigin origin, OptionDefinition option, String unconvertedValue)360   void addOptionValueAtSpecificPriority(
361       OptionInstanceOrigin origin, OptionDefinition option, String unconvertedValue)
362       throws OptionsParsingException {
363     Preconditions.checkNotNull(option);
364     Preconditions.checkNotNull(
365         unconvertedValue,
366         "Cannot set %s to a null value. Pass \"\" if an empty value is required.",
367         option);
368     Preconditions.checkNotNull(
369         origin,
370         "Cannot assign value \'%s\' to %s without a clear origin for this value.",
371         unconvertedValue,
372         option);
373     PriorityCategory priorityCategory = origin.getPriority().getPriorityCategory();
374     boolean isNotDefault = priorityCategory != OptionPriority.PriorityCategory.DEFAULT;
375     Preconditions.checkArgument(
376         isNotDefault,
377         "Attempt to assign value \'%s\' to %s at priority %s failed. Cannot set options at "
378             + "default priority - by definition, that means the option is unset.",
379         unconvertedValue,
380         option,
381         priorityCategory);
382 
383     handleNewParsedOption(
384         ParsedOptionDescription.newParsedOptionDescription(
385             option,
386             String.format("--%s=%s", option.getOptionName(), unconvertedValue),
387             unconvertedValue,
388             origin));
389   }
390 
391   /** Takes care of tracking the parsed option's value in relation to other options. */
handleNewParsedOption(ParsedOptionDescription parsedOption)392   private void handleNewParsedOption(ParsedOptionDescription parsedOption)
393       throws OptionsParsingException {
394     OptionDefinition optionDefinition = parsedOption.getOptionDefinition();
395     // All options can be deprecated; check and warn before doing any option-type specific work.
396     maybeAddDeprecationWarning(optionDefinition);
397     // Track the value, before any remaining option-type specific work that is done outside of
398     // the OptionValueDescription.
399     OptionValueDescription entry =
400         optionValues.computeIfAbsent(
401             optionDefinition,
402             def -> OptionValueDescription.createOptionValueDescription(def, optionsData));
403     ExpansionBundle expansionBundle = entry.addOptionInstance(parsedOption, warnings);
404     @Nullable String unconvertedValue = parsedOption.getUnconvertedValue();
405 
406     // There are 3 types of flags that expand to other flag values. Expansion flags are the
407     // accepted way to do this, but implicit requirements also do this. We rely on the
408     // OptionProcessor compile-time check's guarantee that no option sets
409     // both expansion behaviors. (In Bazel, --config is another such flag, but that expansion
410     // is not controlled within the options parser, so we ignore it here)
411 
412     // As much as possible, we want the behaviors of these different types of flags to be
413     // identical, as this minimizes the number of edge cases, but we do not yet track these values
414     // in the same way.
415     if (parsedOption.getImplicitDependent() == null) {
416       // Log explicit options and expanded options in the order they are parsed (can be sorted
417       // later). This information is needed to correctly canonicalize flags.
418       parsedOptions.add(parsedOption);
419     }
420 
421     if (expansionBundle != null) {
422       ResidueAndPriority residueAndPriority =
423           parse(
424               OptionPriority.getChildPriority(parsedOption.getPriority()),
425               o -> expansionBundle.sourceOfExpansionArgs,
426               optionDefinition.hasImplicitRequirements() ? parsedOption : null,
427               optionDefinition.isExpansionOption() ? parsedOption : null,
428               expansionBundle.expansionArgs);
429       if (!residueAndPriority.residue.isEmpty()) {
430 
431           // Throw an assertion here, because this indicates an error in the definition of this
432           // option's expansion or requirements, not with the input as provided by the user.
433           throw new AssertionError(
434               "Unparsed options remain after processing "
435                   + unconvertedValue
436                   + ": "
437                   + Joiner.on(' ').join(residueAndPriority.residue));
438 
439       }
440     }
441   }
442 
identifyOptionAndPossibleArgument( String arg, Iterator<String> nextArgs, OptionPriority priority, Function<OptionDefinition, String> sourceFunction, ParsedOptionDescription implicitDependent, ParsedOptionDescription expandedFrom)443   private ParsedOptionDescription identifyOptionAndPossibleArgument(
444       String arg,
445       Iterator<String> nextArgs,
446       OptionPriority priority,
447       Function<OptionDefinition, String> sourceFunction,
448       ParsedOptionDescription implicitDependent,
449       ParsedOptionDescription expandedFrom)
450       throws OptionsParsingException {
451 
452     // Store the way this option was parsed on the command line.
453     StringBuilder commandLineForm = new StringBuilder();
454     commandLineForm.append(arg);
455     String unconvertedValue = null;
456     OptionDefinition optionDefinition;
457     boolean booleanValue = true;
458 
459     if (arg.length() == 2) { // -l  (may be nullary or unary)
460       optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1));
461       booleanValue = true;
462 
463     } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean)
464       optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1));
465       booleanValue = false;
466 
467     } else if (allowSingleDashLongOptions // -long_option
468         || arg.startsWith("--")) { // or --long_option
469 
470       int equalsAt = arg.indexOf('=');
471       int nameStartsAt = arg.startsWith("--") ? 2 : 1;
472       String name =
473           equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt);
474       if (name.trim().isEmpty()) {
475         throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
476       }
477       unconvertedValue = equalsAt == -1 ? null : arg.substring(equalsAt + 1);
478       optionDefinition = optionsData.getOptionDefinitionFromName(name);
479 
480       // Look for a "no"-prefixed option name: "no<optionName>".
481       if (optionDefinition == null && name.startsWith("no")) {
482         name = name.substring(2);
483         optionDefinition = optionsData.getOptionDefinitionFromName(name);
484         booleanValue = false;
485         if (optionDefinition != null) {
486           // TODO(bazel-team): Add tests for these cases.
487           if (!optionDefinition.usesBooleanValueSyntax()) {
488             throw new OptionsParsingException(
489                 "Illegal use of 'no' prefix on non-boolean option: " + arg, arg);
490           }
491           if (unconvertedValue != null) {
492             throw new OptionsParsingException(
493                 "Unexpected value after boolean option: " + arg, arg);
494           }
495           // "no<optionname>" signifies a boolean option w/ false value
496           unconvertedValue = "0";
497         }
498       }
499     } else {
500       throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
501     }
502 
503     if (optionDefinition == null
504         || ImmutableList.copyOf(optionDefinition.getOptionMetadataTags())
505             .contains(OptionMetadataTag.INTERNAL)) {
506       // Do not recognize internal options, which are treated as if they did not exist.
507       throw new OptionsParsingException("Unrecognized option: " + arg, arg);
508     }
509 
510     if (unconvertedValue == null) {
511       // Special-case boolean to supply value based on presence of "no" prefix.
512       if (optionDefinition.usesBooleanValueSyntax()) {
513         unconvertedValue = booleanValue ? "1" : "0";
514       } else if (optionDefinition.getType().equals(Void.class)) {
515         // This is expected, Void type options have no args.
516       } else if (nextArgs.hasNext()) {
517         // "--flag value" form
518         unconvertedValue = nextArgs.next();
519         commandLineForm.append(" ").append(unconvertedValue);
520       } else {
521         throw new OptionsParsingException("Expected value after " + arg);
522       }
523     }
524 
525     return ParsedOptionDescription.newParsedOptionDescription(
526         optionDefinition,
527         commandLineForm.toString(),
528         unconvertedValue,
529         new OptionInstanceOrigin(
530             priority, sourceFunction.apply(optionDefinition), implicitDependent, expandedFrom));
531   }
532 
533   /**
534    * Gets the result of parsing the options.
535    */
getParsedOptions(Class<O> optionsClass)536   <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) {
537     // Create the instance:
538     O optionsInstance;
539     try {
540       Constructor<O> constructor = optionsData.getConstructor(optionsClass);
541       if (constructor == null) {
542         return null;
543       }
544       optionsInstance = constructor.newInstance();
545     } catch (ReflectiveOperationException e) {
546       throw new IllegalStateException("Error while instantiating options class", e);
547     }
548 
549     // Set the fields
550     for (OptionDefinition optionDefinition :
551         OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
552       Object value;
553       OptionValueDescription optionValue = optionValues.get(optionDefinition);
554       if (optionValue == null) {
555         value = optionDefinition.getDefaultValue();
556       } else {
557         value = optionValue.getValue();
558       }
559       try {
560         optionDefinition.getField().set(optionsInstance, value);
561       } catch (IllegalArgumentException e) {
562         throw new IllegalStateException(
563             String.format("Unable to set %s to value '%s'.", optionDefinition, value), e);
564       } catch (IllegalAccessException e) {
565         throw new IllegalStateException(
566             "Could not set the field due to access issues. This is impossible, as the "
567                 + "OptionProcessor checks that all options are non-final public fields.",
568             e);
569       }
570     }
571     return optionsInstance;
572   }
573 
getWarnings()574   List<String> getWarnings() {
575     return ImmutableList.copyOf(warnings);
576   }
577 }
578