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