• 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         .sorted()
146         .map(optionDefinition -> optionValues.get(optionDefinition).getCanonicalInstances())
147         .flatMap(Collection::stream)
148         .collect(ImmutableList.toImmutableList());
149   }
150 
151   /** Implements {@link OptionsParser#asListOfOptionValues()}. */
asListOfEffectiveOptions()152   List<OptionValueDescription> asListOfEffectiveOptions() {
153     List<OptionValueDescription> result = new ArrayList<>();
154     for (Map.Entry<String, OptionDefinition> mapEntry : optionsData.getAllOptionDefinitions()) {
155       OptionDefinition optionDefinition = mapEntry.getValue();
156       OptionValueDescription optionValue = optionValues.get(optionDefinition);
157       if (optionValue == null) {
158         result.add(OptionValueDescription.getDefaultOptionValue(optionDefinition));
159       } else {
160         result.add(optionValue);
161       }
162     }
163     return result;
164   }
165 
maybeAddDeprecationWarning(OptionDefinition optionDefinition)166   private void maybeAddDeprecationWarning(OptionDefinition optionDefinition) {
167     // Continue to support the old behavior for @Deprecated options.
168     String warning = optionDefinition.getDeprecationWarning();
169     if (!warning.isEmpty() || (optionDefinition.getField().isAnnotationPresent(Deprecated.class))) {
170       addDeprecationWarning(optionDefinition.getOptionName(), warning);
171     }
172   }
173 
addDeprecationWarning(String optionName, String warning)174   private void addDeprecationWarning(String optionName, String warning) {
175     warnings.add(
176         String.format(
177             "Option '%s' is deprecated%s", optionName, (warning.isEmpty() ? "" : ": " + warning)));
178   }
179 
180 
clearValue(OptionDefinition optionDefinition)181   OptionValueDescription clearValue(OptionDefinition optionDefinition)
182       throws OptionsParsingException {
183     return optionValues.remove(optionDefinition);
184   }
185 
getOptionValueDescription(String name)186   OptionValueDescription getOptionValueDescription(String name) {
187     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
188     if (optionDefinition == null) {
189       throw new IllegalArgumentException("No such option '" + name + "'");
190     }
191     return optionValues.get(optionDefinition);
192   }
193 
getOptionDescription(String name)194   OptionDescription getOptionDescription(String name) throws OptionsParsingException {
195     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
196     if (optionDefinition == null) {
197       return null;
198     }
199     return new OptionDescription(optionDefinition, optionsData);
200   }
201 
202   /**
203    * Implementation of {@link OptionsParser#getExpansionValueDescriptions(OptionDefinition,
204    * OptionInstanceOrigin)}
205    */
getExpansionValueDescriptions( OptionDefinition expansionFlag, OptionInstanceOrigin originOfExpansionFlag)206   ImmutableList<ParsedOptionDescription> getExpansionValueDescriptions(
207       OptionDefinition expansionFlag, OptionInstanceOrigin originOfExpansionFlag)
208       throws OptionsParsingException {
209     ImmutableList.Builder<ParsedOptionDescription> builder = ImmutableList.builder();
210     OptionInstanceOrigin originOfSubflags;
211     ImmutableList<String> options;
212     if (expansionFlag.hasImplicitRequirements()) {
213       options = ImmutableList.copyOf(expansionFlag.getImplicitRequirements());
214       originOfSubflags =
215           new OptionInstanceOrigin(
216               originOfExpansionFlag.getPriority(),
217               String.format(
218                   "implicitly required by %s (source: %s)",
219                   expansionFlag, originOfExpansionFlag.getSource()),
220               expansionFlag,
221               null);
222     } else if (expansionFlag.isExpansionOption()) {
223       options = optionsData.getEvaluatedExpansion(expansionFlag);
224       originOfSubflags =
225           new OptionInstanceOrigin(
226               originOfExpansionFlag.getPriority(),
227               String.format(
228                   "expanded by %s (source: %s)", expansionFlag, originOfExpansionFlag.getSource()),
229               null,
230               expansionFlag);
231     } else {
232       return ImmutableList.of();
233     }
234 
235     Iterator<String> optionsIterator = options.iterator();
236     while (optionsIterator.hasNext()) {
237       String unparsedFlagExpression = optionsIterator.next();
238       ParsedOptionDescription parsedOption =
239           identifyOptionAndPossibleArgument(
240               unparsedFlagExpression,
241               optionsIterator,
242               originOfSubflags.getPriority(),
243               o -> originOfSubflags.getSource(),
244               originOfSubflags.getImplicitDependent(),
245               originOfSubflags.getExpandedFrom());
246       builder.add(parsedOption);
247     }
248     return builder.build();
249   }
250 
containsExplicitOption(String name)251   boolean containsExplicitOption(String name) {
252     OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name);
253     if (optionDefinition == null) {
254       throw new IllegalArgumentException("No such option '" + name + "'");
255     }
256     return optionValues.get(optionDefinition) != null;
257   }
258 
259   /**
260    * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be
261    * called recursively. The option's definition dictates how it reacts to multiple settings. By
262    * default, the arg seen last at the highest priority takes precedence, overriding the early
263    * values. Options that accumulate multiple values will track them in priority and appearance
264    * order.
265    */
parse( PriorityCategory priorityCat, Function<OptionDefinition, String> sourceFunction, List<String> args)266   List<String> parse(
267       PriorityCategory priorityCat,
268       Function<OptionDefinition, String> sourceFunction,
269       List<String> args)
270       throws OptionsParsingException {
271     ResidueAndPriority residueAndPriority =
272         parse(nextPriorityPerPriorityCategory.get(priorityCat), sourceFunction, null, null, args);
273     nextPriorityPerPriorityCategory.put(priorityCat, residueAndPriority.nextPriority);
274     return residueAndPriority.residue;
275   }
276 
277   private static final class ResidueAndPriority {
278     List<String> residue;
279     OptionPriority nextPriority;
280 
ResidueAndPriority(List<String> residue, OptionPriority nextPriority)281     public ResidueAndPriority(List<String> residue, OptionPriority nextPriority) {
282       this.residue = residue;
283       this.nextPriority = nextPriority;
284     }
285   }
286 
287   /** Parses the args at the fixed priority. */
parseOptionsFixedAtSpecificPriority( OptionPriority priority, Function<OptionDefinition, String> sourceFunction, List<String> args)288   List<String> parseOptionsFixedAtSpecificPriority(
289       OptionPriority priority, Function<OptionDefinition, String> sourceFunction, List<String> args)
290       throws OptionsParsingException {
291     ResidueAndPriority residueAndPriority =
292         parse(OptionPriority.getLockedPriority(priority), sourceFunction, null, null, args);
293     return residueAndPriority.residue;
294   }
295 
296   /**
297    * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be
298    * called recursively. Calls may contain intersecting sets of options; in that case, the arg seen
299    * last takes precedence.
300    *
301    * <p>The method treats options that have neither an implicitDependent nor an expandedFrom value
302    * as explicitly set.
303    */
parse( OptionPriority priority, Function<OptionDefinition, String> sourceFunction, OptionDefinition implicitDependent, OptionDefinition expandedFrom, List<String> args)304   private ResidueAndPriority parse(
305       OptionPriority priority,
306       Function<OptionDefinition, String> sourceFunction,
307       OptionDefinition implicitDependent,
308       OptionDefinition expandedFrom,
309       List<String> args)
310       throws OptionsParsingException {
311     List<String> unparsedArgs = new ArrayList<>();
312 
313     Iterator<String> argsIterator = argsPreProcessor.preProcess(args).iterator();
314     while (argsIterator.hasNext()) {
315       String arg = argsIterator.next();
316 
317       if (!arg.startsWith("-")) {
318         unparsedArgs.add(arg);
319         continue;  // not an option arg
320       }
321 
322       if (arg.equals("--")) {  // "--" means all remaining args aren't options
323         Iterators.addAll(unparsedArgs, argsIterator);
324         break;
325       }
326 
327       ParsedOptionDescription parsedOption =
328           identifyOptionAndPossibleArgument(
329               arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom);
330       handleNewParsedOption(parsedOption);
331       priority = OptionPriority.nextOptionPriority(priority);
332     }
333 
334     // Go through the final values and make sure they are valid values for their option. Unlike any
335     // checks that happened above, this also checks that flags that were not set have a valid
336     // default value. getValue() will throw if the value is invalid.
337     for (OptionValueDescription valueDescription : asListOfEffectiveOptions()) {
338       valueDescription.getValue();
339     }
340 
341     return new ResidueAndPriority(unparsedArgs, priority);
342   }
343 
344   /**
345    * Implementation of {@link OptionsParser#addOptionValueAtSpecificPriority(OptionInstanceOrigin,
346    * OptionDefinition, String)}
347    */
addOptionValueAtSpecificPriority( OptionInstanceOrigin origin, OptionDefinition option, String unconvertedValue)348   void addOptionValueAtSpecificPriority(
349       OptionInstanceOrigin origin, OptionDefinition option, String unconvertedValue)
350       throws OptionsParsingException {
351     Preconditions.checkNotNull(option);
352     Preconditions.checkNotNull(
353         unconvertedValue,
354         "Cannot set %s to a null value. Pass \"\" if an empty value is required.",
355         option);
356     Preconditions.checkNotNull(
357         origin,
358         "Cannot assign value \'%s\' to %s without a clear origin for this value.",
359         unconvertedValue,
360         option);
361     PriorityCategory priorityCategory = origin.getPriority().getPriorityCategory();
362     boolean isNotDefault = priorityCategory != OptionPriority.PriorityCategory.DEFAULT;
363     Preconditions.checkArgument(
364         isNotDefault,
365         "Attempt to assign value \'%s\' to %s at priority %s failed. Cannot set options at "
366             + "default priority - by definition, that means the option is unset.",
367         unconvertedValue,
368         option,
369         priorityCategory);
370 
371     handleNewParsedOption(
372         new ParsedOptionDescription(
373             option,
374             String.format("--%s=%s", option.getOptionName(), unconvertedValue),
375             unconvertedValue,
376             origin));
377   }
378 
379   /** Takes care of tracking the parsed option's value in relation to other options. */
handleNewParsedOption(ParsedOptionDescription parsedOption)380   private void handleNewParsedOption(ParsedOptionDescription parsedOption)
381       throws OptionsParsingException {
382     OptionDefinition optionDefinition = parsedOption.getOptionDefinition();
383     // All options can be deprecated; check and warn before doing any option-type specific work.
384     maybeAddDeprecationWarning(optionDefinition);
385     // Track the value, before any remaining option-type specific work that is done outside of
386     // the OptionValueDescription.
387     OptionValueDescription entry =
388         optionValues.computeIfAbsent(
389             optionDefinition,
390             def -> OptionValueDescription.createOptionValueDescription(def, optionsData));
391     ExpansionBundle expansionBundle = entry.addOptionInstance(parsedOption, warnings);
392     @Nullable String unconvertedValue = parsedOption.getUnconvertedValue();
393 
394     // There are 3 types of flags that expand to other flag values. Expansion flags are the
395     // accepted way to do this, but two legacy features remain: implicit requirements and wrapper
396     // options. We rely on the OptionProcessor compile-time check's guarantee that no option sets
397     // multiple of these behaviors. (In Bazel, --config is another such flag, but that expansion
398     // is not controlled within the options parser, so we ignore it here)
399 
400     // As much as possible, we want the behaviors of these different types of flags to be
401     // identical, as this minimizes the number of edge cases, but we do not yet track these values
402     // in the same way. Wrapper options are replaced by their value and implicit requirements are
403     // hidden from the reported lists of parsed options.
404     if (parsedOption.getImplicitDependent() == null && !optionDefinition.isWrapperOption()) {
405       // Log explicit options and expanded options in the order they are parsed (can be sorted
406       // later). This information is needed to correctly canonicalize flags.
407       parsedOptions.add(parsedOption);
408     }
409 
410     if (expansionBundle != null) {
411       ResidueAndPriority residueAndPriority =
412           parse(
413               OptionPriority.getLockedPriority(parsedOption.getPriority()),
414               o -> expansionBundle.sourceOfExpansionArgs,
415               optionDefinition.hasImplicitRequirements() ? optionDefinition : null,
416               optionDefinition.isExpansionOption() ? optionDefinition : null,
417               expansionBundle.expansionArgs);
418       if (!residueAndPriority.residue.isEmpty()) {
419         if (optionDefinition.isWrapperOption()) {
420           throw new OptionsParsingException(
421               "Unparsed options remain after unwrapping "
422                   + unconvertedValue
423                   + ": "
424                   + Joiner.on(' ').join(residueAndPriority.residue));
425         } else {
426           // Throw an assertion here, because this indicates an error in the definition of this
427           // option's expansion or requirements, not with the input as provided by the user.
428           throw new AssertionError(
429               "Unparsed options remain after processing "
430                   + unconvertedValue
431                   + ": "
432                   + Joiner.on(' ').join(residueAndPriority.residue));
433         }
434       }
435     }
436   }
437 
identifyOptionAndPossibleArgument( String arg, Iterator<String> nextArgs, OptionPriority priority, Function<OptionDefinition, String> sourceFunction, OptionDefinition implicitDependent, OptionDefinition expandedFrom)438   private ParsedOptionDescription identifyOptionAndPossibleArgument(
439       String arg,
440       Iterator<String> nextArgs,
441       OptionPriority priority,
442       Function<OptionDefinition, String> sourceFunction,
443       OptionDefinition implicitDependent,
444       OptionDefinition expandedFrom)
445       throws OptionsParsingException {
446 
447     // Store the way this option was parsed on the command line.
448     StringBuilder commandLineForm = new StringBuilder();
449     commandLineForm.append(arg);
450     String unconvertedValue = null;
451     OptionDefinition optionDefinition;
452     boolean booleanValue = true;
453 
454     if (arg.length() == 2) { // -l  (may be nullary or unary)
455       optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1));
456       booleanValue = true;
457 
458     } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean)
459       optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1));
460       booleanValue = false;
461 
462     } else if (allowSingleDashLongOptions // -long_option
463         || arg.startsWith("--")) { // or --long_option
464 
465       int equalsAt = arg.indexOf('=');
466       int nameStartsAt = arg.startsWith("--") ? 2 : 1;
467       String name =
468           equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt);
469       if (name.trim().isEmpty()) {
470         throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
471       }
472       unconvertedValue = equalsAt == -1 ? null : arg.substring(equalsAt + 1);
473       optionDefinition = optionsData.getOptionDefinitionFromName(name);
474 
475       // Look for a "no"-prefixed option name: "no<optionName>".
476       if (optionDefinition == null && name.startsWith("no")) {
477         name = name.substring(2);
478         optionDefinition = optionsData.getOptionDefinitionFromName(name);
479         booleanValue = false;
480         if (optionDefinition != null) {
481           // TODO(bazel-team): Add tests for these cases.
482           if (!optionDefinition.usesBooleanValueSyntax()) {
483             throw new OptionsParsingException(
484                 "Illegal use of 'no' prefix on non-boolean option: " + arg, arg);
485           }
486           if (unconvertedValue != null) {
487             throw new OptionsParsingException(
488                 "Unexpected value after boolean option: " + arg, arg);
489           }
490           // "no<optionname>" signifies a boolean option w/ false value
491           unconvertedValue = "0";
492         }
493       }
494     } else {
495       throw new OptionsParsingException("Invalid options syntax: " + arg, arg);
496     }
497 
498     if (optionDefinition == null
499         || ImmutableList.copyOf(optionDefinition.getOptionMetadataTags())
500             .contains(OptionMetadataTag.INTERNAL)) {
501       // Do not recognize internal options, which are treated as if they did not exist.
502       throw new OptionsParsingException("Unrecognized option: " + arg, arg);
503     }
504 
505     if (unconvertedValue == null) {
506       // Special-case boolean to supply value based on presence of "no" prefix.
507       if (optionDefinition.usesBooleanValueSyntax()) {
508         unconvertedValue = booleanValue ? "1" : "0";
509       } else if (optionDefinition.getType().equals(Void.class)
510           && !optionDefinition.isWrapperOption()) {
511         // This is expected, Void type options have no args (unless they're wrapper options).
512       } else if (nextArgs.hasNext()) {
513         // "--flag value" form
514         unconvertedValue = nextArgs.next();
515         commandLineForm.append(" ").append(unconvertedValue);
516       } else {
517         throw new OptionsParsingException("Expected value after " + arg);
518       }
519     }
520 
521     return new ParsedOptionDescription(
522         optionDefinition,
523         commandLineForm.toString(),
524         unconvertedValue,
525         new OptionInstanceOrigin(
526             priority, sourceFunction.apply(optionDefinition), implicitDependent, expandedFrom));
527   }
528 
529   /**
530    * Gets the result of parsing the options.
531    */
getParsedOptions(Class<O> optionsClass)532   <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) {
533     // Create the instance:
534     O optionsInstance;
535     try {
536       Constructor<O> constructor = optionsData.getConstructor(optionsClass);
537       if (constructor == null) {
538         return null;
539       }
540       optionsInstance = constructor.newInstance();
541     } catch (ReflectiveOperationException e) {
542       throw new IllegalStateException("Error while instantiating options class", e);
543     }
544 
545     // Set the fields
546     for (OptionDefinition optionDefinition :
547         OptionsData.getAllOptionDefinitionsForClass(optionsClass)) {
548       Object value;
549       OptionValueDescription optionValue = optionValues.get(optionDefinition);
550       if (optionValue == null) {
551         value = optionDefinition.getDefaultValue();
552       } else {
553         value = optionValue.getValue();
554       }
555       try {
556         optionDefinition.getField().set(optionsInstance, value);
557       } catch (IllegalArgumentException e) {
558         throw new IllegalStateException(
559             String.format("Unable to set %s to value '%s'.", optionDefinition, value), e);
560       } catch (IllegalAccessException e) {
561         throw new IllegalStateException(
562             "Could not set the field due to access issues. This is impossible, as the "
563                 + "OptionProcessor checks that all options are non-final public fields.",
564             e);
565       }
566     }
567     return optionsInstance;
568   }
569 
getWarnings()570   List<String> getWarnings() {
571     return ImmutableList.copyOf(warnings);
572   }
573 }
574