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.collect.ImmutableList; 22 import com.google.common.collect.ListMultimap; 23 import com.google.common.collect.Lists; 24 import com.google.common.collect.Maps; 25 import com.google.common.escape.Escaper; 26 import java.lang.reflect.Field; 27 import java.nio.file.FileSystem; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.List; 34 import java.util.Map; 35 36 /** 37 * A parser for options. Typical use case in a main method: 38 * 39 * <pre> 40 * OptionsParser parser = OptionsParser.newOptionsParser(FooOptions.class, BarOptions.class); 41 * parser.parseAndExitUponError(args); 42 * FooOptions foo = parser.getOptions(FooOptions.class); 43 * BarOptions bar = parser.getOptions(BarOptions.class); 44 * List<String> otherArguments = parser.getResidue(); 45 * </pre> 46 * 47 * <p>FooOptions and BarOptions would be options specification classes, derived from OptionsBase, 48 * that contain fields annotated with @Option(...). 49 * 50 * <p>Alternatively, rather than calling {@link #parseAndExitUponError(OptionPriority, String, 51 * String[])}, client code may call {@link #parse(OptionPriority,String,List)}, and handle parser 52 * exceptions usage messages themselves. 53 * 54 * <p>This options parsing implementation has (at least) one design flaw. It allows both '--foo=baz' 55 * and '--foo baz' for all options except void, boolean and tristate options. For these, the 'baz' 56 * in '--foo baz' is not treated as a parameter to the option, making it is impossible to switch 57 * options between void/boolean/tristate and everything else without breaking backwards 58 * compatibility. 59 * 60 * @see Options a simpler class which you can use if you only have one options specification class 61 */ 62 public class OptionsParser implements OptionsProvider { 63 64 /** 65 * A cache for the parsed options data. Both keys and values are immutable, so 66 * this is always safe. Only access this field through the {@link 67 * #getOptionsData} method for thread-safety! The cache is very unlikely to 68 * grow to a significant amount of memory, because there's only a fixed set of 69 * options classes on the classpath. 70 */ 71 private static final Map<ImmutableList<Class<? extends OptionsBase>>, OptionsData> optionsData = 72 Maps.newHashMap(); 73 74 /** 75 * Returns {@link OpaqueOptionsData} suitable for passing along to 76 * {@link #newOptionsParser(OpaqueOptionsData optionsData)}. 77 * 78 * This is useful when you want to do the work of analyzing the given {@code optionsClasses} 79 * exactly once, but you want to parse lots of different lists of strings (and thus need to 80 * construct lots of different {@link OptionsParser} instances). 81 */ 82 public static OpaqueOptionsData getOptionsData( 83 ImmutableList<Class<? extends OptionsBase>> optionsClasses) { 84 return getOptionsDataInternal(optionsClasses); 85 } 86 87 private static synchronized OptionsData getOptionsDataInternal( 88 ImmutableList<Class<? extends OptionsBase>> optionsClasses) { 89 OptionsData result = optionsData.get(optionsClasses); 90 if (result == null) { 91 result = OptionsData.from(optionsClasses); 92 optionsData.put(optionsClasses, result); 93 } 94 return result; 95 } 96 97 /** 98 * Returns all the annotated fields for the given class, including inherited 99 * ones. 100 */ 101 static Collection<Field> getAllAnnotatedFields(Class<? extends OptionsBase> optionsClass) { 102 OptionsData data = getOptionsDataInternal( 103 ImmutableList.<Class<? extends OptionsBase>>of(optionsClass)); 104 return data.getFieldsForClass(optionsClass); 105 } 106 107 /** 108 * @see #newOptionsParser(Iterable) 109 */ 110 public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1) { 111 return newOptionsParser(ImmutableList.<Class<? extends OptionsBase>>of(class1)); 112 } 113 114 /** 115 * @see #newOptionsParser(Iterable) 116 */ 117 public static OptionsParser newOptionsParser(Class<? extends OptionsBase> class1, 118 Class<? extends OptionsBase> class2) { 119 return newOptionsParser(ImmutableList.of(class1, class2)); 120 } 121 122 /** 123 * Create a new {@link OptionsParser}. 124 */ 125 public static OptionsParser newOptionsParser( 126 Iterable<? extends Class<? extends OptionsBase>> optionsClasses) { 127 return newOptionsParser( 128 getOptionsDataInternal(ImmutableList.<Class<? extends OptionsBase>>copyOf(optionsClasses))); 129 } 130 131 /** 132 * Create a new {@link OptionsParser}, using {@link OpaqueOptionsData} previously returned from 133 * {@link #getOptionsData}. 134 */ 135 public static OptionsParser newOptionsParser(OpaqueOptionsData optionsData) { 136 return new OptionsParser((OptionsData) optionsData); 137 } 138 139 private final OptionsParserImpl impl; 140 private final List<String> residue = new ArrayList<String>(); 141 private boolean allowResidue = true; 142 143 OptionsParser(OptionsData optionsData) { 144 impl = new OptionsParserImpl(optionsData); 145 } 146 147 /** 148 * Indicates whether or not the parser will allow a non-empty residue; that 149 * is, iff this value is true then a call to one of the {@code parse} 150 * methods will throw {@link OptionsParsingException} unless 151 * {@link #getResidue()} is empty after parsing. 152 */ 153 public void setAllowResidue(boolean allowResidue) { 154 this.allowResidue = allowResidue; 155 } 156 157 /** 158 * Indicates whether or not the parser will allow long options with a 159 * single-dash, instead of the usual double-dash, too, eg. -example instead of just --example. 160 */ 161 public void setAllowSingleDashLongOptions(boolean allowSingleDashLongOptions) { 162 this.impl.setAllowSingleDashLongOptions(allowSingleDashLongOptions); 163 } 164 165 /** Enables the Parser to handle params files loacted insinde the provided {@link FileSystem}. */ 166 public void enableParamsFileSupport(FileSystem fs) { 167 this.impl.setArgsPreProcessor(new ParamsFilePreProcessor(fs)); 168 } 169 170 public void parseAndExitUponError(String[] args) { 171 parseAndExitUponError(OptionPriority.COMMAND_LINE, "unknown", args); 172 } 173 174 /** 175 * A convenience function for use in main methods. Parses the command line 176 * parameters, and exits upon error. Also, prints out the usage message 177 * if "--help" appears anywhere within {@code args}. 178 */ 179 public void parseAndExitUponError(OptionPriority priority, String source, String[] args) { 180 for (String arg : args) { 181 if (arg.equals("--help")) { 182 System.out.println(describeOptions(Collections.<String, String>emptyMap(), 183 HelpVerbosity.LONG)); 184 System.exit(0); 185 } 186 } 187 try { 188 parse(priority, source, Arrays.asList(args)); 189 } catch (OptionsParsingException e) { 190 System.err.println("Error parsing command line: " + e.getMessage()); 191 System.err.println("Try --help."); 192 System.exit(2); 193 } 194 } 195 196 /** 197 * The metadata about an option. 198 */ 199 public static final class OptionDescription { 200 201 private final String name; 202 private final Object defaultValue; 203 private final Converter<?> converter; 204 private final boolean allowMultiple; 205 206 public OptionDescription(String name, Object defaultValue, Converter<?> converter, 207 boolean allowMultiple) { 208 this.name = name; 209 this.defaultValue = defaultValue; 210 this.converter = converter; 211 this.allowMultiple = allowMultiple; 212 } 213 214 public String getName() { 215 return name; 216 } 217 218 public Object getDefaultValue() { 219 return defaultValue; 220 } 221 222 public Converter<?> getConverter() { 223 return converter; 224 } 225 226 public boolean getAllowMultiple() { 227 return allowMultiple; 228 } 229 } 230 231 /** 232 * The name and value of an option with additional metadata describing its 233 * priority, source, whether it was set via an implicit dependency, and if so, 234 * by which other option. 235 */ 236 public static class OptionValueDescription { 237 private final String name; 238 private final Object value; 239 private final OptionPriority priority; 240 private final String source; 241 private final String implicitDependant; 242 private final String expandedFrom; 243 private final boolean allowMultiple; 244 245 public OptionValueDescription( 246 String name, 247 Object value, 248 OptionPriority priority, 249 String source, 250 String implicitDependant, 251 String expandedFrom, 252 boolean allowMultiple) { 253 this.name = name; 254 this.value = value; 255 this.priority = priority; 256 this.source = source; 257 this.implicitDependant = implicitDependant; 258 this.expandedFrom = expandedFrom; 259 this.allowMultiple = allowMultiple; 260 } 261 262 public String getName() { 263 return name; 264 } 265 266 // Need to suppress unchecked warnings, because the "multiple occurrence" 267 // options use unchecked ListMultimaps due to limitations of Java generics. 268 @SuppressWarnings({"unchecked", "rawtypes"}) 269 public Object getValue() { 270 if (allowMultiple) { 271 // Sort the results by option priority and return them in a new list. 272 // The generic type of the list is not known at runtime, so we can't 273 // use it here. It was already checked in the constructor, so this is 274 // type-safe. 275 List result = Lists.newArrayList(); 276 ListMultimap realValue = (ListMultimap) value; 277 for (OptionPriority priority : OptionPriority.values()) { 278 // If there is no mapping for this key, this check avoids object creation (because 279 // ListMultimap has to return a new object on get) and also an unnecessary addAll call. 280 if (realValue.containsKey(priority)) { 281 result.addAll(realValue.get(priority)); 282 } 283 } 284 return result; 285 } 286 return value; 287 } 288 /** 289 * @return the priority of the thing that set this value for this flag 290 */ 291 public OptionPriority getPriority() { 292 return priority; 293 } 294 295 /** 296 * @return the thing that set this value for this flag 297 */ 298 public String getSource() { 299 return source; 300 } 301 302 public String getImplicitDependant() { 303 return implicitDependant; 304 } 305 306 public boolean isImplicitDependency() { 307 return implicitDependant != null; 308 } 309 310 public String getExpansionParent() { 311 return expandedFrom; 312 } 313 314 public boolean isExpansion() { 315 return expandedFrom != null; 316 } 317 318 @Override 319 public String toString() { 320 StringBuilder result = new StringBuilder(); 321 result.append("option '").append(name).append("' "); 322 result.append("set to '").append(value).append("' "); 323 result.append("with priority ").append(priority); 324 if (source != null) { 325 result.append(" and source '").append(source).append("'"); 326 } 327 if (implicitDependant != null) { 328 result.append(" implicitly by "); 329 } 330 return result.toString(); 331 } 332 333 // Need to suppress unchecked warnings, because the "multiple occurrence" 334 // options use unchecked ListMultimaps due to limitations of Java generics. 335 @SuppressWarnings({"unchecked", "rawtypes"}) 336 void addValue(OptionPriority addedPriority, Object addedValue) { 337 Preconditions.checkState(allowMultiple); 338 ListMultimap optionValueList = (ListMultimap) value; 339 if (addedValue instanceof List<?>) { 340 optionValueList.putAll(addedPriority, (List<?>) addedValue); 341 } else { 342 optionValueList.put(addedPriority, addedValue); 343 } 344 } 345 } 346 347 /** 348 * The name and unparsed value of an option with additional metadata describing its 349 * priority, source, whether it was set via an implicit dependency, and if so, 350 * by which other option. 351 * 352 * <p>Note that the unparsed value and the source parameters can both be null. 353 */ 354 public static class UnparsedOptionValueDescription { 355 private final String name; 356 private final Field field; 357 private final String unparsedValue; 358 private final OptionPriority priority; 359 private final String source; 360 private final boolean explicit; 361 362 public UnparsedOptionValueDescription(String name, Field field, String unparsedValue, 363 OptionPriority priority, String source, boolean explicit) { 364 this.name = name; 365 this.field = field; 366 this.unparsedValue = unparsedValue; 367 this.priority = priority; 368 this.source = source; 369 this.explicit = explicit; 370 } 371 372 public String getName() { 373 return name; 374 } 375 376 Field getField() { 377 return field; 378 } 379 380 public boolean isBooleanOption() { 381 return field.getType().equals(boolean.class); 382 } 383 384 private DocumentationLevel documentationLevel() { 385 Option option = field.getAnnotation(Option.class); 386 return OptionsParser.documentationLevel(option.category()); 387 } 388 389 public boolean isDocumented() { 390 return documentationLevel() == DocumentationLevel.DOCUMENTED; 391 } 392 393 public boolean isHidden() { 394 return documentationLevel() == DocumentationLevel.HIDDEN 395 || documentationLevel() == DocumentationLevel.INTERNAL; 396 } 397 398 boolean isExpansion() { 399 Option option = field.getAnnotation(Option.class); 400 return (option.expansion().length > 0 401 || OptionsData.usesExpansionFunction(option)); 402 } 403 404 boolean isImplicitRequirement() { 405 Option option = field.getAnnotation(Option.class); 406 return option.implicitRequirements().length > 0; 407 } 408 409 boolean allowMultiple() { 410 Option option = field.getAnnotation(Option.class); 411 return option.allowMultiple(); 412 } 413 414 public String getUnparsedValue() { 415 return unparsedValue; 416 } 417 418 OptionPriority getPriority() { 419 return priority; 420 } 421 422 public String getSource() { 423 return source; 424 } 425 426 public boolean isExplicit() { 427 return explicit; 428 } 429 430 @Override 431 public String toString() { 432 StringBuilder result = new StringBuilder(); 433 result.append("option '").append(name).append("' "); 434 result.append("set to '").append(unparsedValue).append("' "); 435 result.append("with priority ").append(priority); 436 if (source != null) { 437 result.append(" and source '").append(source).append("'"); 438 } 439 return result.toString(); 440 } 441 } 442 443 /** 444 * The verbosity with which option help messages are displayed: short (just 445 * the name), medium (name, type, default, abbreviation), and long (full 446 * description). 447 */ 448 public enum HelpVerbosity { LONG, MEDIUM, SHORT } 449 450 /** 451 * The level of documentation. Only documented options are output as part of 452 * the help. 453 * 454 * <p>We use 'hidden' so that options that form the protocol between the 455 * client and the server are not logged. 456 * 457 * <p>Options which are 'internal' are not recognized by the parser at all. 458 */ 459 enum DocumentationLevel { 460 DOCUMENTED, UNDOCUMENTED, HIDDEN, INTERNAL 461 } 462 463 /** 464 * Returns a description of all the options this parser can digest. In addition to {@link Option} 465 * annotations, this method also interprets {@link OptionsUsage} annotations which give an 466 * intuitive short description for the options. Options of the same category (see {@link 467 * Option#category}) will be grouped together. 468 * 469 * @param categoryDescriptions a mapping from category names to category descriptions. 470 * Descriptions are optional; if omitted, a string based on the category name will be used. 471 * @param helpVerbosity if {@code long}, the options will be described verbosely, including their 472 * types, defaults and descriptions. If {@code medium}, the descriptions are omitted, and if 473 * {@code short}, the options are just enumerated. 474 */ 475 public String describeOptions( 476 Map<String, String> categoryDescriptions, HelpVerbosity helpVerbosity) { 477 StringBuilder desc = new StringBuilder(); 478 if (!impl.getOptionsClasses().isEmpty()) { 479 List<Field> allFields = Lists.newArrayList(); 480 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 481 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 482 } 483 Collections.sort(allFields, OptionsUsage.BY_CATEGORY); 484 String prevCategory = null; 485 486 for (Field optionField : allFields) { 487 String category = optionField.getAnnotation(Option.class).category(); 488 if (!category.equals(prevCategory)) { 489 prevCategory = category; 490 String description = categoryDescriptions.get(category); 491 if (description == null) { 492 description = "Options category '" + category + "'"; 493 } 494 if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) { 495 desc.append("\n").append(description).append(":\n"); 496 } 497 } 498 499 if (documentationLevel(prevCategory) == DocumentationLevel.DOCUMENTED) { 500 OptionsUsage.getUsage(optionField, desc, helpVerbosity, impl.getOptionsData()); 501 } 502 } 503 } 504 return desc.toString().trim(); 505 } 506 507 /** 508 * Returns a description of all the options this parser can digest. 509 * In addition to {@link Option} annotations, this method also 510 * interprets {@link OptionsUsage} annotations which give an intuitive short 511 * description for the options. 512 * 513 * @param categoryDescriptions a mapping from category names to category 514 * descriptions. Options of the same category (see {@link 515 * Option#category}) will be grouped together, preceded by the description 516 * of the category. 517 */ 518 public String describeOptionsHtml(Map<String, String> categoryDescriptions, Escaper escaper) { 519 StringBuilder desc = new StringBuilder(); 520 if (!impl.getOptionsClasses().isEmpty()) { 521 List<Field> allFields = Lists.newArrayList(); 522 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 523 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 524 } 525 Collections.sort(allFields, OptionsUsage.BY_CATEGORY); 526 String prevCategory = null; 527 528 for (Field optionField : allFields) { 529 String category = optionField.getAnnotation(Option.class).category(); 530 DocumentationLevel level = documentationLevel(category); 531 if (!category.equals(prevCategory) && level == DocumentationLevel.DOCUMENTED) { 532 String description = categoryDescriptions.get(category); 533 if (description == null) { 534 description = "Options category '" + category + "'"; 535 } 536 if (prevCategory != null) { 537 desc.append("</dl>\n\n"); 538 } 539 desc.append(escaper.escape(description)).append(":\n"); 540 desc.append("<dl>"); 541 prevCategory = category; 542 } 543 544 if (level == DocumentationLevel.DOCUMENTED) { 545 OptionsUsage.getUsageHtml(optionField, desc, escaper, impl.getOptionsData()); 546 } 547 } 548 desc.append("</dl>\n"); 549 } 550 return desc.toString(); 551 } 552 553 /** 554 * Returns a string listing the possible flag completion for this command along with the command 555 * completion if any. See {@link OptionsUsage#getCompletion(Field, StringBuilder)} for more 556 * details on the format for the flag completion. 557 */ 558 public String getOptionsCompletion() { 559 StringBuilder desc = new StringBuilder(); 560 561 // List all options 562 List<Field> allFields = Lists.newArrayList(); 563 for (Class<? extends OptionsBase> optionsClass : impl.getOptionsClasses()) { 564 allFields.addAll(impl.getAnnotatedFieldsFor(optionsClass)); 565 } 566 // Sort field for deterministic ordering 567 Collections.sort(allFields, new Comparator<Field>() { 568 @Override 569 public int compare(Field f1, Field f2) { 570 String name1 = f1.getAnnotation(Option.class).name(); 571 String name2 = f2.getAnnotation(Option.class).name(); 572 return name1.compareTo(name2); 573 } 574 }); 575 for (Field optionField : allFields) { 576 String category = optionField.getAnnotation(Option.class).category(); 577 if (documentationLevel(category) == DocumentationLevel.DOCUMENTED) { 578 OptionsUsage.getCompletion(optionField, desc); 579 } 580 } 581 582 return desc.toString(); 583 } 584 585 /** 586 * Returns a description of the option. 587 * 588 * @return The {@link OptionValueDescription} for the option, or null if there is no option by 589 * the given name. 590 */ 591 public OptionDescription getOptionDescription(String name) { 592 return impl.getOptionDescription(name); 593 } 594 595 /** 596 * Returns a description of the option value set by the last previous call to 597 * {@link #parse(OptionPriority, String, List)} that successfully set the given 598 * option. If the option is of type {@link List}, the description will 599 * correspond to any one of the calls, but not necessarily the last. 600 * 601 * @return The {@link OptionValueDescription} for the option, or null if the value has not been 602 * set. 603 * @throws IllegalArgumentException if there is no option by the given name. 604 */ 605 public OptionValueDescription getOptionValueDescription(String name) { 606 return impl.getOptionValueDescription(name); 607 } 608 609 static DocumentationLevel documentationLevel(String category) { 610 if ("undocumented".equals(category)) { 611 return DocumentationLevel.UNDOCUMENTED; 612 } else if ("hidden".equals(category)) { 613 return DocumentationLevel.HIDDEN; 614 } else if ("internal".equals(category)) { 615 return DocumentationLevel.INTERNAL; 616 } else { 617 return DocumentationLevel.DOCUMENTED; 618 } 619 } 620 621 /** 622 * A convenience method, equivalent to 623 * {@code parse(OptionPriority.COMMAND_LINE, null, Arrays.asList(args))}. 624 */ 625 public void parse(String... args) throws OptionsParsingException { 626 parse(OptionPriority.COMMAND_LINE, (String) null, Arrays.asList(args)); 627 } 628 629 /** 630 * A convenience method, equivalent to 631 * {@code parse(OptionPriority.COMMAND_LINE, null, args)}. 632 */ 633 public void parse(List<String> args) throws OptionsParsingException { 634 parse(OptionPriority.COMMAND_LINE, (String) null, args); 635 } 636 637 /** 638 * Parses {@code args}, using the classes registered with this parser. 639 * {@link #getOptions(Class)} and {@link #getResidue()} return the results. 640 * May be called multiple times; later options override existing ones if they 641 * have equal or higher priority. The source of options is a free-form string 642 * that can be used for debugging. Strings that cannot be parsed as options 643 * accumulates as residue, if this parser allows it. 644 * 645 * @see OptionPriority 646 */ 647 public void parse(OptionPriority priority, String source, 648 List<String> args) throws OptionsParsingException { 649 parseWithSourceFunction(priority, Functions.constant(source), args); 650 } 651 652 /** 653 * Parses {@code args}, using the classes registered with this parser. 654 * {@link #getOptions(Class)} and {@link #getResidue()} return the results. May be called 655 * multiple times; later options override existing ones if they have equal or higher priority. 656 * The source of options is given as a function that maps option names to the source of the 657 * option. Strings that cannot be parsed as options accumulates as* residue, if this parser 658 * allows it. 659 */ 660 public void parseWithSourceFunction(OptionPriority priority, 661 Function<? super String, String> sourceFunction, List<String> args) 662 throws OptionsParsingException { 663 Preconditions.checkNotNull(priority); 664 Preconditions.checkArgument(priority != OptionPriority.DEFAULT); 665 residue.addAll(impl.parse(priority, sourceFunction, args)); 666 if (!allowResidue && !residue.isEmpty()) { 667 String errorMsg = "Unrecognized arguments: " + Joiner.on(' ').join(residue); 668 throw new OptionsParsingException(errorMsg); 669 } 670 } 671 672 /** 673 * Clears the given option. Also clears expansion arguments and implicit requirements for that 674 * option. 675 * 676 * <p>This will not affect options objects that have already been retrieved from this parser 677 * through {@link #getOptions(Class)}. 678 * 679 * @param optionName The full name of the option to clear. 680 * @return A map of an option name to the old value of the options that were cleared. 681 * @throws IllegalArgumentException If the flag does not exist. 682 */ 683 public Map<String, OptionValueDescription> clearValue(String optionName) 684 throws OptionsParsingException { 685 Map<String, OptionValueDescription> clearedValues = Maps.newHashMap(); 686 impl.clearValue(optionName, clearedValues); 687 return clearedValues; 688 } 689 690 @Override 691 public List<String> getResidue() { 692 return ImmutableList.copyOf(residue); 693 } 694 695 /** 696 * Returns a list of warnings about problems encountered by previous parse calls. 697 */ 698 public List<String> getWarnings() { 699 return impl.getWarnings(); 700 } 701 702 @Override 703 public <O extends OptionsBase> O getOptions(Class<O> optionsClass) { 704 return impl.getParsedOptions(optionsClass); 705 } 706 707 @Override 708 public boolean containsExplicitOption(String name) { 709 return impl.containsExplicitOption(name); 710 } 711 712 @Override 713 public List<UnparsedOptionValueDescription> asListOfUnparsedOptions() { 714 return impl.asListOfUnparsedOptions(); 715 } 716 717 @Override 718 public List<UnparsedOptionValueDescription> asListOfExplicitOptions() { 719 return impl.asListOfExplicitOptions(); 720 } 721 722 @Override 723 public List<OptionValueDescription> asListOfEffectiveOptions() { 724 return impl.asListOfEffectiveOptions(); 725 } 726 727 @Override 728 public List<String> canonicalize() { 729 return impl.asCanonicalizedList(); 730 } 731 } 732