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