001/* 002 * Copyright 2008-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.util.args; 022 023 024 025import java.io.IOException; 026import java.io.OutputStream; 027import java.io.Serializable; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.LinkedHashSet; 034import java.util.LinkedHashMap; 035import java.util.List; 036import java.util.Set; 037 038import com.unboundid.util.Debug; 039import com.unboundid.util.ObjectPair; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.util.StaticUtils.*; 044import static com.unboundid.util.Validator.*; 045import static com.unboundid.util.args.ArgsMessages.*; 046 047 048 049/** 050 * This class provides an argument parser, which may be used to process command 051 * line arguments provided to Java applications. See the package-level Javadoc 052 * documentation for details regarding the capabilities of the argument parser. 053 */ 054@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 055public final class ArgumentParser 056 implements Serializable 057{ 058 /** 059 * The serial version UID for this serializable class. 060 */ 061 private static final long serialVersionUID = 361008526269946465L; 062 063 064 065 // The maximum number of trailing arguments allowed to be provided. 066 private final int maxTrailingArgs; 067 068 // The set of named arguments associated with this parser, indexed by short 069 // identifier. 070 private final LinkedHashMap<Character,Argument> namedArgsByShortID; 071 072 // The set of named arguments associated with this parser, indexed by long 073 // identifier. 074 private final LinkedHashMap<String,Argument> namedArgsByLongID; 075 076 // The full set of named arguments associated with this parser. 077 private final List<Argument> namedArgs; 078 079 // Sets of arguments in which if the key argument is provided, then at least 080 // one of the value arguments must also be provided. 081 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets; 082 083 // Sets of arguments in which at most one argument in the list is allowed to 084 // be present. 085 private final List<Set<Argument>> exclusiveArgumentSets; 086 087 // Sets of arguments in which at least one argument in the list is required to 088 // be present. 089 private final List<Set<Argument>> requiredArgumentSets; 090 091 // The list of trailing arguments provided on the command line. 092 private final List<String> trailingArgs; 093 094 // The description for the associated command. 095 private final String commandDescription; 096 097 // The name for the associated command. 098 private final String commandName; 099 100 // The placeholder string for the trailing arguments. 101 private final String trailingArgsPlaceholder; 102 103 104 105 /** 106 * Creates a new instance of this argument parser with the provided 107 * information. It will not allow unnamed trailing arguments. 108 * 109 * @param commandName The name of the application or utility with 110 * which this argument parser is associated. It 111 * must not be {@code null}. 112 * @param commandDescription A description of the application or utility 113 * with which this argument parser is associated. 114 * It will be included in generated usage 115 * information. It must not be {@code null}. 116 * 117 * @throws ArgumentException If either the command name or command 118 * description is {@code null}, 119 */ 120 public ArgumentParser(final String commandName, 121 final String commandDescription) 122 throws ArgumentException 123 { 124 this(commandName, commandDescription, 0, null); 125 } 126 127 128 129 /** 130 * Creates a new instance of this argument parser with the provided 131 * information. 132 * 133 * @param commandName The name of the application or utility 134 * with which this argument parser is 135 * associated. It must not be {@code null}. 136 * @param commandDescription A description of the application or 137 * utility with which this argument parser is 138 * associated. It will be included in 139 * generated usage information. It must not 140 * be {@code null}. 141 * @param maxTrailingArgs The maximum number of trailing arguments 142 * that may be provided to this command. A 143 * value of zero indicates that no trailing 144 * arguments will be allowed. A value less 145 * than zero will indicate that there is no 146 * limit on the number of trailing arguments 147 * allowed. 148 * @param trailingArgsPlaceholder A placeholder string that will be included 149 * in usage output to indicate what trailing 150 * arguments may be provided. It must not be 151 * {@code null} if {@code maxTrailingArgs} is 152 * anything other than zero. 153 * 154 * @throws ArgumentException If either the command name or command 155 * description is {@code null}, or if the maximum 156 * number of trailing arguments is non-zero and 157 * the trailing arguments placeholder is 158 * {@code null}. 159 */ 160 public ArgumentParser(final String commandName, 161 final String commandDescription, 162 final int maxTrailingArgs, 163 final String trailingArgsPlaceholder) 164 throws ArgumentException 165 { 166 if (commandName == null) 167 { 168 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get()); 169 } 170 171 if (commandDescription == null) 172 { 173 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get()); 174 } 175 176 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null)) 177 { 178 throw new ArgumentException( 179 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get()); 180 } 181 182 this.commandName = commandName; 183 this.commandDescription = commandDescription; 184 this.trailingArgsPlaceholder = trailingArgsPlaceholder; 185 186 if (maxTrailingArgs >= 0) 187 { 188 this.maxTrailingArgs = maxTrailingArgs; 189 } 190 else 191 { 192 this.maxTrailingArgs = Integer.MAX_VALUE; 193 } 194 195 namedArgsByShortID = new LinkedHashMap<Character,Argument>(); 196 namedArgsByLongID = new LinkedHashMap<String,Argument>(); 197 namedArgs = new ArrayList<Argument>(); 198 trailingArgs = new ArrayList<String>(); 199 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(); 200 exclusiveArgumentSets = new ArrayList<Set<Argument>>(); 201 requiredArgumentSets = new ArrayList<Set<Argument>>(); 202 } 203 204 205 206 /** 207 * Creates a new argument parser that is a "clean" copy of the provided source 208 * argument parser. 209 * 210 * @param source The source argument parser to use for this argument parser. 211 */ 212 private ArgumentParser(final ArgumentParser source) 213 { 214 commandName = source.commandName; 215 commandDescription = source.commandDescription; 216 maxTrailingArgs = source.maxTrailingArgs; 217 trailingArgsPlaceholder = source.trailingArgsPlaceholder; 218 219 trailingArgs = new ArrayList<String>(); 220 221 namedArgs = new ArrayList<Argument>(source.namedArgs.size()); 222 namedArgsByLongID = 223 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size()); 224 namedArgsByShortID = new LinkedHashMap<Character,Argument>( 225 source.namedArgsByShortID.size()); 226 227 final LinkedHashMap<String,Argument> argsByID = 228 new LinkedHashMap<String,Argument>(source.namedArgs.size()); 229 for (final Argument sourceArg : source.namedArgs) 230 { 231 final Argument a = sourceArg.getCleanCopy(); 232 233 try 234 { 235 a.setRegistered(); 236 } 237 catch (final ArgumentException ae) 238 { 239 // This should never happen. 240 Debug.debugException(ae); 241 } 242 243 namedArgs.add(a); 244 argsByID.put(a.getIdentifierString(), a); 245 246 for (final Character c : a.getShortIdentifiers()) 247 { 248 namedArgsByShortID.put(c, a); 249 } 250 251 for (final String s : a.getLongIdentifiers()) 252 { 253 namedArgsByLongID.put(toLowerCase(s), a); 254 } 255 } 256 257 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>( 258 source.dependentArgumentSets.size()); 259 for (final ObjectPair<Argument,Set<Argument>> p : 260 source.dependentArgumentSets) 261 { 262 final Set<Argument> sourceSet = p.getSecond(); 263 final LinkedHashSet<Argument> newSet = 264 new LinkedHashSet<Argument>(sourceSet.size()); 265 for (final Argument a : sourceSet) 266 { 267 newSet.add(argsByID.get(a.getIdentifierString())); 268 } 269 270 final Argument sourceFirst = p.getFirst(); 271 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString()); 272 dependentArgumentSets.add( 273 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet)); 274 } 275 276 exclusiveArgumentSets = 277 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size()); 278 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets) 279 { 280 final LinkedHashSet<Argument> newSet = 281 new LinkedHashSet<Argument>(sourceSet.size()); 282 for (final Argument a : sourceSet) 283 { 284 newSet.add(argsByID.get(a.getIdentifierString())); 285 } 286 287 exclusiveArgumentSets.add(newSet); 288 } 289 290 requiredArgumentSets = 291 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size()); 292 for (final Set<Argument> sourceSet : source.requiredArgumentSets) 293 { 294 final LinkedHashSet<Argument> newSet = 295 new LinkedHashSet<Argument>(sourceSet.size()); 296 for (final Argument a : sourceSet) 297 { 298 newSet.add(argsByID.get(a.getIdentifierString())); 299 } 300 requiredArgumentSets.add(newSet); 301 } 302 } 303 304 305 306 /** 307 * Retrieves the name of the application or utility with which this command 308 * line argument parser is associated. 309 * 310 * @return The name of the application or utility with which this command 311 * line argument parser is associated. 312 */ 313 public String getCommandName() 314 { 315 return commandName; 316 } 317 318 319 320 /** 321 * Retrieves a description of the application or utility with which this 322 * command line argument parser is associated. 323 * 324 * @return A description of the application or utility with which this 325 * command line argument parser is associated. 326 */ 327 public String getCommandDescription() 328 { 329 return commandDescription; 330 } 331 332 333 334 /** 335 * Indicates whether this argument parser allows any unnamed trailing 336 * arguments to be provided. 337 * 338 * @return {@code true} if at least one unnamed trailing argument may be 339 * provided, or {@code false} if not. 340 */ 341 public boolean allowsTrailingArguments() 342 { 343 return (maxTrailingArgs != 0); 344 } 345 346 347 348 /** 349 * Retrieves the placeholder string that will be provided in usage information 350 * to indicate what may be included in the trailing arguments. 351 * 352 * @return The placeholder string that will be provided in usage information 353 * to indicate what may be included in the trailing arguments, or 354 * {@code null} if unnamed trailing arguments are not allowed. 355 */ 356 public String getTrailingArgumentsPlaceholder() 357 { 358 return trailingArgsPlaceholder; 359 } 360 361 362 363 /** 364 * Retrieves the maximum number of unnamed trailing arguments that may be 365 * provided. 366 * 367 * @return The maximum number of unnamed trailing arguments that may be 368 * provided. 369 */ 370 public int getMaxTrailingArguments() 371 { 372 return maxTrailingArgs; 373 } 374 375 376 377 /** 378 * Retrieves the named argument with the specified short identifier. 379 * 380 * @param shortIdentifier The short identifier of the argument to retrieve. 381 * It must not be {@code null}. 382 * 383 * @return The named argument with the specified short identifier, or 384 * {@code null} if there is no such argument. 385 */ 386 public Argument getNamedArgument(final Character shortIdentifier) 387 { 388 ensureNotNull(shortIdentifier); 389 return namedArgsByShortID.get(shortIdentifier); 390 } 391 392 393 394 /** 395 * Retrieves the named argument with the specified long identifier. 396 * 397 * @param longIdentifier The long identifier of the argument to retrieve. 398 * It must not be {@code null}. 399 * 400 * @return The named argument with the specified long identifier, or 401 * {@code null} if there is no such argument. 402 */ 403 public Argument getNamedArgument(final String longIdentifier) 404 { 405 ensureNotNull(longIdentifier); 406 return namedArgsByLongID.get(toLowerCase(longIdentifier)); 407 } 408 409 410 411 /** 412 * Retrieves the set of named arguments defined for use with this argument 413 * parser. 414 * 415 * @return The set of named arguments defined for use with this argument 416 * parser. 417 */ 418 public List<Argument> getNamedArguments() 419 { 420 return Collections.unmodifiableList(namedArgs); 421 } 422 423 424 425 /** 426 * Registers the provided argument with this argument parser. 427 * 428 * @param argument The argument to be registered. 429 * 430 * @throws ArgumentException If the provided argument conflicts with another 431 * argument already registered with this parser. 432 */ 433 public void addArgument(final Argument argument) 434 throws ArgumentException 435 { 436 argument.setRegistered(); 437 for (final Character c : argument.getShortIdentifiers()) 438 { 439 if (namedArgsByShortID.containsKey(c)) 440 { 441 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c)); 442 } 443 } 444 445 for (final String s : argument.getLongIdentifiers()) 446 { 447 if (namedArgsByLongID.containsKey(toLowerCase(s))) 448 { 449 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s)); 450 } 451 } 452 453 for (final Character c : argument.getShortIdentifiers()) 454 { 455 namedArgsByShortID.put(c, argument); 456 } 457 458 for (final String s : argument.getLongIdentifiers()) 459 { 460 namedArgsByLongID.put(toLowerCase(s), argument); 461 } 462 463 namedArgs.add(argument); 464 } 465 466 467 468 /** 469 * Retrieves the list of dependent argument sets for this argument parser. If 470 * an argument contained as the first object in the pair in a dependent 471 * argument set is provided, then at least one of the arguments in the paired 472 * set must also be provided. 473 * 474 * @return The list of dependent argument sets for this argument parser. 475 */ 476 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets() 477 { 478 return Collections.unmodifiableList(dependentArgumentSets); 479 } 480 481 482 483 /** 484 * Adds the provided collection of arguments as dependent upon the given 485 * argument. 486 * 487 * @param targetArgument The argument whose presence indicates that at 488 * least one of the dependent arguments must also 489 * be present. It must not be {@code null}. 490 * @param dependentArguments The set of arguments from which at least one 491 * argument must be present if the target argument 492 * is present. It must not be {@code null} or 493 * empty. 494 */ 495 public void addDependentArgumentSet(final Argument targetArgument, 496 final Collection<Argument> dependentArguments) 497 { 498 ensureNotNull(targetArgument, dependentArguments); 499 500 final LinkedHashSet<Argument> argSet = 501 new LinkedHashSet<Argument>(dependentArguments); 502 dependentArgumentSets.add( 503 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 504 } 505 506 507 508 /** 509 * Adds the provided collection of arguments as dependent upon the given 510 * argument. 511 * 512 * @param targetArgument The argument whose presence indicates that at least 513 * one of the dependent arguments must also be 514 * present. It must not be {@code null}. 515 * @param dependentArg1 The first argument in the set of arguments in which 516 * at least one argument must be present if the target 517 * argument is present. It must not be {@code null}. 518 * @param remaining The remaining arguments in the set of arguments in 519 * which at least one argument must be present if the 520 * target argument is present. 521 */ 522 public void addDependentArgumentSet(final Argument targetArgument, 523 final Argument dependentArg1, 524 final Argument... remaining) 525 { 526 ensureNotNull(targetArgument, dependentArg1); 527 528 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 529 argSet.add(dependentArg1); 530 argSet.addAll(Arrays.asList(remaining)); 531 532 dependentArgumentSets.add( 533 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 534 } 535 536 537 538 /** 539 * Retrieves the list of exclusive argument sets for this argument parser. 540 * If an argument contained in an exclusive argument set is provided, then 541 * none of the other arguments in that set may be provided. It is acceptable 542 * for none of the arguments in the set to be provided, unless the same set 543 * of arguments is also defined as a required argument set. 544 * 545 * @return The list of exclusive argument sets for this argument parser. 546 */ 547 public List<Set<Argument>> getExclusiveArgumentSets() 548 { 549 return Collections.unmodifiableList(exclusiveArgumentSets); 550 } 551 552 553 554 /** 555 * Adds the provided collection of arguments as an exclusive argument set, in 556 * which at most one of the arguments may be provided. 557 * 558 * @param exclusiveArguments The collection of arguments to form an 559 * exclusive argument set. It must not be 560 * {@code null}. 561 */ 562 public void addExclusiveArgumentSet( 563 final Collection<Argument> exclusiveArguments) 564 { 565 ensureNotNull(exclusiveArguments); 566 final LinkedHashSet<Argument> argSet = 567 new LinkedHashSet<Argument>(exclusiveArguments); 568 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 569 } 570 571 572 573 /** 574 * Adds the provided set of arguments as an exclusive argument set, in 575 * which at most one of the arguments may be provided. 576 * 577 * @param arg1 The first argument to include in the exclusive argument 578 * set. It must not be {@code null}. 579 * @param arg2 The second argument to include in the exclusive argument 580 * set. It must not be {@code null}. 581 * @param remaining Any additional arguments to include in the exclusive 582 * argument set. 583 */ 584 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2, 585 final Argument... remaining) 586 { 587 ensureNotNull(arg1, arg2); 588 589 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 590 argSet.add(arg1); 591 argSet.add(arg2); 592 argSet.addAll(Arrays.asList(remaining)); 593 594 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 595 } 596 597 598 599 /** 600 * Retrieves the list of required argument sets for this argument parser. At 601 * least one of the arguments contained in this set must be provided. If this 602 * same set is also defined as an exclusive argument set, then exactly one 603 * of those arguments must be provided. 604 * 605 * @return The list of required argument sets for this argument parser. 606 */ 607 public List<Set<Argument>> getRequiredArgumentSets() 608 { 609 return Collections.unmodifiableList(requiredArgumentSets); 610 } 611 612 613 614 /** 615 * Adds the provided collection of arguments as a required argument set, in 616 * which at least one of the arguments must be provided. 617 * 618 * @param requiredArguments The collection of arguments to form an 619 * required argument set. It must not be 620 * {@code null}. 621 */ 622 public void addRequiredArgumentSet( 623 final Collection<Argument> requiredArguments) 624 { 625 ensureNotNull(requiredArguments); 626 final LinkedHashSet<Argument> argSet = 627 new LinkedHashSet<Argument>(requiredArguments); 628 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 629 } 630 631 632 633 /** 634 * Adds the provided set of arguments as a required argument set, in which 635 * at least one of the arguments must be provided. 636 * 637 * @param arg1 The first argument to include in the required argument 638 * set. It must not be {@code null}. 639 * @param arg2 The second argument to include in the required argument 640 * set. It must not be {@code null}. 641 * @param remaining Any additional arguments to include in the required 642 * argument set. 643 */ 644 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2, 645 final Argument... remaining) 646 { 647 ensureNotNull(arg1, arg2); 648 649 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 650 argSet.add(arg1); 651 argSet.add(arg2); 652 argSet.addAll(Arrays.asList(remaining)); 653 654 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 655 } 656 657 658 659 /** 660 * Retrieves the set of unnamed trailing arguments in the provided command 661 * line arguments. 662 * 663 * @return The set of unnamed trailing arguments in the provided command line 664 * arguments, or an empty list if there were none. 665 */ 666 public List<String> getTrailingArguments() 667 { 668 return Collections.unmodifiableList(trailingArgs); 669 } 670 671 672 673 /** 674 * Creates a copy of this argument parser that is "clean" and appears as if it 675 * has not been used to parse an argument set. The new parser will have all 676 * of the same arguments and constraints as this parser. 677 * 678 * @return The "clean" copy of this argument parser. 679 */ 680 public ArgumentParser getCleanCopy() 681 { 682 return new ArgumentParser(this); 683 } 684 685 686 687 /** 688 * Parses the provided set of arguments. 689 * 690 * @param args An array containing the argument information to parse. It 691 * must not be {@code null}. 692 * 693 * @throws ArgumentException If a problem occurs while attempting to parse 694 * the argument information. 695 */ 696 public void parse(final String[] args) 697 throws ArgumentException 698 { 699 // Iterate through the provided args strings and process them. 700 boolean inTrailingArgs = false; 701 boolean usageArgProvided = false; 702 for (int i=0; i < args.length; i++) 703 { 704 final String s = args[i]; 705 706 if (inTrailingArgs) 707 { 708 if (maxTrailingArgs == 0) 709 { 710 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 711 s, commandName)); 712 } 713 else if (trailingArgs.size() >= maxTrailingArgs) 714 { 715 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s, 716 commandName, maxTrailingArgs)); 717 } 718 else 719 { 720 trailingArgs.add(s); 721 } 722 } 723 else if (s.equals("--")) 724 { 725 // This signifies the end of the named arguments and the beginning of 726 // the trailing arguments. 727 inTrailingArgs = true; 728 } 729 else if (s.startsWith("--")) 730 { 731 // There may be an equal sign to separate the name from the value. 732 final String argName; 733 final int equalPos = s.indexOf('='); 734 if (equalPos > 0) 735 { 736 argName = s.substring(2, equalPos); 737 } 738 else 739 { 740 argName = s.substring(2); 741 } 742 743 final Argument a = namedArgsByLongID.get(toLowerCase(argName)); 744 if (a == null) 745 { 746 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName)); 747 } 748 else if(a.isUsageArgument()) 749 { 750 usageArgProvided = true; 751 } 752 753 a.incrementOccurrences(); 754 if (a.takesValue()) 755 { 756 if (equalPos > 0) 757 { 758 a.addValue(s.substring(equalPos+1)); 759 } 760 else 761 { 762 i++; 763 if (i >= args.length) 764 { 765 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get( 766 argName)); 767 } 768 else 769 { 770 a.addValue(args[i]); 771 } 772 } 773 } 774 else 775 { 776 if (equalPos > 0) 777 { 778 throw new ArgumentException( 779 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName)); 780 } 781 } 782 } 783 else if (s.startsWith("-")) 784 { 785 if (s.length() == 1) 786 { 787 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get()); 788 } 789 else if (s.length() == 2) 790 { 791 final char c = s.charAt(1); 792 final Argument a = namedArgsByShortID.get(c); 793 if (a == null) 794 { 795 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 796 } 797 else if(a.isUsageArgument()) 798 { 799 usageArgProvided = true; 800 } 801 802 a.incrementOccurrences(); 803 if (a.takesValue()) 804 { 805 i++; 806 if (i >= args.length) 807 { 808 throw new ArgumentException( 809 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c)); 810 } 811 else 812 { 813 a.addValue(args[i]); 814 } 815 } 816 } 817 else 818 { 819 char c = s.charAt(1); 820 Argument a = namedArgsByShortID.get(c); 821 if (a == null) 822 { 823 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 824 } 825 else if(a.isUsageArgument()) 826 { 827 usageArgProvided = true; 828 } 829 830 a.incrementOccurrences(); 831 if (a.takesValue()) 832 { 833 a.addValue(s.substring(2)); 834 } 835 else 836 { 837 // The rest of the characters in the string must also resolve to 838 // arguments that don't take values. 839 for (int j=2; j < s.length(); j++) 840 { 841 c = s.charAt(j); 842 a = namedArgsByShortID.get(c); 843 if (a == null) 844 { 845 throw new ArgumentException( 846 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s)); 847 } 848 else if(a.isUsageArgument()) 849 { 850 usageArgProvided = true; 851 } 852 853 a.incrementOccurrences(); 854 if (a.takesValue()) 855 { 856 throw new ArgumentException( 857 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get( 858 c, s)); 859 } 860 } 861 } 862 } 863 } 864 else 865 { 866 inTrailingArgs = true; 867 if (maxTrailingArgs == 0) 868 { 869 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 870 s, commandName)); 871 } 872 else 873 { 874 trailingArgs.add(s); 875 } 876 } 877 } 878 879 880 // If a usage argument was provided, then no further validation should be 881 // performed. 882 if (usageArgProvided) 883 { 884 return; 885 } 886 887 888 // Make sure that all required arguments have values. 889 for (final Argument a : namedArgs) 890 { 891 if (a.isRequired() && (! a.isPresent())) 892 { 893 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get( 894 a.getIdentifierString())); 895 } 896 } 897 898 899 // Make sure that there are no dependent argument set conflicts. 900 for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets) 901 { 902 final Argument targetArg = p.getFirst(); 903 if (targetArg.isPresent()) 904 { 905 final Set<Argument> argSet = p.getSecond(); 906 boolean found = false; 907 for (final Argument a : argSet) 908 { 909 if (a.isPresent()) 910 { 911 found = true; 912 break; 913 } 914 } 915 916 if (! found) 917 { 918 if (argSet.size() == 1) 919 { 920 throw new ArgumentException( 921 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get( 922 targetArg.getIdentifierString(), 923 argSet.iterator().next().getIdentifierString())); 924 } 925 else 926 { 927 boolean first = true; 928 final StringBuilder buffer = new StringBuilder(); 929 for (final Argument a : argSet) 930 { 931 if (first) 932 { 933 first = false; 934 } 935 else 936 { 937 buffer.append(", "); 938 } 939 buffer.append(a.getIdentifierString()); 940 } 941 throw new ArgumentException( 942 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get( 943 targetArg.getIdentifierString(), buffer.toString())); 944 } 945 } 946 } 947 } 948 949 950 // Make sure that there are no exclusive argument set conflicts. 951 for (final Set<Argument> argSet : exclusiveArgumentSets) 952 { 953 Argument setArg = null; 954 for (final Argument a : argSet) 955 { 956 if (a.isPresent()) 957 { 958 if (setArg == null) 959 { 960 setArg = a; 961 } 962 else 963 { 964 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 965 setArg.getIdentifierString(), 966 a.getIdentifierString())); 967 } 968 } 969 } 970 } 971 972 // Make sure that there are no required argument set conflicts. 973 for (final Set<Argument> argSet : requiredArgumentSets) 974 { 975 boolean found = false; 976 for (final Argument a : argSet) 977 { 978 if (a.isPresent()) 979 { 980 found = true; 981 break; 982 } 983 } 984 985 if (! found) 986 { 987 boolean first = true; 988 final StringBuilder buffer = new StringBuilder(); 989 for (final Argument a : argSet) 990 { 991 if (first) 992 { 993 first = false; 994 } 995 else 996 { 997 buffer.append(", "); 998 } 999 buffer.append(a.getIdentifierString()); 1000 } 1001 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get( 1002 buffer.toString())); 1003 } 1004 } 1005 } 1006 1007 1008 1009 /** 1010 * Retrieves lines that make up the usage information for this program, 1011 * optionally wrapping long lines. 1012 * 1013 * @param maxWidth The maximum line width to use for the output. If this is 1014 * less than or equal to zero, then no wrapping will be 1015 * performed. 1016 * 1017 * @return The lines that make up the usage information for this program. 1018 */ 1019 public List<String> getUsage(final int maxWidth) 1020 { 1021 final ArrayList<String> lines = new ArrayList<String>(100); 1022 1023 // First is a description of the command. 1024 lines.addAll(wrapLine(commandDescription, maxWidth)); 1025 lines.add(""); 1026 1027 // Next comes the usage. It may include neither, either, or both of the 1028 // set of options and trailing arguments. 1029 if (namedArgs.isEmpty()) 1030 { 1031 if (maxTrailingArgs == 0) 1032 { 1033 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), 1034 maxWidth)); 1035 } 1036 else 1037 { 1038 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get( 1039 commandName, trailingArgsPlaceholder), 1040 maxWidth)); 1041 } 1042 } 1043 else 1044 { 1045 if (maxTrailingArgs == 0) 1046 { 1047 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), 1048 maxWidth)); 1049 } 1050 else 1051 { 1052 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get( 1053 commandName, trailingArgsPlaceholder), 1054 maxWidth)); 1055 } 1056 1057 lines.add(""); 1058 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 1059 1060 1061 // We know that there are named arguments, so we'll want to display them 1062 // and their descriptions. 1063 for (final Argument a : namedArgs) 1064 { 1065 if (a.isHidden()) 1066 { 1067 // This shouldn't be included in the usage output. 1068 continue; 1069 } 1070 1071 final StringBuilder argLine = new StringBuilder(); 1072 boolean first = true; 1073 for (final Character c : a.getShortIdentifiers()) 1074 { 1075 if (first) 1076 { 1077 argLine.append('-'); 1078 first = false; 1079 } 1080 else 1081 { 1082 argLine.append(", -"); 1083 } 1084 argLine.append(c); 1085 } 1086 1087 for (final String s : a.getLongIdentifiers()) 1088 { 1089 if (first) 1090 { 1091 argLine.append("--"); 1092 } 1093 else 1094 { 1095 argLine.append(", --"); 1096 } 1097 argLine.append(s); 1098 } 1099 1100 final String valuePlaceholder = a.getValuePlaceholder(); 1101 if (valuePlaceholder != null) 1102 { 1103 argLine.append(' '); 1104 argLine.append(valuePlaceholder); 1105 } 1106 1107 // NOTE: This line won't be wrapped. That's intentional because I 1108 // think it would probably look bad no matter how we did it. 1109 lines.add(argLine.toString()); 1110 1111 // The description should be wrapped, if necessary. We'll also want to 1112 // indent it (unless someone chose an absurdly small wrap width) to make 1113 // it stand out from the argument lines. 1114 final String description = a.getDescription(); 1115 if (maxWidth > 10) 1116 { 1117 final List<String> descLines = wrapLine(description, (maxWidth-4)); 1118 for (final String s : descLines) 1119 { 1120 lines.add(" " + s); 1121 } 1122 } 1123 else 1124 { 1125 lines.addAll(wrapLine(description, maxWidth)); 1126 } 1127 } 1128 } 1129 1130 return lines; 1131 } 1132 1133 1134 1135 /** 1136 * Writes usage information for this program to the provided output stream 1137 * using the UTF-8 encoding, optionally wrapping long lines. 1138 * 1139 * @param outputStream The output stream to which the usage information 1140 * should be written. It must not be {@code null}. 1141 * @param maxWidth The maximum line width to use for the output. If 1142 * this is less than or equal to zero, then no wrapping 1143 * will be performed. 1144 * 1145 * @throws IOException If an error occurs while attempting to write to the 1146 * provided output stream. 1147 */ 1148 public void getUsage(final OutputStream outputStream, final int maxWidth) 1149 throws IOException 1150 { 1151 final List<String> usageLines = getUsage(maxWidth); 1152 for (final String s : usageLines) 1153 { 1154 outputStream.write(getBytes(s)); 1155 outputStream.write(EOL_BYTES); 1156 } 1157 } 1158 1159 1160 1161 /** 1162 * Retrieves a string representation of the usage information. 1163 * 1164 * @param maxWidth The maximum line width to use for the output. If this is 1165 * less than or equal to zero, then no wrapping will be 1166 * performed. 1167 * 1168 * @return A string representation of the usage information 1169 */ 1170 public String getUsageString(final int maxWidth) 1171 { 1172 final StringBuilder buffer = new StringBuilder(); 1173 getUsageString(buffer, maxWidth); 1174 return buffer.toString(); 1175 } 1176 1177 1178 1179 /** 1180 * Appends a string representation of the usage information to the provided 1181 * buffer. 1182 * 1183 * @param buffer The buffer to which the information should be appended. 1184 * @param maxWidth The maximum line width to use for the output. If this is 1185 * less than or equal to zero, then no wrapping will be 1186 * performed. 1187 */ 1188 public void getUsageString(final StringBuilder buffer, final int maxWidth) 1189 { 1190 for (final String line : getUsage(maxWidth)) 1191 { 1192 buffer.append(line); 1193 buffer.append(EOL); 1194 } 1195 } 1196 1197 1198 1199 /** 1200 * Retrieves a string representation of this argument parser. 1201 * 1202 * @return A string representation of this argument parser. 1203 */ 1204 @Override() 1205 public String toString() 1206 { 1207 final StringBuilder buffer = new StringBuilder(); 1208 toString(buffer); 1209 return buffer.toString(); 1210 } 1211 1212 1213 1214 /** 1215 * Appends a string representation of this argument parser to the provided 1216 * buffer. 1217 * 1218 * @param buffer The buffer to which the information should be appended. 1219 */ 1220 public void toString(final StringBuilder buffer) 1221 { 1222 buffer.append("ArgumentParser(commandName='"); 1223 buffer.append(commandName); 1224 buffer.append("', commandDescription='"); 1225 buffer.append(commandDescription); 1226 buffer.append("', maxTrailingArgs="); 1227 buffer.append(maxTrailingArgs); 1228 1229 if (trailingArgsPlaceholder != null) 1230 { 1231 buffer.append(", trailingArgsPlaceholder='"); 1232 buffer.append(trailingArgsPlaceholder); 1233 buffer.append('\''); 1234 } 1235 1236 buffer.append("namedArgs={"); 1237 1238 final Iterator<Argument> iterator = namedArgs.iterator(); 1239 while (iterator.hasNext()) 1240 { 1241 iterator.next().toString(buffer); 1242 if (iterator.hasNext()) 1243 { 1244 buffer.append(", "); 1245 } 1246 } 1247 1248 buffer.append("})"); 1249 } 1250}