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