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     */
021    package com.unboundid.util.args;
022    
023    
024    
025    import java.io.BufferedReader;
026    import java.io.File;
027    import java.io.FileReader;
028    import java.io.IOException;
029    import java.io.OutputStream;
030    import java.io.PrintWriter;
031    import java.io.Serializable;
032    import java.util.ArrayList;
033    import java.util.Arrays;
034    import java.util.Collection;
035    import java.util.Collections;
036    import java.util.HashMap;
037    import java.util.Iterator;
038    import java.util.LinkedHashSet;
039    import java.util.LinkedHashMap;
040    import java.util.List;
041    import java.util.Map;
042    import java.util.Set;
043    
044    import com.unboundid.util.Debug;
045    import com.unboundid.util.ObjectPair;
046    import com.unboundid.util.ThreadSafety;
047    import com.unboundid.util.ThreadSafetyLevel;
048    
049    import static com.unboundid.util.StaticUtils.*;
050    import static com.unboundid.util.Validator.*;
051    import 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)
061    public 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    }