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