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