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 */ OptionsParserImpl(OptionsData optionsData)100 OptionsParserImpl(OptionsData optionsData) { 101 this.optionsData = optionsData; 102 } 103 getOptionsData()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 */ setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions)112 void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) { 113 this.allowSingleDashLongOptions = allowSingleDashLongOptions; 114 } 115 116 /** Sets the ArgsPreProcessor for manipulations of the options before parsing. */ setArgsPreProcessor(ArgsPreProcessor preProcessor)117 void setArgsPreProcessor(ArgsPreProcessor preProcessor) { 118 this.argsPreProcessor = Preconditions.checkNotNull(preProcessor); 119 } 120 121 /** 122 * Implements {@link OptionsParser#asListOfUnparsedOptions()}. 123 */ asListOfUnparsedOptions()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 */ asListOfExplicitOptions()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 */ asCanonicalizedList()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 */ asListOfEffectiveOptions()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 maybeAddDeprecationWarning(Field field)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 addDeprecationWarning(String optionName, String warning)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. setValue(Field field, String name, Object value, OptionPriority priority, String source, String implicitDependant, String expandedFrom)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 addListValue(Field field, String originalName, Object value, OptionPriority priority, String source, String implicitDependant, String expandedFrom)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 clearValue(String optionName)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 getOptionValueDescription(String name)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 getOptionDescription(String name)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 */ getExpansionDescriptions( String[] optionStrings, String expandedFrom, String implicitDependant)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 containsExplicitOption(String name)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 */ parse(OptionPriority priority, Function<? super String, String> sourceFunction, List<String> args)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 */ parse( OptionPriority priority, Function<? super String, String> sourceFunction, String implicitDependent, String expandedFrom, List<String> args)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 ParseOptionResult(Field field, Option option, String value)585 ParseOptionResult(Field field, Option option, String value) { 586 this.field = field; 587 this.option = option; 588 this.value = value; 589 } 590 } 591 parseOption(String arg, Iterator<String> nextArgs)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 */ getParsedOptions(Class<O> optionsClass)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 getWarnings()709 List<String> getWarnings() { 710 return ImmutableList.copyOf(warnings); 711 } 712 getDefaultOptionString(Field optionField)713 static String getDefaultOptionString(Field optionField) { 714 Option annotation = optionField.getAnnotation(Option.class); 715 return annotation.defaultValue(); 716 } 717 isSpecialNullDefault(String defaultValueString, Field optionField)718 static boolean isSpecialNullDefault(String defaultValueString, Field optionField) { 719 return defaultValueString.equals("null") && !optionField.getType().isPrimitive(); 720 } 721 } 722