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