001/* 002 * Copyright 2008-2017 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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()) 424 { 425 namedArgsByShortID.put(c, a); 426 } 427 428 for (final String s : a.getLongIdentifiers()) 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()) 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"); 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"); 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"); 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"); 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()) 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()) 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()) 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()) 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()) 1156 { 1157 namedArgsByShortID.put(c, argument); 1158 } 1159 1160 for (final String s : argument.getLongIdentifiers()) 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()) 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()) 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 (a.getNumOccurrences() > 0) 2835 { 2836 // The argument was provided on the command line, and that will always 2837 // override anything that might be in the properties file. 2838 continue; 2839 } 2840 2841 2842 // If we should use a subcommand, then see if the properties file has a 2843 // property that is specific to the selected subcommand. Then fall back 2844 // to a property that is specific to the tool, and finally fall back to 2845 // checking for a set of values that are generic to any tool that has an 2846 // argument with that name. 2847 List<String> values = null; 2848 if (useSubCommand) 2849 { 2850 values = propertyMap.get(commandName + '.' + 2851 selectedSubCommand.getPrimaryName() + '.' + 2852 a.getIdentifierString()); 2853 } 2854 2855 if (values == null) 2856 { 2857 values = propertyMap.get(commandName + '.' + a.getIdentifierString()); 2858 } 2859 2860 if (values == null) 2861 { 2862 values = propertyMap.get(a.getIdentifierString()); 2863 } 2864 2865 if (values != null) 2866 { 2867 for (final String value : values) 2868 { 2869 if (a instanceof BooleanArgument) 2870 { 2871 // We'll treat this as a BooleanValueArgument. 2872 final BooleanValueArgument bva = new BooleanValueArgument( 2873 a.getShortIdentifier(), a.getLongIdentifier(), false, null, 2874 a.getDescription()); 2875 bva.addValue(value); 2876 if (bva.getValue()) 2877 { 2878 a.incrementOccurrences(); 2879 argumentsSetFromPropertiesFile.add(a.getIdentifierString()); 2880 } 2881 } 2882 else 2883 { 2884 a.addValue(value); 2885 a.incrementOccurrences(); 2886 2887 argumentsSetFromPropertiesFile.add(a.getIdentifierString()); 2888 if (a.isSensitive()) 2889 { 2890 argumentsSetFromPropertiesFile.add("***REDACTED***"); 2891 } 2892 else 2893 { 2894 argumentsSetFromPropertiesFile.add(value); 2895 } 2896 } 2897 } 2898 } 2899 } 2900 } 2901 2902 2903 2904 /** 2905 * Retrieves lines that make up the usage information for this program, 2906 * optionally wrapping long lines. 2907 * 2908 * @param maxWidth The maximum line width to use for the output. If this is 2909 * less than or equal to zero, then no wrapping will be 2910 * performed. 2911 * 2912 * @return The lines that make up the usage information for this program. 2913 */ 2914 public List<String> getUsage(final int maxWidth) 2915 { 2916 // If a subcommand was selected, then provide usage specific to that 2917 // subcommand. 2918 if (selectedSubCommand != null) 2919 { 2920 return getSubCommandUsage(maxWidth); 2921 } 2922 2923 // First is a description of the command. 2924 final ArrayList<String> lines = new ArrayList<String>(100); 2925 lines.addAll(wrapLine(commandDescription, maxWidth)); 2926 lines.add(""); 2927 2928 2929 // If the tool supports subcommands, and if there are fewer than 10 2930 // subcommands, then display them inline. 2931 if ((! subCommands.isEmpty()) && (subCommands.size() < 10)) 2932 { 2933 lines.add(INFO_USAGE_SUBCOMMANDS_HEADER.get()); 2934 lines.add(""); 2935 2936 for (final SubCommand sc : subCommands) 2937 { 2938 final StringBuilder nameBuffer = new StringBuilder(); 2939 nameBuffer.append(" "); 2940 2941 final Iterator<String> nameIterator = sc.getNames().iterator(); 2942 while (nameIterator.hasNext()) 2943 { 2944 nameBuffer.append(nameIterator.next()); 2945 if (nameIterator.hasNext()) 2946 { 2947 nameBuffer.append(", "); 2948 } 2949 } 2950 lines.add(nameBuffer.toString()); 2951 2952 for (final String descriptionLine : 2953 wrapLine(sc.getDescription(), (maxWidth - 4))) 2954 { 2955 lines.add(" " + descriptionLine); 2956 } 2957 lines.add(""); 2958 } 2959 } 2960 2961 2962 // Next comes the usage. It may include neither, either, or both of the 2963 // set of options and trailing arguments. 2964 if (! subCommands.isEmpty()) 2965 { 2966 lines.addAll(wrapLine(INFO_USAGE_SUBCOMMAND_USAGE.get(commandName), 2967 maxWidth)); 2968 } 2969 else if (namedArgs.isEmpty()) 2970 { 2971 if (maxTrailingArgs == 0) 2972 { 2973 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), 2974 maxWidth)); 2975 } 2976 else 2977 { 2978 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get( 2979 commandName, trailingArgsPlaceholder), 2980 maxWidth)); 2981 } 2982 } 2983 else 2984 { 2985 if (maxTrailingArgs == 0) 2986 { 2987 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), 2988 maxWidth)); 2989 } 2990 else 2991 { 2992 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get( 2993 commandName, trailingArgsPlaceholder), 2994 maxWidth)); 2995 } 2996 } 2997 2998 if (! namedArgs.isEmpty()) 2999 { 3000 lines.add(""); 3001 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 3002 3003 3004 // If there are any argument groups, then collect the arguments in those 3005 // groups. 3006 boolean hasRequired = false; 3007 final LinkedHashMap<String,List<Argument>> argumentsByGroup = 3008 new LinkedHashMap<String,List<Argument>>(10); 3009 final ArrayList<Argument> argumentsWithoutGroup = 3010 new ArrayList<Argument>(namedArgs.size()); 3011 final ArrayList<Argument> usageArguments = 3012 new ArrayList<Argument>(namedArgs.size()); 3013 for (final Argument a : namedArgs) 3014 { 3015 if (a.isHidden()) 3016 { 3017 // This argument shouldn't be included in the usage output. 3018 continue; 3019 } 3020 3021 if (a.isRequired() && (! a.hasDefaultValue())) 3022 { 3023 hasRequired = true; 3024 } 3025 3026 final String argumentGroup = a.getArgumentGroupName(); 3027 if (argumentGroup == null) 3028 { 3029 if (a.isUsageArgument()) 3030 { 3031 usageArguments.add(a); 3032 } 3033 else 3034 { 3035 argumentsWithoutGroup.add(a); 3036 } 3037 } 3038 else 3039 { 3040 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup); 3041 if (groupArgs == null) 3042 { 3043 groupArgs = new ArrayList<Argument>(10); 3044 argumentsByGroup.put(argumentGroup, groupArgs); 3045 } 3046 3047 groupArgs.add(a); 3048 } 3049 } 3050 3051 3052 // Iterate through the defined argument groups and display usage 3053 // information for each of them. 3054 for (final Map.Entry<String,List<Argument>> e : 3055 argumentsByGroup.entrySet()) 3056 { 3057 lines.add(""); 3058 lines.add(" " + e.getKey()); 3059 lines.add(""); 3060 for (final Argument a : e.getValue()) 3061 { 3062 getArgUsage(a, lines, true, maxWidth); 3063 } 3064 } 3065 3066 if (! argumentsWithoutGroup.isEmpty()) 3067 { 3068 if (argumentsByGroup.isEmpty()) 3069 { 3070 for (final Argument a : argumentsWithoutGroup) 3071 { 3072 getArgUsage(a, lines, false, maxWidth); 3073 } 3074 } 3075 else 3076 { 3077 lines.add(""); 3078 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get()); 3079 lines.add(""); 3080 for (final Argument a : argumentsWithoutGroup) 3081 { 3082 getArgUsage(a, lines, true, maxWidth); 3083 } 3084 } 3085 } 3086 3087 if (! usageArguments.isEmpty()) 3088 { 3089 if (argumentsByGroup.isEmpty()) 3090 { 3091 for (final Argument a : usageArguments) 3092 { 3093 getArgUsage(a, lines, false, maxWidth); 3094 } 3095 } 3096 else 3097 { 3098 lines.add(""); 3099 lines.add(" " + INFO_USAGE_USAGE_ARGS.get()); 3100 lines.add(""); 3101 for (final Argument a : usageArguments) 3102 { 3103 getArgUsage(a, lines, true, maxWidth); 3104 } 3105 } 3106 } 3107 3108 if (hasRequired) 3109 { 3110 lines.add(""); 3111 if (argumentsByGroup.isEmpty()) 3112 { 3113 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3114 } 3115 else 3116 { 3117 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3118 } 3119 } 3120 } 3121 3122 return lines; 3123 } 3124 3125 3126 3127 /** 3128 * Retrieves lines that make up the usage information for the selected 3129 * subcommand. 3130 * 3131 * @param maxWidth The maximum line width to use for the output. If this is 3132 * less than or equal to zero, then no wrapping will be 3133 * performed. 3134 * 3135 * @return The lines that make up the usage information for the selected 3136 * subcommand. 3137 */ 3138 private List<String> getSubCommandUsage(final int maxWidth) 3139 { 3140 // First is a description of the subcommand. 3141 final ArrayList<String> lines = new ArrayList<String>(100); 3142 lines.addAll(wrapLine(selectedSubCommand.getDescription(), maxWidth)); 3143 lines.add(""); 3144 3145 // Next comes the usage. 3146 lines.addAll(wrapLine( 3147 INFO_SUBCOMMAND_USAGE_OPTIONS.get(commandName, 3148 selectedSubCommand.getPrimaryName()), 3149 maxWidth)); 3150 3151 3152 final ArgumentParser parser = selectedSubCommand.getArgumentParser(); 3153 if (! parser.namedArgs.isEmpty()) 3154 { 3155 lines.add(""); 3156 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 3157 3158 3159 // If there are any argument groups, then collect the arguments in those 3160 // groups. 3161 boolean hasRequired = false; 3162 final LinkedHashMap<String,List<Argument>> argumentsByGroup = 3163 new LinkedHashMap<String,List<Argument>>(10); 3164 final ArrayList<Argument> argumentsWithoutGroup = 3165 new ArrayList<Argument>(parser.namedArgs.size()); 3166 final ArrayList<Argument> usageArguments = 3167 new ArrayList<Argument>(parser.namedArgs.size()); 3168 for (final Argument a : parser.namedArgs) 3169 { 3170 if (a.isHidden()) 3171 { 3172 // This argument shouldn't be included in the usage output. 3173 continue; 3174 } 3175 3176 if (a.isRequired() && (! a.hasDefaultValue())) 3177 { 3178 hasRequired = true; 3179 } 3180 3181 final String argumentGroup = a.getArgumentGroupName(); 3182 if (argumentGroup == null) 3183 { 3184 if (a.isUsageArgument()) 3185 { 3186 usageArguments.add(a); 3187 } 3188 else 3189 { 3190 argumentsWithoutGroup.add(a); 3191 } 3192 } 3193 else 3194 { 3195 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup); 3196 if (groupArgs == null) 3197 { 3198 groupArgs = new ArrayList<Argument>(10); 3199 argumentsByGroup.put(argumentGroup, groupArgs); 3200 } 3201 3202 groupArgs.add(a); 3203 } 3204 } 3205 3206 3207 // Iterate through the defined argument groups and display usage 3208 // information for each of them. 3209 for (final Map.Entry<String,List<Argument>> e : 3210 argumentsByGroup.entrySet()) 3211 { 3212 lines.add(""); 3213 lines.add(" " + e.getKey()); 3214 lines.add(""); 3215 for (final Argument a : e.getValue()) 3216 { 3217 getArgUsage(a, lines, true, maxWidth); 3218 } 3219 } 3220 3221 if (! argumentsWithoutGroup.isEmpty()) 3222 { 3223 if (argumentsByGroup.isEmpty()) 3224 { 3225 for (final Argument a : argumentsWithoutGroup) 3226 { 3227 getArgUsage(a, lines, false, maxWidth); 3228 } 3229 } 3230 else 3231 { 3232 lines.add(""); 3233 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get()); 3234 lines.add(""); 3235 for (final Argument a : argumentsWithoutGroup) 3236 { 3237 getArgUsage(a, lines, true, maxWidth); 3238 } 3239 } 3240 } 3241 3242 if (! usageArguments.isEmpty()) 3243 { 3244 if (argumentsByGroup.isEmpty()) 3245 { 3246 for (final Argument a : usageArguments) 3247 { 3248 getArgUsage(a, lines, false, maxWidth); 3249 } 3250 } 3251 else 3252 { 3253 lines.add(""); 3254 lines.add(" " + INFO_USAGE_USAGE_ARGS.get()); 3255 lines.add(""); 3256 for (final Argument a : usageArguments) 3257 { 3258 getArgUsage(a, lines, true, maxWidth); 3259 } 3260 } 3261 } 3262 3263 if (hasRequired) 3264 { 3265 lines.add(""); 3266 if (argumentsByGroup.isEmpty()) 3267 { 3268 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3269 } 3270 else 3271 { 3272 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get()); 3273 } 3274 } 3275 } 3276 3277 return lines; 3278 } 3279 3280 3281 3282 /** 3283 * Adds usage information for the provided argument to the given list. 3284 * 3285 * @param a The argument for which to get the usage information. 3286 * @param lines The list to which the resulting lines should be added. 3287 * @param indent Indicates whether to indent each line. 3288 * @param maxWidth The maximum width of each line, in characters. 3289 */ 3290 private static void getArgUsage(final Argument a, final List<String> lines, 3291 final boolean indent, final int maxWidth) 3292 { 3293 final StringBuilder argLine = new StringBuilder(); 3294 if (indent && (maxWidth > 10)) 3295 { 3296 if (a.isRequired() && (! a.hasDefaultValue())) 3297 { 3298 argLine.append(" * "); 3299 } 3300 else 3301 { 3302 argLine.append(" "); 3303 } 3304 } 3305 else if (a.isRequired() && (! a.hasDefaultValue())) 3306 { 3307 argLine.append("* "); 3308 } 3309 3310 boolean first = true; 3311 for (final Character c : a.getShortIdentifiers()) 3312 { 3313 if (first) 3314 { 3315 argLine.append('-'); 3316 first = false; 3317 } 3318 else 3319 { 3320 argLine.append(", -"); 3321 } 3322 argLine.append(c); 3323 } 3324 3325 for (final String s : a.getLongIdentifiers()) 3326 { 3327 if (first) 3328 { 3329 argLine.append("--"); 3330 first = false; 3331 } 3332 else 3333 { 3334 argLine.append(", --"); 3335 } 3336 argLine.append(s); 3337 } 3338 3339 final String valuePlaceholder = a.getValuePlaceholder(); 3340 if (valuePlaceholder != null) 3341 { 3342 argLine.append(' '); 3343 argLine.append(valuePlaceholder); 3344 } 3345 3346 // If we need to wrap the argument line, then align the dashes on the left 3347 // edge. 3348 int subsequentLineWidth = maxWidth - 4; 3349 if (subsequentLineWidth < 4) 3350 { 3351 subsequentLineWidth = maxWidth; 3352 } 3353 final List<String> identifierLines = 3354 wrapLine(argLine.toString(), maxWidth, subsequentLineWidth); 3355 for (int i=0; i < identifierLines.size(); i++) 3356 { 3357 if (i == 0) 3358 { 3359 lines.add(identifierLines.get(0)); 3360 } 3361 else 3362 { 3363 lines.add(" " + identifierLines.get(i)); 3364 } 3365 } 3366 3367 3368 // The description should be wrapped, if necessary. We'll also want to 3369 // indent it (unless someone chose an absurdly small wrap width) to make 3370 // it stand out from the argument lines. 3371 final String description = a.getDescription(); 3372 if (maxWidth > 10) 3373 { 3374 final String indentString; 3375 if (indent) 3376 { 3377 indentString = " "; 3378 } 3379 else 3380 { 3381 indentString = " "; 3382 } 3383 3384 final List<String> descLines = wrapLine(description, 3385 (maxWidth-indentString.length())); 3386 for (final String s : descLines) 3387 { 3388 lines.add(indentString + s); 3389 } 3390 } 3391 else 3392 { 3393 lines.addAll(wrapLine(description, maxWidth)); 3394 } 3395 } 3396 3397 3398 3399 /** 3400 * Writes usage information for this program to the provided output stream 3401 * using the UTF-8 encoding, optionally wrapping long lines. 3402 * 3403 * @param outputStream The output stream to which the usage information 3404 * should be written. It must not be {@code null}. 3405 * @param maxWidth The maximum line width to use for the output. If 3406 * this is less than or equal to zero, then no wrapping 3407 * will be performed. 3408 * 3409 * @throws IOException If an error occurs while attempting to write to the 3410 * provided output stream. 3411 */ 3412 public void getUsage(final OutputStream outputStream, final int maxWidth) 3413 throws IOException 3414 { 3415 final List<String> usageLines = getUsage(maxWidth); 3416 for (final String s : usageLines) 3417 { 3418 outputStream.write(getBytes(s)); 3419 outputStream.write(EOL_BYTES); 3420 } 3421 } 3422 3423 3424 3425 /** 3426 * Retrieves a string representation of the usage information. 3427 * 3428 * @param maxWidth The maximum line width to use for the output. If this is 3429 * less than or equal to zero, then no wrapping will be 3430 * performed. 3431 * 3432 * @return A string representation of the usage information 3433 */ 3434 public String getUsageString(final int maxWidth) 3435 { 3436 final StringBuilder buffer = new StringBuilder(); 3437 getUsageString(buffer, maxWidth); 3438 return buffer.toString(); 3439 } 3440 3441 3442 3443 /** 3444 * Appends a string representation of the usage information to the provided 3445 * buffer. 3446 * 3447 * @param buffer The buffer to which the information should be appended. 3448 * @param maxWidth The maximum line width to use for the output. If this is 3449 * less than or equal to zero, then no wrapping will be 3450 * performed. 3451 */ 3452 public void getUsageString(final StringBuilder buffer, final int maxWidth) 3453 { 3454 for (final String line : getUsage(maxWidth)) 3455 { 3456 buffer.append(line); 3457 buffer.append(EOL); 3458 } 3459 } 3460 3461 3462 3463 /** 3464 * Retrieves a string representation of this argument parser. 3465 * 3466 * @return A string representation of this argument parser. 3467 */ 3468 @Override() 3469 public String toString() 3470 { 3471 final StringBuilder buffer = new StringBuilder(); 3472 toString(buffer); 3473 return buffer.toString(); 3474 } 3475 3476 3477 3478 /** 3479 * Appends a string representation of this argument parser to the provided 3480 * buffer. 3481 * 3482 * @param buffer The buffer to which the information should be appended. 3483 */ 3484 public void toString(final StringBuilder buffer) 3485 { 3486 buffer.append("ArgumentParser(commandName='"); 3487 buffer.append(commandName); 3488 buffer.append("', commandDescription='"); 3489 buffer.append(commandDescription); 3490 buffer.append("', minTrailingArgs="); 3491 buffer.append(minTrailingArgs); 3492 buffer.append("', maxTrailingArgs="); 3493 buffer.append(maxTrailingArgs); 3494 3495 if (trailingArgsPlaceholder != null) 3496 { 3497 buffer.append(", trailingArgsPlaceholder='"); 3498 buffer.append(trailingArgsPlaceholder); 3499 buffer.append('\''); 3500 } 3501 3502 buffer.append("namedArgs={"); 3503 3504 final Iterator<Argument> iterator = namedArgs.iterator(); 3505 while (iterator.hasNext()) 3506 { 3507 iterator.next().toString(buffer); 3508 if (iterator.hasNext()) 3509 { 3510 buffer.append(", "); 3511 } 3512 } 3513 3514 buffer.append('}'); 3515 3516 if (! subCommands.isEmpty()) 3517 { 3518 buffer.append(", subCommands={"); 3519 3520 final Iterator<SubCommand> subCommandIterator = subCommands.iterator(); 3521 while (subCommandIterator.hasNext()) 3522 { 3523 subCommandIterator.next().toString(buffer); 3524 if (subCommandIterator.hasNext()) 3525 { 3526 buffer.append(", "); 3527 } 3528 } 3529 3530 buffer.append('}'); 3531 } 3532 3533 buffer.append(')'); 3534 } 3535}