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