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