001/* 002 * Copyright 2008-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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.BufferedReader; 026import java.io.File; 027import java.io.FileReader; 028import java.io.IOException; 029import java.io.OutputStream; 030import java.io.PrintWriter; 031import java.io.Serializable; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.Collection; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.LinkedHashSet; 039import java.util.LinkedHashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043 044import com.unboundid.util.Debug; 045import com.unboundid.util.ObjectPair; 046import com.unboundid.util.ThreadSafety; 047import com.unboundid.util.ThreadSafetyLevel; 048 049import static com.unboundid.util.StaticUtils.*; 050import static com.unboundid.util.Validator.*; 051import static com.unboundid.util.args.ArgsMessages.*; 052 053 054 055/** 056 * This class provides an argument parser, which may be used to process command 057 * line arguments provided to Java applications. See the package-level Javadoc 058 * documentation for details regarding the capabilities of the argument parser. 059 */ 060@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 061public final class ArgumentParser 062 implements Serializable 063{ 064 /** 065 * The name of the system property that can be used to specify the default 066 * properties file that should be used to obtain the default values for 067 * arguments not specified via the command line. 068 */ 069 public static final String PROPERTY_DEFAULT_PROPERTIES_FILE_PATH = 070 ArgumentParser.class.getName() + ".propertiesFilePath"; 071 072 073 074 /** 075 * The name of an environment variable that can be used to specify the default 076 * properties file that should be used to obtain the default values for 077 * arguments not specified via the command line. 078 */ 079 public static final String ENV_DEFAULT_PROPERTIES_FILE_PATH = 080 "UNBOUNDID_TOOL_PROPERTIES_FILE_PATH"; 081 082 083 084 /** 085 * The name of the argument used to specify the path to a file to which all 086 * output should be written. 087 */ 088 private static final String ARG_NAME_OUTPUT_FILE = "outputFile"; 089 090 091 092 /** 093 * The name of the argument used to indicate that output should be written to 094 * both the output file and the console. 095 */ 096 private static final String ARG_NAME_TEE_OUTPUT = "teeOutput"; 097 098 099 100 /** 101 * The name of the argument used to specify the path to a properties file from 102 * which to obtain the default values for arguments not specified via the 103 * command line. 104 */ 105 private static final String ARG_NAME_PROPERTIES_FILE_PATH = 106 "propertiesFilePath"; 107 108 109 110 /** 111 * The name of the argument used to specify the path to a file to be generated 112 * with information about the properties that the tool supports. 113 */ 114 private static final String ARG_NAME_GENERATE_PROPERTIES_FILE = 115 "generatePropertiesFile"; 116 117 118 119 /** 120 * The name of the argument used to indicate that the tool should not use any 121 * properties file to obtain default values for arguments not specified via 122 * the command line. 123 */ 124 private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile"; 125 126 127 128 /** 129 * The serial version UID for this serializable class. 130 */ 131 private static final long serialVersionUID = 3053102992180360269L; 132 133 134 135 // The properties file used to obtain arguments for this tool. 136 private volatile File propertiesFileUsed; 137 138 // The maximum number of trailing arguments allowed to be provided. 139 private final int maxTrailingArgs; 140 141 // The minimum number of trailing arguments allowed to be provided. 142 private final int minTrailingArgs; 143 144 // The set of named arguments associated with this parser, indexed by short 145 // identifier. 146 private final LinkedHashMap<Character,Argument> namedArgsByShortID; 147 148 // The set of named arguments associated with this parser, indexed by long 149 // identifier. 150 private final LinkedHashMap<String,Argument> namedArgsByLongID; 151 152 // The set of subcommands associated with this parser, indexed by name. 153 private final LinkedHashMap<String,SubCommand> subCommandsByName; 154 155 // The full set of named arguments associated with this parser. 156 private final List<Argument> namedArgs; 157 158 // Sets of arguments in which if the key argument is provided, then at least 159 // one of the value arguments must also be provided. 160 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets; 161 162 // Sets of arguments in which at most one argument in the list is allowed to 163 // be present. 164 private final List<Set<Argument>> exclusiveArgumentSets; 165 166 // Sets of arguments in which at least one argument in the list is required to 167 // be present. 168 private final List<Set<Argument>> requiredArgumentSets; 169 170 // A list of any arguments set from the properties file rather than explicitly 171 // provided on the command line. 172 private final List<String> argumentsSetFromPropertiesFile; 173 174 // The list of trailing arguments provided on the command line. 175 private final List<String> trailingArgs; 176 177 // The full list of subcommands associated with this argument parser. 178 private final List<SubCommand> subCommands; 179 180 // The description for the associated command. 181 private final String commandDescription; 182 183 // The name for the associated command. 184 private final String commandName; 185 186 // The placeholder string for the trailing arguments. 187 private final String trailingArgsPlaceholder; 188 189 // The subcommand with which this argument parser is associated. 190 private volatile SubCommand parentSubCommand; 191 192 // The subcommand that was included in the set of command-line arguments. 193 private volatile SubCommand selectedSubCommand; 194 195 196 197 /** 198 * Creates a new instance of this argument parser with the provided 199 * information. It will not allow unnamed trailing arguments. 200 * 201 * @param commandName The name of the application or utility with 202 * which this argument parser is associated. It 203 * must not be {@code null}. 204 * @param commandDescription A description of the application or utility 205 * with which this argument parser is associated. 206 * It will be included in generated usage 207 * information. It must not be {@code null}. 208 * 209 * @throws ArgumentException If either the command name or command 210 * description is {@code null}, 211 */ 212 public ArgumentParser(final String commandName, 213 final String commandDescription) 214 throws ArgumentException 215 { 216 this(commandName, commandDescription, 0, null); 217 } 218 219 220 221 /** 222 * Creates a new instance of this argument parser with the provided 223 * information. 224 * 225 * @param commandName The name of the application or utility 226 * with which this argument parser is 227 * associated. It must not be {@code null}. 228 * @param commandDescription A description of the application or 229 * utility with which this argument parser is 230 * associated. It will be included in 231 * generated usage information. It must not 232 * be {@code null}. 233 * @param maxTrailingArgs The maximum number of trailing arguments 234 * that may be provided to this command. A 235 * value of zero indicates that no trailing 236 * arguments will be allowed. A value less 237 * than zero will indicate that there is no 238 * limit on the number of trailing arguments 239 * allowed. 240 * @param trailingArgsPlaceholder A placeholder string that will be included 241 * in usage output to indicate what trailing 242 * arguments may be provided. It must not be 243 * {@code null} if {@code maxTrailingArgs} is 244 * anything other than zero. 245 * 246 * @throws ArgumentException If either the command name or command 247 * description is {@code null}, or if the maximum 248 * number of trailing arguments is non-zero and 249 * the trailing arguments placeholder is 250 * {@code null}. 251 */ 252 public ArgumentParser(final String commandName, 253 final String commandDescription, 254 final int maxTrailingArgs, 255 final String trailingArgsPlaceholder) 256 throws ArgumentException 257 { 258 this(commandName, commandDescription, 0, maxTrailingArgs, 259 trailingArgsPlaceholder); 260 } 261 262 263 264 /** 265 * Creates a new instance of this argument parser with the provided 266 * information. 267 * 268 * @param commandName The name of the application or utility 269 * with which this argument parser is 270 * associated. It must not be {@code null}. 271 * @param commandDescription A description of the application or 272 * utility with which this argument parser is 273 * associated. It will be included in 274 * generated usage information. It must not 275 * be {@code null}. 276 * @param minTrailingArgs The minimum number of trailing arguments 277 * that must be provided for this command. A 278 * value of zero indicates that the command 279 * may be invoked without any trailing 280 * arguments. 281 * @param maxTrailingArgs The maximum number of trailing arguments 282 * that may be provided to this command. A 283 * value of zero indicates that no trailing 284 * arguments will be allowed. A value less 285 * than zero will indicate that there is no 286 * limit on the number of trailing arguments 287 * allowed. 288 * @param trailingArgsPlaceholder A placeholder string that will be included 289 * in usage output to indicate what trailing 290 * arguments may be provided. It must not be 291 * {@code null} if {@code maxTrailingArgs} is 292 * anything other than zero. 293 * 294 * @throws ArgumentException If either the command name or command 295 * description is {@code null}, or if the maximum 296 * number of trailing arguments is non-zero and 297 * the trailing arguments placeholder is 298 * {@code null}. 299 */ 300 public ArgumentParser(final String commandName, 301 final String commandDescription, 302 final int minTrailingArgs, 303 final int maxTrailingArgs, 304 final String trailingArgsPlaceholder) 305 throws ArgumentException 306 { 307 if (commandName == null) 308 { 309 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get()); 310 } 311 312 if (commandDescription == null) 313 { 314 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get()); 315 } 316 317 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null)) 318 { 319 throw new ArgumentException( 320 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get()); 321 } 322 323 this.commandName = commandName; 324 this.commandDescription = commandDescription; 325 this.trailingArgsPlaceholder = trailingArgsPlaceholder; 326 327 if (minTrailingArgs >= 0) 328 { 329 this.minTrailingArgs = minTrailingArgs; 330 } 331 else 332 { 333 this.minTrailingArgs = 0; 334 } 335 336 if (maxTrailingArgs >= 0) 337 { 338 this.maxTrailingArgs = maxTrailingArgs; 339 } 340 else 341 { 342 this.maxTrailingArgs = Integer.MAX_VALUE; 343 } 344 345 if (this.minTrailingArgs > this.maxTrailingArgs) 346 { 347 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get( 348 this.minTrailingArgs, this.maxTrailingArgs)); 349 } 350 351 namedArgsByShortID = new LinkedHashMap<Character,Argument>(); 352 namedArgsByLongID = new LinkedHashMap<String,Argument>(); 353 namedArgs = new ArrayList<Argument>(); 354 trailingArgs = new ArrayList<String>(); 355 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(); 356 exclusiveArgumentSets = new ArrayList<Set<Argument>>(); 357 requiredArgumentSets = new ArrayList<Set<Argument>>(); 358 parentSubCommand = null; 359 selectedSubCommand = null; 360 subCommands = new ArrayList<SubCommand>(); 361 subCommandsByName = new LinkedHashMap<String,SubCommand>(10); 362 propertiesFileUsed = null; 363 argumentsSetFromPropertiesFile = new ArrayList<String>(); 364 } 365 366 367 368 /** 369 * Creates a new argument parser that is a "clean" copy of the provided source 370 * argument parser. 371 * 372 * @param source The source argument parser to use for this argument 373 * parser. 374 * @param subCommand The subcommand with which this argument parser is to be 375 * associated. 376 */ 377 ArgumentParser(final ArgumentParser source, final SubCommand subCommand) 378 { 379 commandName = source.commandName; 380 commandDescription = source.commandDescription; 381 minTrailingArgs = source.minTrailingArgs; 382 maxTrailingArgs = source.maxTrailingArgs; 383 trailingArgsPlaceholder = source.trailingArgsPlaceholder; 384 385 propertiesFileUsed = null; 386 argumentsSetFromPropertiesFile = new ArrayList<String>(); 387 trailingArgs = new ArrayList<String>(); 388 389 namedArgs = new ArrayList<Argument>(source.namedArgs.size()); 390 namedArgsByLongID = 391 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size()); 392 namedArgsByShortID = new LinkedHashMap<Character,Argument>( 393 source.namedArgsByShortID.size()); 394 395 final LinkedHashMap<String,Argument> argsByID = 396 new LinkedHashMap<String,Argument>(source.namedArgs.size()); 397 for (final Argument sourceArg : source.namedArgs) 398 { 399 final Argument a = sourceArg.getCleanCopy(); 400 401 try 402 { 403 a.setRegistered(); 404 } 405 catch (final ArgumentException ae) 406 { 407 // This should never happen. 408 Debug.debugException(ae); 409 } 410 411 namedArgs.add(a); 412 argsByID.put(a.getIdentifierString(), a); 413 414 for (final Character c : a.getShortIdentifiers()) 415 { 416 namedArgsByShortID.put(c, a); 417 } 418 419 for (final String s : a.getLongIdentifiers()) 420 { 421 namedArgsByLongID.put(toLowerCase(s), a); 422 } 423 } 424 425 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>( 426 source.dependentArgumentSets.size()); 427 for (final ObjectPair<Argument,Set<Argument>> p : 428 source.dependentArgumentSets) 429 { 430 final Set<Argument> sourceSet = p.getSecond(); 431 final LinkedHashSet<Argument> newSet = 432 new LinkedHashSet<Argument>(sourceSet.size()); 433 for (final Argument a : sourceSet) 434 { 435 newSet.add(argsByID.get(a.getIdentifierString())); 436 } 437 438 final Argument sourceFirst = p.getFirst(); 439 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString()); 440 dependentArgumentSets.add( 441 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet)); 442 } 443 444 exclusiveArgumentSets = 445 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size()); 446 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets) 447 { 448 final LinkedHashSet<Argument> newSet = 449 new LinkedHashSet<Argument>(sourceSet.size()); 450 for (final Argument a : sourceSet) 451 { 452 newSet.add(argsByID.get(a.getIdentifierString())); 453 } 454 455 exclusiveArgumentSets.add(newSet); 456 } 457 458 requiredArgumentSets = 459 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size()); 460 for (final Set<Argument> sourceSet : source.requiredArgumentSets) 461 { 462 final LinkedHashSet<Argument> newSet = 463 new LinkedHashSet<Argument>(sourceSet.size()); 464 for (final Argument a : sourceSet) 465 { 466 newSet.add(argsByID.get(a.getIdentifierString())); 467 } 468 requiredArgumentSets.add(newSet); 469 } 470 471 parentSubCommand = subCommand; 472 selectedSubCommand = null; 473 subCommands = new ArrayList<SubCommand>(source.subCommands.size()); 474 subCommandsByName = 475 new LinkedHashMap<String,SubCommand>(source.subCommandsByName.size()); 476 for (final SubCommand sc : source.subCommands) 477 { 478 subCommands.add(sc.getCleanCopy()); 479 for (final String name : sc.getNames()) 480 { 481 subCommandsByName.put(toLowerCase(name), sc); 482 } 483 } 484 } 485 486 487 488 /** 489 * Retrieves the name of the application or utility with which this command 490 * line argument parser is associated. 491 * 492 * @return The name of the application or utility with which this command 493 * line argument parser is associated. 494 */ 495 public String getCommandName() 496 { 497 return commandName; 498 } 499 500 501 502 /** 503 * Retrieves a description of the application or utility with which this 504 * command line argument parser is associated. 505 * 506 * @return A description of the application or utility with which this 507 * command line argument parser is associated. 508 */ 509 public String getCommandDescription() 510 { 511 return commandDescription; 512 } 513 514 515 516 /** 517 * Indicates whether this argument parser allows any unnamed trailing 518 * arguments to be provided. 519 * 520 * @return {@code true} if at least one unnamed trailing argument may be 521 * provided, or {@code false} if not. 522 */ 523 public boolean allowsTrailingArguments() 524 { 525 return (maxTrailingArgs != 0); 526 } 527 528 529 530 /** 531 * Indicates whether this argument parser requires at least unnamed trailing 532 * argument to be provided. 533 * 534 * @return {@code true} if at least one unnamed trailing argument must be 535 * provided, or {@code false} if the tool may be invoked without any 536 * such arguments. 537 */ 538 public boolean requiresTrailingArguments() 539 { 540 return (minTrailingArgs != 0); 541 } 542 543 544 545 /** 546 * Retrieves the placeholder string that will be provided in usage information 547 * to indicate what may be included in the trailing arguments. 548 * 549 * @return The placeholder string that will be provided in usage information 550 * to indicate what may be included in the trailing arguments, or 551 * {@code null} if unnamed trailing arguments are not allowed. 552 */ 553 public String getTrailingArgumentsPlaceholder() 554 { 555 return trailingArgsPlaceholder; 556 } 557 558 559 560 /** 561 * Retrieves the minimum number of unnamed trailing arguments that must be 562 * provided. 563 * 564 * @return The minimum number of unnamed trailing arguments that must be 565 * provided. 566 */ 567 public int getMinTrailingArguments() 568 { 569 return minTrailingArgs; 570 } 571 572 573 574 /** 575 * Retrieves the maximum number of unnamed trailing arguments that may be 576 * provided. 577 * 578 * @return The maximum number of unnamed trailing arguments that may be 579 * provided. 580 */ 581 public int getMaxTrailingArguments() 582 { 583 return maxTrailingArgs; 584 } 585 586 587 588 /** 589 * Updates this argument parser to enable support for a properties file that 590 * can be used to specify the default values for any properties that were not 591 * supplied via the command line. This method should be invoked after the 592 * argument parser has been configured with all of the other arguments that it 593 * supports and before the {@link #parse} method is invoked. In addition, 594 * after invoking the {@code parse} method, the caller must also invoke the 595 * {@link #getGeneratedPropertiesFile} method to determine if the only 596 * processing performed that should be performed is the generation of a 597 * properties file that will have already been performed. 598 * <BR><BR> 599 * This method will update the argument parser to add the following additional 600 * arguments: 601 * <UL> 602 * <LI> 603 * {@code propertiesFilePath} -- Specifies the path to the properties file 604 * that should be used to obtain default values for any arguments not 605 * provided on the command line. If this is not specified and the 606 * {@code noPropertiesFile} argument is not present, then the argument 607 * parser may use a default properties file path specified using either 608 * the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath} 609 * system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH} 610 * environment variable. 611 * </LI> 612 * <LI> 613 * {@code generatePropertiesFile} -- Indicates that the tool should 614 * generate a properties file for this argument parser and write it to the 615 * specified location. The generated properties file will not have any 616 * properties set, but will include comments that describe all of the 617 * supported arguments, as well general information about the use of a 618 * properties file. If this argument is specified on the command line, 619 * then no other arguments should be given. 620 * </LI> 621 * <LI> 622 * {@code noPropertiesFile} -- Indicates that the tool should not use a 623 * properties file to obtain default values for any arguments not provided 624 * on the command line. 625 * </LI> 626 * </UL> 627 * 628 * @throws ArgumentException If any of the arguments related to properties 629 * file processing conflicts with an argument that 630 * has already been added to the argument parser. 631 */ 632 public void enablePropertiesFileSupport() 633 throws ArgumentException 634 { 635 final FileArgument propertiesFilePath = new FileArgument(null, 636 ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null, 637 INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false); 638 propertiesFilePath.setUsageArgument(true); 639 propertiesFilePath.addLongIdentifier("properties-file-path"); 640 addArgument(propertiesFilePath); 641 642 final FileArgument generatePropertiesFile = new FileArgument(null, 643 ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null, 644 INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false); 645 generatePropertiesFile.setUsageArgument(true); 646 generatePropertiesFile.addLongIdentifier("generate-properties-file"); 647 addArgument(generatePropertiesFile); 648 649 final BooleanArgument noPropertiesFile = new BooleanArgument(null, 650 ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get()); 651 noPropertiesFile.setUsageArgument(true); 652 noPropertiesFile.addLongIdentifier("no-properties-file"); 653 addArgument(noPropertiesFile); 654 655 656 // The propertiesFilePath and noPropertiesFile arguments cannot be used 657 // together. 658 addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile); 659 } 660 661 662 663 /** 664 * Indicates whether this argument parser was used to generate a properties 665 * file. If so, then the tool invoking the parser should return without 666 * performing any further processing. 667 * 668 * @return A {@code File} object that represents the path to the properties 669 * file that was generated, or {@code null} if no properties file was 670 * generated. 671 */ 672 public File getGeneratedPropertiesFile() 673 { 674 final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE); 675 if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument))) 676 { 677 return null; 678 } 679 680 return ((FileArgument) a).getValue(); 681 } 682 683 684 685 /** 686 * Retrieves the named argument with the specified short identifier. 687 * 688 * @param shortIdentifier The short identifier of the argument to retrieve. 689 * It must not be {@code null}. 690 * 691 * @return The named argument with the specified short identifier, or 692 * {@code null} if there is no such argument. 693 */ 694 public Argument getNamedArgument(final Character shortIdentifier) 695 { 696 ensureNotNull(shortIdentifier); 697 return namedArgsByShortID.get(shortIdentifier); 698 } 699 700 701 702 /** 703 * Retrieves the named argument with the specified identifier. 704 * 705 * @param identifier The identifier of the argument to retrieve. It may be 706 * the long identifier without any dashes, the short 707 * identifier character preceded by a single dash, or the 708 * long identifier preceded by two dashes. It must not be 709 * {@code null}. 710 * 711 * @return The named argument with the specified long identifier, or 712 * {@code null} if there is no such argument. 713 */ 714 public Argument getNamedArgument(final String identifier) 715 { 716 ensureNotNull(identifier); 717 718 if (identifier.startsWith("--") && (identifier.length() > 2)) 719 { 720 return namedArgsByLongID.get(toLowerCase(identifier.substring(2))); 721 } 722 else if (identifier.startsWith("-") && (identifier.length() == 2)) 723 { 724 return namedArgsByShortID.get(identifier.charAt(1)); 725 } 726 else 727 { 728 return namedArgsByLongID.get(toLowerCase(identifier)); 729 } 730 } 731 732 733 734 /** 735 * Retrieves the argument list argument with the specified identifier. 736 * 737 * @param identifier The identifier of the argument to retrieve. It may be 738 * the long identifier without any dashes, the short 739 * identifier character preceded by a single dash, or the 740 * long identifier preceded by two dashes. It must not be 741 * {@code null}. 742 * 743 * @return The argument list argument with the specified identifier, or 744 * {@code null} if there is no such argument. 745 */ 746 public ArgumentListArgument getArgumentListArgument(final String identifier) 747 { 748 final Argument a = getNamedArgument(identifier); 749 if (a == null) 750 { 751 return null; 752 } 753 else 754 { 755 return (ArgumentListArgument) a; 756 } 757 } 758 759 760 761 /** 762 * Retrieves the Boolean argument with the specified identifier. 763 * 764 * @param identifier The identifier of the argument to retrieve. It may be 765 * the long identifier without any dashes, the short 766 * identifier character preceded by a single dash, or the 767 * long identifier preceded by two dashes. It must not be 768 * {@code null}. 769 * 770 * @return The Boolean argument with the specified identifier, or 771 * {@code null} if there is no such argument. 772 */ 773 public BooleanArgument getBooleanArgument(final String identifier) 774 { 775 final Argument a = getNamedArgument(identifier); 776 if (a == null) 777 { 778 return null; 779 } 780 else 781 { 782 return (BooleanArgument) a; 783 } 784 } 785 786 787 788 /** 789 * Retrieves the Boolean value argument with the specified identifier. 790 * 791 * @param identifier The identifier of the argument to retrieve. It may be 792 * the long identifier without any dashes, the short 793 * identifier character preceded by a single dash, or the 794 * long identifier preceded by two dashes. It must not be 795 * {@code null}. 796 * 797 * @return The Boolean value argument with the specified identifier, or 798 * {@code null} if there is no such argument. 799 */ 800 public BooleanValueArgument getBooleanValueArgument(final String identifier) 801 { 802 final Argument a = getNamedArgument(identifier); 803 if (a == null) 804 { 805 return null; 806 } 807 else 808 { 809 return (BooleanValueArgument) a; 810 } 811 } 812 813 814 815 /** 816 * Retrieves the control argument with the specified identifier. 817 * 818 * @param identifier The identifier of the argument to retrieve. It may be 819 * the long identifier without any dashes, the short 820 * identifier character preceded by a single dash, or the 821 * long identifier preceded by two dashes. It must not be 822 * {@code null}. 823 * 824 * @return The control argument with the specified identifier, or 825 * {@code null} if there is no such argument. 826 */ 827 public ControlArgument getControlArgument(final String identifier) 828 { 829 final Argument a = getNamedArgument(identifier); 830 if (a == null) 831 { 832 return null; 833 } 834 else 835 { 836 return (ControlArgument) a; 837 } 838 } 839 840 841 842 /** 843 * Retrieves the DN argument with the specified identifier. 844 * 845 * @param identifier The identifier of the argument to retrieve. It may be 846 * the long identifier without any dashes, the short 847 * identifier character preceded by a single dash, or the 848 * long identifier preceded by two dashes. It must not be 849 * {@code null}. 850 * 851 * @return The DN argument with the specified identifier, or 852 * {@code null} if there is no such argument. 853 */ 854 public DNArgument getDNArgument(final String identifier) 855 { 856 final Argument a = getNamedArgument(identifier); 857 if (a == null) 858 { 859 return null; 860 } 861 else 862 { 863 return (DNArgument) a; 864 } 865 } 866 867 868 869 /** 870 * Retrieves the duration argument with the specified identifier. 871 * 872 * @param identifier The identifier of the argument to retrieve. It may be 873 * the long identifier without any dashes, the short 874 * identifier character preceded by a single dash, or the 875 * long identifier preceded by two dashes. It must not be 876 * {@code null}. 877 * 878 * @return The duration argument with the specified identifier, or 879 * {@code null} if there is no such argument. 880 */ 881 public DurationArgument getDurationArgument(final String identifier) 882 { 883 final Argument a = getNamedArgument(identifier); 884 if (a == null) 885 { 886 return null; 887 } 888 else 889 { 890 return (DurationArgument) a; 891 } 892 } 893 894 895 896 /** 897 * Retrieves the file argument with the specified identifier. 898 * 899 * @param identifier The identifier of the argument to retrieve. It may be 900 * the long identifier without any dashes, the short 901 * identifier character preceded by a single dash, or the 902 * long identifier preceded by two dashes. It must not be 903 * {@code null}. 904 * 905 * @return The file argument with the specified identifier, or 906 * {@code null} if there is no such argument. 907 */ 908 public FileArgument getFileArgument(final String identifier) 909 { 910 final Argument a = getNamedArgument(identifier); 911 if (a == null) 912 { 913 return null; 914 } 915 else 916 { 917 return (FileArgument) a; 918 } 919 } 920 921 922 923 /** 924 * Retrieves the filter argument with the specified identifier. 925 * 926 * @param identifier The identifier of the argument to retrieve. It may be 927 * the long identifier without any dashes, the short 928 * identifier character preceded by a single dash, or the 929 * long identifier preceded by two dashes. It must not be 930 * {@code null}. 931 * 932 * @return The filter argument with the specified identifier, or 933 * {@code null} if there is no such argument. 934 */ 935 public FilterArgument getFilterArgument(final String identifier) 936 { 937 final Argument a = getNamedArgument(identifier); 938 if (a == null) 939 { 940 return null; 941 } 942 else 943 { 944 return (FilterArgument) a; 945 } 946 } 947 948 949 950 /** 951 * Retrieves the integer argument with the specified identifier. 952 * 953 * @param identifier The identifier of the argument to retrieve. It may be 954 * the long identifier without any dashes, the short 955 * identifier character preceded by a single dash, or the 956 * long identifier preceded by two dashes. It must not be 957 * {@code null}. 958 * 959 * @return The integer argument with the specified identifier, or 960 * {@code null} if there is no such argument. 961 */ 962 public IntegerArgument getIntegerArgument(final String identifier) 963 { 964 final Argument a = getNamedArgument(identifier); 965 if (a == null) 966 { 967 return null; 968 } 969 else 970 { 971 return (IntegerArgument) a; 972 } 973 } 974 975 976 977 /** 978 * Retrieves the scope argument with the specified identifier. 979 * 980 * @param identifier The identifier of the argument to retrieve. It may be 981 * the long identifier without any dashes, the short 982 * identifier character preceded by a single dash, or the 983 * long identifier preceded by two dashes. It must not be 984 * {@code null}. 985 * 986 * @return The scope argument with the specified identifier, or 987 * {@code null} if there is no such argument. 988 */ 989 public ScopeArgument getScopeArgument(final String identifier) 990 { 991 final Argument a = getNamedArgument(identifier); 992 if (a == null) 993 { 994 return null; 995 } 996 else 997 { 998 return (ScopeArgument) a; 999 } 1000 } 1001 1002 1003 1004 /** 1005 * Retrieves the string argument with the specified identifier. 1006 * 1007 * @param identifier The identifier of the argument to retrieve. It may be 1008 * the long identifier without any dashes, the short 1009 * identifier character preceded by a single dash, or the 1010 * long identifier preceded by two dashes. It must not be 1011 * {@code null}. 1012 * 1013 * @return The string argument with the specified identifier, or 1014 * {@code null} if there is no such argument. 1015 */ 1016 public StringArgument getStringArgument(final String identifier) 1017 { 1018 final Argument a = getNamedArgument(identifier); 1019 if (a == null) 1020 { 1021 return null; 1022 } 1023 else 1024 { 1025 return (StringArgument) a; 1026 } 1027 } 1028 1029 1030 1031 /** 1032 * Retrieves the timestamp argument with the specified identifier. 1033 * 1034 * @param identifier The identifier of the argument to retrieve. It may be 1035 * the long identifier without any dashes, the short 1036 * identifier character preceded by a single dash, or the 1037 * long identifier preceded by two dashes. It must not be 1038 * {@code null}. 1039 * 1040 * @return The timestamp argument with the specified identifier, or 1041 * {@code null} if there is no such argument. 1042 */ 1043 public TimestampArgument getTimestampArgument(final String identifier) 1044 { 1045 final Argument a = getNamedArgument(identifier); 1046 if (a == null) 1047 { 1048 return null; 1049 } 1050 else 1051 { 1052 return (TimestampArgument) a; 1053 } 1054 } 1055 1056 1057 1058 /** 1059 * Retrieves the set of named arguments defined for use with this argument 1060 * parser. 1061 * 1062 * @return The set of named arguments defined for use with this argument 1063 * parser. 1064 */ 1065 public List<Argument> getNamedArguments() 1066 { 1067 return Collections.unmodifiableList(namedArgs); 1068 } 1069 1070 1071 1072 /** 1073 * Registers the provided argument with this argument parser. 1074 * 1075 * @param argument The argument to be registered. 1076 * 1077 * @throws ArgumentException If the provided argument conflicts with another 1078 * argument already registered with this parser. 1079 */ 1080 public void addArgument(final Argument argument) 1081 throws ArgumentException 1082 { 1083 argument.setRegistered(); 1084 for (final Character c : argument.getShortIdentifiers()) 1085 { 1086 if (namedArgsByShortID.containsKey(c)) 1087 { 1088 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c)); 1089 } 1090 1091 if ((parentSubCommand != null) && 1092 (parentSubCommand.getArgumentParser().namedArgsByShortID.containsKey( 1093 c))) 1094 { 1095 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c)); 1096 } 1097 } 1098 1099 for (final String s : argument.getLongIdentifiers()) 1100 { 1101 if (namedArgsByLongID.containsKey(toLowerCase(s))) 1102 { 1103 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s)); 1104 } 1105 1106 if ((parentSubCommand != null) && 1107 (parentSubCommand.getArgumentParser().namedArgsByLongID.containsKey( 1108 toLowerCase(s)))) 1109 { 1110 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s)); 1111 } 1112 } 1113 1114 for (final SubCommand sc : subCommands) 1115 { 1116 final ArgumentParser parser = sc.getArgumentParser(); 1117 for (final Character c : argument.getShortIdentifiers()) 1118 { 1119 if (parser.namedArgsByShortID.containsKey(c)) 1120 { 1121 throw new ArgumentException( 1122 ERR_PARSER_SHORT_ID_CONFLICT_WITH_SUBCOMMAND.get(c, 1123 sc.getPrimaryName())); 1124 } 1125 } 1126 1127 for (final String s : argument.getLongIdentifiers()) 1128 { 1129 if (parser.namedArgsByLongID.containsKey(toLowerCase(s))) 1130 { 1131 throw new ArgumentException( 1132 ERR_PARSER_LONG_ID_CONFLICT_WITH_SUBCOMMAND.get(s, 1133 sc.getPrimaryName())); 1134 } 1135 } 1136 } 1137 1138 for (final Character c : argument.getShortIdentifiers()) 1139 { 1140 namedArgsByShortID.put(c, argument); 1141 } 1142 1143 for (final String s : argument.getLongIdentifiers()) 1144 { 1145 namedArgsByLongID.put(toLowerCase(s), argument); 1146 } 1147 1148 namedArgs.add(argument); 1149 } 1150 1151 1152 1153 /** 1154 * Retrieves the list of dependent argument sets for this argument parser. If 1155 * an argument contained as the first object in the pair in a dependent 1156 * argument set is provided, then at least one of the arguments in the paired 1157 * set must also be provided. 1158 * 1159 * @return The list of dependent argument sets for this argument parser. 1160 */ 1161 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets() 1162 { 1163 return Collections.unmodifiableList(dependentArgumentSets); 1164 } 1165 1166 1167 1168 /** 1169 * Adds the provided collection of arguments as dependent upon the given 1170 * argument. All of the arguments must have already been registered with this 1171 * argument parser using the {@link #addArgument} method. 1172 * 1173 * @param targetArgument The argument whose presence indicates that at 1174 * least one of the dependent arguments must also 1175 * be present. It must not be {@code null}, and 1176 * it must have already been registered with this 1177 * argument parser. 1178 * @param dependentArguments The set of arguments from which at least one 1179 * argument must be present if the target argument 1180 * is present. It must not be {@code null} or 1181 * empty, and all arguments must have already been 1182 * registered with this argument parser. 1183 */ 1184 public void addDependentArgumentSet(final Argument targetArgument, 1185 final Collection<Argument> dependentArguments) 1186 { 1187 ensureNotNull(targetArgument, dependentArguments); 1188 1189 ensureFalse(dependentArguments.isEmpty(), 1190 "The ArgumentParser.addDependentArgumentSet method must not be " + 1191 "called with an empty collection of dependentArguments"); 1192 1193 ensureTrue(namedArgs.contains(targetArgument), 1194 "The ArgumentParser.addDependentArgumentSet method may only be used " + 1195 "if all of the provided arguments have already been registered " + 1196 "with the argument parser via the ArgumentParser.addArgument " + 1197 "method. The " + targetArgument.getIdentifierString() + 1198 " argument has not been registered with the argument parser."); 1199 for (final Argument a : dependentArguments) 1200 { 1201 ensureTrue(namedArgs.contains(a), 1202 "The ArgumentParser.addDependentArgumentSet method may only be " + 1203 "used if all of the provided arguments have already been " + 1204 "registered with the argument parser via the " + 1205 "ArgumentParser.addArgument method. The " + 1206 a.getIdentifierString() + " argument has not been registered " + 1207 "with the argument parser."); 1208 } 1209 1210 final LinkedHashSet<Argument> argSet = 1211 new LinkedHashSet<Argument>(dependentArguments); 1212 dependentArgumentSets.add( 1213 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 1214 } 1215 1216 1217 1218 /** 1219 * Adds the provided collection of arguments as dependent upon the given 1220 * argument. All of the arguments must have already been registered with this 1221 * argument parser using the {@link #addArgument} method. 1222 * 1223 * @param targetArgument The argument whose presence indicates that at least 1224 * one of the dependent arguments must also be 1225 * present. It must not be {@code null}, and it must 1226 * have already been registered with this argument 1227 * parser. 1228 * @param dependentArg1 The first argument in the set of arguments in which 1229 * at least one argument must be present if the target 1230 * argument is present. It must not be {@code null}, 1231 * and it must have already been registered with this 1232 * argument parser. 1233 * @param remaining The remaining arguments in the set of arguments in 1234 * which at least one argument must be present if the 1235 * target argument is present. It may be {@code null} 1236 * or empty if no additional dependent arguments are 1237 * needed, but if it is non-empty then all arguments 1238 * must have already been registered with this 1239 * argument parser. 1240 */ 1241 public void addDependentArgumentSet(final Argument targetArgument, 1242 final Argument dependentArg1, 1243 final Argument... remaining) 1244 { 1245 ensureNotNull(targetArgument, dependentArg1); 1246 1247 ensureTrue(namedArgs.contains(targetArgument), 1248 "The ArgumentParser.addDependentArgumentSet method may only be used " + 1249 "if all of the provided arguments have already been registered " + 1250 "with the argument parser via the ArgumentParser.addArgument " + 1251 "method. The " + targetArgument.getIdentifierString() + 1252 " argument has not been registered with the argument parser."); 1253 ensureTrue(namedArgs.contains(dependentArg1), 1254 "The ArgumentParser.addDependentArgumentSet method may only be used " + 1255 "if all of the provided arguments have already been registered " + 1256 "with the argument parser via the ArgumentParser.addArgument " + 1257 "method. The " + dependentArg1.getIdentifierString() + 1258 " argument has not been registered with the argument parser."); 1259 if (remaining != null) 1260 { 1261 for (final Argument a : remaining) 1262 { 1263 ensureTrue(namedArgs.contains(a), 1264 "The ArgumentParser.addDependentArgumentSet method may only be " + 1265 "used if all of the provided arguments have already been " + 1266 "registered with the argument parser via the " + 1267 "ArgumentParser.addArgument method. The " + 1268 a.getIdentifierString() + " argument has not been " + 1269 "registered with the argument parser."); 1270 } 1271 } 1272 1273 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1274 argSet.add(dependentArg1); 1275 if (remaining != null) 1276 { 1277 argSet.addAll(Arrays.asList(remaining)); 1278 } 1279 1280 dependentArgumentSets.add( 1281 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 1282 } 1283 1284 1285 1286 /** 1287 * Retrieves the list of exclusive argument sets for this argument parser. 1288 * If an argument contained in an exclusive argument set is provided, then 1289 * none of the other arguments in that set may be provided. It is acceptable 1290 * for none of the arguments in the set to be provided, unless the same set 1291 * of arguments is also defined as a required argument set. 1292 * 1293 * @return The list of exclusive argument sets for this argument parser. 1294 */ 1295 public List<Set<Argument>> getExclusiveArgumentSets() 1296 { 1297 return Collections.unmodifiableList(exclusiveArgumentSets); 1298 } 1299 1300 1301 1302 /** 1303 * Adds the provided collection of arguments as an exclusive argument set, in 1304 * which at most one of the arguments may be provided. All of the arguments 1305 * must have already been registered with this argument parser using the 1306 * {@link #addArgument} method. 1307 * 1308 * @param exclusiveArguments The collection of arguments to form an 1309 * exclusive argument set. It must not be 1310 * {@code null}, and all of the arguments must 1311 * have already been registered with this argument 1312 * parser. 1313 */ 1314 public void addExclusiveArgumentSet( 1315 final Collection<Argument> exclusiveArguments) 1316 { 1317 ensureNotNull(exclusiveArguments); 1318 1319 for (final Argument a : exclusiveArguments) 1320 { 1321 ensureTrue(namedArgs.contains(a), 1322 "The ArgumentParser.addExclusiveArgumentSet method may only be " + 1323 "used if all of the provided arguments have already been " + 1324 "registered with the argument parser via the " + 1325 "ArgumentParser.addArgument method. The " + 1326 a.getIdentifierString() + " argument has not been " + 1327 "registered with the argument parser."); 1328 } 1329 1330 final LinkedHashSet<Argument> argSet = 1331 new LinkedHashSet<Argument>(exclusiveArguments); 1332 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 1333 } 1334 1335 1336 1337 /** 1338 * Adds the provided set of arguments as an exclusive argument set, in 1339 * which at most one of the arguments may be provided. All of the arguments 1340 * must have already been registered with this argument parser using the 1341 * {@link #addArgument} method. 1342 * 1343 * @param arg1 The first argument to include in the exclusive argument 1344 * set. It must not be {@code null}, and it must have 1345 * already been registered with this argument parser. 1346 * @param arg2 The second argument to include in the exclusive argument 1347 * set. It must not be {@code null}, and it must have 1348 * already been registered with this argument parser. 1349 * @param remaining Any additional arguments to include in the exclusive 1350 * argument set. It may be {@code null} or empty if no 1351 * additional exclusive arguments are needed, but if it is 1352 * non-empty then all arguments must have already been 1353 * registered with this argument parser. 1354 */ 1355 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2, 1356 final Argument... remaining) 1357 { 1358 ensureNotNull(arg1, arg2); 1359 1360 ensureTrue(namedArgs.contains(arg1), 1361 "The ArgumentParser.addExclusiveArgumentSet method may only be " + 1362 "used if all of the provided arguments have already been " + 1363 "registered with the argument parser via the " + 1364 "ArgumentParser.addArgument method. The " + 1365 arg1.getIdentifierString() + " argument has not been " + 1366 "registered with the argument parser."); 1367 ensureTrue(namedArgs.contains(arg2), 1368 "The ArgumentParser.addExclusiveArgumentSet method may only be " + 1369 "used if all of the provided arguments have already been " + 1370 "registered with the argument parser via the " + 1371 "ArgumentParser.addArgument method. The " + 1372 arg2.getIdentifierString() + " argument has not been " + 1373 "registered with the argument parser."); 1374 1375 if (remaining != null) 1376 { 1377 for (final Argument a : remaining) 1378 { 1379 ensureTrue(namedArgs.contains(a), 1380 "The ArgumentParser.addExclusiveArgumentSet method may only be " + 1381 "used if all of the provided arguments have already been " + 1382 "registered with the argument parser via the " + 1383 "ArgumentParser.addArgument method. The " + 1384 a.getIdentifierString() + " argument has not been " + 1385 "registered with the argument parser."); 1386 } 1387 } 1388 1389 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1390 argSet.add(arg1); 1391 argSet.add(arg2); 1392 argSet.addAll(Arrays.asList(remaining)); 1393 1394 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 1395 } 1396 1397 1398 1399 /** 1400 * Retrieves the list of required argument sets for this argument parser. At 1401 * least one of the arguments contained in this set must be provided. If this 1402 * same set is also defined as an exclusive argument set, then exactly one 1403 * of those arguments must be provided. 1404 * 1405 * @return The list of required argument sets for this argument parser. 1406 */ 1407 public List<Set<Argument>> getRequiredArgumentSets() 1408 { 1409 return Collections.unmodifiableList(requiredArgumentSets); 1410 } 1411 1412 1413 1414 /** 1415 * Adds the provided collection of arguments as a required argument set, in 1416 * which at least one of the arguments must be provided. All of the arguments 1417 * must have already been registered with this argument parser using the 1418 * {@link #addArgument} method. 1419 * 1420 * @param requiredArguments The collection of arguments to form an 1421 * required argument set. It must not be 1422 * {@code null}, and all of the arguments must have 1423 * already been registered with this argument 1424 * parser. 1425 */ 1426 public void addRequiredArgumentSet( 1427 final Collection<Argument> requiredArguments) 1428 { 1429 ensureNotNull(requiredArguments); 1430 1431 for (final Argument a : requiredArguments) 1432 { 1433 ensureTrue(namedArgs.contains(a), 1434 "The ArgumentParser.addRequiredArgumentSet method may only be " + 1435 "used if all of the provided arguments have already been " + 1436 "registered with the argument parser via the " + 1437 "ArgumentParser.addArgument method. The " + 1438 a.getIdentifierString() + " argument has not been " + 1439 "registered with the argument parser."); 1440 } 1441 1442 final LinkedHashSet<Argument> argSet = 1443 new LinkedHashSet<Argument>(requiredArguments); 1444 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 1445 } 1446 1447 1448 1449 /** 1450 * Adds the provided set of arguments as a required argument set, in which 1451 * at least one of the arguments must be provided. All of the arguments must 1452 * have already been registered with this argument parser using the 1453 * {@link #addArgument} method. 1454 * 1455 * @param arg1 The first argument to include in the required argument 1456 * set. It must not be {@code null}, and it must have 1457 * already been registered with this argument parser. 1458 * @param arg2 The second argument to include in the required argument 1459 * set. It must not be {@code null}, and it must have 1460 * already been registered with this argument parser. 1461 * @param remaining Any additional arguments to include in the required 1462 * argument set. It may be {@code null} or empty if no 1463 * additional required arguments are needed, but if it is 1464 * non-empty then all arguments must have already been 1465 * registered with this argument parser. 1466 */ 1467 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2, 1468 final Argument... remaining) 1469 { 1470 ensureNotNull(arg1, arg2); 1471 1472 ensureTrue(namedArgs.contains(arg1), 1473 "The ArgumentParser.addRequiredArgumentSet method may only be " + 1474 "used if all of the provided arguments have already been " + 1475 "registered with the argument parser via the " + 1476 "ArgumentParser.addArgument method. The " + 1477 arg1.getIdentifierString() + " argument has not been " + 1478 "registered with the argument parser."); 1479 ensureTrue(namedArgs.contains(arg2), 1480 "The ArgumentParser.addRequiredArgumentSet method may only be " + 1481 "used if all of the provided arguments have already been " + 1482 "registered with the argument parser via the " + 1483 "ArgumentParser.addArgument method. The " + 1484 arg2.getIdentifierString() + " argument has not been " + 1485 "registered with the argument parser."); 1486 1487 if (remaining != null) 1488 { 1489 for (final Argument a : remaining) 1490 { 1491 ensureTrue(namedArgs.contains(a), 1492 "The ArgumentParser.addRequiredArgumentSet method may only be " + 1493 "used if all of the provided arguments have already been " + 1494 "registered with the argument parser via the " + 1495 "ArgumentParser.addArgument method. The " + 1496 a.getIdentifierString() + " argument has not been " + 1497 "registered with the argument parser."); 1498 } 1499 } 1500 1501 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1502 argSet.add(arg1); 1503 argSet.add(arg2); 1504 argSet.addAll(Arrays.asList(remaining)); 1505 1506 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 1507 } 1508 1509 1510 1511 /** 1512 * Indicates whether any subcommands have been registered with this argument 1513 * parser. 1514 * 1515 * @return {@code true} if one or more subcommands have been registered with 1516 * this argument parser, or {@code false} if not. 1517 */ 1518 public boolean hasSubCommands() 1519 { 1520 return (! subCommands.isEmpty()); 1521 } 1522 1523 1524 1525 /** 1526 * Retrieves the subcommand that was provided in the set of command-line 1527 * arguments, if any. 1528 * 1529 * @return The subcommand that was provided in the set of command-line 1530 * arguments, or {@code null} if there is none. 1531 */ 1532 public SubCommand getSelectedSubCommand() 1533 { 1534 return selectedSubCommand; 1535 } 1536 1537 1538 1539 /** 1540 * Specifies the subcommand that was provided in the set of command-line 1541 * arguments. 1542 * 1543 * @param subcommand The subcommand that was provided in the set of 1544 * command-line arguments. It may be {@code null} if no 1545 * subcommand should be used. 1546 */ 1547 void setSelectedSubCommand(final SubCommand subcommand) 1548 { 1549 selectedSubCommand = subcommand; 1550 if (subcommand != null) 1551 { 1552 subcommand.setPresent(); 1553 } 1554 } 1555 1556 1557 1558 /** 1559 * Retrieves a list of all subcommands associated with this argument parser. 1560 * 1561 * @return A list of all subcommands associated with this argument parser, or 1562 * an empty list if there are no associated subcommands. 1563 */ 1564 public List<SubCommand> getSubCommands() 1565 { 1566 return Collections.unmodifiableList(subCommands); 1567 } 1568 1569 1570 1571 /** 1572 * Retrieves the subcommand for the provided name. 1573 * 1574 * @param name The name of the subcommand to retrieve. 1575 * 1576 * @return The subcommand with the provided name, or {@code null} if there is 1577 * no such subcommand. 1578 */ 1579 public SubCommand getSubCommand(final String name) 1580 { 1581 if (name == null) 1582 { 1583 return null; 1584 } 1585 1586 return subCommandsByName.get(toLowerCase(name)); 1587 } 1588 1589 1590 1591 /** 1592 * Registers the provided subcommand with this argument parser. 1593 * 1594 * @param subCommand The subcommand to register with this argument parser. 1595 * It must not be {@code null}. 1596 * 1597 * @throws ArgumentException If this argument parser does not allow 1598 * subcommands, if there is a conflict between any 1599 * of the names of the provided subcommand and an 1600 * already-registered subcommand, or if there is a 1601 * conflict between any of the subcommand-specific 1602 * arguments and global arguments. 1603 */ 1604 public void addSubCommand(final SubCommand subCommand) 1605 throws ArgumentException 1606 { 1607 // Ensure that the subcommand isn't already registered with an argument 1608 // parser. 1609 if (subCommand.getGlobalArgumentParser() != null) 1610 { 1611 throw new ArgumentException( 1612 ERR_PARSER_SUBCOMMAND_ALREADY_REGISTERED_WITH_PARSER.get()); 1613 } 1614 1615 // Ensure that the caller isn't trying to create a nested subcommand. 1616 if (this.parentSubCommand != null) 1617 { 1618 throw new ArgumentException( 1619 ERR_PARSER_CANNOT_CREATE_NESTED_SUBCOMMAND.get( 1620 this.parentSubCommand.getPrimaryName())); 1621 } 1622 1623 // Ensure that this argument parser doesn't allow trailing arguments. 1624 if (allowsTrailingArguments()) 1625 { 1626 throw new ArgumentException( 1627 ERR_PARSER_WITH_TRAILING_ARGS_CANNOT_HAVE_SUBCOMMANDS.get()); 1628 } 1629 1630 // Ensure that the subcommand doesn't have any names that conflict with an 1631 // existing subcommand. 1632 for (final String name : subCommand.getNames()) 1633 { 1634 if (subCommandsByName.containsKey(toLowerCase(name))) 1635 { 1636 throw new ArgumentException( 1637 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name)); 1638 } 1639 } 1640 1641 // Register the subcommand. 1642 for (final String name : subCommand.getNames()) 1643 { 1644 subCommandsByName.put(toLowerCase(name), subCommand); 1645 } 1646 subCommands.add(subCommand); 1647 subCommand.setGlobalArgumentParser(this); 1648 } 1649 1650 1651 1652 /** 1653 * Registers the provided additional name for this subcommand. 1654 * 1655 * @param name The name to be registered. It must not be 1656 * {@code null} or empty. 1657 * @param subCommand The subcommand with which the name is associated. It 1658 * must not be {@code null}. 1659 * 1660 * @throws ArgumentException If the provided name is already in use. 1661 */ 1662 void addSubCommand(final String name, final SubCommand subCommand) 1663 throws ArgumentException 1664 { 1665 final String lowerName = toLowerCase(name); 1666 if (subCommandsByName.containsKey(lowerName)) 1667 { 1668 throw new ArgumentException( 1669 ERR_SUBCOMMAND_NAME_ALREADY_IN_USE.get(name)); 1670 } 1671 1672 subCommandsByName.put(lowerName, subCommand); 1673 } 1674 1675 1676 1677 /** 1678 * Retrieves the set of unnamed trailing arguments in the provided command 1679 * line arguments. 1680 * 1681 * @return The set of unnamed trailing arguments in the provided command line 1682 * arguments, or an empty list if there were none. 1683 */ 1684 public List<String> getTrailingArguments() 1685 { 1686 return Collections.unmodifiableList(trailingArgs); 1687 } 1688 1689 1690 1691 /** 1692 * Clears the set of trailing arguments for this argument parser. 1693 */ 1694 void resetTrailingArguments() 1695 { 1696 trailingArgs.clear(); 1697 } 1698 1699 1700 1701 /** 1702 * Adds the provided value to the set of trailing arguments. 1703 * 1704 * @param value The value to add to the set of trailing arguments. 1705 * 1706 * @throws ArgumentException If the parser already has the maximum allowed 1707 * number of trailing arguments. 1708 */ 1709 void addTrailingArgument(final String value) 1710 throws ArgumentException 1711 { 1712 if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs)) 1713 { 1714 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value, 1715 commandName, maxTrailingArgs)); 1716 } 1717 1718 trailingArgs.add(value); 1719 } 1720 1721 1722 1723 /** 1724 * Retrieves the properties file that was used to obtain values for arguments 1725 * not set on the command line. 1726 * 1727 * @return The properties file that was used to obtain values for arguments 1728 * not set on the command line, or {@code null} if no properties file 1729 * was used. 1730 */ 1731 public File getPropertiesFileUsed() 1732 { 1733 return propertiesFileUsed; 1734 } 1735 1736 1737 1738 /** 1739 * Retrieves a list of the string representations of any arguments used for 1740 * the associated tool that were set from a properties file rather than 1741 * provided on the command line. The values of any arguments marked as 1742 * sensitive will be obscured. 1743 * 1744 * @return A list of the string representations any arguments used for the 1745 * associated tool that were set from a properties file rather than 1746 * provided on the command line, or an empty list if no arguments 1747 * were set from a properties file. 1748 */ 1749 public List<String> getArgumentsSetFromPropertiesFile() 1750 { 1751 return Collections.unmodifiableList(argumentsSetFromPropertiesFile); 1752 } 1753 1754 1755 1756 /** 1757 * Creates a copy of this argument parser that is "clean" and appears as if it 1758 * has not been used to parse an argument set. The new parser will have all 1759 * of the same arguments and constraints as this parser. 1760 * 1761 * @return The "clean" copy of this argument parser. 1762 */ 1763 public ArgumentParser getCleanCopy() 1764 { 1765 return new ArgumentParser(this, null); 1766 } 1767 1768 1769 1770 /** 1771 * Parses the provided set of arguments. 1772 * 1773 * @param args An array containing the argument information to parse. It 1774 * must not be {@code null}. 1775 * 1776 * @throws ArgumentException If a problem occurs while attempting to parse 1777 * the argument information. 1778 */ 1779 public void parse(final String[] args) 1780 throws ArgumentException 1781 { 1782 // Iterate through the provided args strings and process them. 1783 ArgumentParser subCommandParser = null; 1784 boolean inTrailingArgs = false; 1785 boolean skipFinalValidation = false; 1786 String subCommandName = null; 1787 for (int i=0; i < args.length; i++) 1788 { 1789 final String s = args[i]; 1790 1791 if (inTrailingArgs) 1792 { 1793 if (maxTrailingArgs == 0) 1794 { 1795 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 1796 s, commandName)); 1797 } 1798 else if (trailingArgs.size() >= maxTrailingArgs) 1799 { 1800 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s, 1801 commandName, maxTrailingArgs)); 1802 } 1803 else 1804 { 1805 trailingArgs.add(s); 1806 } 1807 } 1808 else if (s.equals("--")) 1809 { 1810 // This signifies the end of the named arguments and the beginning of 1811 // the trailing arguments. 1812 inTrailingArgs = true; 1813 } 1814 else if (s.startsWith("--")) 1815 { 1816 // There may be an equal sign to separate the name from the value. 1817 final String argName; 1818 final int equalPos = s.indexOf('='); 1819 if (equalPos > 0) 1820 { 1821 argName = s.substring(2, equalPos); 1822 } 1823 else 1824 { 1825 argName = s.substring(2); 1826 } 1827 1828 final String lowerName = toLowerCase(argName); 1829 Argument a = namedArgsByLongID.get(lowerName); 1830 if ((a == null) && (subCommandParser != null)) 1831 { 1832 a = subCommandParser.namedArgsByLongID.get(lowerName); 1833 } 1834 1835 if (a == null) 1836 { 1837 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName)); 1838 } 1839 else if (a.isUsageArgument()) 1840 { 1841 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1842 } 1843 1844 a.incrementOccurrences(); 1845 if (a.takesValue()) 1846 { 1847 if (equalPos > 0) 1848 { 1849 a.addValue(s.substring(equalPos+1)); 1850 } 1851 else 1852 { 1853 i++; 1854 if (i >= args.length) 1855 { 1856 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get( 1857 argName)); 1858 } 1859 else 1860 { 1861 a.addValue(args[i]); 1862 } 1863 } 1864 } 1865 else 1866 { 1867 if (equalPos > 0) 1868 { 1869 throw new ArgumentException( 1870 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName)); 1871 } 1872 } 1873 } 1874 else if (s.startsWith("-")) 1875 { 1876 if (s.length() == 1) 1877 { 1878 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get()); 1879 } 1880 else if (s.length() == 2) 1881 { 1882 final char c = s.charAt(1); 1883 1884 Argument a = namedArgsByShortID.get(c); 1885 if ((a == null) && (subCommandParser != null)) 1886 { 1887 a = subCommandParser.namedArgsByShortID.get(c); 1888 } 1889 1890 if (a == null) 1891 { 1892 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 1893 } 1894 else if (a.isUsageArgument()) 1895 { 1896 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1897 } 1898 1899 a.incrementOccurrences(); 1900 if (a.takesValue()) 1901 { 1902 i++; 1903 if (i >= args.length) 1904 { 1905 throw new ArgumentException( 1906 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c)); 1907 } 1908 else 1909 { 1910 a.addValue(args[i]); 1911 } 1912 } 1913 } 1914 else 1915 { 1916 char c = s.charAt(1); 1917 Argument a = namedArgsByShortID.get(c); 1918 if ((a == null) && (subCommandParser != null)) 1919 { 1920 a = subCommandParser.namedArgsByShortID.get(c); 1921 } 1922 1923 if (a == null) 1924 { 1925 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 1926 } 1927 else if (a.isUsageArgument()) 1928 { 1929 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1930 } 1931 1932 a.incrementOccurrences(); 1933 if (a.takesValue()) 1934 { 1935 a.addValue(s.substring(2)); 1936 } 1937 else 1938 { 1939 // The rest of the characters in the string must also resolve to 1940 // arguments that don't take values. 1941 for (int j=2; j < s.length(); j++) 1942 { 1943 c = s.charAt(j); 1944 a = namedArgsByShortID.get(c); 1945 if ((a == null) && (subCommandParser != null)) 1946 { 1947 a = subCommandParser.namedArgsByShortID.get(c); 1948 } 1949 1950 if (a == null) 1951 { 1952 throw new ArgumentException( 1953 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s)); 1954 } 1955 else if (a.isUsageArgument()) 1956 { 1957 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1958 } 1959 1960 a.incrementOccurrences(); 1961 if (a.takesValue()) 1962 { 1963 throw new ArgumentException( 1964 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get( 1965 c, s)); 1966 } 1967 } 1968 } 1969 } 1970 } 1971 else if (subCommands.isEmpty()) 1972 { 1973 inTrailingArgs = true; 1974 if (maxTrailingArgs == 0) 1975 { 1976 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 1977 s, commandName)); 1978 } 1979 else 1980 { 1981 trailingArgs.add(s); 1982 } 1983 } 1984 else 1985 { 1986 if (selectedSubCommand == null) 1987 { 1988 subCommandName = s; 1989 selectedSubCommand = subCommandsByName.get(toLowerCase(s)); 1990 if (selectedSubCommand == null) 1991 { 1992 throw new ArgumentException(ERR_PARSER_NO_SUCH_SUBCOMMAND.get(s, 1993 commandName)); 1994 } 1995 else 1996 { 1997 selectedSubCommand.setPresent(); 1998 subCommandParser = selectedSubCommand.getArgumentParser(); 1999 } 2000 } 2001 else 2002 { 2003 throw new ArgumentException(ERR_PARSER_CONFLICTING_SUBCOMMANDS.get( 2004 subCommandName, s)); 2005 } 2006 } 2007 } 2008 2009 2010 // Perform any appropriate processing related to the use of a properties 2011 // file. 2012 if (! handlePropertiesFile()) 2013 { 2014 return; 2015 } 2016 2017 2018 // If a usage argument was provided, then no further validation should be 2019 // performed. 2020 if (skipFinalValidation) 2021 { 2022 return; 2023 } 2024 2025 2026 // If any subcommands are defined, then one must have been provided. 2027 if ((! subCommands.isEmpty()) && (selectedSubCommand == null)) 2028 { 2029 throw new ArgumentException( 2030 ERR_PARSER_MISSING_SUBCOMMAND.get(commandName)); 2031 } 2032 2033 2034 doFinalValidation(this); 2035 if (selectedSubCommand != null) 2036 { 2037 doFinalValidation(selectedSubCommand.getArgumentParser()); 2038 } 2039 } 2040 2041 2042 2043 /** 2044 * Performs the final validation for the provided argument parser. 2045 * 2046 * @param parser The argument parser for which to perform the final 2047 * validation. 2048 * 2049 * @throws ArgumentException If a validation problem is encountered. 2050 */ 2051 private static void doFinalValidation(final ArgumentParser parser) 2052 throws ArgumentException 2053 { 2054 // Make sure that all required arguments have values. 2055 for (final Argument a : parser.namedArgs) 2056 { 2057 if (a.isRequired() && (! a.isPresent())) 2058 { 2059 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get( 2060 a.getIdentifierString())); 2061 } 2062 } 2063 2064 2065 // Make sure that at least the minimum number of trailing arguments were 2066 // provided. 2067 if (parser.trailingArgs.size() < parser.minTrailingArgs) 2068 { 2069 throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get( 2070 parser.commandName, parser.minTrailingArgs, 2071 parser.trailingArgsPlaceholder)); 2072 } 2073 2074 2075 // Make sure that there are no dependent argument set conflicts. 2076 for (final ObjectPair<Argument,Set<Argument>> p : 2077 parser.dependentArgumentSets) 2078 { 2079 final Argument targetArg = p.getFirst(); 2080 if (targetArg.getNumOccurrences() > 0) 2081 { 2082 final Set<Argument> argSet = p.getSecond(); 2083 boolean found = false; 2084 for (final Argument a : argSet) 2085 { 2086 if (a.getNumOccurrences() > 0) 2087 { 2088 found = true; 2089 break; 2090 } 2091 } 2092 2093 if (! found) 2094 { 2095 if (argSet.size() == 1) 2096 { 2097 throw new ArgumentException( 2098 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get( 2099 targetArg.getIdentifierString(), 2100 argSet.iterator().next().getIdentifierString())); 2101 } 2102 else 2103 { 2104 boolean first = true; 2105 final StringBuilder buffer = new StringBuilder(); 2106 for (final Argument a : argSet) 2107 { 2108 if (first) 2109 { 2110 first = false; 2111 } 2112 else 2113 { 2114 buffer.append(", "); 2115 } 2116 buffer.append(a.getIdentifierString()); 2117 } 2118 throw new ArgumentException( 2119 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get( 2120 targetArg.getIdentifierString(), buffer.toString())); 2121 } 2122 } 2123 } 2124 } 2125 2126 2127 // Make sure that there are no exclusive argument set conflicts. 2128 for (final Set<Argument> argSet : parser.exclusiveArgumentSets) 2129 { 2130 Argument setArg = null; 2131 for (final Argument a : argSet) 2132 { 2133 if (a.getNumOccurrences() > 0) 2134 { 2135 if (setArg == null) 2136 { 2137 setArg = a; 2138 } 2139 else 2140 { 2141 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 2142 setArg.getIdentifierString(), 2143 a.getIdentifierString())); 2144 } 2145 } 2146 } 2147 } 2148 2149 // Make sure that there are no required argument set conflicts. 2150 for (final Set<Argument> argSet : parser.requiredArgumentSets) 2151 { 2152 boolean found = false; 2153 for (final Argument a : argSet) 2154 { 2155 if (a.getNumOccurrences() > 0) 2156 { 2157 found = true; 2158 break; 2159 } 2160 } 2161 2162 if (! found) 2163 { 2164 boolean first = true; 2165 final StringBuilder buffer = new StringBuilder(); 2166 for (final Argument a : argSet) 2167 { 2168 if (first) 2169 { 2170 first = false; 2171 } 2172 else 2173 { 2174 buffer.append(", "); 2175 } 2176 buffer.append(a.getIdentifierString()); 2177 } 2178 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get( 2179 buffer.toString())); 2180 } 2181 } 2182 } 2183 2184 2185 2186 /** 2187 * Indicates whether the provided argument is one that indicates that the 2188 * parser should skip all validation except that performed when assigning 2189 * values from command-line arguments. Validation that will be skipped 2190 * includes ensuring that all required arguments have values, ensuring that 2191 * the minimum number of trailing arguments were provided, and ensuring that 2192 * there were no dependent/exclusive/required argument set conflicts. 2193 * 2194 * @param a The argument for which to make the determination. 2195 * 2196 * @return {@code true} if the provided argument is one that indicates that 2197 * final validation should be skipped, or {@code false} if not. 2198 */ 2199 private static boolean skipFinalValidationBecauseOfArgument(final Argument a) 2200 { 2201 // We will skip final validation for all usage arguments except the 2202 // propertiesFilePath and noPropertiesFile arguments. 2203 if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) || 2204 ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()) || 2205 ARG_NAME_OUTPUT_FILE.equals(a.getLongIdentifier()) || 2206 ARG_NAME_TEE_OUTPUT.equals(a.getLongIdentifier())) 2207 { 2208 return false; 2209 } 2210 2211 return a.isUsageArgument(); 2212 } 2213 2214 2215 2216 /** 2217 * Performs any appropriate properties file processing for this argument 2218 * parser. 2219 * 2220 * @return {@code true} if the tool should continue processing, or 2221 * {@code false} if it should return immediately. 2222 * 2223 * @throws ArgumentException If a problem is encountered while attempting 2224 * to parse a properties file or update arguments 2225 * with the values contained in it. 2226 */ 2227 private boolean handlePropertiesFile() 2228 throws ArgumentException 2229 { 2230 final BooleanArgument noPropertiesFile; 2231 final FileArgument generatePropertiesFile; 2232 final FileArgument propertiesFilePath; 2233 try 2234 { 2235 propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH); 2236 generatePropertiesFile = 2237 getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE); 2238 noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE); 2239 } 2240 catch (final Exception e) 2241 { 2242 Debug.debugException(e); 2243 2244 // This should only ever happen if the argument parser has an argument 2245 // with a name that conflicts with one of the properties file arguments 2246 // but isn't of the right type. In this case, we'll assume that no 2247 // properties file will be used. 2248 return true; 2249 } 2250 2251 2252 // If any of the properties file arguments isn't defined, then we'll assume 2253 // that no properties file will be used. 2254 if ((propertiesFilePath == null) || (generatePropertiesFile == null) || 2255 (noPropertiesFile == null)) 2256 { 2257 return true; 2258 } 2259 2260 2261 // If the noPropertiesFile argument is present, then don't do anything but 2262 // make sure that neither of the other arguments was specified. 2263 if (noPropertiesFile.isPresent()) 2264 { 2265 if (propertiesFilePath.isPresent()) 2266 { 2267 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 2268 noPropertiesFile.getIdentifierString(), 2269 propertiesFilePath.getIdentifierString())); 2270 } 2271 else if (generatePropertiesFile.isPresent()) 2272 { 2273 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 2274 noPropertiesFile.getIdentifierString(), 2275 generatePropertiesFile.getIdentifierString())); 2276 } 2277 else 2278 { 2279 return true; 2280 } 2281 } 2282 2283 2284 // If the generatePropertiesFile argument is present, then make sure the 2285 // propertiesFilePath argument is not set and generate the output. 2286 if (generatePropertiesFile.isPresent()) 2287 { 2288 if (propertiesFilePath.isPresent()) 2289 { 2290 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 2291 generatePropertiesFile.getIdentifierString(), 2292 propertiesFilePath.getIdentifierString())); 2293 } 2294 else 2295 { 2296 generatePropertiesFile( 2297 generatePropertiesFile.getValue().getAbsolutePath()); 2298 return false; 2299 } 2300 } 2301 2302 2303 // If the propertiesFilePath argument is present, then try to make use of 2304 // the specified file. 2305 if (propertiesFilePath.isPresent()) 2306 { 2307 final File propertiesFile = propertiesFilePath.getValue(); 2308 if (propertiesFile.exists() && propertiesFile.isFile()) 2309 { 2310 handlePropertiesFile(propertiesFilePath.getValue()); 2311 } 2312 else 2313 { 2314 throw new ArgumentException( 2315 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get( 2316 propertiesFilePath.getIdentifierString(), 2317 propertiesFile.getAbsolutePath())); 2318 } 2319 return true; 2320 } 2321 2322 2323 // We may still use a properties file if the path was specified in either a 2324 // JVM property or an environment variable. If both are defined, the JVM 2325 // property will take precedence. If a property or environment variable 2326 // specifies an invalid value, then we'll just ignore it. 2327 String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH); 2328 if (path == null) 2329 { 2330 path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH); 2331 } 2332 2333 if (path != null) 2334 { 2335 final File propertiesFile = new File(path); 2336 if (propertiesFile.exists() && propertiesFile.isFile()) 2337 { 2338 handlePropertiesFile(propertiesFile); 2339 } 2340 } 2341 2342 return true; 2343 } 2344 2345 2346 2347 /** 2348 * Write an empty properties file for this argument parser to the specified 2349 * path. 2350 * 2351 * @param path The path to the properties file to be written. 2352 * 2353 * @throws ArgumentException If a problem is encountered while writing the 2354 * properties file. 2355 */ 2356 private void generatePropertiesFile(final String path) 2357 throws ArgumentException 2358 { 2359 final PrintWriter w; 2360 try 2361 { 2362 w = new PrintWriter(path); 2363 } 2364 catch (final Exception e) 2365 { 2366 Debug.debugException(e); 2367 throw new ArgumentException( 2368 ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path, 2369 getExceptionMessage(e)), 2370 e); 2371 } 2372 2373 try 2374 { 2375 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName)); 2376 w.println('#'); 2377 wrapComment(w, 2378 INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName, 2379 ARG_NAME_PROPERTIES_FILE_PATH, 2380 PROPERTY_DEFAULT_PROPERTIES_FILE_PATH, 2381 ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE)); 2382 w.println('#'); 2383 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_3.get()); 2384 w.println('#'); 2385 2386 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get()); 2387 w.println('#'); 2388 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName)); 2389 2390 for (final Argument a : getNamedArguments()) 2391 { 2392 writeArgumentProperties(w, null, a); 2393 } 2394 2395 for (final SubCommand sc : getSubCommands()) 2396 { 2397 for (final Argument a : sc.getArgumentParser().getNamedArguments()) 2398 { 2399 writeArgumentProperties(w, sc, a); 2400 } 2401 } 2402 } 2403 finally 2404 { 2405 w.close(); 2406 } 2407 } 2408 2409 2410 2411 /** 2412 * Writes information about the provided argument to the given writer. 2413 * 2414 * @param w The writer to which the properties should be written. It must 2415 * not be {@code null}. 2416 * @param sc The subcommand with which the argument is associated. It may 2417 * be {@code null} if the provided argument is a global argument. 2418 * @param a The argument for which to write the properties. It must not be 2419 * {@code null}. 2420 */ 2421 private void writeArgumentProperties(final PrintWriter w, 2422 final SubCommand sc, 2423 final Argument a) 2424 { 2425 if (a.isUsageArgument() || a.isHidden()) 2426 { 2427 return; 2428 } 2429 2430 w.println(); 2431 w.println(); 2432 wrapComment(w, a.getDescription()); 2433 w.println('#'); 2434 2435 final String constraints = a.getValueConstraints(); 2436 if ((constraints != null) && (constraints.length() > 0) && 2437 (! (a instanceof BooleanArgument))) 2438 { 2439 wrapComment(w, constraints); 2440 w.println('#'); 2441 } 2442 2443 final String identifier; 2444 if (a.getLongIdentifier() != null) 2445 { 2446 identifier = a.getLongIdentifier(); 2447 } 2448 else 2449 { 2450 identifier = a.getIdentifierString(); 2451 } 2452 2453 String placeholder = a.getValuePlaceholder(); 2454 if (placeholder == null) 2455 { 2456 if (a instanceof BooleanArgument) 2457 { 2458 placeholder = "{true|false}"; 2459 } 2460 else 2461 { 2462 placeholder = ""; 2463 } 2464 } 2465 2466 final String propertyName; 2467 if (sc == null) 2468 { 2469 propertyName = commandName + '.' + identifier; 2470 } 2471 else 2472 { 2473 propertyName = commandName + '.' + sc.getPrimaryName() + '.' + identifier; 2474 } 2475 2476 w.println("# " + propertyName + '=' + placeholder); 2477 2478 if (a.isPresent()) 2479 { 2480 for (final String s : a.getValueStringRepresentations(false)) 2481 { 2482 w.println(propertyName + '=' + s); 2483 } 2484 } 2485 } 2486 2487 2488 2489 /** 2490 * Wraps the given string and writes it as a comment to the provided writer. 2491 * 2492 * @param w The writer to use to write the wrapped and commented string. 2493 * @param s The string to be wrapped and written. 2494 */ 2495 private static void wrapComment(final PrintWriter w, final String s) 2496 { 2497 for (final String line : wrapLine(s, 77)) 2498 { 2499 w.println("# " + line); 2500 } 2501 } 2502 2503 2504 2505 /** 2506 * Reads the contents of the specified properties file and updates the 2507 * configured arguments as appropriate. 2508 * 2509 * @param propertiesFile The properties file to process. 2510 * 2511 * @throws ArgumentException If a problem is encountered while examining the 2512 * properties file, or while trying to assign a 2513 * property value to a corresponding argument. 2514 */ 2515 private void handlePropertiesFile(final File propertiesFile) 2516 throws ArgumentException 2517 { 2518 final BufferedReader reader; 2519 try 2520 { 2521 reader = new BufferedReader(new FileReader(propertiesFile)); 2522 } 2523 catch (final Exception e) 2524 { 2525 Debug.debugException(e); 2526 throw new ArgumentException( 2527 ERR_PARSER_CANNOT_OPEN_PROP_FILE.get( 2528 propertiesFile.getAbsolutePath(), getExceptionMessage(e)), 2529 e); 2530 } 2531 2532 try 2533 { 2534 // Read all of the lines of the file, ignoring comments and unwrapping 2535 // properties that span multiple lines. 2536 boolean lineIsContinued = false; 2537 int lineNumber = 0; 2538 final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines = 2539 new ArrayList<ObjectPair<Integer,StringBuilder>>(10); 2540 while (true) 2541 { 2542 String line; 2543 try 2544 { 2545 line = reader.readLine(); 2546 lineNumber++; 2547 } 2548 catch (final Exception e) 2549 { 2550 Debug.debugException(e); 2551 throw new ArgumentException( 2552 ERR_PARSER_ERROR_READING_PROP_FILE.get( 2553 propertiesFile.getAbsolutePath(), getExceptionMessage(e)), 2554 e); 2555 } 2556 2557 2558 // If the line is null, then we've reached the end of the file. If we 2559 // expect a previous line to have been continued, then this is an error. 2560 if (line == null) 2561 { 2562 if (lineIsContinued) 2563 { 2564 throw new ArgumentException( 2565 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get( 2566 (lineNumber-1), propertiesFile.getAbsolutePath())); 2567 } 2568 break; 2569 } 2570 2571 2572 // See if the line has any leading whitespace, and if so then trim it 2573 // off. If there is leading whitespace, then make sure that we expect 2574 // the previous line to be continued. 2575 final int initialLength = line.length(); 2576 line = trimLeading(line); 2577 final boolean hasLeadingWhitespace = (line.length() < initialLength); 2578 if (hasLeadingWhitespace && (! lineIsContinued)) 2579 { 2580 throw new ArgumentException( 2581 ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get( 2582 propertiesFile.getAbsolutePath(), lineNumber)); 2583 } 2584 2585 2586 // If the line is empty or starts with "#", then skip it. But make sure 2587 // we didn't expect the previous line to be continued. 2588 if ((line.length() == 0) || line.startsWith("#")) 2589 { 2590 if (lineIsContinued) 2591 { 2592 throw new ArgumentException( 2593 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get( 2594 (lineNumber-1), propertiesFile.getAbsolutePath())); 2595 } 2596 continue; 2597 } 2598 2599 2600 // See if the line ends with a backslash and if so then trim it off. 2601 final boolean hasTrailingBackslash = line.endsWith("\\"); 2602 if (line.endsWith("\\")) 2603 { 2604 line = line.substring(0, (line.length() - 1)); 2605 } 2606 2607 2608 // If the previous line needs to be continued, then append the new line 2609 // to it. Otherwise, add it as a new line. 2610 if (lineIsContinued) 2611 { 2612 propertyLines.get(propertyLines.size() - 1).getSecond().append(line); 2613 } 2614 else 2615 { 2616 propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber, 2617 new StringBuilder(line))); 2618 } 2619 2620 lineIsContinued = hasTrailingBackslash; 2621 } 2622 2623 2624 // Parse all of the lines into a map of identifiers and their 2625 // corresponding values. 2626 propertiesFileUsed = propertiesFile; 2627 if (propertyLines.isEmpty()) 2628 { 2629 return; 2630 } 2631 2632 final HashMap<String,ArrayList<String>> propertyMap = 2633 new HashMap<String,ArrayList<String>>(propertyLines.size()); 2634 for (final ObjectPair<Integer,StringBuilder> p : propertyLines) 2635 { 2636 final String line = p.getSecond().toString(); 2637 final int equalPos = line.indexOf('='); 2638 if (equalPos <= 0) 2639 { 2640 throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get( 2641 propertiesFile.getAbsolutePath(), p.getFirst(), line)); 2642 } 2643 2644 final String propertyName = line.substring(0, equalPos).trim(); 2645 final String propertyValue = line.substring(equalPos+1).trim(); 2646 if (propertyValue.length() == 0) 2647 { 2648 // The property doesn't have a value, so we can ignore it. 2649 continue; 2650 } 2651 2652 2653 // An argument can have multiple identifiers, and we will allow any of 2654 // them to be used to reference it. To deal with this, we'll map the 2655 // argument identifier to its corresponding argument and then use the 2656 // preferred identifier for that argument in the map. The same applies 2657 // to subcommand names. 2658 boolean prefixedWithToolName = false; 2659 boolean prefixedWithSubCommandName = false; 2660 Argument a = getNamedArgument(propertyName); 2661 if (a == null) 2662 { 2663 // It could be that the argument name was prefixed with the tool name. 2664 // Check to see if that was the case. 2665 if (propertyName.startsWith(commandName + '.')) 2666 { 2667 prefixedWithToolName = true; 2668 2669 String basePropertyName = 2670 propertyName.substring(commandName.length()+1); 2671 a = getNamedArgument(basePropertyName); 2672 2673 if (a == null) 2674 { 2675 final int periodPos = basePropertyName.indexOf('.'); 2676 if (periodPos > 0) 2677 { 2678 final String subCommandName = 2679 basePropertyName.substring(0, periodPos); 2680 if ((selectedSubCommand != null) && 2681 selectedSubCommand.hasName(subCommandName)) 2682 { 2683 prefixedWithSubCommandName = true; 2684 basePropertyName = basePropertyName.substring(periodPos+1); 2685 a = selectedSubCommand.getArgumentParser().getNamedArgument( 2686 basePropertyName); 2687 } 2688 } 2689 else if (selectedSubCommand != null) 2690 { 2691 a = selectedSubCommand.getArgumentParser().getNamedArgument( 2692 basePropertyName); 2693 } 2694 } 2695 } 2696 else if (selectedSubCommand != null) 2697 { 2698 a = selectedSubCommand.getArgumentParser().getNamedArgument( 2699 propertyName); 2700 } 2701 } 2702 2703 if (a == null) 2704 { 2705 // This could mean that there's a typo in the property name, but it's 2706 // more likely the case that the property is for a different tool. In 2707 // either case, we'll ignore it. 2708 continue; 2709 } 2710 2711 final String canonicalPropertyName; 2712 if (prefixedWithToolName) 2713 { 2714 if (prefixedWithSubCommandName) 2715 { 2716 canonicalPropertyName = commandName + '.' + 2717 selectedSubCommand.getPrimaryName() + '.' + 2718 a.getIdentifierString(); 2719 } 2720 else 2721 { 2722 canonicalPropertyName = commandName + '.' + a.getIdentifierString(); 2723 } 2724 } 2725 else 2726 { 2727 canonicalPropertyName = a.getIdentifierString(); 2728 } 2729 2730 ArrayList<String> valueList = propertyMap.get(canonicalPropertyName); 2731 if (valueList == null) 2732 { 2733 valueList = new ArrayList<String>(5); 2734 propertyMap.put(canonicalPropertyName, valueList); 2735 } 2736 valueList.add(propertyValue); 2737 } 2738 2739 2740 // Iterate through all of the named arguments for the argument parser and 2741 // see if we should use the properties to assign values to any of the 2742 // arguments that weren't provided on the command line. 2743 setArgsFromPropertiesFile(propertyMap, false); 2744 2745 2746 // If there is a selected subcommand, then iterate through all of its 2747 // arguments. 2748 if (selectedSubCommand != null) 2749 { 2750 setArgsFromPropertiesFile(propertyMap, true); 2751 } 2752 } 2753 finally 2754 { 2755 try 2756 { 2757 reader.close(); 2758 } 2759 catch (final Exception e) 2760 { 2761 Debug.debugException(e); 2762 } 2763 } 2764 } 2765 2766 2767 2768 /** 2769 * Sets the values of any arguments not provided on the command line but 2770 * defined in the properties file. 2771 * 2772 * @param propertyMap A map of properties read from the properties file. 2773 * @param useSubCommand Indicates whether to use the argument parser 2774 * associated with the selected subcommand rather than 2775 * the global argument parser. 2776 * 2777 * @throws ArgumentException If a problem is encountered while examining the 2778 * properties file, or while trying to assign a 2779 * property value to a corresponding argument. 2780 */ 2781 private void setArgsFromPropertiesFile( 2782 final Map<String,ArrayList<String>> propertyMap, 2783 final boolean useSubCommand) 2784 throws ArgumentException 2785 { 2786 final ArgumentParser p; 2787 if (useSubCommand) 2788 { 2789 p = selectedSubCommand.getArgumentParser(); 2790 } 2791 else 2792 { 2793 p = this; 2794 } 2795 2796 2797 for (final Argument a : p.namedArgs) 2798 { 2799 if (a.getNumOccurrences() > 0) 2800 { 2801 // The argument was provided on the command line, and that will always 2802 // override anything that might be in the properties file. 2803 continue; 2804 } 2805 2806 2807 // If we should use a subcommand, then see if the properties file has a 2808 // property that is specific to the selected subcommand. Then fall back 2809 // to a property that is specific to the tool, and finally fall back to 2810 // checking for a set of values that are generic to any tool that has an 2811 // argument with that name. 2812 List<String> values = null; 2813 if (useSubCommand) 2814 { 2815 values = propertyMap.get(commandName + '.' + 2816 selectedSubCommand.getPrimaryName() + '.' + 2817 a.getIdentifierString()); 2818 } 2819 2820 if (values == null) 2821 { 2822 values = propertyMap.get(commandName + '.' + a.getIdentifierString()); 2823 } 2824 2825 if (values == null) 2826 { 2827 values = propertyMap.get(a.getIdentifierString()); 2828 } 2829 2830 if (values != null) 2831 { 2832 for (final String value : values) 2833 { 2834 if (a instanceof BooleanArgument) 2835 { 2836 // We'll treat this as a BooleanValueArgument. 2837 final BooleanValueArgument bva = new BooleanValueArgument( 2838 a.getShortIdentifier(), a.getLongIdentifier(), false, null, 2839 a.getDescription()); 2840 bva.addValue(value); 2841 if (bva.getValue()) 2842 { 2843 a.incrementOccurrences(); 2844 } 2845 2846 argumentsSetFromPropertiesFile.add(a.getIdentifierString()); 2847 } 2848 else 2849 { 2850 a.addValue(value); 2851 a.incrementOccurrences(); 2852 2853 argumentsSetFromPropertiesFile.add(a.getIdentifierString()); 2854 if (a.isSensitive()) 2855 { 2856 argumentsSetFromPropertiesFile.add("***REDACTED***"); 2857 } 2858 else 2859 { 2860 argumentsSetFromPropertiesFile.add(value); 2861 } 2862 } 2863 } 2864 } 2865 } 2866 } 2867 2868 2869 2870 /** 2871 * Retrieves lines that make up the usage information for this program, 2872 * optionally wrapping long lines. 2873 * 2874 * @param maxWidth The maximum line width to use for the output. If this is 2875 * less than or equal to zero, then no wrapping will be 2876 * performed. 2877 * 2878 * @return The lines that make up the usage information for this program. 2879 */ 2880 public List<String> getUsage(final int maxWidth) 2881 { 2882 // If a subcommand was selected, then provide usage specific to that 2883 // subcommand. 2884 if (selectedSubCommand != null) 2885 { 2886 return getSubCommandUsage(maxWidth); 2887 } 2888 2889 // First is a description of the command. 2890 final ArrayList<String> lines = new ArrayList<String>(100); 2891 lines.addAll(wrapLine(commandDescription, maxWidth)); 2892 lines.add(""); 2893 2894 2895 // If the tool supports subcommands, and if there are fewer than 10 2896 // subcommands, then display them inline. 2897 if ((! subCommands.isEmpty()) && (subCommands.size() < 10)) 2898 { 2899 lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get()); 2900 lines.add(""); 2901 2902 for (final SubCommand sc : subCommands) 2903 { 2904 final StringBuilder nameBuffer = new StringBuilder(); 2905 nameBuffer.append(" "); 2906 2907 final Iterator<String> nameIterator = sc.getNames().iterator(); 2908 while (nameIterator.hasNext()) 2909 { 2910 nameBuffer.append(nameIterator.next()); 2911 if (nameIterator.hasNext()) 2912 { 2913 nameBuffer.append(", "); 2914 } 2915 } 2916 lines.add(nameBuffer.toString()); 2917 2918 for (final String descriptionLine : 2919 wrapLine(sc.getDescription(), (maxWidth - 4))) 2920 { 2921 lines.add(" " + descriptionLine); 2922 } 2923 lines.add(""); 2924 } 2925 } 2926 2927 2928 // Next comes the usage. It may include neither, either, or both of the 2929 // set of options and trailing arguments. 2930 if (! subCommands.isEmpty()) 2931 { 2932 lines.addAll(wrapLine(INFO_USAGE_SUBCOMMAND_USAGE.get(commandName), 2933 maxWidth)); 2934 } 2935 else if (namedArgs.isEmpty()) 2936 { 2937 if (maxTrailingArgs == 0) 2938 { 2939 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), 2940 maxWidth)); 2941 } 2942 else 2943 { 2944 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get( 2945 commandName, trailingArgsPlaceholder), 2946 maxWidth)); 2947 } 2948 } 2949 else 2950 { 2951 if (maxTrailingArgs == 0) 2952 { 2953 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), 2954 maxWidth)); 2955 } 2956 else 2957 { 2958 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get( 2959 commandName, trailingArgsPlaceholder), 2960 maxWidth)); 2961 } 2962 } 2963 2964 if (! namedArgs.isEmpty()) 2965 { 2966 lines.add(""); 2967 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 2968 2969 2970 // If there are any argument groups, then collect the arguments in those 2971 // groups. 2972 boolean hasRequired = false; 2973 final LinkedHashMap<String,List<Argument>> argumentsByGroup = 2974 new LinkedHashMap<String,List<Argument>>(10); 2975 final ArrayList<Argument> argumentsWithoutGroup = 2976 new ArrayList<Argument>(namedArgs.size()); 2977 final ArrayList<Argument> usageArguments = 2978 new ArrayList<Argument>(namedArgs.size()); 2979 for (final Argument a : namedArgs) 2980 { 2981 if (a.isHidden()) 2982 { 2983 // This argument shouldn't be included in the usage output. 2984 continue; 2985 } 2986 2987 if (a.isRequired() && (! a.hasDefaultValue())) 2988 { 2989 hasRequired = true; 2990 } 2991 2992 final String argumentGroup = a.getArgumentGroupName(); 2993 if (argumentGroup == null) 2994 { 2995 if (a.isUsageArgument()) 2996 { 2997 usageArguments.add(a); 2998 } 2999 else 3000 { 3001 argumentsWithoutGroup.add(a); 3002 } 3003 } 3004 else 3005 { 3006 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup); 3007 if (groupArgs == null) 3008 { 3009 groupArgs = new ArrayList<Argument>(10); 3010 argumentsByGroup.put(argumentGroup, groupArgs); 3011 } 3012 3013 groupArgs.add(a); 3014 } 3015 } 3016 3017 3018 // Iterate through the defined argument groups and display usage 3019 // information for each of them. 3020 for (final Map.Entry<String,List<Argument>> e : 3021 argumentsByGroup.entrySet()) 3022 { 3023 lines.add(""); 3024 lines.add(" " + e.getKey()); 3025 lines.add(""); 3026 for (final Argument a : e.getValue()) 3027 { 3028 getArgUsage(a, lines, true, maxWidth); 3029 } 3030 } 3031 3032 if (! argumentsWithoutGroup.isEmpty()) 3033 { 3034 if (argumentsByGroup.isEmpty()) 3035 { 3036 for (final Argument a : argumentsWithoutGroup) 3037 { 3038 getArgUsage(a, lines, false, maxWidth); 3039 } 3040 } 3041 else 3042 { 3043 lines.add(""); 3044 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get()); 3045 lines.add(""); 3046 for (final Argument a : argumentsWithoutGroup) 3047 { 3048 getArgUsage(a, lines, true, maxWidth); 3049 } 3050 } 3051 } 3052 3053 if (! usageArguments.isEmpty()) 3054 { 3055 if (argumentsByGroup.isEmpty()) 3056 { 3057 for (final Argument a : usageArguments) 3058 { 3059 getArgUsage(a, lines, false, maxWidth); 3060 } 3061 } 3062 else 3063 { 3064 lines.add(""); 3065 lines.add(" " + INFO_USAGE_USAGE_ARGS.get()); 3066 lines.add(""); 3067 for (final Argument a : usageArguments) 3068 { 3069 getArgUsage(a, lines, true, maxWidth); 3070 } 3071 } 3072 } 3073 3074 if (hasRequired) 3075 { 3076 lines.add(""); 3077 if (argumentsByGroup.isEmpty()) 3078 { 3079 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3080 } 3081 else 3082 { 3083 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3084 } 3085 } 3086 } 3087 3088 return lines; 3089 } 3090 3091 3092 3093 /** 3094 * Retrieves lines that make up the usage information for the selected 3095 * subcommand. 3096 * 3097 * @param maxWidth The maximum line width to use for the output. If this is 3098 * less than or equal to zero, then no wrapping will be 3099 * performed. 3100 * 3101 * @return The lines that make up the usage information for the selected 3102 * subcommand. 3103 */ 3104 private List<String> getSubCommandUsage(final int maxWidth) 3105 { 3106 // First is a description of the subcommand. 3107 final ArrayList<String> lines = new ArrayList<String>(100); 3108 lines.addAll(wrapLine(selectedSubCommand.getDescription(), maxWidth)); 3109 lines.add(""); 3110 3111 // Next comes the usage. 3112 lines.addAll(wrapLine( 3113 INFO_SUBCOMMAND_USAGE_OPTIONS.get(commandName, 3114 selectedSubCommand.getPrimaryName()), 3115 maxWidth)); 3116 3117 3118 final ArgumentParser parser = selectedSubCommand.getArgumentParser(); 3119 if (! parser.namedArgs.isEmpty()) 3120 { 3121 lines.add(""); 3122 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 3123 3124 3125 // If there are any argument groups, then collect the arguments in those 3126 // groups. 3127 boolean hasRequired = false; 3128 final LinkedHashMap<String,List<Argument>> argumentsByGroup = 3129 new LinkedHashMap<String,List<Argument>>(10); 3130 final ArrayList<Argument> argumentsWithoutGroup = 3131 new ArrayList<Argument>(parser.namedArgs.size()); 3132 final ArrayList<Argument> usageArguments = 3133 new ArrayList<Argument>(parser.namedArgs.size()); 3134 for (final Argument a : parser.namedArgs) 3135 { 3136 if (a.isHidden()) 3137 { 3138 // This argument shouldn't be included in the usage output. 3139 continue; 3140 } 3141 3142 if (a.isRequired() && (! a.hasDefaultValue())) 3143 { 3144 hasRequired = true; 3145 } 3146 3147 final String argumentGroup = a.getArgumentGroupName(); 3148 if (argumentGroup == null) 3149 { 3150 if (a.isUsageArgument()) 3151 { 3152 usageArguments.add(a); 3153 } 3154 else 3155 { 3156 argumentsWithoutGroup.add(a); 3157 } 3158 } 3159 else 3160 { 3161 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup); 3162 if (groupArgs == null) 3163 { 3164 groupArgs = new ArrayList<Argument>(10); 3165 argumentsByGroup.put(argumentGroup, groupArgs); 3166 } 3167 3168 groupArgs.add(a); 3169 } 3170 } 3171 3172 3173 // Iterate through the defined argument groups and display usage 3174 // information for each of them. 3175 for (final Map.Entry<String,List<Argument>> e : 3176 argumentsByGroup.entrySet()) 3177 { 3178 lines.add(""); 3179 lines.add(" " + e.getKey()); 3180 lines.add(""); 3181 for (final Argument a : e.getValue()) 3182 { 3183 getArgUsage(a, lines, true, maxWidth); 3184 } 3185 } 3186 3187 if (! argumentsWithoutGroup.isEmpty()) 3188 { 3189 if (argumentsByGroup.isEmpty()) 3190 { 3191 for (final Argument a : argumentsWithoutGroup) 3192 { 3193 getArgUsage(a, lines, false, maxWidth); 3194 } 3195 } 3196 else 3197 { 3198 lines.add(""); 3199 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get()); 3200 lines.add(""); 3201 for (final Argument a : argumentsWithoutGroup) 3202 { 3203 getArgUsage(a, lines, true, maxWidth); 3204 } 3205 } 3206 } 3207 3208 if (! usageArguments.isEmpty()) 3209 { 3210 if (argumentsByGroup.isEmpty()) 3211 { 3212 for (final Argument a : usageArguments) 3213 { 3214 getArgUsage(a, lines, false, maxWidth); 3215 } 3216 } 3217 else 3218 { 3219 lines.add(""); 3220 lines.add(" " + INFO_USAGE_USAGE_ARGS.get()); 3221 lines.add(""); 3222 for (final Argument a : usageArguments) 3223 { 3224 getArgUsage(a, lines, true, maxWidth); 3225 } 3226 } 3227 } 3228 3229 if (hasRequired) 3230 { 3231 lines.add(""); 3232 if (argumentsByGroup.isEmpty()) 3233 { 3234 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3235 } 3236 else 3237 { 3238 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3239 } 3240 } 3241 } 3242 3243 return lines; 3244 } 3245 3246 3247 3248 /** 3249 * Adds usage information for the provided argument to the given list. 3250 * 3251 * @param a The argument for which to get the usage information. 3252 * @param lines The list to which the resulting lines should be added. 3253 * @param indent Indicates whether to indent each line. 3254 * @param maxWidth The maximum width of each line, in characters. 3255 */ 3256 private static void getArgUsage(final Argument a, final List<String> lines, 3257 final boolean indent, final int maxWidth) 3258 { 3259 final StringBuilder argLine = new StringBuilder(); 3260 if (indent && (maxWidth > 10)) 3261 { 3262 if (a.isRequired() && (! a.hasDefaultValue())) 3263 { 3264 argLine.append(" * "); 3265 } 3266 else 3267 { 3268 argLine.append(" "); 3269 } 3270 } 3271 else if (a.isRequired() && (! a.hasDefaultValue())) 3272 { 3273 argLine.append("* "); 3274 } 3275 3276 boolean first = true; 3277 for (final Character c : a.getShortIdentifiers()) 3278 { 3279 if (first) 3280 { 3281 argLine.append('-'); 3282 first = false; 3283 } 3284 else 3285 { 3286 argLine.append(", -"); 3287 } 3288 argLine.append(c); 3289 } 3290 3291 for (final String s : a.getLongIdentifiers()) 3292 { 3293 if (first) 3294 { 3295 argLine.append("--"); 3296 first = false; 3297 } 3298 else 3299 { 3300 argLine.append(", --"); 3301 } 3302 argLine.append(s); 3303 } 3304 3305 final String valuePlaceholder = a.getValuePlaceholder(); 3306 if (valuePlaceholder != null) 3307 { 3308 argLine.append(' '); 3309 argLine.append(valuePlaceholder); 3310 } 3311 3312 // If we need to wrap the argument line, then align the dashes on the left 3313 // edge. 3314 int subsequentLineWidth = maxWidth - 4; 3315 if (subsequentLineWidth < 4) 3316 { 3317 subsequentLineWidth = maxWidth; 3318 } 3319 final List<String> identifierLines = 3320 wrapLine(argLine.toString(), maxWidth, subsequentLineWidth); 3321 for (int i=0; i < identifierLines.size(); i++) 3322 { 3323 if (i == 0) 3324 { 3325 lines.add(identifierLines.get(0)); 3326 } 3327 else 3328 { 3329 lines.add(" " + identifierLines.get(i)); 3330 } 3331 } 3332 3333 3334 // The description should be wrapped, if necessary. We'll also want to 3335 // indent it (unless someone chose an absurdly small wrap width) to make 3336 // it stand out from the argument lines. 3337 final String description = a.getDescription(); 3338 if (maxWidth > 10) 3339 { 3340 final String indentString; 3341 if (indent) 3342 { 3343 indentString = " "; 3344 } 3345 else 3346 { 3347 indentString = " "; 3348 } 3349 3350 final List<String> descLines = wrapLine(description, 3351 (maxWidth-indentString.length())); 3352 for (final String s : descLines) 3353 { 3354 lines.add(indentString + s); 3355 } 3356 } 3357 else 3358 { 3359 lines.addAll(wrapLine(description, maxWidth)); 3360 } 3361 } 3362 3363 3364 3365 /** 3366 * Writes usage information for this program to the provided output stream 3367 * using the UTF-8 encoding, optionally wrapping long lines. 3368 * 3369 * @param outputStream The output stream to which the usage information 3370 * should be written. It must not be {@code null}. 3371 * @param maxWidth The maximum line width to use for the output. If 3372 * this is less than or equal to zero, then no wrapping 3373 * will be performed. 3374 * 3375 * @throws IOException If an error occurs while attempting to write to the 3376 * provided output stream. 3377 */ 3378 public void getUsage(final OutputStream outputStream, final int maxWidth) 3379 throws IOException 3380 { 3381 final List<String> usageLines = getUsage(maxWidth); 3382 for (final String s : usageLines) 3383 { 3384 outputStream.write(getBytes(s)); 3385 outputStream.write(EOL_BYTES); 3386 } 3387 } 3388 3389 3390 3391 /** 3392 * Retrieves a string representation of the usage information. 3393 * 3394 * @param maxWidth The maximum line width to use for the output. If this is 3395 * less than or equal to zero, then no wrapping will be 3396 * performed. 3397 * 3398 * @return A string representation of the usage information 3399 */ 3400 public String getUsageString(final int maxWidth) 3401 { 3402 final StringBuilder buffer = new StringBuilder(); 3403 getUsageString(buffer, maxWidth); 3404 return buffer.toString(); 3405 } 3406 3407 3408 3409 /** 3410 * Appends a string representation of the usage information to the provided 3411 * buffer. 3412 * 3413 * @param buffer The buffer to which the information should be appended. 3414 * @param maxWidth The maximum line width to use for the output. If this is 3415 * less than or equal to zero, then no wrapping will be 3416 * performed. 3417 */ 3418 public void getUsageString(final StringBuilder buffer, final int maxWidth) 3419 { 3420 for (final String line : getUsage(maxWidth)) 3421 { 3422 buffer.append(line); 3423 buffer.append(EOL); 3424 } 3425 } 3426 3427 3428 3429 /** 3430 * Retrieves a string representation of this argument parser. 3431 * 3432 * @return A string representation of this argument parser. 3433 */ 3434 @Override() 3435 public String toString() 3436 { 3437 final StringBuilder buffer = new StringBuilder(); 3438 toString(buffer); 3439 return buffer.toString(); 3440 } 3441 3442 3443 3444 /** 3445 * Appends a string representation of this argument parser to the provided 3446 * buffer. 3447 * 3448 * @param buffer The buffer to which the information should be appended. 3449 */ 3450 public void toString(final StringBuilder buffer) 3451 { 3452 buffer.append("ArgumentParser(commandName='"); 3453 buffer.append(commandName); 3454 buffer.append("', commandDescription='"); 3455 buffer.append(commandDescription); 3456 buffer.append("', minTrailingArgs="); 3457 buffer.append(minTrailingArgs); 3458 buffer.append("', maxTrailingArgs="); 3459 buffer.append(maxTrailingArgs); 3460 3461 if (trailingArgsPlaceholder != null) 3462 { 3463 buffer.append(", trailingArgsPlaceholder='"); 3464 buffer.append(trailingArgsPlaceholder); 3465 buffer.append('\''); 3466 } 3467 3468 buffer.append("namedArgs={"); 3469 3470 final Iterator<Argument> iterator = namedArgs.iterator(); 3471 while (iterator.hasNext()) 3472 { 3473 iterator.next().toString(buffer); 3474 if (iterator.hasNext()) 3475 { 3476 buffer.append(", "); 3477 } 3478 } 3479 3480 buffer.append('}'); 3481 3482 if (! subCommands.isEmpty()) 3483 { 3484 buffer.append(", subCommands={"); 3485 3486 final Iterator<SubCommand> subCommandIterator = subCommands.iterator(); 3487 while (subCommandIterator.hasNext()) 3488 { 3489 subCommandIterator.next().toString(buffer); 3490 if (subCommandIterator.hasNext()) 3491 { 3492 buffer.append(", "); 3493 } 3494 } 3495 3496 buffer.append('}'); 3497 } 3498 3499 buffer.append(')'); 3500 } 3501}