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