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 properties file from 086 * which to obtain the default values for arguments not specified via the 087 * command line. 088 */ 089 private static final String ARG_NAME_PROPERTIES_FILE_PATH = 090 "propertiesFilePath"; 091 092 093 094 /** 095 * The name of the argument used to specify the path to a file to be generated 096 * with information about the properties that the tool supports. 097 */ 098 private static final String ARG_NAME_GENERATE_PROPERTIES_FILE = 099 "generatePropertiesFile"; 100 101 102 103 /** 104 * The name of the argument used to indicate that the tool should not use any 105 * properties file to obtain default values for arguments not specified via 106 * the command line. 107 */ 108 private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile"; 109 110 111 112 /** 113 * The serial version UID for this serializable class. 114 */ 115 private static final long serialVersionUID = 3053102992180360269L; 116 117 118 119 // The maximum number of trailing arguments allowed to be provided. 120 private final int maxTrailingArgs; 121 122 // The minimum number of trailing arguments allowed to be provided. 123 private final int minTrailingArgs; 124 125 // The set of named arguments associated with this parser, indexed by short 126 // identifier. 127 private final LinkedHashMap<Character,Argument> namedArgsByShortID; 128 129 // The set of named arguments associated with this parser, indexed by long 130 // identifier. 131 private final LinkedHashMap<String,Argument> namedArgsByLongID; 132 133 // The full set of named arguments associated with this parser. 134 private final List<Argument> namedArgs; 135 136 // Sets of arguments in which if the key argument is provided, then at least 137 // one of the value arguments must also be provided. 138 private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets; 139 140 // Sets of arguments in which at most one argument in the list is allowed to 141 // be present. 142 private final List<Set<Argument>> exclusiveArgumentSets; 143 144 // Sets of arguments in which at least one argument in the list is required to 145 // be present. 146 private final List<Set<Argument>> requiredArgumentSets; 147 148 // The list of trailing arguments provided on the command line. 149 private final List<String> trailingArgs; 150 151 // The description for the associated command. 152 private final String commandDescription; 153 154 // The name for the associated command. 155 private final String commandName; 156 157 // The placeholder string for the trailing arguments. 158 private final String trailingArgsPlaceholder; 159 160 161 162 /** 163 * Creates a new instance of this argument parser with the provided 164 * information. It will not allow unnamed trailing arguments. 165 * 166 * @param commandName The name of the application or utility with 167 * which this argument parser is associated. It 168 * must not be {@code null}. 169 * @param commandDescription A description of the application or utility 170 * with which this argument parser is associated. 171 * It will be included in generated usage 172 * information. It must not be {@code null}. 173 * 174 * @throws ArgumentException If either the command name or command 175 * description is {@code null}, 176 */ 177 public ArgumentParser(final String commandName, 178 final String commandDescription) 179 throws ArgumentException 180 { 181 this(commandName, commandDescription, 0, null); 182 } 183 184 185 186 /** 187 * Creates a new instance of this argument parser with the provided 188 * information. 189 * 190 * @param commandName The name of the application or utility 191 * with which this argument parser is 192 * associated. It must not be {@code null}. 193 * @param commandDescription A description of the application or 194 * utility with which this argument parser is 195 * associated. It will be included in 196 * generated usage information. It must not 197 * be {@code null}. 198 * @param maxTrailingArgs The maximum number of trailing arguments 199 * that may be provided to this command. A 200 * value of zero indicates that no trailing 201 * arguments will be allowed. A value less 202 * than zero will indicate that there is no 203 * limit on the number of trailing arguments 204 * allowed. 205 * @param trailingArgsPlaceholder A placeholder string that will be included 206 * in usage output to indicate what trailing 207 * arguments may be provided. It must not be 208 * {@code null} if {@code maxTrailingArgs} is 209 * anything other than zero. 210 * 211 * @throws ArgumentException If either the command name or command 212 * description is {@code null}, or if the maximum 213 * number of trailing arguments is non-zero and 214 * the trailing arguments placeholder is 215 * {@code null}. 216 */ 217 public ArgumentParser(final String commandName, 218 final String commandDescription, 219 final int maxTrailingArgs, 220 final String trailingArgsPlaceholder) 221 throws ArgumentException 222 { 223 this(commandName, commandDescription, 0, maxTrailingArgs, 224 trailingArgsPlaceholder); 225 } 226 227 228 229 /** 230 * Creates a new instance of this argument parser with the provided 231 * information. 232 * 233 * @param commandName The name of the application or utility 234 * with which this argument parser is 235 * associated. It must not be {@code null}. 236 * @param commandDescription A description of the application or 237 * utility with which this argument parser is 238 * associated. It will be included in 239 * generated usage information. It must not 240 * be {@code null}. 241 * @param minTrailingArgs The minimum number of trailing arguments 242 * that must be provided for this command. A 243 * value of zero indicates that the command 244 * may be invoked without any trailing 245 * arguments. 246 * @param maxTrailingArgs The maximum number of trailing arguments 247 * that may be provided to this command. A 248 * value of zero indicates that no trailing 249 * arguments will be allowed. A value less 250 * than zero will indicate that there is no 251 * limit on the number of trailing arguments 252 * allowed. 253 * @param trailingArgsPlaceholder A placeholder string that will be included 254 * in usage output to indicate what trailing 255 * arguments may be provided. It must not be 256 * {@code null} if {@code maxTrailingArgs} is 257 * anything other than zero. 258 * 259 * @throws ArgumentException If either the command name or command 260 * description is {@code null}, or if the maximum 261 * number of trailing arguments is non-zero and 262 * the trailing arguments placeholder is 263 * {@code null}. 264 */ 265 public ArgumentParser(final String commandName, 266 final String commandDescription, 267 final int minTrailingArgs, 268 final int maxTrailingArgs, 269 final String trailingArgsPlaceholder) 270 throws ArgumentException 271 { 272 if (commandName == null) 273 { 274 throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get()); 275 } 276 277 if (commandDescription == null) 278 { 279 throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get()); 280 } 281 282 if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null)) 283 { 284 throw new ArgumentException( 285 ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get()); 286 } 287 288 this.commandName = commandName; 289 this.commandDescription = commandDescription; 290 this.trailingArgsPlaceholder = trailingArgsPlaceholder; 291 292 if (minTrailingArgs >= 0) 293 { 294 this.minTrailingArgs = minTrailingArgs; 295 } 296 else 297 { 298 this.minTrailingArgs = 0; 299 } 300 301 if (maxTrailingArgs >= 0) 302 { 303 this.maxTrailingArgs = maxTrailingArgs; 304 } 305 else 306 { 307 this.maxTrailingArgs = Integer.MAX_VALUE; 308 } 309 310 if (this.minTrailingArgs > this.maxTrailingArgs) 311 { 312 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get( 313 this.minTrailingArgs, this.maxTrailingArgs)); 314 } 315 316 namedArgsByShortID = new LinkedHashMap<Character,Argument>(); 317 namedArgsByLongID = new LinkedHashMap<String,Argument>(); 318 namedArgs = new ArrayList<Argument>(); 319 trailingArgs = new ArrayList<String>(); 320 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(); 321 exclusiveArgumentSets = new ArrayList<Set<Argument>>(); 322 requiredArgumentSets = new ArrayList<Set<Argument>>(); 323 } 324 325 326 327 /** 328 * Creates a new argument parser that is a "clean" copy of the provided source 329 * argument parser. 330 * 331 * @param source The source argument parser to use for this argument parser. 332 */ 333 private ArgumentParser(final ArgumentParser source) 334 { 335 commandName = source.commandName; 336 commandDescription = source.commandDescription; 337 minTrailingArgs = source.minTrailingArgs; 338 maxTrailingArgs = source.maxTrailingArgs; 339 trailingArgsPlaceholder = source.trailingArgsPlaceholder; 340 341 trailingArgs = new ArrayList<String>(); 342 343 namedArgs = new ArrayList<Argument>(source.namedArgs.size()); 344 namedArgsByLongID = 345 new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size()); 346 namedArgsByShortID = new LinkedHashMap<Character,Argument>( 347 source.namedArgsByShortID.size()); 348 349 final LinkedHashMap<String,Argument> argsByID = 350 new LinkedHashMap<String,Argument>(source.namedArgs.size()); 351 for (final Argument sourceArg : source.namedArgs) 352 { 353 final Argument a = sourceArg.getCleanCopy(); 354 355 try 356 { 357 a.setRegistered(); 358 } 359 catch (final ArgumentException ae) 360 { 361 // This should never happen. 362 Debug.debugException(ae); 363 } 364 365 namedArgs.add(a); 366 argsByID.put(a.getIdentifierString(), a); 367 368 for (final Character c : a.getShortIdentifiers()) 369 { 370 namedArgsByShortID.put(c, a); 371 } 372 373 for (final String s : a.getLongIdentifiers()) 374 { 375 namedArgsByLongID.put(toLowerCase(s), a); 376 } 377 } 378 379 dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>( 380 source.dependentArgumentSets.size()); 381 for (final ObjectPair<Argument,Set<Argument>> p : 382 source.dependentArgumentSets) 383 { 384 final Set<Argument> sourceSet = p.getSecond(); 385 final LinkedHashSet<Argument> newSet = 386 new LinkedHashSet<Argument>(sourceSet.size()); 387 for (final Argument a : sourceSet) 388 { 389 newSet.add(argsByID.get(a.getIdentifierString())); 390 } 391 392 final Argument sourceFirst = p.getFirst(); 393 final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString()); 394 dependentArgumentSets.add( 395 new ObjectPair<Argument, Set<Argument>>(newFirst, newSet)); 396 } 397 398 exclusiveArgumentSets = 399 new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size()); 400 for (final Set<Argument> sourceSet : source.exclusiveArgumentSets) 401 { 402 final LinkedHashSet<Argument> newSet = 403 new LinkedHashSet<Argument>(sourceSet.size()); 404 for (final Argument a : sourceSet) 405 { 406 newSet.add(argsByID.get(a.getIdentifierString())); 407 } 408 409 exclusiveArgumentSets.add(newSet); 410 } 411 412 requiredArgumentSets = 413 new ArrayList<Set<Argument>>(source.requiredArgumentSets.size()); 414 for (final Set<Argument> sourceSet : source.requiredArgumentSets) 415 { 416 final LinkedHashSet<Argument> newSet = 417 new LinkedHashSet<Argument>(sourceSet.size()); 418 for (final Argument a : sourceSet) 419 { 420 newSet.add(argsByID.get(a.getIdentifierString())); 421 } 422 requiredArgumentSets.add(newSet); 423 } 424 } 425 426 427 428 /** 429 * Retrieves the name of the application or utility with which this command 430 * line argument parser is associated. 431 * 432 * @return The name of the application or utility with which this command 433 * line argument parser is associated. 434 */ 435 public String getCommandName() 436 { 437 return commandName; 438 } 439 440 441 442 /** 443 * Retrieves a description of the application or utility with which this 444 * command line argument parser is associated. 445 * 446 * @return A description of the application or utility with which this 447 * command line argument parser is associated. 448 */ 449 public String getCommandDescription() 450 { 451 return commandDescription; 452 } 453 454 455 456 /** 457 * Indicates whether this argument parser allows any unnamed trailing 458 * arguments to be provided. 459 * 460 * @return {@code true} if at least one unnamed trailing argument may be 461 * provided, or {@code false} if not. 462 */ 463 public boolean allowsTrailingArguments() 464 { 465 return (maxTrailingArgs != 0); 466 } 467 468 469 470 /** 471 * Indicates whether this argument parser requires at least unnamed trailing 472 * argument to be provided. 473 * 474 * @return {@code true} if at least one unnamed trailing argument must be 475 * provided, or {@code false} if the tool may be invoked without any 476 * such arguments. 477 */ 478 public boolean requiresTrailingArguments() 479 { 480 return (minTrailingArgs != 0); 481 } 482 483 484 485 /** 486 * Retrieves the placeholder string that will be provided in usage information 487 * to indicate what may be included in the trailing arguments. 488 * 489 * @return The placeholder string that will be provided in usage information 490 * to indicate what may be included in the trailing arguments, or 491 * {@code null} if unnamed trailing arguments are not allowed. 492 */ 493 public String getTrailingArgumentsPlaceholder() 494 { 495 return trailingArgsPlaceholder; 496 } 497 498 499 500 /** 501 * Retrieves the minimum number of unnamed trailing arguments that must be 502 * provided. 503 * 504 * @return The minimum number of unnamed trailing arguments that must be 505 * provided. 506 */ 507 public int getMinTrailingArguments() 508 { 509 return minTrailingArgs; 510 } 511 512 513 514 /** 515 * Retrieves the maximum number of unnamed trailing arguments that may be 516 * provided. 517 * 518 * @return The maximum number of unnamed trailing arguments that may be 519 * provided. 520 */ 521 public int getMaxTrailingArguments() 522 { 523 return maxTrailingArgs; 524 } 525 526 527 528 /** 529 * Updates this argument parser to enable support for a properties file that 530 * can be used to specify the default values for any properties that were not 531 * supplied via the command line. This method should be invoked after the 532 * argument parser has been configured with all of the other arguments that it 533 * supports and before the {@link #parse} method is invoked. In addition, 534 * after invoking the {@code parse} method, the caller must also invoke the 535 * {@link #getGeneratedPropertiesFile} method to determine if the only 536 * processing performed that should be performed is the generation of a 537 * properties file that will have already been performed. 538 * <BR><BR> 539 * This method will update the argument parser to add the following additional 540 * arguments: 541 * <UL> 542 * <LI> 543 * {@code propertiesFilePath} -- Specifies the path to the properties file 544 * that should be used to obtain default values for any arguments not 545 * provided on the command line. If this is not specified and the 546 * {@code noPropertiesFile} argument is not present, then the argument 547 * parser may use a default properties file path specified using either 548 * the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath} 549 * system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH} 550 * environment variable. 551 * </LI> 552 * <LI> 553 * {@code generatePropertiesFile} -- Indicates that the tool should 554 * generate a properties file for this argument parser and write it to the 555 * specified location. The generated properties file will not have any 556 * properties set, but will include comments that describe all of the 557 * supported arguments, as well general information about the use of a 558 * properties file. If this argument is specified on the command line, 559 * then no other arguments should be given. 560 * </LI> 561 * <LI> 562 * {@code noPropertiesFile} -- Indicates that the tool should not use a 563 * properties file to obtain default values for any arguments not provided 564 * on the command line. 565 * </LI> 566 * </UL> 567 * 568 * @throws ArgumentException If any of the arguments related to properties 569 * file processing conflicts with an argument that 570 * has already been added to the argument parser. 571 */ 572 public void enablePropertiesFileSupport() 573 throws ArgumentException 574 { 575 final FileArgument propertiesFilePath = new FileArgument(null, 576 ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null, 577 INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false); 578 propertiesFilePath.setUsageArgument(true); 579 propertiesFilePath.addLongIdentifier("properties-file-path"); 580 addArgument(propertiesFilePath); 581 582 final FileArgument generatePropertiesFile = new FileArgument(null, 583 ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null, 584 INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false); 585 generatePropertiesFile.setUsageArgument(true); 586 generatePropertiesFile.addLongIdentifier("generate-properties-file"); 587 addArgument(generatePropertiesFile); 588 589 final BooleanArgument noPropertiesFile = new BooleanArgument(null, 590 ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get()); 591 noPropertiesFile.setUsageArgument(true); 592 noPropertiesFile.addLongIdentifier("no-properties-file"); 593 addArgument(noPropertiesFile); 594 595 596 // The propertiesFilePath and noPropertiesFile arguments cannot be used 597 // together. 598 addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile); 599 } 600 601 602 603 /** 604 * Indicates whether this argument parser was used to generate a properties 605 * file. If so, then the tool invoking the parser should return without 606 * performing any further processing. 607 * 608 * @return A {@code File} object that represents the path to the properties 609 * file that was generated, or {@code null} if no properties file was 610 * generated. 611 */ 612 public File getGeneratedPropertiesFile() 613 { 614 final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE); 615 if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument))) 616 { 617 return null; 618 } 619 620 return ((FileArgument) a).getValue(); 621 } 622 623 624 625 /** 626 * Retrieves the named argument with the specified short identifier. 627 * 628 * @param shortIdentifier The short identifier of the argument to retrieve. 629 * It must not be {@code null}. 630 * 631 * @return The named argument with the specified short identifier, or 632 * {@code null} if there is no such argument. 633 */ 634 public Argument getNamedArgument(final Character shortIdentifier) 635 { 636 ensureNotNull(shortIdentifier); 637 return namedArgsByShortID.get(shortIdentifier); 638 } 639 640 641 642 /** 643 * Retrieves the named argument with the specified identifier. 644 * 645 * @param identifier The identifier of the argument to retrieve. It may be 646 * the long identifier without any dashes, the short 647 * identifier character preceded by a single dash, or the 648 * long identifier preceded by two dashes. It must not be 649 * {@code null}. 650 * 651 * @return The named argument with the specified long identifier, or 652 * {@code null} if there is no such argument. 653 */ 654 public Argument getNamedArgument(final String identifier) 655 { 656 ensureNotNull(identifier); 657 658 if (identifier.startsWith("--") && (identifier.length() > 2)) 659 { 660 return namedArgsByLongID.get(toLowerCase(identifier.substring(2))); 661 } 662 else if (identifier.startsWith("-") && (identifier.length() == 2)) 663 { 664 return namedArgsByShortID.get(identifier.charAt(1)); 665 } 666 else 667 { 668 return namedArgsByLongID.get(toLowerCase(identifier)); 669 } 670 } 671 672 673 674 /** 675 * Retrieves the argument list argument with the specified identifier. 676 * 677 * @param identifier The identifier of the argument to retrieve. It may be 678 * the long identifier without any dashes, the short 679 * identifier character preceded by a single dash, or the 680 * long identifier preceded by two dashes. It must not be 681 * {@code null}. 682 * 683 * @return The argument list argument with the specified identifier, or 684 * {@code null} if there is no such argument. 685 */ 686 public ArgumentListArgument getArgumentListArgument(final String identifier) 687 { 688 final Argument a = getNamedArgument(identifier); 689 if (a == null) 690 { 691 return null; 692 } 693 else 694 { 695 return (ArgumentListArgument) a; 696 } 697 } 698 699 700 701 /** 702 * Retrieves the Boolean argument with the specified identifier. 703 * 704 * @param identifier The identifier of the argument to retrieve. It may be 705 * the long identifier without any dashes, the short 706 * identifier character preceded by a single dash, or the 707 * long identifier preceded by two dashes. It must not be 708 * {@code null}. 709 * 710 * @return The Boolean argument with the specified identifier, or 711 * {@code null} if there is no such argument. 712 */ 713 public BooleanArgument getBooleanArgument(final String identifier) 714 { 715 final Argument a = getNamedArgument(identifier); 716 if (a == null) 717 { 718 return null; 719 } 720 else 721 { 722 return (BooleanArgument) a; 723 } 724 } 725 726 727 728 /** 729 * Retrieves the Boolean value argument with the specified identifier. 730 * 731 * @param identifier The identifier of the argument to retrieve. It may be 732 * the long identifier without any dashes, the short 733 * identifier character preceded by a single dash, or the 734 * long identifier preceded by two dashes. It must not be 735 * {@code null}. 736 * 737 * @return The Boolean value argument with the specified identifier, or 738 * {@code null} if there is no such argument. 739 */ 740 public BooleanValueArgument getBooleanValueArgument(final String identifier) 741 { 742 final Argument a = getNamedArgument(identifier); 743 if (a == null) 744 { 745 return null; 746 } 747 else 748 { 749 return (BooleanValueArgument) a; 750 } 751 } 752 753 754 755 /** 756 * Retrieves the control argument with the specified identifier. 757 * 758 * @param identifier The identifier of the argument to retrieve. It may be 759 * the long identifier without any dashes, the short 760 * identifier character preceded by a single dash, or the 761 * long identifier preceded by two dashes. It must not be 762 * {@code null}. 763 * 764 * @return The control argument with the specified identifier, or 765 * {@code null} if there is no such argument. 766 */ 767 public ControlArgument getControlArgument(final String identifier) 768 { 769 final Argument a = getNamedArgument(identifier); 770 if (a == null) 771 { 772 return null; 773 } 774 else 775 { 776 return (ControlArgument) a; 777 } 778 } 779 780 781 782 /** 783 * Retrieves the DN argument with the specified identifier. 784 * 785 * @param identifier The identifier of the argument to retrieve. It may be 786 * the long identifier without any dashes, the short 787 * identifier character preceded by a single dash, or the 788 * long identifier preceded by two dashes. It must not be 789 * {@code null}. 790 * 791 * @return The DN argument with the specified identifier, or 792 * {@code null} if there is no such argument. 793 */ 794 public DNArgument getDNArgument(final String identifier) 795 { 796 final Argument a = getNamedArgument(identifier); 797 if (a == null) 798 { 799 return null; 800 } 801 else 802 { 803 return (DNArgument) a; 804 } 805 } 806 807 808 809 /** 810 * Retrieves the duration argument with the specified identifier. 811 * 812 * @param identifier The identifier of the argument to retrieve. It may be 813 * the long identifier without any dashes, the short 814 * identifier character preceded by a single dash, or the 815 * long identifier preceded by two dashes. It must not be 816 * {@code null}. 817 * 818 * @return The duration argument with the specified identifier, or 819 * {@code null} if there is no such argument. 820 */ 821 public DurationArgument getDurationArgument(final String identifier) 822 { 823 final Argument a = getNamedArgument(identifier); 824 if (a == null) 825 { 826 return null; 827 } 828 else 829 { 830 return (DurationArgument) a; 831 } 832 } 833 834 835 836 /** 837 * Retrieves the file argument with the specified identifier. 838 * 839 * @param identifier The identifier of the argument to retrieve. It may be 840 * the long identifier without any dashes, the short 841 * identifier character preceded by a single dash, or the 842 * long identifier preceded by two dashes. It must not be 843 * {@code null}. 844 * 845 * @return The file argument with the specified identifier, or 846 * {@code null} if there is no such argument. 847 */ 848 public FileArgument getFileArgument(final String identifier) 849 { 850 final Argument a = getNamedArgument(identifier); 851 if (a == null) 852 { 853 return null; 854 } 855 else 856 { 857 return (FileArgument) a; 858 } 859 } 860 861 862 863 /** 864 * Retrieves the filter argument with the specified identifier. 865 * 866 * @param identifier The identifier of the argument to retrieve. It may be 867 * the long identifier without any dashes, the short 868 * identifier character preceded by a single dash, or the 869 * long identifier preceded by two dashes. It must not be 870 * {@code null}. 871 * 872 * @return The filter argument with the specified identifier, or 873 * {@code null} if there is no such argument. 874 */ 875 public FilterArgument getFilterArgument(final String identifier) 876 { 877 final Argument a = getNamedArgument(identifier); 878 if (a == null) 879 { 880 return null; 881 } 882 else 883 { 884 return (FilterArgument) a; 885 } 886 } 887 888 889 890 /** 891 * Retrieves the integer argument with the specified identifier. 892 * 893 * @param identifier The identifier of the argument to retrieve. It may be 894 * the long identifier without any dashes, the short 895 * identifier character preceded by a single dash, or the 896 * long identifier preceded by two dashes. It must not be 897 * {@code null}. 898 * 899 * @return The integer argument with the specified identifier, or 900 * {@code null} if there is no such argument. 901 */ 902 public IntegerArgument getIntegerArgument(final String identifier) 903 { 904 final Argument a = getNamedArgument(identifier); 905 if (a == null) 906 { 907 return null; 908 } 909 else 910 { 911 return (IntegerArgument) a; 912 } 913 } 914 915 916 917 /** 918 * Retrieves the scope argument with the specified identifier. 919 * 920 * @param identifier The identifier of the argument to retrieve. It may be 921 * the long identifier without any dashes, the short 922 * identifier character preceded by a single dash, or the 923 * long identifier preceded by two dashes. It must not be 924 * {@code null}. 925 * 926 * @return The scope argument with the specified identifier, or 927 * {@code null} if there is no such argument. 928 */ 929 public ScopeArgument getScopeArgument(final String identifier) 930 { 931 final Argument a = getNamedArgument(identifier); 932 if (a == null) 933 { 934 return null; 935 } 936 else 937 { 938 return (ScopeArgument) a; 939 } 940 } 941 942 943 944 /** 945 * Retrieves the string argument with the specified identifier. 946 * 947 * @param identifier The identifier of the argument to retrieve. It may be 948 * the long identifier without any dashes, the short 949 * identifier character preceded by a single dash, or the 950 * long identifier preceded by two dashes. It must not be 951 * {@code null}. 952 * 953 * @return The string argument with the specified identifier, or 954 * {@code null} if there is no such argument. 955 */ 956 public StringArgument getStringArgument(final String identifier) 957 { 958 final Argument a = getNamedArgument(identifier); 959 if (a == null) 960 { 961 return null; 962 } 963 else 964 { 965 return (StringArgument) a; 966 } 967 } 968 969 970 971 /** 972 * Retrieves the set of named arguments defined for use with this argument 973 * parser. 974 * 975 * @return The set of named arguments defined for use with this argument 976 * parser. 977 */ 978 public List<Argument> getNamedArguments() 979 { 980 return Collections.unmodifiableList(namedArgs); 981 } 982 983 984 985 /** 986 * Registers the provided argument with this argument parser. 987 * 988 * @param argument The argument to be registered. 989 * 990 * @throws ArgumentException If the provided argument conflicts with another 991 * argument already registered with this parser. 992 */ 993 public void addArgument(final Argument argument) 994 throws ArgumentException 995 { 996 argument.setRegistered(); 997 for (final Character c : argument.getShortIdentifiers()) 998 { 999 if (namedArgsByShortID.containsKey(c)) 1000 { 1001 throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c)); 1002 } 1003 } 1004 1005 for (final String s : argument.getLongIdentifiers()) 1006 { 1007 if (namedArgsByLongID.containsKey(toLowerCase(s))) 1008 { 1009 throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s)); 1010 } 1011 } 1012 1013 for (final Character c : argument.getShortIdentifiers()) 1014 { 1015 namedArgsByShortID.put(c, argument); 1016 } 1017 1018 for (final String s : argument.getLongIdentifiers()) 1019 { 1020 namedArgsByLongID.put(toLowerCase(s), argument); 1021 } 1022 1023 namedArgs.add(argument); 1024 } 1025 1026 1027 1028 /** 1029 * Retrieves the list of dependent argument sets for this argument parser. If 1030 * an argument contained as the first object in the pair in a dependent 1031 * argument set is provided, then at least one of the arguments in the paired 1032 * set must also be provided. 1033 * 1034 * @return The list of dependent argument sets for this argument parser. 1035 */ 1036 public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets() 1037 { 1038 return Collections.unmodifiableList(dependentArgumentSets); 1039 } 1040 1041 1042 1043 /** 1044 * Adds the provided collection of arguments as dependent upon the given 1045 * argument. 1046 * 1047 * @param targetArgument The argument whose presence indicates that at 1048 * least one of the dependent arguments must also 1049 * be present. It must not be {@code null}. 1050 * @param dependentArguments The set of arguments from which at least one 1051 * argument must be present if the target argument 1052 * is present. It must not be {@code null} or 1053 * empty. 1054 */ 1055 public void addDependentArgumentSet(final Argument targetArgument, 1056 final Collection<Argument> dependentArguments) 1057 { 1058 ensureNotNull(targetArgument, dependentArguments); 1059 1060 final LinkedHashSet<Argument> argSet = 1061 new LinkedHashSet<Argument>(dependentArguments); 1062 dependentArgumentSets.add( 1063 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 1064 } 1065 1066 1067 1068 /** 1069 * Adds the provided collection of arguments as dependent upon the given 1070 * argument. 1071 * 1072 * @param targetArgument The argument whose presence indicates that at least 1073 * one of the dependent arguments must also be 1074 * present. It must not be {@code null}. 1075 * @param dependentArg1 The first argument in the set of arguments in which 1076 * at least one argument must be present if the target 1077 * argument is present. It must not be {@code null}. 1078 * @param remaining The remaining arguments in the set of arguments in 1079 * which at least one argument must be present if the 1080 * target argument is present. 1081 */ 1082 public void addDependentArgumentSet(final Argument targetArgument, 1083 final Argument dependentArg1, 1084 final Argument... remaining) 1085 { 1086 ensureNotNull(targetArgument, dependentArg1); 1087 1088 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1089 argSet.add(dependentArg1); 1090 argSet.addAll(Arrays.asList(remaining)); 1091 1092 dependentArgumentSets.add( 1093 new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet)); 1094 } 1095 1096 1097 1098 /** 1099 * Retrieves the list of exclusive argument sets for this argument parser. 1100 * If an argument contained in an exclusive argument set is provided, then 1101 * none of the other arguments in that set may be provided. It is acceptable 1102 * for none of the arguments in the set to be provided, unless the same set 1103 * of arguments is also defined as a required argument set. 1104 * 1105 * @return The list of exclusive argument sets for this argument parser. 1106 */ 1107 public List<Set<Argument>> getExclusiveArgumentSets() 1108 { 1109 return Collections.unmodifiableList(exclusiveArgumentSets); 1110 } 1111 1112 1113 1114 /** 1115 * Adds the provided collection of arguments as an exclusive argument set, in 1116 * which at most one of the arguments may be provided. 1117 * 1118 * @param exclusiveArguments The collection of arguments to form an 1119 * exclusive argument set. It must not be 1120 * {@code null}. 1121 */ 1122 public void addExclusiveArgumentSet( 1123 final Collection<Argument> exclusiveArguments) 1124 { 1125 ensureNotNull(exclusiveArguments); 1126 final LinkedHashSet<Argument> argSet = 1127 new LinkedHashSet<Argument>(exclusiveArguments); 1128 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 1129 } 1130 1131 1132 1133 /** 1134 * Adds the provided set of arguments as an exclusive argument set, in 1135 * which at most one of the arguments may be provided. 1136 * 1137 * @param arg1 The first argument to include in the exclusive argument 1138 * set. It must not be {@code null}. 1139 * @param arg2 The second argument to include in the exclusive argument 1140 * set. It must not be {@code null}. 1141 * @param remaining Any additional arguments to include in the exclusive 1142 * argument set. 1143 */ 1144 public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2, 1145 final Argument... remaining) 1146 { 1147 ensureNotNull(arg1, arg2); 1148 1149 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1150 argSet.add(arg1); 1151 argSet.add(arg2); 1152 argSet.addAll(Arrays.asList(remaining)); 1153 1154 exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet)); 1155 } 1156 1157 1158 1159 /** 1160 * Retrieves the list of required argument sets for this argument parser. At 1161 * least one of the arguments contained in this set must be provided. If this 1162 * same set is also defined as an exclusive argument set, then exactly one 1163 * of those arguments must be provided. 1164 * 1165 * @return The list of required argument sets for this argument parser. 1166 */ 1167 public List<Set<Argument>> getRequiredArgumentSets() 1168 { 1169 return Collections.unmodifiableList(requiredArgumentSets); 1170 } 1171 1172 1173 1174 /** 1175 * Adds the provided collection of arguments as a required argument set, in 1176 * which at least one of the arguments must be provided. 1177 * 1178 * @param requiredArguments The collection of arguments to form an 1179 * required argument set. It must not be 1180 * {@code null}. 1181 */ 1182 public void addRequiredArgumentSet( 1183 final Collection<Argument> requiredArguments) 1184 { 1185 ensureNotNull(requiredArguments); 1186 final LinkedHashSet<Argument> argSet = 1187 new LinkedHashSet<Argument>(requiredArguments); 1188 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 1189 } 1190 1191 1192 1193 /** 1194 * Adds the provided set of arguments as a required argument set, in which 1195 * at least one of the arguments must be provided. 1196 * 1197 * @param arg1 The first argument to include in the required argument 1198 * set. It must not be {@code null}. 1199 * @param arg2 The second argument to include in the required argument 1200 * set. It must not be {@code null}. 1201 * @param remaining Any additional arguments to include in the required 1202 * argument set. 1203 */ 1204 public void addRequiredArgumentSet(final Argument arg1, final Argument arg2, 1205 final Argument... remaining) 1206 { 1207 ensureNotNull(arg1, arg2); 1208 1209 final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>(); 1210 argSet.add(arg1); 1211 argSet.add(arg2); 1212 argSet.addAll(Arrays.asList(remaining)); 1213 1214 requiredArgumentSets.add(Collections.unmodifiableSet(argSet)); 1215 } 1216 1217 1218 1219 /** 1220 * Retrieves the set of unnamed trailing arguments in the provided command 1221 * line arguments. 1222 * 1223 * @return The set of unnamed trailing arguments in the provided command line 1224 * arguments, or an empty list if there were none. 1225 */ 1226 public List<String> getTrailingArguments() 1227 { 1228 return Collections.unmodifiableList(trailingArgs); 1229 } 1230 1231 1232 1233 /** 1234 * Clears the set of trailing arguments for this argument parser. 1235 */ 1236 void resetTrailingArguments() 1237 { 1238 trailingArgs.clear(); 1239 } 1240 1241 1242 1243 /** 1244 * Adds the provided value to the set of trailing arguments. 1245 * 1246 * @param value The value to add to the set of trailing arguments. 1247 * 1248 * @throws ArgumentException If the parser already has the maximum allowed 1249 * number of trailing arguments. 1250 */ 1251 void addTrailingArgument(final String value) 1252 throws ArgumentException 1253 { 1254 if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs)) 1255 { 1256 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value, 1257 commandName, maxTrailingArgs)); 1258 } 1259 1260 trailingArgs.add(value); 1261 } 1262 1263 1264 1265 /** 1266 * Creates a copy of this argument parser that is "clean" and appears as if it 1267 * has not been used to parse an argument set. The new parser will have all 1268 * of the same arguments and constraints as this parser. 1269 * 1270 * @return The "clean" copy of this argument parser. 1271 */ 1272 public ArgumentParser getCleanCopy() 1273 { 1274 return new ArgumentParser(this); 1275 } 1276 1277 1278 1279 /** 1280 * Parses the provided set of arguments. 1281 * 1282 * @param args An array containing the argument information to parse. It 1283 * must not be {@code null}. 1284 * 1285 * @throws ArgumentException If a problem occurs while attempting to parse 1286 * the argument information. 1287 */ 1288 public void parse(final String[] args) 1289 throws ArgumentException 1290 { 1291 // Iterate through the provided args strings and process them. 1292 boolean inTrailingArgs = false; 1293 boolean skipFinalValidation = false; 1294 for (int i=0; i < args.length; i++) 1295 { 1296 final String s = args[i]; 1297 1298 if (inTrailingArgs) 1299 { 1300 if (maxTrailingArgs == 0) 1301 { 1302 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 1303 s, commandName)); 1304 } 1305 else if (trailingArgs.size() >= maxTrailingArgs) 1306 { 1307 throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s, 1308 commandName, maxTrailingArgs)); 1309 } 1310 else 1311 { 1312 trailingArgs.add(s); 1313 } 1314 } 1315 else if (s.equals("--")) 1316 { 1317 // This signifies the end of the named arguments and the beginning of 1318 // the trailing arguments. 1319 inTrailingArgs = true; 1320 } 1321 else if (s.startsWith("--")) 1322 { 1323 // There may be an equal sign to separate the name from the value. 1324 final String argName; 1325 final int equalPos = s.indexOf('='); 1326 if (equalPos > 0) 1327 { 1328 argName = s.substring(2, equalPos); 1329 } 1330 else 1331 { 1332 argName = s.substring(2); 1333 } 1334 1335 final Argument a = namedArgsByLongID.get(toLowerCase(argName)); 1336 if (a == null) 1337 { 1338 throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName)); 1339 } 1340 else if (a.isUsageArgument()) 1341 { 1342 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1343 } 1344 1345 a.incrementOccurrences(); 1346 if (a.takesValue()) 1347 { 1348 if (equalPos > 0) 1349 { 1350 a.addValue(s.substring(equalPos+1)); 1351 } 1352 else 1353 { 1354 i++; 1355 if (i >= args.length) 1356 { 1357 throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get( 1358 argName)); 1359 } 1360 else 1361 { 1362 a.addValue(args[i]); 1363 } 1364 } 1365 } 1366 else 1367 { 1368 if (equalPos > 0) 1369 { 1370 throw new ArgumentException( 1371 ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName)); 1372 } 1373 } 1374 } 1375 else if (s.startsWith("-")) 1376 { 1377 if (s.length() == 1) 1378 { 1379 throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get()); 1380 } 1381 else if (s.length() == 2) 1382 { 1383 final char c = s.charAt(1); 1384 final Argument a = namedArgsByShortID.get(c); 1385 if (a == null) 1386 { 1387 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 1388 } 1389 else if (a.isUsageArgument()) 1390 { 1391 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1392 } 1393 1394 a.incrementOccurrences(); 1395 if (a.takesValue()) 1396 { 1397 i++; 1398 if (i >= args.length) 1399 { 1400 throw new ArgumentException( 1401 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c)); 1402 } 1403 else 1404 { 1405 a.addValue(args[i]); 1406 } 1407 } 1408 } 1409 else 1410 { 1411 char c = s.charAt(1); 1412 Argument a = namedArgsByShortID.get(c); 1413 if (a == null) 1414 { 1415 throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c)); 1416 } 1417 else if (a.isUsageArgument()) 1418 { 1419 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1420 } 1421 1422 a.incrementOccurrences(); 1423 if (a.takesValue()) 1424 { 1425 a.addValue(s.substring(2)); 1426 } 1427 else 1428 { 1429 // The rest of the characters in the string must also resolve to 1430 // arguments that don't take values. 1431 for (int j=2; j < s.length(); j++) 1432 { 1433 c = s.charAt(j); 1434 a = namedArgsByShortID.get(c); 1435 if (a == null) 1436 { 1437 throw new ArgumentException( 1438 ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s)); 1439 } 1440 else if (a.isUsageArgument()) 1441 { 1442 skipFinalValidation |= skipFinalValidationBecauseOfArgument(a); 1443 } 1444 1445 a.incrementOccurrences(); 1446 if (a.takesValue()) 1447 { 1448 throw new ArgumentException( 1449 ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get( 1450 c, s)); 1451 } 1452 } 1453 } 1454 } 1455 } 1456 else 1457 { 1458 inTrailingArgs = true; 1459 if (maxTrailingArgs == 0) 1460 { 1461 throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get( 1462 s, commandName)); 1463 } 1464 else 1465 { 1466 trailingArgs.add(s); 1467 } 1468 } 1469 } 1470 1471 1472 // Perform any appropriate processing related to the use of a properties 1473 // file. 1474 if (! handlePropertiesFile()) 1475 { 1476 return; 1477 } 1478 1479 1480 // If a usage argument was provided, then no further validation should be 1481 // performed. 1482 if (skipFinalValidation) 1483 { 1484 return; 1485 } 1486 1487 1488 // Make sure that all required arguments have values. 1489 for (final Argument a : namedArgs) 1490 { 1491 if (a.isRequired() && (! a.isPresent())) 1492 { 1493 throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get( 1494 a.getIdentifierString())); 1495 } 1496 } 1497 1498 1499 // Make sure that at least the minimum number of trailing arguments were 1500 // provided. 1501 if (trailingArgs.size() < minTrailingArgs) 1502 { 1503 throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get( 1504 commandName, minTrailingArgs, trailingArgsPlaceholder)); 1505 } 1506 1507 1508 // Make sure that there are no dependent argument set conflicts. 1509 for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets) 1510 { 1511 final Argument targetArg = p.getFirst(); 1512 if (targetArg.isPresent()) 1513 { 1514 final Set<Argument> argSet = p.getSecond(); 1515 boolean found = false; 1516 for (final Argument a : argSet) 1517 { 1518 if (a.isPresent()) 1519 { 1520 found = true; 1521 break; 1522 } 1523 } 1524 1525 if (! found) 1526 { 1527 if (argSet.size() == 1) 1528 { 1529 throw new ArgumentException( 1530 ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get( 1531 targetArg.getIdentifierString(), 1532 argSet.iterator().next().getIdentifierString())); 1533 } 1534 else 1535 { 1536 boolean first = true; 1537 final StringBuilder buffer = new StringBuilder(); 1538 for (final Argument a : argSet) 1539 { 1540 if (first) 1541 { 1542 first = false; 1543 } 1544 else 1545 { 1546 buffer.append(", "); 1547 } 1548 buffer.append(a.getIdentifierString()); 1549 } 1550 throw new ArgumentException( 1551 ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get( 1552 targetArg.getIdentifierString(), buffer.toString())); 1553 } 1554 } 1555 } 1556 } 1557 1558 1559 // Make sure that there are no exclusive argument set conflicts. 1560 for (final Set<Argument> argSet : exclusiveArgumentSets) 1561 { 1562 Argument setArg = null; 1563 for (final Argument a : argSet) 1564 { 1565 if (a.isPresent()) 1566 { 1567 if (setArg == null) 1568 { 1569 setArg = a; 1570 } 1571 else 1572 { 1573 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 1574 setArg.getIdentifierString(), 1575 a.getIdentifierString())); 1576 } 1577 } 1578 } 1579 } 1580 1581 // Make sure that there are no required argument set conflicts. 1582 for (final Set<Argument> argSet : requiredArgumentSets) 1583 { 1584 boolean found = false; 1585 for (final Argument a : argSet) 1586 { 1587 if (a.isPresent()) 1588 { 1589 found = true; 1590 break; 1591 } 1592 } 1593 1594 if (! found) 1595 { 1596 boolean first = true; 1597 final StringBuilder buffer = new StringBuilder(); 1598 for (final Argument a : argSet) 1599 { 1600 if (first) 1601 { 1602 first = false; 1603 } 1604 else 1605 { 1606 buffer.append(", "); 1607 } 1608 buffer.append(a.getIdentifierString()); 1609 } 1610 throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get( 1611 buffer.toString())); 1612 } 1613 } 1614 } 1615 1616 1617 1618 /** 1619 * Indicates whether the provided argument is one that indicates that the 1620 * parser should skip all validation except that performed when assigning 1621 * values from command-line arguments. Validation that will be skipped 1622 * includes ensuring that all required arguments have values, ensuring that 1623 * the minimum number of trailing arguments were provided, and ensuring that 1624 * there were no dependent/exclusive/required argument set conflicts. 1625 * 1626 * @param a The argument for which to make the determination. 1627 * 1628 * @return {@code true} if the provided argument is one that indicates that 1629 * final validation should be skipped, or {@code false} if not. 1630 */ 1631 private static boolean skipFinalValidationBecauseOfArgument(final Argument a) 1632 { 1633 // We will skip final validation for all usage arguments except the 1634 // propertiesFilePath and noPropertiesFile arguments. 1635 if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) || 1636 ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier())) 1637 { 1638 return false; 1639 } 1640 1641 return a.isUsageArgument(); 1642 } 1643 1644 1645 1646 /** 1647 * Performs any appropriate properties file processing for this argument 1648 * parser. 1649 * 1650 * @return {@code true} if the tool should continue processing, or 1651 * {@code false} if it should return immediately. 1652 * 1653 * @throws ArgumentException If a problem is encountered while attempting 1654 * to parse a properties file or update arguments 1655 * with the values contained in it. 1656 */ 1657 private boolean handlePropertiesFile() 1658 throws ArgumentException 1659 { 1660 final BooleanArgument noPropertiesFile; 1661 final FileArgument generatePropertiesFile; 1662 final FileArgument propertiesFilePath; 1663 try 1664 { 1665 propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH); 1666 generatePropertiesFile = 1667 getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE); 1668 noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE); 1669 } 1670 catch (final Exception e) 1671 { 1672 Debug.debugException(e); 1673 1674 // This should only ever happen if the argument parser has an argument 1675 // with a name that conflicts with one of the properties file arguments 1676 // but isn't of the right type. In this case, we'll assume that no 1677 // properties file will be used. 1678 return true; 1679 } 1680 1681 1682 // If any of the properties file arguments isn't defined, then we'll assume 1683 // that no properties file will be used. 1684 if ((propertiesFilePath == null) || (generatePropertiesFile == null) || 1685 (noPropertiesFile == null)) 1686 { 1687 return true; 1688 } 1689 1690 1691 // If the noPropertiesFile argument is present, then don't do anything but 1692 // make sure that neither of the other arguments was specified. 1693 if (noPropertiesFile.isPresent()) 1694 { 1695 if (propertiesFilePath.isPresent()) 1696 { 1697 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 1698 noPropertiesFile.getIdentifierString(), 1699 propertiesFilePath.getIdentifierString())); 1700 } 1701 else if (generatePropertiesFile.isPresent()) 1702 { 1703 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 1704 noPropertiesFile.getIdentifierString(), 1705 generatePropertiesFile.getIdentifierString())); 1706 } 1707 else 1708 { 1709 return true; 1710 } 1711 } 1712 1713 1714 // If the generatePropertiesFile argument is present, then make sure the 1715 // propertiesFilePath argument is not set and generate the output. 1716 if (generatePropertiesFile.isPresent()) 1717 { 1718 if (propertiesFilePath.isPresent()) 1719 { 1720 throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get( 1721 generatePropertiesFile.getIdentifierString(), 1722 propertiesFilePath.getIdentifierString())); 1723 } 1724 else 1725 { 1726 generatePropertiesFile( 1727 generatePropertiesFile.getValue().getAbsolutePath()); 1728 return false; 1729 } 1730 } 1731 1732 1733 // If the propertiesFilePath argument is present, then try to make use of 1734 // the specified file. 1735 if (propertiesFilePath.isPresent()) 1736 { 1737 final File propertiesFile = propertiesFilePath.getValue(); 1738 if (propertiesFile.exists() && propertiesFile.isFile()) 1739 { 1740 handlePropertiesFile(propertiesFilePath.getValue()); 1741 } 1742 else 1743 { 1744 throw new ArgumentException( 1745 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get( 1746 propertiesFilePath.getIdentifierString(), 1747 propertiesFile.getAbsolutePath())); 1748 } 1749 return true; 1750 } 1751 1752 1753 // We may still use a properties file if the path was specified in either a 1754 // JVM property or an environment variable. If both are defined, the JVM 1755 // property will take precedence. If a property or environment variable 1756 // specifies an invalid value, then we'll just ignore it. 1757 String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH); 1758 if (path == null) 1759 { 1760 path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH); 1761 } 1762 1763 if (path != null) 1764 { 1765 final File propertiesFile = new File(path); 1766 if (propertiesFile.exists() && propertiesFile.isFile()) 1767 { 1768 handlePropertiesFile(propertiesFile); 1769 } 1770 } 1771 1772 return true; 1773 } 1774 1775 1776 1777 /** 1778 * Write an empty properties file for this argument parser to the specified 1779 * path. 1780 * 1781 * @param path The path to the properties file to be written. 1782 * 1783 * @throws ArgumentException If a problem is encountered while writing the 1784 * properties file. 1785 */ 1786 private void generatePropertiesFile(final String path) 1787 throws ArgumentException 1788 { 1789 final PrintWriter w; 1790 try 1791 { 1792 w = new PrintWriter(path); 1793 } 1794 catch (final Exception e) 1795 { 1796 Debug.debugException(e); 1797 throw new ArgumentException( 1798 ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path, 1799 getExceptionMessage(e)), 1800 e); 1801 } 1802 1803 try 1804 { 1805 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName)); 1806 w.println('#'); 1807 wrapComment(w, 1808 INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName, 1809 ARG_NAME_PROPERTIES_FILE_PATH, 1810 PROPERTY_DEFAULT_PROPERTIES_FILE_PATH, 1811 ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE)); 1812 w.println('#'); 1813 1814 for (final Argument a : getNamedArguments()) 1815 { 1816 if (a.isUsageArgument() || a.isHidden()) 1817 { 1818 continue; 1819 } 1820 1821 final String argName = a.getLongIdentifier(); 1822 if (argName != null) 1823 { 1824 wrapComment(w, 1825 INFO_PARSER_GEN_PROPS_HEADER_3.get(commandName, argName)); 1826 w.println('#'); 1827 break; 1828 } 1829 } 1830 1831 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get()); 1832 w.println('#'); 1833 wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName)); 1834 1835 for (final Argument a : getNamedArguments()) 1836 { 1837 if (a.isUsageArgument() || a.isHidden()) 1838 { 1839 continue; 1840 } 1841 1842 w.println(); 1843 w.println(); 1844 wrapComment(w, a.getDescription()); 1845 w.println('#'); 1846 1847 final String constraints = a.getValueConstraints(); 1848 if ((constraints != null) && (constraints.length() > 0) && 1849 (! (a instanceof BooleanArgument))) 1850 { 1851 wrapComment(w, constraints); 1852 w.println('#'); 1853 } 1854 1855 final String identifier; 1856 if (a.getLongIdentifier() != null) 1857 { 1858 identifier = a.getLongIdentifier(); 1859 } 1860 else 1861 { 1862 identifier = a.getIdentifierString(); 1863 } 1864 1865 String placeholder = a.getValuePlaceholder(); 1866 if (placeholder == null) 1867 { 1868 if (a instanceof BooleanArgument) 1869 { 1870 placeholder = "{true|false}"; 1871 } 1872 else 1873 { 1874 placeholder = ""; 1875 } 1876 } 1877 1878 final String propertyName = commandName + '.' + identifier; 1879 w.println("# " + propertyName + '=' + placeholder); 1880 1881 if (a.isPresent()) 1882 { 1883 for (final String s : a.getValueStringRepresentations(false)) 1884 { 1885 w.println(propertyName + '=' + s); 1886 } 1887 } 1888 } 1889 } 1890 finally 1891 { 1892 w.close(); 1893 } 1894 } 1895 1896 1897 1898 /** 1899 * Wraps the given string and writes it as a comment to the provided writer. 1900 * 1901 * @param w The writer to use to write the wrapped and commented string. 1902 * @param s The string to be wrapped and written. 1903 */ 1904 private static void wrapComment(final PrintWriter w, final String s) 1905 { 1906 for (final String line : wrapLine(s, 77)) 1907 { 1908 w.println("# " + line); 1909 } 1910 } 1911 1912 1913 1914 /** 1915 * Reads the contents of the specified properties file and updates the 1916 * configured arguments as appropriate. 1917 * 1918 * @param propertiesFile The properties file to process. 1919 * 1920 * @throws ArgumentException If a problem is encountered while examining the 1921 * properties file, or while trying to assign a 1922 * property value to a corresponding argument. 1923 */ 1924 private void handlePropertiesFile(final File propertiesFile) 1925 throws ArgumentException 1926 { 1927 final BufferedReader reader; 1928 try 1929 { 1930 reader = new BufferedReader(new FileReader(propertiesFile)); 1931 } 1932 catch (final Exception e) 1933 { 1934 Debug.debugException(e); 1935 throw new ArgumentException( 1936 ERR_PARSER_CANNOT_OPEN_PROP_FILE.get( 1937 propertiesFile.getAbsolutePath(), getExceptionMessage(e)), 1938 e); 1939 } 1940 1941 try 1942 { 1943 // Read all of the lines of the file, ignoring comments and unwrapping 1944 // properties that span multiple lines. 1945 boolean lineIsContinued = false; 1946 int lineNumber = 0; 1947 final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines = 1948 new ArrayList<ObjectPair<Integer,StringBuilder>>(10); 1949 while (true) 1950 { 1951 String line; 1952 try 1953 { 1954 line = reader.readLine(); 1955 lineNumber++; 1956 } 1957 catch (final Exception e) 1958 { 1959 Debug.debugException(e); 1960 throw new ArgumentException( 1961 ERR_PARSER_ERROR_READING_PROP_FILE.get( 1962 propertiesFile.getAbsolutePath(), getExceptionMessage(e)), 1963 e); 1964 } 1965 1966 1967 // If the line is null, then we've reached the end of the file. If we 1968 // expect a previous line to have been continued, then this is an error. 1969 if (line == null) 1970 { 1971 if (lineIsContinued) 1972 { 1973 throw new ArgumentException( 1974 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get( 1975 (lineNumber-1), propertiesFile.getAbsolutePath())); 1976 } 1977 break; 1978 } 1979 1980 1981 // See if the line has any leading whitespace, and if so then trim it 1982 // off. If there is leading whitespace, then make sure that we expect 1983 // the previous line to be continued. 1984 final int initialLength = line.length(); 1985 line = trimLeading(line); 1986 final boolean hasLeadingWhitespace = (line.length() < initialLength); 1987 if (hasLeadingWhitespace && (! lineIsContinued)) 1988 { 1989 throw new ArgumentException( 1990 ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get( 1991 propertiesFile.getAbsolutePath(), lineNumber)); 1992 } 1993 1994 1995 // If the line is empty or starts with "#", then skip it. But make sure 1996 // we didn't expect the previous line to be continued. 1997 if ((line.length() == 0) || line.startsWith("#")) 1998 { 1999 if (lineIsContinued) 2000 { 2001 throw new ArgumentException( 2002 ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get( 2003 (lineNumber-1), propertiesFile.getAbsolutePath())); 2004 } 2005 continue; 2006 } 2007 2008 2009 // See if the line ends with a backslash and if so then trim it off. 2010 final boolean hasTrailingBackslash = line.endsWith("\\"); 2011 if (line.endsWith("\\")) 2012 { 2013 line = line.substring(0, (line.length() - 1)); 2014 } 2015 2016 2017 // If the previous line needs to be continued, then append the new line 2018 // to it. Otherwise, add it as a new line. 2019 if (lineIsContinued) 2020 { 2021 propertyLines.get(propertyLines.size() - 1).getSecond().append(line); 2022 } 2023 else 2024 { 2025 propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber, 2026 new StringBuilder(line))); 2027 } 2028 2029 lineIsContinued = hasTrailingBackslash; 2030 } 2031 2032 2033 // Parse all of the lines into a map of identifiers and their 2034 // corresponding values. 2035 if (propertyLines.isEmpty()) 2036 { 2037 return; 2038 } 2039 2040 final HashMap<String,ArrayList<String>> propertyMap = 2041 new HashMap<String,ArrayList<String>>(propertyLines.size()); 2042 for (final ObjectPair<Integer,StringBuilder> p : propertyLines) 2043 { 2044 final String line = p.getSecond().toString(); 2045 final int equalPos = line.indexOf('='); 2046 if (equalPos <= 0) 2047 { 2048 throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get( 2049 propertiesFile.getAbsolutePath(), p.getFirst(), line)); 2050 } 2051 2052 final String propertyName = line.substring(0, equalPos).trim(); 2053 final String propertyValue = line.substring(equalPos+1).trim(); 2054 if (propertyValue.length() == 0) 2055 { 2056 // The property doesn't have a value, so we can ignore it. 2057 continue; 2058 } 2059 2060 2061 // An argument can have multiple identifiers, and we will allow any of 2062 // them to be used to reference it. To deal with this, we'll map the 2063 // argument identifier to its corresponding argument and then use the 2064 // preferred identifier for that argument in the map. 2065 boolean prefixedWithToolName = false; 2066 Argument a = getNamedArgument(propertyName); 2067 if (a == null) 2068 { 2069 // It could be that the argument name was prefixed with the tool name. 2070 // Check to see if that was the case. 2071 if (propertyName.startsWith(commandName + '.')) 2072 { 2073 final String basePropertyName = 2074 propertyName.substring(commandName.length()+1); 2075 a = getNamedArgument(basePropertyName); 2076 prefixedWithToolName = true; 2077 } 2078 } 2079 2080 if (a == null) 2081 { 2082 // This could mean that there's a typo in the property name, but it's 2083 // more likely the case that the property is for a different tool. In 2084 // either case, we'll ignore it. 2085 continue; 2086 } 2087 2088 final String canonicalPropertyName; 2089 if (prefixedWithToolName) 2090 { 2091 canonicalPropertyName = commandName + '.' + a.getIdentifierString(); 2092 } 2093 else 2094 { 2095 canonicalPropertyName = a.getIdentifierString(); 2096 } 2097 2098 ArrayList<String> valueList = propertyMap.get(canonicalPropertyName); 2099 if (valueList == null) 2100 { 2101 valueList = new ArrayList<String>(5); 2102 propertyMap.put(canonicalPropertyName, valueList); 2103 } 2104 valueList.add(propertyValue); 2105 } 2106 2107 2108 // Iterate through all of the named arguments for the argument parser and 2109 // see if we should use the properties to assign values to any of the 2110 // arguments that weren't provided on the command line. 2111 for (final Argument a : namedArgs) 2112 { 2113 if (a.getNumOccurrences() > 0) 2114 { 2115 // The argument was provided on the command line, and that will always 2116 // override anything that might be in the properties file. 2117 continue; 2118 } 2119 2120 2121 // See if the properties file had a property that is specific to the 2122 // tool. If so, then try to assign its values to the argument. If not, 2123 // then fall back to checking for a set of values that are generic to 2124 // any tool that has an argument with that name. 2125 List<String> values = 2126 propertyMap.get(commandName + '.' + a.getIdentifierString()); 2127 if (values == null) 2128 { 2129 values = propertyMap.get(a.getIdentifierString()); 2130 } 2131 2132 if (values != null) 2133 { 2134 for (final String value : values) 2135 { 2136 if (a instanceof BooleanArgument) 2137 { 2138 // We'll treat this as a BooleanValueArgument. 2139 final BooleanValueArgument bva = new BooleanValueArgument( 2140 a.getShortIdentifier(), a.getLongIdentifier(), false, null, 2141 a.getDescription()); 2142 bva.addValue(value); 2143 if (bva.getValue()) 2144 { 2145 a.incrementOccurrences(); 2146 } 2147 } 2148 else 2149 { 2150 a.addValue(value); 2151 a.incrementOccurrences(); 2152 } 2153 } 2154 } 2155 } 2156 } 2157 finally 2158 { 2159 try 2160 { 2161 reader.close(); 2162 } 2163 catch (final Exception e) 2164 { 2165 Debug.debugException(e); 2166 } 2167 } 2168 } 2169 2170 2171 2172 /** 2173 * Retrieves lines that make up the usage information for this program, 2174 * optionally wrapping long lines. 2175 * 2176 * @param maxWidth The maximum line width to use for the output. If this is 2177 * less than or equal to zero, then no wrapping will be 2178 * performed. 2179 * 2180 * @return The lines that make up the usage information for this program. 2181 */ 2182 public List<String> getUsage(final int maxWidth) 2183 { 2184 final ArrayList<String> lines = new ArrayList<String>(100); 2185 2186 // First is a description of the command. 2187 lines.addAll(wrapLine(commandDescription, maxWidth)); 2188 lines.add(""); 2189 2190 // Next comes the usage. It may include neither, either, or both of the 2191 // set of options and trailing arguments. 2192 if (namedArgs.isEmpty()) 2193 { 2194 if (maxTrailingArgs == 0) 2195 { 2196 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName), 2197 maxWidth)); 2198 } 2199 else 2200 { 2201 lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get( 2202 commandName, trailingArgsPlaceholder), 2203 maxWidth)); 2204 } 2205 } 2206 else 2207 { 2208 if (maxTrailingArgs == 0) 2209 { 2210 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName), 2211 maxWidth)); 2212 } 2213 else 2214 { 2215 lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get( 2216 commandName, trailingArgsPlaceholder), 2217 maxWidth)); 2218 } 2219 2220 lines.add(""); 2221 lines.add(INFO_USAGE_OPTIONS_INCLUDE.get()); 2222 2223 2224 // If there are any argument groups, then collect the arguments in those 2225 // groups. 2226 boolean hasRequired = false; 2227 final LinkedHashMap<String,List<Argument>> argumentsByGroup = 2228 new LinkedHashMap<String,List<Argument>>(10); 2229 final ArrayList<Argument> argumentsWithoutGroup = 2230 new ArrayList<Argument>(namedArgs.size()); 2231 final ArrayList<Argument> usageArguments = 2232 new ArrayList<Argument>(namedArgs.size()); 2233 for (final Argument a : namedArgs) 2234 { 2235 if (a.isHidden()) 2236 { 2237 // This argument shouldn't be included in the usage output. 2238 continue; 2239 } 2240 2241 if (a.isRequired() && (! a.hasDefaultValue())) 2242 { 2243 hasRequired = true; 2244 } 2245 2246 final String argumentGroup = a.getArgumentGroupName(); 2247 if (argumentGroup == null) 2248 { 2249 if (a.isUsageArgument()) 2250 { 2251 usageArguments.add(a); 2252 } 2253 else 2254 { 2255 argumentsWithoutGroup.add(a); 2256 } 2257 } 2258 else 2259 { 2260 List<Argument> groupArgs = argumentsByGroup.get(argumentGroup); 2261 if (groupArgs == null) 2262 { 2263 groupArgs = new ArrayList<Argument>(10); 2264 argumentsByGroup.put(argumentGroup, groupArgs); 2265 } 2266 2267 groupArgs.add(a); 2268 } 2269 } 2270 2271 2272 // Iterate through the defined argument groups and display usage 2273 // information for each of them. 2274 for (final Map.Entry<String,List<Argument>> e : 2275 argumentsByGroup.entrySet()) 2276 { 2277 lines.add(""); 2278 lines.add(" " + e.getKey()); 2279 lines.add(""); 2280 for (final Argument a : e.getValue()) 2281 { 2282 getArgUsage(a, lines, true, maxWidth); 2283 } 2284 } 2285 2286 if (! argumentsWithoutGroup.isEmpty()) 2287 { 2288 if (argumentsByGroup.isEmpty()) 2289 { 2290 for (final Argument a : argumentsWithoutGroup) 2291 { 2292 getArgUsage(a, lines, false, maxWidth); 2293 } 2294 } 2295 else 2296 { 2297 lines.add(""); 2298 lines.add(" " + INFO_USAGE_UNGROUPED_ARGS.get()); 2299 lines.add(""); 2300 for (final Argument a : argumentsWithoutGroup) 2301 { 2302 getArgUsage(a, lines, true, maxWidth); 2303 } 2304 } 2305 } 2306 2307 if (! usageArguments.isEmpty()) 2308 { 2309 if (argumentsByGroup.isEmpty()) 2310 { 2311 for (final Argument a : usageArguments) 2312 { 2313 getArgUsage(a, lines, false, maxWidth); 2314 } 2315 } 2316 else 2317 { 2318 lines.add(""); 2319 lines.add(" " + INFO_USAGE_USAGE_ARGS.get()); 2320 lines.add(""); 2321 for (final Argument a : usageArguments) 2322 { 2323 getArgUsage(a, lines, true, maxWidth); 2324 } 2325 } 2326 } 2327 2328 if (hasRequired) 2329 { 2330 lines.add(""); 2331 if (argumentsByGroup.isEmpty()) 2332 { 2333 lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get()); 2334 } 2335 else 2336 { 2337 lines.add(" * " + INFO_USAGE_ARG_IS_REQUIRED.get()); 2338 } 2339 } 2340 } 2341 2342 return lines; 2343 } 2344 2345 2346 2347 /** 2348 * Adds usage information for the provided argument to the given list. 2349 * 2350 * @param a The argument for which to get the usage information. 2351 * @param lines The list to which the resulting lines should be added. 2352 * @param indent Indicates whether to indent each line. 2353 * @param maxWidth The maximum width of each line, in characters. 2354 */ 2355 private static void getArgUsage(final Argument a, final List<String> lines, 2356 final boolean indent, final int maxWidth) 2357 { 2358 final StringBuilder argLine = new StringBuilder(); 2359 if (indent && (maxWidth > 10)) 2360 { 2361 if (a.isRequired() && (! a.hasDefaultValue())) 2362 { 2363 argLine.append(" * "); 2364 } 2365 else 2366 { 2367 argLine.append(" "); 2368 } 2369 } 2370 else if (a.isRequired() && (! a.hasDefaultValue())) 2371 { 2372 argLine.append("* "); 2373 } 2374 2375 boolean first = true; 2376 for (final Character c : a.getShortIdentifiers()) 2377 { 2378 if (first) 2379 { 2380 argLine.append('-'); 2381 first = false; 2382 } 2383 else 2384 { 2385 argLine.append(", -"); 2386 } 2387 argLine.append(c); 2388 } 2389 2390 for (final String s : a.getLongIdentifiers()) 2391 { 2392 if (first) 2393 { 2394 argLine.append("--"); 2395 first = false; 2396 } 2397 else 2398 { 2399 argLine.append(", --"); 2400 } 2401 argLine.append(s); 2402 } 2403 2404 final String valuePlaceholder = a.getValuePlaceholder(); 2405 if (valuePlaceholder != null) 2406 { 2407 argLine.append(' '); 2408 argLine.append(valuePlaceholder); 2409 } 2410 2411 // NOTE: This line won't be wrapped. That's intentional because I 2412 // think it would probably look bad no matter how we did it. 2413 lines.add(argLine.toString()); 2414 2415 2416 // The description should be wrapped, if necessary. We'll also want to 2417 // indent it (unless someone chose an absurdly small wrap width) to make 2418 // it stand out from the argument lines. 2419 final String description = a.getDescription(); 2420 if (maxWidth > 10) 2421 { 2422 final String indentString; 2423 if (indent) 2424 { 2425 indentString = " "; 2426 } 2427 else 2428 { 2429 indentString = " "; 2430 } 2431 2432 final List<String> descLines = wrapLine(description, 2433 (maxWidth-indentString.length())); 2434 for (final String s : descLines) 2435 { 2436 lines.add(indentString + s); 2437 } 2438 } 2439 else 2440 { 2441 lines.addAll(wrapLine(description, maxWidth)); 2442 } 2443 } 2444 2445 2446 2447 /** 2448 * Writes usage information for this program to the provided output stream 2449 * using the UTF-8 encoding, optionally wrapping long lines. 2450 * 2451 * @param outputStream The output stream to which the usage information 2452 * should be written. It must not be {@code null}. 2453 * @param maxWidth The maximum line width to use for the output. If 2454 * this is less than or equal to zero, then no wrapping 2455 * will be performed. 2456 * 2457 * @throws IOException If an error occurs while attempting to write to the 2458 * provided output stream. 2459 */ 2460 public void getUsage(final OutputStream outputStream, final int maxWidth) 2461 throws IOException 2462 { 2463 final List<String> usageLines = getUsage(maxWidth); 2464 for (final String s : usageLines) 2465 { 2466 outputStream.write(getBytes(s)); 2467 outputStream.write(EOL_BYTES); 2468 } 2469 } 2470 2471 2472 2473 /** 2474 * Retrieves a string representation of the usage information. 2475 * 2476 * @param maxWidth The maximum line width to use for the output. If this is 2477 * less than or equal to zero, then no wrapping will be 2478 * performed. 2479 * 2480 * @return A string representation of the usage information 2481 */ 2482 public String getUsageString(final int maxWidth) 2483 { 2484 final StringBuilder buffer = new StringBuilder(); 2485 getUsageString(buffer, maxWidth); 2486 return buffer.toString(); 2487 } 2488 2489 2490 2491 /** 2492 * Appends a string representation of the usage information to the provided 2493 * buffer. 2494 * 2495 * @param buffer The buffer to which the information should be appended. 2496 * @param maxWidth The maximum line width to use for the output. If this is 2497 * less than or equal to zero, then no wrapping will be 2498 * performed. 2499 */ 2500 public void getUsageString(final StringBuilder buffer, final int maxWidth) 2501 { 2502 for (final String line : getUsage(maxWidth)) 2503 { 2504 buffer.append(line); 2505 buffer.append(EOL); 2506 } 2507 } 2508 2509 2510 2511 /** 2512 * Retrieves a string representation of this argument parser. 2513 * 2514 * @return A string representation of this argument parser. 2515 */ 2516 @Override() 2517 public String toString() 2518 { 2519 final StringBuilder buffer = new StringBuilder(); 2520 toString(buffer); 2521 return buffer.toString(); 2522 } 2523 2524 2525 2526 /** 2527 * Appends a string representation of this argument parser to the provided 2528 * buffer. 2529 * 2530 * @param buffer The buffer to which the information should be appended. 2531 */ 2532 public void toString(final StringBuilder buffer) 2533 { 2534 buffer.append("ArgumentParser(commandName='"); 2535 buffer.append(commandName); 2536 buffer.append("', commandDescription='"); 2537 buffer.append(commandDescription); 2538 buffer.append("', minTrailingArgs="); 2539 buffer.append(minTrailingArgs); 2540 buffer.append("', maxTrailingArgs="); 2541 buffer.append(maxTrailingArgs); 2542 2543 if (trailingArgsPlaceholder != null) 2544 { 2545 buffer.append(", trailingArgsPlaceholder='"); 2546 buffer.append(trailingArgsPlaceholder); 2547 buffer.append('\''); 2548 } 2549 2550 buffer.append("namedArgs={"); 2551 2552 final Iterator<Argument> iterator = namedArgs.iterator(); 2553 while (iterator.hasNext()) 2554 { 2555 iterator.next().toString(buffer); 2556 if (iterator.hasNext()) 2557 { 2558 buffer.append(", "); 2559 } 2560 } 2561 2562 buffer.append("})"); 2563 } 2564}