001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.util.args; 028 import org.opends.messages.Message; 029 import org.opends.messages.MessageBuilder; 030 031 import static org.opends.messages.UtilityMessages.*; 032 import static org.opends.server.tools.ToolConstants.*; 033 import static org.opends.server.util.ServerConstants.*; 034 import static org.opends.server.util.StaticUtils.*; 035 036 import java.io.FileInputStream; 037 import java.io.IOException; 038 import java.io.OutputStream; 039 import java.util.ArrayList; 040 import java.util.Collection; 041 import java.util.Collections; 042 import java.util.HashMap; 043 import java.util.LinkedList; 044 import java.util.Map; 045 import java.util.Properties; 046 import java.util.SortedMap; 047 import java.util.TreeMap; 048 049 import org.opends.server.core.DirectoryServer; 050 import org.opends.server.util.SetupUtils; 051 052 053 054 /** 055 * This class defines a variant of the argument parser that can be used with 056 * applications that use subcommands to customize their behavior and that have a 057 * different set of options per subcommand (e.g, "cvs checkout" takes different 058 * options than "cvs commit"). This parser also has the ability to use global 059 * options that will always be applicable regardless of the subcommand in 060 * addition to the subcommand-specific arguments. There must not be any 061 * conflicts between the global options and the option for any subcommand, but 062 * it is allowed to re-use subcommand-specific options for different purposes 063 * between different subcommands. 064 */ 065 public class SubCommandArgumentParser extends ArgumentParser 066 { 067 // The argument that will be used to trigger the display of usage information. 068 private Argument usageArgument; 069 070 // The arguments that will be used to trigger the display of usage 071 // information for groups of sub-commands. 072 private Map<Argument, Collection<SubCommand>> usageGroupArguments; 073 074 // The set of unnamed trailing arguments that were provided for this parser. 075 private ArrayList<String> trailingArguments; 076 077 // Indicates whether subcommand and long argument names should be treated in a 078 // case-sensitive manner. 079 private boolean longArgumentsCaseSensitive; 080 081 // Indicates whether the usage information has been displayed. 082 private boolean usageOrVersionDisplayed; 083 084 // The set of global arguments defined for this parser, referenced by short 085 // ID. 086 private HashMap<Character,Argument> globalShortIDMap; 087 088 // The set of global arguments defined for this parser, referenced by 089 // argument name. 090 private HashMap<String,Argument> globalArgumentMap; 091 092 // The set of global arguments defined for this parser, referenced by long 093 // ID. 094 private HashMap<String,Argument> globalLongIDMap; 095 096 // The set of subcommands defined for this parser, referenced by subcommand 097 // name. 098 private SortedMap<String,SubCommand> subCommands; 099 100 // The total set of global arguments defined for this parser. 101 private LinkedList<Argument> globalArgumentList; 102 103 // The output stream to which usage information should be printed. 104 private OutputStream usageOutputStream; 105 106 // The fully-qualified name of the Java class that should be invoked to launch 107 // the program with which this argument parser is associated. 108 private String mainClassName; 109 110 // A human-readable description for the tool, which will be included when 111 // displaying usage information. 112 private Message toolDescription; 113 114 // The raw set of command-line arguments that were provided. 115 private String[] rawArguments; 116 117 // The subcommand requested by the user as part of the command-line arguments. 118 private SubCommand subCommand; 119 120 //Indicates whether the version argument was provided. 121 private boolean versionPresent; 122 123 private final static String INDENT = " "; 124 private final static int MAX_LENGTH = SetupUtils.isWindows() ? 79 : 80; 125 126 127 /** 128 * Creates a new instance of this subcommand argument parser with no 129 * arguments. 130 * 131 * @param mainClassName The fully-qualified name of the Java 132 * class that should be invoked to launch 133 * the program with which this argument 134 * parser is associated. 135 * @param toolDescription A human-readable description for the 136 * tool, which will be included when 137 * displaying usage information. 138 * @param longArgumentsCaseSensitive Indicates whether subcommand and long 139 * argument names should be treated in a 140 * case-sensitive manner. 141 */ 142 public SubCommandArgumentParser(String mainClassName, Message toolDescription, 143 boolean longArgumentsCaseSensitive) 144 { 145 super(mainClassName, toolDescription, longArgumentsCaseSensitive); 146 this.mainClassName = mainClassName; 147 this.toolDescription = toolDescription; 148 this.longArgumentsCaseSensitive = longArgumentsCaseSensitive; 149 150 trailingArguments = new ArrayList<String>(); 151 globalArgumentList = new LinkedList<Argument>(); 152 globalArgumentMap = new HashMap<String,Argument>(); 153 globalShortIDMap = new HashMap<Character,Argument>(); 154 globalLongIDMap = new HashMap<String,Argument>(); 155 usageGroupArguments = new HashMap<Argument, Collection<SubCommand>>(); 156 subCommands = new TreeMap<String,SubCommand>(); 157 usageOrVersionDisplayed = false; 158 rawArguments = null; 159 subCommand = null; 160 usageArgument = null; 161 usageOutputStream = null; 162 } 163 164 165 166 /** 167 * Retrieves the fully-qualified name of the Java class that should be invoked 168 * to launch the program with which this argument parser is associated. 169 * 170 * @return The fully-qualified name of the Java class that should be invoked 171 * to launch the program with which this argument parser is 172 * associated. 173 */ 174 public String getMainClassName() 175 { 176 return mainClassName; 177 } 178 179 180 181 /** 182 * Retrieves a human-readable description for this tool, which should be 183 * included at the top of the command-line usage information. 184 * 185 * @return A human-readable description for this tool, or {@code null} if 186 * none is available. 187 */ 188 public Message getToolDescription() 189 { 190 return toolDescription; 191 } 192 193 194 195 /** 196 * Indicates whether subcommand names and long argument strings should be 197 * treated in a case-sensitive manner. 198 * 199 * @return <CODE>true</CODE> if subcommand names and long argument strings 200 * should be treated in a case-sensitive manner, or 201 * <CODE>false</CODE> if they should not. 202 */ 203 public boolean longArgumentsCaseSensitive() 204 { 205 return longArgumentsCaseSensitive; 206 } 207 208 209 210 /** 211 * Retrieves the list of all global arguments that have been defined for this 212 * argument parser. 213 * 214 * @return The list of all global arguments that have been defined for this 215 * argument parser. 216 */ 217 public LinkedList<Argument> getGlobalArgumentList() 218 { 219 return globalArgumentList; 220 } 221 222 223 224 /** 225 * Indicates whether this argument parser contains a global argument with the 226 * specified name. 227 * 228 * @param argumentName The name for which to make the determination. 229 * 230 * @return <CODE>true</CODE> if a global argument exists with the specified 231 * name, or <CODE>false</CODE> if not. 232 */ 233 public boolean hasGlobalArgument(String argumentName) 234 { 235 return globalArgumentMap.containsKey(argumentName); 236 } 237 238 239 240 /** 241 * Retrieves the global argument with the specified name. 242 * 243 * @param name The name of the global argument to retrieve. 244 * 245 * @return The global argument with the specified name, or <CODE>null</CODE> 246 * if there is no such argument. 247 */ 248 public Argument getGlobalArgument(String name) 249 { 250 return globalArgumentMap.get(name); 251 } 252 253 254 255 /** 256 * Retrieves the set of global arguments mapped by the short identifier that 257 * may be used to reference them. Note that arguments that do not have a 258 * short identifier will not be present in this list. 259 * 260 * @return The set of global arguments mapped by the short identifier that 261 * may be used to reference them. 262 */ 263 public HashMap<Character,Argument> getGlobalArgumentsByShortID() 264 { 265 return globalShortIDMap; 266 } 267 268 269 270 /** 271 * Indicates whether this argument parser has a global argument with the 272 * specified short ID. 273 * 274 * @param shortID The short ID character for which to make the 275 * determination. 276 * 277 * @return <CODE>true</CODE> if a global argument exists with the specified 278 * short ID, or <CODE>false</CODE> if not. 279 */ 280 public boolean hasGlobalArgumentWithShortID(Character shortID) 281 { 282 return globalShortIDMap.containsKey(shortID); 283 } 284 285 286 287 /** 288 * Retrieves the global argument with the specified short identifier. 289 * 290 * @param shortID The short identifier for the global argument to retrieve. 291 * 292 * @return The global argument with the specified short identifier, or 293 * <CODE>null</CODE> if there is no such argument. 294 */ 295 public Argument getGlobalArgumentForShortID(Character shortID) 296 { 297 return globalShortIDMap.get(shortID); 298 } 299 300 301 302 /** 303 * Retrieves the set of global arguments mapped by the long identifier that 304 * may be used to reference them. Note that arguments that do not have a long 305 * identifier will not be present in this list. 306 * 307 * @return The set of global arguments mapped by the long identifier that may 308 * be used to reference them. 309 */ 310 public HashMap<String,Argument> getGlobalArgumentsByLongID() 311 { 312 return globalLongIDMap; 313 } 314 315 316 317 /** 318 * Indicates whether this argument parser has a global argument with the 319 * specified long ID. 320 * 321 * @param longID The long ID string for which to make the determination. 322 * 323 * @return <CODE>true</CODE> if a global argument exists with the specified 324 * long ID, or <CODE>false</CODE> if not. 325 */ 326 public boolean hasGlobalArgumentWithLongID(String longID) 327 { 328 return globalLongIDMap.containsKey(longID); 329 } 330 331 332 333 /** 334 * Retrieves the global argument with the specified long identifier. 335 * 336 * @param longID The long identifier for the global argument to retrieve. 337 * 338 * @return The global argument with the specified long identifier, or 339 * <CODE>null</CODE> if there is no such argument. 340 */ 341 public Argument getGlobalArgumentForLongID(String longID) 342 { 343 return globalLongIDMap.get(longID); 344 } 345 346 347 348 /** 349 * Retrieves the set of subcommands defined for this argument parser, 350 * referenced by subcommand name. 351 * 352 * @return The set of subcommands defined for this argument parser, 353 * referenced by subcommand name. 354 */ 355 public SortedMap<String,SubCommand> getSubCommands() 356 { 357 return subCommands; 358 } 359 360 361 362 /** 363 * Indicates whether this argument parser has a subcommand with the specified 364 * name. 365 * 366 * @param name The subcommand name for which to make the determination. 367 * 368 * @return <CODE>true</CODE> if this argument parser has a subcommand with 369 * the specified name, or <CODE>false</CODE> if it does not. 370 */ 371 public boolean hasSubCommand(String name) 372 { 373 return subCommands.containsKey(name); 374 } 375 376 377 378 /** 379 * Retrieves the subcommand with the specified name. 380 * 381 * @param name The name of the subcommand to retrieve. 382 * 383 * @return The subcommand with the specified name, or <CODE>null</CODE> if no 384 * such subcommand is defined. 385 */ 386 public SubCommand getSubCommand(String name) 387 { 388 return subCommands.get(name); 389 } 390 391 392 393 /** 394 * Retrieves the subcommand that was selected in the set of command-line 395 * arguments. 396 * 397 * @return The subcommand that was selected in the set of command-line 398 * arguments, or <CODE>null</CODE> if none was selected. 399 */ 400 public SubCommand getSubCommand() 401 { 402 return subCommand; 403 } 404 405 406 407 /** 408 * Retrieves the raw set of arguments that were provided. 409 * 410 * @return The raw set of arguments that were provided, or <CODE>null</CODE> 411 * if the argument list has not yet been parsed. 412 */ 413 public String[] getRawArguments() 414 { 415 return rawArguments; 416 } 417 418 419 420 /** 421 * Adds the provided argument to the set of global arguments handled by this 422 * parser. 423 * 424 * @param argument The argument to be added. 425 * 426 * @throws ArgumentException If the provided argument conflicts with another 427 * global or subcommand argument that has already 428 * been defined. 429 */ 430 public void addGlobalArgument(Argument argument) 431 throws ArgumentException 432 { 433 addGlobalArgument(argument, null); 434 } 435 436 437 /** 438 * Adds the provided argument to the set of global arguments handled by this 439 * parser. 440 * 441 * @param argument The argument to be added. 442 * @param group The argument group to which the argument belongs. 443 * @throws ArgumentException If the provided argument conflicts with another 444 * global or subcommand argument that has already 445 * been defined. 446 */ 447 public void addGlobalArgument(Argument argument, ArgumentGroup group) 448 throws ArgumentException 449 { 450 451 String argumentName = argument.getName(); 452 if (globalArgumentMap.containsKey(argumentName)) 453 { 454 Message message = 455 ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_NAME.get(argumentName); 456 throw new ArgumentException(message); 457 } 458 for (SubCommand s : subCommands.values()) 459 { 460 if (s.getArgumentForName(argumentName) != null) 461 { 462 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_NAME_SUBCMD_CONFLICT.get( 463 argumentName, s.getName()); 464 throw new ArgumentException(message); 465 } 466 } 467 468 469 Character shortID = argument.getShortIdentifier(); 470 if (shortID != null) 471 { 472 if (globalShortIDMap.containsKey(shortID)) 473 { 474 String name = globalShortIDMap.get(shortID).getName(); 475 476 Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_SHORT_ID.get( 477 String.valueOf(shortID), argumentName, name); 478 throw new ArgumentException(message); 479 } 480 481 for (SubCommand s : subCommands.values()) 482 { 483 if (s.getArgument(shortID) != null) 484 { 485 String cmdName = s.getName(); 486 String name = s.getArgument(shortID).getName(); 487 488 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_SHORT_ID_CONFLICT.get( 489 String.valueOf(shortID), argumentName, name, cmdName); 490 throw new ArgumentException(message); 491 } 492 } 493 } 494 495 496 String longID = argument.getLongIdentifier(); 497 if (longID != null) 498 { 499 if (! longArgumentsCaseSensitive) 500 { 501 longID = toLowerCase(longID); 502 } 503 504 if (globalLongIDMap.containsKey(longID)) 505 { 506 String name = globalLongIDMap.get(longID).getName(); 507 508 Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_LONG_ID.get( 509 argument.getLongIdentifier(), argumentName, name); 510 throw new ArgumentException(message); 511 } 512 513 for (SubCommand s : subCommands.values()) 514 { 515 if (s.getArgument(longID) != null) 516 { 517 String cmdName = s.getName(); 518 String name = s.getArgument(longID).getName(); 519 520 Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_LONG_ID_CONFLICT.get( 521 argument.getLongIdentifier(), argumentName, name, cmdName); 522 throw new ArgumentException(message); 523 } 524 } 525 } 526 527 528 if (shortID != null) 529 { 530 globalShortIDMap.put(shortID, argument); 531 } 532 533 if (longID != null) 534 { 535 globalLongIDMap.put(longID, argument); 536 } 537 538 globalArgumentList.add(argument); 539 540 if (group == null) { 541 group = getStandardGroup(argument); 542 } 543 group.addArgument(argument); 544 argumentGroups.add(group); 545 } 546 547 /** 548 * Removes the provided argument from the set of global arguments handled by 549 * this parser. 550 * 551 * @param argument The argument to be removed. 552 */ 553 protected void removeGlobalArgument(Argument argument) 554 { 555 String argumentName = argument.getName(); 556 globalArgumentMap.remove(argumentName); 557 558 Character shortID = argument.getShortIdentifier(); 559 if (shortID != null) 560 { 561 globalShortIDMap.remove(shortID); 562 } 563 564 String longID = argument.getLongIdentifier(); 565 if (longID != null) 566 { 567 if (! longArgumentsCaseSensitive) 568 { 569 longID = toLowerCase(longID); 570 } 571 572 globalLongIDMap.remove(longID); 573 } 574 575 globalArgumentList.remove(argument); 576 } 577 578 579 /** 580 * Sets the provided argument as one which will automatically 581 * trigger the output of full usage information if it is provided on 582 * the command line and no further argument validation will be 583 * performed. 584 * <p> 585 * If sub-command groups are defined using the 586 * {@link #setUsageGroupArgument(Argument, Collection)} method, then 587 * this usage argument, when specified, will result in usage 588 * information being displayed which does not include information on 589 * sub-commands. 590 * <p> 591 * Note that the caller will still need to add this argument to the 592 * parser with the {@link #addGlobalArgument(Argument)} method, and 593 * the argument should not be required and should not take a value. 594 * Also, the caller will still need to check for the presence of the 595 * usage argument after calling {@link #parseArguments(String[])} to 596 * know that no further processing will be required. 597 * 598 * @param argument 599 * The argument whose presence should automatically trigger 600 * the display of full usage information. 601 * @param outputStream 602 * The output stream to which the usage information should 603 * be written. 604 */ 605 public void setUsageArgument(Argument argument, OutputStream outputStream) { 606 usageArgument = argument; 607 usageOutputStream = outputStream; 608 609 usageGroupArguments.put(argument, Collections.<SubCommand>emptySet()); 610 } 611 612 613 614 /** 615 * Sets the provided argument as one which will automatically 616 * trigger the output of partial usage information if it is provided 617 * on the command line and no further argument validation will be 618 * performed. 619 * <p> 620 * Partial usage information will include a usage synopsis, a 621 * summary of each of the sub-commands listed in the provided 622 * sub-commands collection, and a summary of the global options. 623 * <p> 624 * Note that the caller will still need to add this argument to the 625 * parser with the {@link #addGlobalArgument(Argument)} method, and 626 * the argument should not be required and should not take a value. 627 * Also, the caller will still need to check for the presence of the 628 * usage argument after calling {@link #parseArguments(String[])} to 629 * know that no further processing will be required. 630 * 631 * @param argument 632 * The argument whose presence should automatically trigger 633 * the display of partial usage information. 634 * @param subCommands 635 * The list of sub-commands which should have their usage 636 * displayed. 637 */ 638 public void setUsageGroupArgument(Argument argument, 639 Collection<SubCommand> subCommands) { 640 usageGroupArguments.put(argument, subCommands); 641 } 642 643 644 /** 645 * Parses the provided set of arguments and updates the information associated 646 * with this parser accordingly. 647 * 648 * @param rawArguments The raw set of arguments to parse. 649 * 650 * @throws ArgumentException If a problem was encountered while parsing the 651 * provided arguments. 652 */ 653 public void parseArguments(String[] rawArguments) 654 throws ArgumentException 655 { 656 parseArguments(rawArguments, null); 657 } 658 659 660 661 /** 662 * Parses the provided set of arguments and updates the information associated 663 * with this parser accordingly. Default values for unspecified arguments 664 * may be read from the specified properties file. 665 * 666 * @param rawArguments The set of raw arguments to parse. 667 * @param propertiesFile The path to the properties file to use to 668 * obtain default values for unspecified 669 * properties. 670 * @param requirePropertiesFile Indicates whether the parsing should fail if 671 * the provided properties file does not exist 672 * or is not accessible. 673 * 674 * @throws ArgumentException If a problem was encountered while parsing the 675 * provided arguments or interacting with the 676 * properties file. 677 */ 678 public void parseArguments(String[] rawArguments, String propertiesFile, 679 boolean requirePropertiesFile) 680 throws ArgumentException 681 { 682 this.rawArguments = rawArguments; 683 684 Properties argumentProperties = null; 685 686 try 687 { 688 Properties p = new Properties(); 689 FileInputStream fis = new FileInputStream(propertiesFile); 690 p.load(fis); 691 fis.close(); 692 argumentProperties = p; 693 } 694 catch (Exception e) 695 { 696 if (requirePropertiesFile) 697 { 698 Message message = ERR_SUBCMDPARSER_CANNOT_READ_PROPERTIES_FILE.get( 699 String.valueOf(propertiesFile), getExceptionMessage(e)); 700 throw new ArgumentException(message, e); 701 } 702 } 703 704 parseArguments(rawArguments, argumentProperties); 705 } 706 707 708 709 /** 710 * Parses the provided set of arguments and updates the information associated 711 * with this parser accordingly. Default values for unspecified arguments may 712 * be read from the specified properties if any are provided. 713 * 714 * @param rawArguments The set of raw arguments to parse. 715 * @param argumentProperties A set of properties that may be used to provide 716 * default values for arguments not included in 717 * the given raw arguments. 718 * 719 * @throws ArgumentException If a problem was encountered while parsing the 720 * provided arguments. 721 */ 722 public void parseArguments(String[] rawArguments, 723 Properties argumentProperties) 724 throws ArgumentException 725 { 726 this.rawArguments = rawArguments; 727 this.subCommand = null; 728 this.trailingArguments = new ArrayList<String>(); 729 this.usageOrVersionDisplayed = false; 730 731 boolean inTrailingArgs = false; 732 733 int numArguments = rawArguments.length; 734 for (int i=0; i < numArguments; i++) 735 { 736 String arg = rawArguments[i]; 737 738 if (inTrailingArgs) 739 { 740 trailingArguments.add(arg); 741 if ((subCommand.getMaxTrailingArguments() > 0) && 742 (trailingArguments.size() > subCommand.getMaxTrailingArguments())) 743 { 744 Message message = ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS.get( 745 subCommand.getMaxTrailingArguments()); 746 throw new ArgumentException(message); 747 } 748 749 continue; 750 } 751 752 if (arg.equals("--")) 753 { 754 inTrailingArgs = true; 755 } 756 else if (arg.startsWith("--")) 757 { 758 // This indicates that we are using the long name to reference the 759 // argument. It may be in any of the following forms: 760 // --name 761 // --name value 762 // --name=value 763 764 String argName = arg.substring(2); 765 String argValue = null; 766 int equalPos = argName.indexOf('='); 767 if (equalPos < 0) 768 { 769 // This is fine. The value is not part of the argument name token. 770 } 771 else if (equalPos == 0) 772 { 773 // The argument starts with "--=", which is not acceptable. 774 Message message = ERR_SUBCMDPARSER_LONG_ARG_WITHOUT_NAME.get(arg); 775 throw new ArgumentException(message); 776 } 777 else 778 { 779 // The argument is in the form --name=value, so parse them both out. 780 argValue = argName.substring(equalPos+1); 781 argName = argName.substring(0, equalPos); 782 } 783 784 // If we're not case-sensitive, then convert the name to lowercase. 785 String origArgName = argName; 786 if (! longArgumentsCaseSensitive) 787 { 788 argName = toLowerCase(argName); 789 } 790 791 // See if the specified name references a global argument. If not, then 792 // see if it references a subcommand argument. 793 Argument a = globalLongIDMap.get(argName); 794 if (a == null) 795 { 796 if (subCommand == null) 797 { 798 if (argName.equals("help")) 799 { 800 // "--help" will always be interpreted as requesting usage 801 // information. 802 try 803 { 804 getUsage(usageOutputStream); 805 } catch (Exception e) {} 806 807 return; 808 } 809 else 810 if (argName.equals(OPTION_LONG_PRODUCT_VERSION)) 811 { 812 // "--version" will always be interpreted as requesting usage 813 // information. 814 try 815 { 816 versionPresent = true; 817 DirectoryServer.printVersion(usageOutputStream); 818 usageOrVersionDisplayed = true ; 819 } catch (Exception e) {} 820 821 return; 822 } 823 else 824 { 825 // There is no such global argument. 826 Message message = 827 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_LONG_ID.get( 828 origArgName); 829 throw new ArgumentException(message); 830 } 831 } 832 else 833 { 834 a = subCommand.getArgument(argName); 835 if (a == null) 836 { 837 if (argName.equals("help")) 838 { 839 // "--help" will always be interpreted as requesting usage 840 // information. 841 try 842 { 843 getUsage(usageOutputStream); 844 } catch (Exception e) {} 845 846 return; 847 } 848 else 849 if (argName.equals(OPTION_LONG_PRODUCT_VERSION)) 850 { 851 // "--version" will always be interpreted as requesting usage 852 // information. 853 try 854 { 855 versionPresent = true; 856 DirectoryServer.printVersion(usageOutputStream); 857 usageOrVersionDisplayed = true ; 858 } catch (Exception e) {} 859 860 return; 861 } 862 else 863 { 864 // There is no such global or subcommand argument. 865 Message message = 866 ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_LONG_ID.get(origArgName); 867 throw new ArgumentException(message); 868 } 869 } 870 } 871 } 872 873 a.setPresent(true); 874 875 // If this is a usage argument, then immediately stop and print 876 // usage information. 877 if (usageGroupArguments.containsKey(a)) 878 { 879 try 880 { 881 getUsage(a, usageOutputStream); 882 } catch (Exception e) {} 883 884 return; 885 } 886 887 // See if the argument takes a value. If so, then make sure one was 888 // provided. If not, then make sure none was provided. 889 if (a.needsValue()) 890 { 891 if (argValue == null) 892 { 893 if ((i+1) == numArguments) 894 { 895 Message message = 896 ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID. 897 get(argName); 898 throw new ArgumentException(message); 899 } 900 901 argValue = rawArguments[++i]; 902 } 903 904 MessageBuilder invalidReason = new MessageBuilder(); 905 if (! a.valueIsAcceptable(argValue, invalidReason)) 906 { 907 Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID. 908 get(argValue, argName, invalidReason.toString()); 909 throw new ArgumentException(message); 910 } 911 912 // If the argument already has a value, then make sure it is 913 // acceptable to have more than one. 914 if (a.hasValue() && (! a.isMultiValued())) 915 { 916 Message message = 917 ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_LONG_ID.get(origArgName); 918 throw new ArgumentException(message); 919 } 920 921 a.addValue(argValue); 922 } 923 else 924 { 925 if (argValue != null) 926 { 927 Message message = 928 ERR_SUBCMDPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE.get( 929 origArgName); 930 throw new ArgumentException(message); 931 } 932 } 933 } 934 else if (arg.startsWith("-")) 935 { 936 // This indicates that we are using the 1-character name to reference 937 // the argument. It may be in any of the following forms: 938 // -n 939 // -nvalue 940 // -n value 941 if (arg.equals("-")) 942 { 943 Message message = ERR_SUBCMDPARSER_INVALID_DASH_AS_ARGUMENT.get(); 944 throw new ArgumentException(message); 945 } 946 947 char argCharacter = arg.charAt(1); 948 String argValue; 949 if (arg.length() > 2) 950 { 951 argValue = arg.substring(2); 952 } 953 else 954 { 955 argValue = null; 956 } 957 958 959 // Get the argument with the specified short ID. It may be either a 960 // global argument or a subcommand-specific argument. 961 Argument a = globalShortIDMap.get(argCharacter); 962 if (a == null) 963 { 964 if (subCommand == null) 965 { 966 if (argCharacter == '?') 967 { 968 // "-?" will always be interpreted as requesting usage. 969 try 970 { 971 getUsage(usageOutputStream); 972 if (usageArgument != null) 973 { 974 usageArgument.setPresent(true); 975 } 976 } catch (Exception e) {} 977 978 return; 979 } 980 else 981 if (argCharacter == OPTION_SHORT_PRODUCT_VERSION) 982 { 983 // "-V" will always be interpreted as requesting 984 // version information except if it's already defined. 985 boolean dashVAccepted = true; 986 if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)) 987 { 988 dashVAccepted = false; 989 } 990 else 991 { 992 for (SubCommand subCmd : subCommands.values()) 993 { 994 if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION) != null) 995 { 996 dashVAccepted = false; 997 break; 998 } 999 } 1000 } 1001 if (dashVAccepted) 1002 { 1003 usageOrVersionDisplayed = true; 1004 versionPresent = true; 1005 try 1006 { 1007 DirectoryServer.printVersion(usageOutputStream); 1008 } 1009 catch (Exception e) 1010 { 1011 } 1012 return; 1013 } 1014 else 1015 { 1016 // -V is defined in another suncommand, so we can 1017 // accepted it as the version information argument 1018 Message message = 1019 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID. 1020 get(String.valueOf(argCharacter)); 1021 throw new ArgumentException(message); 1022 } 1023 } 1024 else 1025 { 1026 // There is no such argument registered. 1027 Message message = 1028 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID. 1029 get(String.valueOf(argCharacter)); 1030 throw new ArgumentException(message); 1031 } 1032 } 1033 else 1034 { 1035 a = subCommand.getArgument(argCharacter); 1036 if (a == null) 1037 { 1038 if (argCharacter == '?') 1039 { 1040 // "-?" will always be interpreted as requesting usage. 1041 try 1042 { 1043 getUsage(usageOutputStream); 1044 } catch (Exception e) {} 1045 1046 return; 1047 } 1048 else 1049 if (argCharacter == OPTION_SHORT_PRODUCT_VERSION) 1050 { 1051 // "-V" will always be interpreted as requesting 1052 // version information except if it's already defined. 1053 boolean dashVAccepted = true; 1054 if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)) 1055 { 1056 dashVAccepted = false; 1057 } 1058 else 1059 { 1060 for (SubCommand subCmd : subCommands.values()) 1061 { 1062 if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION)!=null) 1063 { 1064 dashVAccepted = false; 1065 break; 1066 } 1067 } 1068 } 1069 if (dashVAccepted) 1070 { 1071 usageOrVersionDisplayed = true; 1072 versionPresent = true; 1073 try 1074 { 1075 DirectoryServer.printVersion(usageOutputStream); 1076 } 1077 catch (Exception e) 1078 { 1079 } 1080 return; 1081 } 1082 } 1083 else 1084 { 1085 // There is no such argument registered. 1086 Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.get( 1087 String.valueOf(argCharacter)); 1088 throw new ArgumentException(message); 1089 } 1090 } 1091 } 1092 } 1093 1094 a.setPresent(true); 1095 1096 // If this is the usage argument, then immediately stop and print 1097 // usage information. 1098 if (usageGroupArguments.containsKey(a)) 1099 { 1100 try 1101 { 1102 getUsage(a, usageOutputStream); 1103 } catch (Exception e) {} 1104 1105 return; 1106 } 1107 1108 // See if the argument takes a value. If so, then make sure one was 1109 // provided. If not, then make sure none was provided. 1110 if (a.needsValue()) 1111 { 1112 if (argValue == null) 1113 { 1114 if ((i+1) == numArguments) 1115 { 1116 Message message = 1117 ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID. 1118 get(String.valueOf(argCharacter)); 1119 throw new ArgumentException(message); 1120 } 1121 1122 argValue = rawArguments[++i]; 1123 } 1124 1125 MessageBuilder invalidReason = new MessageBuilder(); 1126 if (! a.valueIsAcceptable(argValue, invalidReason)) 1127 { 1128 Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID. 1129 get(argValue, String.valueOf(argCharacter), 1130 invalidReason.toString()); 1131 throw new ArgumentException(message); 1132 } 1133 1134 // If the argument already has a value, then make sure it is 1135 // acceptable to have more than one. 1136 if (a.hasValue() && (! a.isMultiValued())) 1137 { 1138 Message message = ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get( 1139 String.valueOf(argCharacter)); 1140 throw new ArgumentException(message); 1141 } 1142 1143 a.addValue(argValue); 1144 } 1145 else 1146 { 1147 if (argValue != null) 1148 { 1149 // If we've gotten here, then it means that we're in a scenario like 1150 // "-abc" where "a" is a valid argument that doesn't take a value. 1151 // However, this could still be valid if all remaining characters in 1152 // the value are also valid argument characters that don't take 1153 // values. 1154 int valueLength = argValue.length(); 1155 for (int j=0; j < valueLength; j++) 1156 { 1157 char c = argValue.charAt(j); 1158 Argument b = globalShortIDMap.get(c); 1159 if (b == null) 1160 { 1161 if (subCommand == null) 1162 { 1163 Message message = 1164 ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID. 1165 get(String.valueOf(argCharacter)); 1166 throw new ArgumentException(message); 1167 } 1168 else 1169 { 1170 b = subCommand.getArgument(c); 1171 if (b == null) 1172 { 1173 Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID. 1174 get(String.valueOf(argCharacter)); 1175 throw new ArgumentException(message); 1176 } 1177 } 1178 } 1179 1180 if (b.needsValue()) 1181 { 1182 // This means we're in a scenario like "-abc" where b is a 1183 // valid argument that takes a value. We don't support that. 1184 Message message = ERR_SUBCMDPARSER_CANT_MIX_ARGS_WITH_VALUES. 1185 get(String.valueOf(argCharacter), argValue, 1186 String.valueOf(c)); 1187 throw new ArgumentException(message); 1188 } 1189 else 1190 { 1191 b.setPresent(true); 1192 1193 // If this is the usage argument, then immediately stop and 1194 // print usage information. 1195 if (usageGroupArguments.containsKey(b)) 1196 { 1197 try 1198 { 1199 getUsage(b, usageOutputStream); 1200 } catch (Exception e) {} 1201 1202 return; 1203 } 1204 } 1205 } 1206 } 1207 } 1208 } 1209 else if (subCommand != null) 1210 { 1211 // It's not a short or long identifier and the sub-command has 1212 // already been specified, so it must be the first trailing argument. 1213 if (subCommand.allowsTrailingArguments()) 1214 { 1215 trailingArguments.add(arg); 1216 inTrailingArgs = true; 1217 } 1218 else 1219 { 1220 // Trailing arguments are not allowed for this sub-command. 1221 Message message = ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg); 1222 throw new ArgumentException(message); 1223 } 1224 } 1225 else 1226 { 1227 // It must be the sub-command. 1228 String nameToCheck = arg; 1229 if (! longArgumentsCaseSensitive) 1230 { 1231 nameToCheck = toLowerCase(arg); 1232 } 1233 1234 SubCommand sc = subCommands.get(nameToCheck); 1235 if (sc == null) 1236 { 1237 Message message = ERR_SUBCMDPARSER_INVALID_ARGUMENT.get(arg); 1238 throw new ArgumentException(message); 1239 } 1240 else 1241 { 1242 subCommand = sc; 1243 } 1244 } 1245 } 1246 1247 // If we have a sub-command and it allows trailing arguments and 1248 // there is a minimum number, then make sure at least that many 1249 // were provided. 1250 if (subCommand != null) 1251 { 1252 int minTrailingArguments = subCommand.getMinTrailingArguments(); 1253 if (subCommand.allowsTrailingArguments() && (minTrailingArguments > 0)) 1254 { 1255 if (trailingArguments.size() < minTrailingArguments) 1256 { 1257 Message message = ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS.get( 1258 minTrailingArguments); 1259 throw new ArgumentException(message); 1260 } 1261 } 1262 } 1263 1264 // If we don't have the argumentProperties, try to load a properties file. 1265 if (argumentProperties == null) 1266 { 1267 argumentProperties = checkExternalProperties(); 1268 } 1269 1270 // Iterate through all the global arguments and make sure that they have 1271 // values or a suitable default is available. 1272 for (Argument a : globalArgumentList) 1273 { 1274 if (! a.isPresent()) 1275 { 1276 // See if there is a value in the properties that can be used 1277 if ((argumentProperties != null) && (a.getPropertyName() != null)) 1278 { 1279 String value = argumentProperties.getProperty(a.getPropertyName() 1280 .toLowerCase()); 1281 MessageBuilder invalidReason = new MessageBuilder(); 1282 if (value != null) 1283 { 1284 Boolean addValue = true; 1285 if (!( a instanceof BooleanArgument)) 1286 { 1287 addValue = a.valueIsAcceptable(value, invalidReason); 1288 } 1289 if (addValue) 1290 { 1291 a.addValue(value); 1292 if (a.needsValue()) 1293 { 1294 a.setPresent(true); 1295 } 1296 a.setValueSetByProperty(true); 1297 } 1298 } 1299 } 1300 } 1301 1302 if ((! a.isPresent()) && a.needsValue()) 1303 { 1304 // ISee if the argument defines a default. 1305 if (a.getDefaultValue() != null) 1306 { 1307 a.addValue(a.getDefaultValue()); 1308 } 1309 1310 // If there is still no value and the argument is required, then that's 1311 // a problem. 1312 if ((! a.hasValue()) && a.isRequired()) 1313 { 1314 Message message = 1315 ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName()); 1316 throw new ArgumentException(message); 1317 } 1318 } 1319 } 1320 1321 1322 // Iterate through all the subcommand-specific arguments and make sure that 1323 // they have values or a suitable default is available. 1324 if (subCommand != null) 1325 { 1326 for (Argument a : subCommand.getArguments()) 1327 { 1328 if (! a.isPresent()) 1329 { 1330 // See if there is a value in the properties that can be used 1331 if ((argumentProperties != null) && (a.getPropertyName() != null)) 1332 { 1333 String value = argumentProperties.getProperty(a.getPropertyName() 1334 .toLowerCase()); 1335 MessageBuilder invalidReason = new MessageBuilder(); 1336 if (value != null) 1337 { 1338 Boolean addValue = true; 1339 if (!( a instanceof BooleanArgument)) 1340 { 1341 addValue = a.valueIsAcceptable(value, invalidReason); 1342 } 1343 if (addValue) 1344 { 1345 a.addValue(value); 1346 if (a.needsValue()) 1347 { 1348 a.setPresent(true); 1349 } 1350 a.setValueSetByProperty(true); 1351 } 1352 } 1353 } 1354 } 1355 1356 if ((! a.isPresent()) && a.needsValue()) 1357 { 1358 // See if the argument defines a default. 1359 if (a.getDefaultValue() != null) 1360 { 1361 a.addValue(a.getDefaultValue()); 1362 } 1363 1364 // If there is still no value and the argument is required, then 1365 // that's a problem. 1366 if ((! a.hasValue()) && a.isRequired()) 1367 { 1368 Message message = 1369 ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName()); 1370 throw new ArgumentException(message); 1371 } 1372 } 1373 } 1374 } 1375 } 1376 1377 1378 1379 /** 1380 * Appends usage information for the specified subcommand to the provided 1381 * buffer. 1382 * 1383 * @param buffer The buffer to which the usage information should be 1384 * appended. 1385 * @param subCommand The subcommand for which to display the usage 1386 * information. 1387 */ 1388 public void getSubCommandUsage(MessageBuilder buffer, SubCommand subCommand) 1389 { 1390 usageOrVersionDisplayed = true; 1391 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME); 1392 if ((scriptName == null) || (scriptName.length() == 0)) 1393 { 1394 scriptName = "java " + mainClassName; 1395 } 1396 buffer.append(INFO_ARGPARSER_USAGE.get()); 1397 buffer.append(" "); 1398 buffer.append(scriptName); 1399 1400 buffer.append(" "); 1401 buffer.append(subCommand.getName()); 1402 buffer.append(" ").append(INFO_SUBCMDPARSER_OPTIONS.get()); 1403 if (subCommand.allowsTrailingArguments()) { 1404 buffer.append(' '); 1405 buffer.append(subCommand.getTrailingArgumentsDisplayName()); 1406 } 1407 buffer.append(EOL); 1408 buffer.append(subCommand.getDescription()); 1409 buffer.append(EOL); 1410 1411 if ( ! globalArgumentList.isEmpty()) 1412 { 1413 buffer.append(EOL); 1414 buffer.append(INFO_GLOBAL_OPTIONS.get()); 1415 buffer.append(EOL); 1416 buffer.append(" "); 1417 buffer.append(INFO_GLOBAL_OPTIONS_REFERENCE.get(scriptName)); 1418 buffer.append(EOL); 1419 } 1420 1421 if ( ! subCommand.getArguments().isEmpty() ) 1422 { 1423 buffer.append(EOL); 1424 buffer.append(INFO_SUBCMD_OPTIONS.get()); 1425 buffer.append(EOL); 1426 } 1427 for (Argument a : subCommand.getArguments()) 1428 { 1429 // If this argument is hidden, then skip it. 1430 if (a.isHidden()) 1431 { 1432 continue; 1433 } 1434 1435 1436 // Write a line with the short and/or long identifiers that may be used 1437 // for the argument. 1438 Character shortID = a.getShortIdentifier(); 1439 String longID = a.getLongIdentifier(); 1440 if (shortID != null) 1441 { 1442 int currentLength = buffer.length(); 1443 1444 if (a.equals(usageArgument)) 1445 { 1446 buffer.append("-?, "); 1447 } 1448 1449 buffer.append("-"); 1450 buffer.append(shortID.charValue()); 1451 1452 if (a.needsValue() && longID == null) 1453 { 1454 buffer.append(" "); 1455 buffer.append(a.getValuePlaceholder()); 1456 } 1457 1458 if (longID != null) 1459 { 1460 StringBuilder newBuffer = new StringBuilder(); 1461 newBuffer.append(", --"); 1462 newBuffer.append(longID); 1463 1464 if (a.needsValue()) 1465 { 1466 newBuffer.append(" "); 1467 newBuffer.append(a.getValuePlaceholder()); 1468 } 1469 1470 int lineLength = (buffer.length() - currentLength) + 1471 newBuffer.length(); 1472 if (lineLength > MAX_LENGTH) 1473 { 1474 buffer.append(EOL); 1475 buffer.append(newBuffer.toString()); 1476 } 1477 else 1478 { 1479 buffer.append(newBuffer.toString()); 1480 } 1481 } 1482 1483 buffer.append(EOL); 1484 } 1485 else 1486 { 1487 if (longID != null) 1488 { 1489 if (a.equals(usageArgument)) 1490 { 1491 buffer.append("-?, "); 1492 } 1493 buffer.append("--"); 1494 buffer.append(longID); 1495 1496 if (a.needsValue()) 1497 { 1498 buffer.append(" "); 1499 buffer.append(a.getValuePlaceholder()); 1500 } 1501 1502 buffer.append(EOL); 1503 } 1504 } 1505 1506 1507 // Write one or more lines with the description of the argument. We will 1508 // indent the description five characters and try our best to wrap at or 1509 // before column 79 so it will be friendly to 80-column displays. 1510 Message description = a.getDescription(); 1511 int maxLength = MAX_LENGTH - INDENT.length() - 1; 1512 if (description.length() <= maxLength) 1513 { 1514 buffer.append(INDENT); 1515 buffer.append(description); 1516 buffer.append(EOL); 1517 } 1518 else 1519 { 1520 String s = description.toString(); 1521 while (s.length() > maxLength) 1522 { 1523 int spacePos = s.lastIndexOf(' ', maxLength); 1524 if (spacePos > 0) 1525 { 1526 buffer.append(INDENT); 1527 buffer.append(s.substring(0, spacePos).trim()); 1528 s = s.substring(spacePos+1).trim(); 1529 buffer.append(EOL); 1530 } 1531 else 1532 { 1533 // There are no spaces in the first 74 columns. See if there is one 1534 // after that point. If so, then break there. If not, then don't 1535 // break at all. 1536 spacePos = s.indexOf(' '); 1537 if (spacePos > 0) 1538 { 1539 buffer.append(INDENT); 1540 buffer.append(s.substring(0, spacePos).trim()); 1541 s = s.substring(spacePos+1).trim(); 1542 buffer.append(EOL); 1543 } 1544 else 1545 { 1546 buffer.append(INDENT); 1547 buffer.append(s); 1548 s = ""; 1549 buffer.append(EOL); 1550 } 1551 } 1552 } 1553 1554 if (s.length() > 0) 1555 { 1556 buffer.append(" "); 1557 buffer.append(s); 1558 buffer.append(EOL); 1559 } 1560 } 1561 } 1562 } 1563 1564 1565 1566 /** 1567 * Retrieves a string containing usage information based on the defined 1568 * arguments. 1569 * 1570 * @return A string containing usage information based on the defined 1571 * arguments. 1572 */ 1573 public String getUsage() 1574 { 1575 MessageBuilder buffer = new MessageBuilder(); 1576 1577 if (subCommand == null) { 1578 if (usageGroupArguments.size() > 1) { 1579 // We have sub-command groups, so don't display any 1580 // sub-commands by default. 1581 getFullUsage(Collections.<SubCommand> emptySet(), true, buffer); 1582 } else { 1583 // No grouping, so display all sub-commands. 1584 getFullUsage(subCommands.values(), true, buffer); 1585 } 1586 } else { 1587 getSubCommandUsage(buffer, subCommand); 1588 } 1589 1590 return buffer.toMessage().toString(); 1591 } 1592 1593 1594 1595 /** 1596 * Retrieves a string describing how the user can get more help. 1597 * 1598 * @return A string describing how the user can get more help. 1599 */ 1600 public Message getHelpUsageReference() 1601 { 1602 usageOrVersionDisplayed = true; 1603 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME); 1604 if ((scriptName == null) || (scriptName.length() == 0)) 1605 { 1606 scriptName = "java " + mainClassName; 1607 } 1608 1609 MessageBuilder buffer = new MessageBuilder(); 1610 buffer.append(INFO_GLOBAL_HELP_REFERENCE.get(scriptName)); 1611 buffer.append(EOL); 1612 return buffer.toMessage(); 1613 } 1614 1615 1616 1617 /** 1618 * Retrieves the set of unnamed trailing arguments that were 1619 * provided on the command line. 1620 * 1621 * @return The set of unnamed trailing arguments that were provided 1622 * on the command line. 1623 */ 1624 public ArrayList<String> getTrailingArguments() 1625 { 1626 return trailingArguments; 1627 } 1628 1629 1630 1631 /** 1632 * Indicates whether the usage information has been displayed to the end user 1633 * either by an explicit argument like "-H" or "--help", or by a built-in 1634 * argument like "-?". 1635 * 1636 * @return {@code true} if the usage information has been displayed, or 1637 * {@code false} if not. 1638 */ 1639 public boolean usageOrVersionDisplayed() 1640 { 1641 return usageOrVersionDisplayed; 1642 } 1643 1644 1645 1646 /** 1647 * Adds the provided subcommand to this argument parser. This is only 1648 * intended for use by the <CODE>SubCommand</CODE> constructor and does not 1649 * do any validation of its own to ensure that there are no conflicts with the 1650 * subcommand or any of its arguments. 1651 * 1652 * @param subCommand The subcommand to add to this argument parser. 1653 */ 1654 void addSubCommand(SubCommand subCommand) 1655 { 1656 subCommands.put(toLowerCase(subCommand.getName()), subCommand); 1657 } 1658 1659 1660 1661 // Get usage for a specific usage argument. 1662 private void getUsage(Argument a, OutputStream outputStream) 1663 throws IOException { 1664 MessageBuilder buffer = new MessageBuilder(); 1665 1666 if (a.equals(usageArgument) && subCommand != null) { 1667 getSubCommandUsage(buffer, subCommand); 1668 } else if (a.equals(usageArgument) && usageGroupArguments.size() <= 1) { 1669 // No groups - so display all sub-commands. 1670 getFullUsage(subCommands.values(), true, buffer); 1671 } else if (a.equals(usageArgument)) { 1672 // Using groups - so display all sub-commands group help. 1673 getFullUsage(Collections.<SubCommand> emptySet(), true, buffer); 1674 } else { 1675 // Requested help on specific group - don't display global 1676 // options. 1677 getFullUsage(usageGroupArguments.get(a), false, buffer); 1678 } 1679 1680 outputStream.write(getBytes(buffer.toString())); 1681 } 1682 1683 1684 /** 1685 * {@inheritDoc} 1686 */ 1687 public void getUsage(OutputStream outputStream) 1688 throws IOException { 1689 outputStream.write(getBytes(String.valueOf(getUsage()))); 1690 } 1691 1692 1693 1694 // Appends complete usage information for the specified set of 1695 // sub-commands. 1696 private void getFullUsage(Collection<SubCommand> c, 1697 boolean showGlobalOptions, MessageBuilder buffer) { 1698 usageOrVersionDisplayed = true; 1699 if ((toolDescription != null) && (toolDescription.length() > 0)) 1700 { 1701 buffer.append(wrapText(toolDescription, MAX_LENGTH - 1)); 1702 buffer.append(EOL); 1703 buffer.append(EOL); 1704 } 1705 1706 String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME); 1707 if ((scriptName == null) || (scriptName.length() == 0)) 1708 { 1709 scriptName = "java " + mainClassName; 1710 } 1711 buffer.append(INFO_ARGPARSER_USAGE.get()); 1712 buffer.append(" "); 1713 buffer.append(scriptName); 1714 1715 if (subCommands.isEmpty()) 1716 { 1717 buffer.append(" "+INFO_SUBCMDPARSER_OPTIONS.get()); 1718 } 1719 else 1720 { 1721 buffer.append(" "+INFO_SUBCMDPARSER_SUBCMD_AND_OPTIONS.get()); 1722 } 1723 1724 if (!subCommands.isEmpty()) 1725 { 1726 buffer.append(EOL); 1727 buffer.append(EOL); 1728 if (c.isEmpty()) 1729 { 1730 buffer.append(INFO_SUBCMDPARSER_SUBCMD_HELP_HEADING.get()); 1731 } 1732 else 1733 { 1734 buffer.append(INFO_SUBCMDPARSER_SUBCMD_HEADING.get()); 1735 } 1736 buffer.append(EOL); 1737 } 1738 1739 if (c.isEmpty()) { 1740 // Display usage arguments (except the default one). 1741 for (Argument a : globalArgumentList) { 1742 if (a.isHidden()) { 1743 continue; 1744 } 1745 1746 if (usageGroupArguments.containsKey(a)) { 1747 if (!a.equals(usageArgument)) { 1748 printArgumentUsage(a, buffer); 1749 } 1750 } 1751 } 1752 } else { 1753 boolean isFirst = true; 1754 for (SubCommand sc : c) { 1755 if (sc.isHidden()) { 1756 continue; 1757 } 1758 if (isFirst) 1759 { 1760 buffer.append(EOL); 1761 } 1762 buffer.append(sc.getName()); 1763 buffer.append(EOL); 1764 indentAndWrap(Message.raw(INDENT), sc.getDescription(), buffer); 1765 buffer.append(EOL); 1766 isFirst = false; 1767 } 1768 } 1769 1770 buffer.append(EOL); 1771 1772 if (showGlobalOptions) { 1773 if (subCommands.isEmpty()) 1774 { 1775 buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get()); 1776 buffer.append(EOL); 1777 } 1778 else 1779 { 1780 buffer.append(INFO_SUBCMDPARSER_GLOBAL_HEADING.get()); 1781 buffer.append(EOL); 1782 } 1783 buffer.append(EOL); 1784 1785 boolean printGroupHeaders = printUsageGroupHeaders(); 1786 1787 // Display non-usage arguments. 1788 for (ArgumentGroup argGroup : argumentGroups) 1789 { 1790 if (argGroup.containsArguments() && printGroupHeaders) 1791 { 1792 // Print the groups description if any 1793 Message groupDesc = argGroup.getDescription(); 1794 if (groupDesc != null && !Message.EMPTY.equals(groupDesc)) { 1795 buffer.append(EOL); 1796 buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1)); 1797 buffer.append(EOL); 1798 buffer.append(EOL); 1799 } 1800 } 1801 1802 for (Argument a : argGroup.getArguments()) { 1803 if (a.isHidden()) { 1804 continue; 1805 } 1806 1807 if (!usageGroupArguments.containsKey(a)) { 1808 printArgumentUsage(a, buffer); 1809 } 1810 } 1811 } 1812 1813 // Finally print default usage argument. 1814 if (usageArgument != null) { 1815 printArgumentUsage(usageArgument, buffer); 1816 } else { 1817 buffer.append("-?"); 1818 } 1819 buffer.append(EOL); 1820 } 1821 } 1822 1823 1824 1825 /** 1826 * Appends argument usage information to the provided buffer. 1827 * 1828 * @param a 1829 * The argument to handle. 1830 * @param buffer 1831 * The buffer to which the usage information should be 1832 * appended. 1833 */ 1834 private void printArgumentUsage(Argument a, MessageBuilder buffer) { 1835 String value; 1836 if (a.needsValue()) 1837 { 1838 Message pHolder = a.getValuePlaceholder(); 1839 if (pHolder == null) 1840 { 1841 value = " {value}"; 1842 } 1843 else 1844 { 1845 value = " " + pHolder; 1846 } 1847 } 1848 else 1849 { 1850 value = ""; 1851 } 1852 1853 Character shortIDChar = a.getShortIdentifier(); 1854 if (shortIDChar != null) 1855 { 1856 if (a.equals(usageArgument)) 1857 { 1858 buffer.append("-?, "); 1859 } 1860 buffer.append("-"); 1861 buffer.append(shortIDChar); 1862 1863 String longIDString = a.getLongIdentifier(); 1864 if (longIDString != null) 1865 { 1866 buffer.append(", --"); 1867 buffer.append(longIDString); 1868 } 1869 buffer.append(value); 1870 } 1871 else 1872 { 1873 String longIDString = a.getLongIdentifier(); 1874 if (longIDString != null) 1875 { 1876 if (a.equals(usageArgument)) 1877 { 1878 buffer.append("-?, "); 1879 } 1880 buffer.append("--"); 1881 buffer.append(longIDString); 1882 buffer.append(value); 1883 } 1884 } 1885 1886 buffer.append(EOL); 1887 indentAndWrap(Message.raw(INDENT), a.getDescription(), buffer); 1888 } 1889 1890 1891 1892 /** 1893 * Write one or more lines with the description of the argument. We will 1894 * indent the description five characters and try our best to wrap at or 1895 * before column 79 so it will be friendly to 80-column displays. 1896 */ 1897 private void indentAndWrap(Message indent, Message text, 1898 MessageBuilder buffer) 1899 { 1900 int actualSize = MAX_LENGTH - indent.length(); 1901 if (text.length() <= actualSize) 1902 { 1903 buffer.append(indent); 1904 buffer.append(text); 1905 buffer.append(EOL); 1906 } 1907 else 1908 { 1909 String s = text.toString(); 1910 while (s.length() > actualSize) 1911 { 1912 int spacePos = s.lastIndexOf(' ', actualSize); 1913 if (spacePos > 0) 1914 { 1915 buffer.append(indent); 1916 buffer.append(s.substring(0, spacePos).trim()); 1917 s = s.substring(spacePos + 1).trim(); 1918 buffer.append(EOL); 1919 } 1920 else 1921 { 1922 // There are no spaces in the first actualSize -1 columns. See 1923 // if there is one after that point. If so, then break there. 1924 // If not, then don't break at all. 1925 spacePos = s.indexOf(' '); 1926 if (spacePos > 0) 1927 { 1928 buffer.append(indent); 1929 buffer.append(s.substring(0, spacePos).trim()); 1930 s = s.substring(spacePos + 1).trim(); 1931 buffer.append(EOL); 1932 } 1933 else 1934 { 1935 buffer.append(indent); 1936 buffer.append(s); 1937 s = ""; 1938 buffer.append(EOL); 1939 } 1940 } 1941 } 1942 1943 if (s.length() > 0) 1944 { 1945 buffer.append(indent); 1946 buffer.append(s); 1947 buffer.append(EOL); 1948 } 1949 } 1950 } 1951 1952 /** 1953 * Returns whether the usage argument was provided or not. This method 1954 * should be called after a call to parseArguments. 1955 * @return <CODE>true</CODE> if the usage argument was provided and 1956 * <CODE>false</CODE> otherwise. 1957 */ 1958 public boolean isUsageArgumentPresent() 1959 { 1960 boolean isUsageArgumentPresent = false; 1961 if (usageArgument != null) 1962 { 1963 isUsageArgumentPresent = usageArgument.isPresent(); 1964 } 1965 return isUsageArgumentPresent; 1966 } 1967 1968 /** 1969 * Returns whether the version argument was provided or not. This method 1970 * should be called after a call to parseArguments. 1971 * @return <CODE>true</CODE> if the version argument was provided and 1972 * <CODE>false</CODE> otherwise. 1973 */ 1974 public boolean isVersionArgumentPresent() 1975 { 1976 boolean isPresent; 1977 if (!super.isVersionArgumentPresent()) 1978 { 1979 isPresent = versionPresent; 1980 } 1981 else 1982 { 1983 isPresent = true; 1984 } 1985 return isPresent; 1986 } 1987 } 1988