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