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