001/* 002 Licensed to the Apache Software Foundation (ASF) under one or more 003 contributor license agreements. See the NOTICE file distributed with 004 this work for additional information regarding copyright ownership. 005 The ASF licenses this file to You under the Apache License, Version 2.0 006 (the "License"); you may not use this file except in compliance with 007 the License. You may obtain a copy of the License at 008 009 http://www.apache.org/licenses/LICENSE-2.0 010 011 Unless required by applicable law or agreed to in writing, software 012 distributed under the License is distributed on an "AS IS" BASIS, 013 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 See the License for the specific language governing permissions and 015 limitations under the License. 016 */ 017 018package org.apache.commons.cli; 019 020import java.util.ArrayList; 021import java.util.Enumeration; 022import java.util.List; 023import java.util.Properties; 024 025/** 026 * Default parser. 027 * 028 * @since 1.3 029 */ 030public class DefaultParser implements CommandLineParser { 031 032 /** 033 * A nested builder class to create {@code DefaultParser} instances 034 * using descriptive methods. 035 * 036 * Example usage: 037 * <pre> 038 * DefaultParser parser = Option.builder() 039 * .setAllowPartialMatching(false) 040 * .setStripLeadingAndTrailingQuotes(false) 041 * .build(); 042 * </pre> 043 * 044 * @since 1.5.0 045 */ 046 public static final class Builder { 047 048 /** Flag indicating if partial matching of long options is supported. */ 049 private boolean allowPartialMatching = true; 050 051 /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments. */ 052 private Boolean stripLeadingAndTrailingQuotes; 053 054 /** 055 * Constructs a new {@code Builder} for a {@code DefaultParser} instance. 056 * 057 * Both allowPartialMatching and stripLeadingAndTrailingQuotes are true by default, 058 * mimicking the argument-less constructor. 059 */ 060 private Builder() { 061 } 062 063 /** 064 * Builds an DefaultParser with the values declared by this {@link Builder}. 065 * 066 * @return the new {@link DefaultParser} 067 * @since 1.5.0 068 */ 069 public DefaultParser build() { 070 return new DefaultParser(allowPartialMatching, stripLeadingAndTrailingQuotes); 071 } 072 073 /** 074 * Sets if partial matching of long options is supported. 075 * 076 * By "partial matching" we mean that given the following code: 077 * 078 * <pre> 079 * { 080 * @code 081 * final Options options = new Options(); 082 * options.addOption(new Option("d", "debug", false, "Turn on debug.")); 083 * options.addOption(new Option("e", "extract", false, "Turn on extract.")); 084 * options.addOption(new Option("o", "option", true, "Turn on option with argument.")); 085 * } 086 * </pre> 087 * 088 * If "partial matching" is turned on, {@code -de} only matches the {@code "debug"} option. However, with 089 * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract} 090 * 091 * @param allowPartialMatching whether to allow partial matching of long options 092 * @return this builder, to allow method chaining 093 * @since 1.5.0 094 */ 095 public Builder setAllowPartialMatching(final boolean allowPartialMatching) { 096 this.allowPartialMatching = allowPartialMatching; 097 return this; 098 } 099 100 /** 101 * Sets if balanced leading and trailing double quotes should be stripped from option arguments. 102 * 103 * If "stripping of balanced leading and trailing double quotes from option arguments" is true, 104 * the outermost balanced double quotes of option arguments values will be removed. 105 * For example, {@code -o '"x"'} getValue() will return {@code x}, instead of {@code "x"} 106 * 107 * If "stripping of balanced leading and trailing double quotes from option arguments" is null, 108 * then quotes will be stripped from option values separated by space from the option, but 109 * kept in other cases, which is the historic behaviour. 110 * 111 * @param stripLeadingAndTrailingQuotes whether balanced leading and trailing double quotes should be stripped from option arguments. 112 * @return this builder, to allow method chaining 113 * @since 1.5.0 114 */ 115 public Builder setStripLeadingAndTrailingQuotes(final Boolean stripLeadingAndTrailingQuotes) { 116 this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes; 117 return this; 118 } 119 } 120 121 /** 122 * Creates a new {@link Builder} to create an {@link DefaultParser} using descriptive 123 * methods. 124 * 125 * @return a new {@link Builder} instance 126 * @since 1.5.0 127 */ 128 public static Builder builder() { 129 return new Builder(); 130 } 131 132 /** The command-line instance. */ 133 protected CommandLine cmd; 134 135 /** The current options. */ 136 protected Options options; 137 138 /** 139 * Flag indicating how unrecognized tokens are handled. {@code true} to stop the parsing and add the remaining 140 * tokens to the args list. {@code false} to throw an exception. 141 */ 142 protected boolean stopAtNonOption; 143 144 /** The token currently processed. */ 145 protected String currentToken; 146 147 /** The last option parsed. */ 148 protected Option currentOption; 149 150 /** Flag indicating if tokens should no longer be analyzed and simply added as arguments of the command line. */ 151 protected boolean skipParsing; 152 153 /** The required options and groups expected to be found when parsing the command line. */ 154 protected List expectedOpts; 155 156 /** Flag indicating if partial matching of long options is supported. */ 157 private final boolean allowPartialMatching; 158 159 /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments. 160 * null represents the historic arbitrary behaviour */ 161 private final Boolean stripLeadingAndTrailingQuotes; 162 163 /** 164 * Creates a new DefaultParser instance with partial matching enabled. 165 * 166 * By "partial matching" we mean that given the following code: 167 * 168 * <pre> 169 * { 170 * @code 171 * final Options options = new Options(); 172 * options.addOption(new Option("d", "debug", false, "Turn on debug.")); 173 * options.addOption(new Option("e", "extract", false, "Turn on extract.")); 174 * options.addOption(new Option("o", "option", true, "Turn on option with argument.")); 175 * } 176 * </pre> 177 * 178 * with "partial matching" turned on, {@code -de} only matches the {@code "debug"} option. However, with 179 * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract} 180 * options. 181 */ 182 public DefaultParser() { 183 this.allowPartialMatching = true; 184 this.stripLeadingAndTrailingQuotes = null; 185 } 186 187 /** 188 * Create a new DefaultParser instance with the specified partial matching policy. 189 * 190 * By "partial matching" we mean that given the following code: 191 * 192 * <pre> 193 * { 194 * @code 195 * final Options options = new Options(); 196 * options.addOption(new Option("d", "debug", false, "Turn on debug.")); 197 * options.addOption(new Option("e", "extract", false, "Turn on extract.")); 198 * options.addOption(new Option("o", "option", true, "Turn on option with argument.")); 199 * } 200 * </pre> 201 * 202 * with "partial matching" turned on, {@code -de} only matches the {@code "debug"} option. However, with 203 * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract} 204 * options. 205 * 206 * @param allowPartialMatching if partial matching of long options shall be enabled 207 */ 208 public DefaultParser(final boolean allowPartialMatching) { 209 this.allowPartialMatching = allowPartialMatching; 210 this.stripLeadingAndTrailingQuotes = null; 211 } 212 213 /** 214 * Creates a new DefaultParser instance with the specified partial matching and quote 215 * stripping policy. 216 * 217 * @param allowPartialMatching if partial matching of long options shall be enabled 218 * @param stripLeadingAndTrailingQuotes if balanced outer double quoutes should be stripped 219 */ 220 private DefaultParser(final boolean allowPartialMatching, 221 final Boolean stripLeadingAndTrailingQuotes) { 222 this.allowPartialMatching = allowPartialMatching; 223 this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes; 224 } 225 226 /** 227 * Throws a {@link MissingArgumentException} if the current option didn't receive the number of arguments expected. 228 */ 229 private void checkRequiredArgs() throws ParseException { 230 if (currentOption != null && currentOption.requiresArg()) { 231 throw new MissingArgumentException(currentOption); 232 } 233 } 234 235 /** 236 * Throws a {@link MissingOptionException} if all of the required options are not present. 237 * 238 * @throws MissingOptionException if any of the required Options are not present. 239 */ 240 protected void checkRequiredOptions() throws MissingOptionException { 241 // if there are required options that have not been processed 242 if (!expectedOpts.isEmpty()) { 243 throw new MissingOptionException(expectedOpts); 244 } 245 } 246 247 /** 248 * Searches for a prefix that is the long name of an option (-Xmx512m) 249 * 250 * @param token 251 */ 252 private String getLongPrefix(final String token) { 253 final String t = Util.stripLeadingHyphens(token); 254 255 int i; 256 String opt = null; 257 for (i = t.length() - 2; i > 1; i--) { 258 final String prefix = t.substring(0, i); 259 if (options.hasLongOption(prefix)) { 260 opt = prefix; 261 break; 262 } 263 } 264 265 return opt; 266 } 267 268 /** 269 * Gets a list of matching option strings for the given token, depending on the selected partial matching policy. 270 * 271 * @param token the token (may contain leading dashes) 272 * @return the list of matching option strings or an empty list if no matching option could be found 273 */ 274 private List<String> getMatchingLongOptions(final String token) { 275 if (allowPartialMatching) { 276 return options.getMatchingOptions(token); 277 } 278 final List<String> matches = new ArrayList<>(1); 279 if (options.hasLongOption(token)) { 280 final Option option = options.getOption(token); 281 matches.add(option.getLongOpt()); 282 } 283 284 return matches; 285 } 286 287 /** 288 * Breaks {@code token} into its constituent parts using the following algorithm. 289 * 290 * <ul> 291 * <li>ignore the first character ("<b>-</b>")</li> 292 * <li>for each remaining character check if an {@link Option} exists with that id.</li> 293 * <li>if an {@link Option} does exist then add that character prepended with "<b>-</b>" to the list of processed 294 * tokens.</li> 295 * <li>if the {@link Option} can have an argument value and there are remaining characters in the token then add the 296 * remaining characters as a token to the list of processed tokens.</li> 297 * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS</b> set then add the 298 * special token "<b>--</b>" followed by the remaining characters and also the remaining tokens directly to the 299 * processed tokens list.</li> 300 * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS NOT</b> set then add 301 * that character prepended with "<b>-</b>".</li> 302 * </ul> 303 * 304 * @param token The current token to be <b>burst</b> at the first non-Option encountered. 305 * @throws ParseException if there are any problems encountered while parsing the command line token. 306 */ 307 protected void handleConcatenatedOptions(final String token) throws ParseException { 308 for (int i = 1; i < token.length(); i++) { 309 final String ch = String.valueOf(token.charAt(i)); 310 311 if (!options.hasOption(ch)) { 312 handleUnknownToken(stopAtNonOption && i > 1 ? token.substring(i) : token); 313 break; 314 } 315 handleOption(options.getOption(ch)); 316 317 if (currentOption != null && token.length() != i + 1) { 318 // add the trail as an argument of the option 319 currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(token.substring(i + 1))); 320 break; 321 } 322 } 323 } 324 325 /** 326 * Handles the following tokens: 327 * 328 * --L --L=V --L V --l 329 * 330 * @param token the command line token to handle 331 */ 332 private void handleLongOption(final String token) throws ParseException { 333 if (token.indexOf('=') == -1) { 334 handleLongOptionWithoutEqual(token); 335 } else { 336 handleLongOptionWithEqual(token); 337 } 338 } 339 340 /** 341 * Handles the following tokens: 342 * 343 * --L=V -L=V --l=V -l=V 344 * 345 * @param token the command line token to handle 346 */ 347 private void handleLongOptionWithEqual(final String token) throws ParseException { 348 final int pos = token.indexOf('='); 349 350 final String value = token.substring(pos + 1); 351 352 final String opt = token.substring(0, pos); 353 354 final List<String> matchingOpts = getMatchingLongOptions(opt); 355 if (matchingOpts.isEmpty()) { 356 handleUnknownToken(currentToken); 357 } else if (matchingOpts.size() > 1 && !options.hasLongOption(opt)) { 358 throw new AmbiguousOptionException(opt, matchingOpts); 359 } else { 360 final String key = options.hasLongOption(opt) ? opt : matchingOpts.get(0); 361 final Option option = options.getOption(key); 362 363 if (option.acceptsArg()) { 364 handleOption(option); 365 currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(value)); 366 currentOption = null; 367 } else { 368 handleUnknownToken(currentToken); 369 } 370 } 371 } 372 373 /** 374 * Handles the following tokens: 375 * 376 * --L -L --l -l 377 * 378 * @param token the command line token to handle 379 */ 380 private void handleLongOptionWithoutEqual(final String token) throws ParseException { 381 final List<String> matchingOpts = getMatchingLongOptions(token); 382 if (matchingOpts.isEmpty()) { 383 handleUnknownToken(currentToken); 384 } else if (matchingOpts.size() > 1 && !options.hasLongOption(token)) { 385 throw new AmbiguousOptionException(token, matchingOpts); 386 } else { 387 final String key = options.hasLongOption(token) ? token : matchingOpts.get(0); 388 handleOption(options.getOption(key)); 389 } 390 } 391 392 private void handleOption(Option option) throws ParseException { 393 // check the previous option before handling the next one 394 checkRequiredArgs(); 395 396 option = (Option) option.clone(); 397 398 updateRequiredOptions(option); 399 400 cmd.addOption(option); 401 402 if (option.hasArg()) { 403 currentOption = option; 404 } else { 405 currentOption = null; 406 } 407 } 408 409 /** 410 * Sets the values of Options using the values in {@code properties}. 411 * 412 * @param properties The value properties to be processed. 413 */ 414 private void handleProperties(final Properties properties) throws ParseException { 415 if (properties == null) { 416 return; 417 } 418 419 for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) { 420 final String option = e.nextElement().toString(); 421 422 final Option opt = options.getOption(option); 423 if (opt == null) { 424 throw new UnrecognizedOptionException("Default option wasn't defined", option); 425 } 426 427 // if the option is part of a group, check if another option of the group has been selected 428 final OptionGroup group = options.getOptionGroup(opt); 429 final boolean selected = group != null && group.getSelected() != null; 430 431 if (!cmd.hasOption(option) && !selected) { 432 // get the value from the properties 433 final String value = properties.getProperty(option); 434 435 if (opt.hasArg()) { 436 if (opt.getValues() == null || opt.getValues().length == 0) { 437 opt.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(value)); 438 } 439 } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) { 440 // if the value is not yes, true or 1 then don't add the option to the CommandLine 441 continue; 442 } 443 444 handleOption(opt); 445 currentOption = null; 446 } 447 } 448 } 449 450 /** 451 * Handles the following tokens: 452 * 453 * -S -SV -S V -S=V -S1S2 -S1S2 V -SV1=V2 454 * 455 * -L -LV -L V -L=V -l 456 * 457 * @param token the command line token to handle 458 */ 459 private void handleShortAndLongOption(final String token) throws ParseException { 460 final String t = Util.stripLeadingHyphens(token); 461 462 final int pos = t.indexOf('='); 463 464 if (t.length() == 1) { 465 // -S 466 if (options.hasShortOption(t)) { 467 handleOption(options.getOption(t)); 468 } else { 469 handleUnknownToken(token); 470 } 471 } else if (pos == -1) { 472 // no equal sign found (-xxx) 473 if (options.hasShortOption(t)) { 474 handleOption(options.getOption(t)); 475 } else if (!getMatchingLongOptions(t).isEmpty()) { 476 // -L or -l 477 handleLongOptionWithoutEqual(token); 478 } else { 479 // look for a long prefix (-Xmx512m) 480 final String opt = getLongPrefix(t); 481 482 if (opt != null && options.getOption(opt).acceptsArg()) { 483 handleOption(options.getOption(opt)); 484 currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(t.substring(opt.length()))); 485 currentOption = null; 486 } else if (isJavaProperty(t)) { 487 // -SV1 (-Dflag) 488 handleOption(options.getOption(t.substring(0, 1))); 489 currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(t.substring(1))); 490 currentOption = null; 491 } else { 492 // -S1S2S3 or -S1S2V 493 handleConcatenatedOptions(token); 494 } 495 } 496 } else { 497 // equal sign found (-xxx=yyy) 498 final String opt = t.substring(0, pos); 499 final String value = t.substring(pos + 1); 500 501 if (opt.length() == 1) { 502 // -S=V 503 final Option option = options.getOption(opt); 504 if (option != null && option.acceptsArg()) { 505 handleOption(option); 506 currentOption.addValueForProcessing(value); 507 currentOption = null; 508 } else { 509 handleUnknownToken(token); 510 } 511 } else if (isJavaProperty(opt)) { 512 // -SV1=V2 (-Dkey=value) 513 handleOption(options.getOption(opt.substring(0, 1))); 514 currentOption.addValueForProcessing(opt.substring(1)); 515 currentOption.addValueForProcessing(value); 516 currentOption = null; 517 } else { 518 // -L=V or -l=V 519 handleLongOptionWithEqual(token); 520 } 521 } 522 } 523 524 /** 525 * Handles any command line token. 526 * 527 * @param token the command line token to handle 528 * @throws ParseException 529 */ 530 private void handleToken(final String token) throws ParseException { 531 currentToken = token; 532 533 if (skipParsing) { 534 cmd.addArg(token); 535 } else if ("--".equals(token)) { 536 skipParsing = true; 537 } else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) { 538 currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOn(token)); 539 } else if (token.startsWith("--")) { 540 handleLongOption(token); 541 } else if (token.startsWith("-") && !"-".equals(token)) { 542 handleShortAndLongOption(token); 543 } else { 544 handleUnknownToken(token); 545 } 546 547 if (currentOption != null && !currentOption.acceptsArg()) { 548 currentOption = null; 549 } 550 } 551 552 /** 553 * Handles an unknown token. If the token starts with a dash an UnrecognizedOptionException is thrown. Otherwise the 554 * token is added to the arguments of the command line. If the stopAtNonOption flag is set, this stops the parsing and 555 * the remaining tokens are added as-is in the arguments of the command line. 556 * 557 * @param token the command line token to handle 558 */ 559 private void handleUnknownToken(final String token) throws ParseException { 560 if (token.startsWith("-") && token.length() > 1 && !stopAtNonOption) { 561 throw new UnrecognizedOptionException("Unrecognized option: " + token, token); 562 } 563 564 cmd.addArg(token); 565 if (stopAtNonOption) { 566 skipParsing = true; 567 } 568 } 569 570 /** 571 * Tests if the token is a valid argument. 572 * 573 * @param token 574 */ 575 private boolean isArgument(final String token) { 576 return !isOption(token) || isNegativeNumber(token); 577 } 578 579 /** 580 * Tests if the specified token is a Java-like property (-Dkey=value). 581 */ 582 private boolean isJavaProperty(final String token) { 583 final String opt = token.substring(0, 1); 584 final Option option = options.getOption(opt); 585 586 return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES); 587 } 588 589 /** 590 * Tests if the token looks like a long option. 591 * 592 * @param token 593 */ 594 private boolean isLongOption(final String token) { 595 if (token == null || !token.startsWith("-") || token.length() == 1) { 596 return false; 597 } 598 599 final int pos = token.indexOf("="); 600 final String t = pos == -1 ? token : token.substring(0, pos); 601 602 if (!getMatchingLongOptions(t).isEmpty()) { 603 // long or partial long options (--L, -L, --L=V, -L=V, --l, --l=V) 604 return true; 605 } 606 if (getLongPrefix(token) != null && !token.startsWith("--")) { 607 // -LV 608 return true; 609 } 610 611 return false; 612 } 613 614 /** 615 * Tests if the token is a negative number. 616 * 617 * @param token 618 */ 619 private boolean isNegativeNumber(final String token) { 620 try { 621 Double.parseDouble(token); 622 return true; 623 } catch (final NumberFormatException e) { 624 return false; 625 } 626 } 627 628 /** 629 * Tests if the token looks like an option. 630 * 631 * @param token 632 */ 633 private boolean isOption(final String token) { 634 return isLongOption(token) || isShortOption(token); 635 } 636 637 /** 638 * Tests if the token looks like a short option. 639 * 640 * @param token 641 */ 642 private boolean isShortOption(final String token) { 643 // short options (-S, -SV, -S=V, -SV1=V2, -S1S2) 644 if (token == null || !token.startsWith("-") || token.length() == 1) { 645 return false; 646 } 647 648 // remove leading "-" and "=value" 649 final int pos = token.indexOf("="); 650 final String optName = pos == -1 ? token.substring(1) : token.substring(1, pos); 651 if (options.hasShortOption(optName)) { 652 return true; 653 } 654 // check for several concatenated short options 655 return !optName.isEmpty() && options.hasShortOption(String.valueOf(optName.charAt(0))); 656 } 657 658 @Override 659 public CommandLine parse(final Options options, final String[] arguments) throws ParseException { 660 return parse(options, arguments, null); 661 } 662 663 @Override 664 public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException { 665 return parse(options, arguments, null, stopAtNonOption); 666 } 667 668 /** 669 * Parses the arguments according to the specified options and properties. 670 * 671 * @param options the specified Options 672 * @param arguments the command line arguments 673 * @param properties command line option name-value pairs 674 * @return the list of atomic option and value tokens 675 * 676 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 677 */ 678 public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException { 679 return parse(options, arguments, properties, false); 680 } 681 682 /** 683 * Parses the arguments according to the specified options and properties. 684 * 685 * @param options the specified Options 686 * @param arguments the command line arguments 687 * @param properties command line option name-value pairs 688 * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments 689 * are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a 690 * ParseException. 691 * 692 * @return the list of atomic option and value tokens 693 * @throws ParseException if there are any problems encountered while parsing the command line tokens. 694 */ 695 public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption) 696 throws ParseException { 697 this.options = options; 698 this.stopAtNonOption = stopAtNonOption; 699 skipParsing = false; 700 currentOption = null; 701 expectedOpts = new ArrayList<>(options.getRequiredOptions()); 702 703 // clear the data from the groups 704 for (final OptionGroup group : options.getOptionGroups()) { 705 group.setSelected(null); 706 } 707 708 cmd = new CommandLine(); 709 710 if (arguments != null) { 711 for (final String argument : arguments) { 712 handleToken(argument); 713 } 714 } 715 716 // check the arguments of the last option 717 checkRequiredArgs(); 718 719 // add the default options 720 handleProperties(properties); 721 722 checkRequiredOptions(); 723 724 return cmd; 725 } 726 727 /** 728 * Strips balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set 729 * If stripLeadingAndTrailingQuotes is null, then do not strip 730 * 731 * @param token a string 732 * @return token with the quotes stripped (if set) 733 */ 734 private String stripLeadingAndTrailingQuotesDefaultOff(final String token) { 735 if (stripLeadingAndTrailingQuotes != null && stripLeadingAndTrailingQuotes) { 736 return Util.stripLeadingAndTrailingQuotes(token); 737 } 738 return token; 739 } 740 741 /** 742 * Strips balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set 743 * If stripLeadingAndTrailingQuotes is null, then do not strip 744 * 745 * @param token a string 746 * @return token with the quotes stripped (if set) 747 */ 748 private String stripLeadingAndTrailingQuotesDefaultOn(final String token) { 749 if (stripLeadingAndTrailingQuotes == null || stripLeadingAndTrailingQuotes) { 750 return Util.stripLeadingAndTrailingQuotes(token); 751 } 752 return token; 753 } 754 755 /** 756 * Removes the option or its group from the list of expected elements. 757 * 758 * @param option 759 */ 760 private void updateRequiredOptions(final Option option) throws AlreadySelectedException { 761 if (option.isRequired()) { 762 expectedOpts.remove(option.getKey()); 763 } 764 765 // if the option is in an OptionGroup make that option the selected option of the group 766 if (options.getOptionGroup(option) != null) { 767 final OptionGroup group = options.getOptionGroup(option); 768 769 if (group.isRequired()) { 770 expectedOpts.remove(group); 771 } 772 773 group.setSelected(option); 774 } 775 } 776}