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