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 properties file from
086       * which to obtain the default values for arguments not specified via the
087       * command line.
088       */
089      private static final String ARG_NAME_PROPERTIES_FILE_PATH =
090           "propertiesFilePath";
091    
092    
093    
094      /**
095       * The name of the argument used to specify the path to a file to be generated
096       * with information about the properties that the tool supports.
097       */
098      private static final String ARG_NAME_GENERATE_PROPERTIES_FILE =
099           "generatePropertiesFile";
100    
101    
102    
103      /**
104       * The name of the argument used to indicate that the tool should not use any
105       * properties file to obtain default values for arguments not specified via
106       * the command line.
107       */
108      private static final String ARG_NAME_NO_PROPERTIES_FILE = "noPropertiesFile";
109    
110    
111    
112      /**
113       * The serial version UID for this serializable class.
114       */
115      private static final long serialVersionUID = 3053102992180360269L;
116    
117    
118    
119      // The maximum number of trailing arguments allowed to be provided.
120      private final int maxTrailingArgs;
121    
122      // The minimum number of trailing arguments allowed to be provided.
123      private final int minTrailingArgs;
124    
125      // The set of named arguments associated with this parser, indexed by short
126      // identifier.
127      private final LinkedHashMap<Character,Argument> namedArgsByShortID;
128    
129      // The set of named arguments associated with this parser, indexed by long
130      // identifier.
131      private final LinkedHashMap<String,Argument> namedArgsByLongID;
132    
133      // The full set of named arguments associated with this parser.
134      private final List<Argument> namedArgs;
135    
136      // Sets of arguments in which if the key argument is provided, then at least
137      // one of the value arguments must also be provided.
138      private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
139    
140      // Sets of arguments in which at most one argument in the list is allowed to
141      // be present.
142      private final List<Set<Argument>> exclusiveArgumentSets;
143    
144      // Sets of arguments in which at least one argument in the list is required to
145      // be present.
146      private final List<Set<Argument>> requiredArgumentSets;
147    
148      // The list of trailing arguments provided on the command line.
149      private final List<String> trailingArgs;
150    
151      // The description for the associated command.
152      private final String commandDescription;
153    
154      // The name for the associated command.
155      private final String commandName;
156    
157      // The placeholder string for the trailing arguments.
158      private final String trailingArgsPlaceholder;
159    
160    
161    
162      /**
163       * Creates a new instance of this argument parser with the provided
164       * information.  It will not allow unnamed trailing arguments.
165       *
166       * @param  commandName         The name of the application or utility with
167       *                             which this argument parser is associated.  It
168       *                             must not be {@code null}.
169       * @param  commandDescription  A description of the application or utility
170       *                             with which this argument parser is associated.
171       *                             It will be included in generated usage
172       *                             information.  It must not be {@code null}.
173       *
174       * @throws  ArgumentException  If either the command name or command
175       *                             description is {@code null},
176       */
177      public ArgumentParser(final String commandName,
178                            final String commandDescription)
179             throws ArgumentException
180      {
181        this(commandName, commandDescription, 0, null);
182      }
183    
184    
185    
186      /**
187       * Creates a new instance of this argument parser with the provided
188       * information.
189       *
190       * @param  commandName              The name of the application or utility
191       *                                  with which this argument parser is
192       *                                  associated.  It must not be {@code null}.
193       * @param  commandDescription       A description of the application or
194       *                                  utility with which this argument parser is
195       *                                  associated.  It will be included in
196       *                                  generated usage information.  It must not
197       *                                  be {@code null}.
198       * @param  maxTrailingArgs          The maximum number of trailing arguments
199       *                                  that may be provided to this command.  A
200       *                                  value of zero indicates that no trailing
201       *                                  arguments will be allowed.  A value less
202       *                                  than zero will indicate that there is no
203       *                                  limit on the number of trailing arguments
204       *                                  allowed.
205       * @param  trailingArgsPlaceholder  A placeholder string that will be included
206       *                                  in usage output to indicate what trailing
207       *                                  arguments may be provided.  It must not be
208       *                                  {@code null} if {@code maxTrailingArgs} is
209       *                                  anything other than zero.
210       *
211       * @throws  ArgumentException  If either the command name or command
212       *                             description is {@code null}, or if the maximum
213       *                             number of trailing arguments is non-zero and
214       *                             the trailing arguments placeholder is
215       *                             {@code null}.
216       */
217      public ArgumentParser(final String commandName,
218                            final String commandDescription,
219                            final int maxTrailingArgs,
220                            final String trailingArgsPlaceholder)
221             throws ArgumentException
222      {
223        this(commandName, commandDescription, 0, maxTrailingArgs,
224             trailingArgsPlaceholder);
225      }
226    
227    
228    
229      /**
230       * Creates a new instance of this argument parser with the provided
231       * information.
232       *
233       * @param  commandName              The name of the application or utility
234       *                                  with which this argument parser is
235       *                                  associated.  It must not be {@code null}.
236       * @param  commandDescription       A description of the application or
237       *                                  utility with which this argument parser is
238       *                                  associated.  It will be included in
239       *                                  generated usage information.  It must not
240       *                                  be {@code null}.
241       * @param  minTrailingArgs          The minimum number of trailing arguments
242       *                                  that must be provided for this command.  A
243       *                                  value of zero indicates that the command
244       *                                  may be invoked without any trailing
245       *                                  arguments.
246       * @param  maxTrailingArgs          The maximum number of trailing arguments
247       *                                  that may be provided to this command.  A
248       *                                  value of zero indicates that no trailing
249       *                                  arguments will be allowed.  A value less
250       *                                  than zero will indicate that there is no
251       *                                  limit on the number of trailing arguments
252       *                                  allowed.
253       * @param  trailingArgsPlaceholder  A placeholder string that will be included
254       *                                  in usage output to indicate what trailing
255       *                                  arguments may be provided.  It must not be
256       *                                  {@code null} if {@code maxTrailingArgs} is
257       *                                  anything other than zero.
258       *
259       * @throws  ArgumentException  If either the command name or command
260       *                             description is {@code null}, or if the maximum
261       *                             number of trailing arguments is non-zero and
262       *                             the trailing arguments placeholder is
263       *                             {@code null}.
264       */
265      public ArgumentParser(final String commandName,
266                            final String commandDescription,
267                            final int minTrailingArgs,
268                            final int maxTrailingArgs,
269                            final String trailingArgsPlaceholder)
270             throws ArgumentException
271      {
272        if (commandName == null)
273        {
274          throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
275        }
276    
277        if (commandDescription == null)
278        {
279          throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
280        }
281    
282        if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
283        {
284          throw new ArgumentException(
285                         ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
286        }
287    
288        this.commandName             = commandName;
289        this.commandDescription      = commandDescription;
290        this.trailingArgsPlaceholder = trailingArgsPlaceholder;
291    
292        if (minTrailingArgs >= 0)
293        {
294          this.minTrailingArgs = minTrailingArgs;
295        }
296        else
297        {
298          this.minTrailingArgs = 0;
299        }
300    
301        if (maxTrailingArgs >= 0)
302        {
303          this.maxTrailingArgs = maxTrailingArgs;
304        }
305        else
306        {
307          this.maxTrailingArgs = Integer.MAX_VALUE;
308        }
309    
310        if (this.minTrailingArgs > this.maxTrailingArgs)
311        {
312          throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_COUNT_MISMATCH.get(
313               this.minTrailingArgs, this.maxTrailingArgs));
314        }
315    
316        namedArgsByShortID    = new LinkedHashMap<Character,Argument>();
317        namedArgsByLongID     = new LinkedHashMap<String,Argument>();
318        namedArgs             = new ArrayList<Argument>();
319        trailingArgs          = new ArrayList<String>();
320        dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>();
321        exclusiveArgumentSets = new ArrayList<Set<Argument>>();
322        requiredArgumentSets  = new ArrayList<Set<Argument>>();
323      }
324    
325    
326    
327      /**
328       * Creates a new argument parser that is a "clean" copy of the provided source
329       * argument parser.
330       *
331       * @param  source  The source argument parser to use for this argument parser.
332       */
333      private ArgumentParser(final ArgumentParser source)
334      {
335        commandName             = source.commandName;
336        commandDescription      = source.commandDescription;
337        minTrailingArgs         = source.minTrailingArgs;
338        maxTrailingArgs         = source.maxTrailingArgs;
339        trailingArgsPlaceholder = source.trailingArgsPlaceholder;
340    
341        trailingArgs = new ArrayList<String>();
342    
343        namedArgs = new ArrayList<Argument>(source.namedArgs.size());
344        namedArgsByLongID =
345             new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size());
346        namedArgsByShortID = new LinkedHashMap<Character,Argument>(
347             source.namedArgsByShortID.size());
348    
349        final LinkedHashMap<String,Argument> argsByID =
350             new LinkedHashMap<String,Argument>(source.namedArgs.size());
351        for (final Argument sourceArg : source.namedArgs)
352        {
353          final Argument a = sourceArg.getCleanCopy();
354    
355          try
356          {
357            a.setRegistered();
358          }
359          catch (final ArgumentException ae)
360          {
361            // This should never happen.
362            Debug.debugException(ae);
363          }
364    
365          namedArgs.add(a);
366          argsByID.put(a.getIdentifierString(), a);
367    
368          for (final Character c : a.getShortIdentifiers())
369          {
370            namedArgsByShortID.put(c, a);
371          }
372    
373          for (final String s : a.getLongIdentifiers())
374          {
375            namedArgsByLongID.put(toLowerCase(s), a);
376          }
377        }
378    
379        dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(
380             source.dependentArgumentSets.size());
381        for (final ObjectPair<Argument,Set<Argument>> p :
382             source.dependentArgumentSets)
383        {
384          final Set<Argument> sourceSet = p.getSecond();
385          final LinkedHashSet<Argument> newSet =
386               new LinkedHashSet<Argument>(sourceSet.size());
387          for (final Argument a : sourceSet)
388          {
389            newSet.add(argsByID.get(a.getIdentifierString()));
390          }
391    
392          final Argument sourceFirst = p.getFirst();
393          final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
394          dependentArgumentSets.add(
395               new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
396        }
397    
398        exclusiveArgumentSets =
399             new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size());
400        for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
401        {
402          final LinkedHashSet<Argument> newSet =
403               new LinkedHashSet<Argument>(sourceSet.size());
404          for (final Argument a : sourceSet)
405          {
406            newSet.add(argsByID.get(a.getIdentifierString()));
407          }
408    
409          exclusiveArgumentSets.add(newSet);
410        }
411    
412        requiredArgumentSets =
413             new ArrayList<Set<Argument>>(source.requiredArgumentSets.size());
414        for (final Set<Argument> sourceSet : source.requiredArgumentSets)
415        {
416          final LinkedHashSet<Argument> newSet =
417               new LinkedHashSet<Argument>(sourceSet.size());
418          for (final Argument a : sourceSet)
419          {
420            newSet.add(argsByID.get(a.getIdentifierString()));
421          }
422          requiredArgumentSets.add(newSet);
423        }
424      }
425    
426    
427    
428      /**
429       * Retrieves the name of the application or utility with which this command
430       * line argument parser is associated.
431       *
432       * @return  The name of the application or utility with which this command
433       *          line argument parser is associated.
434       */
435      public String getCommandName()
436      {
437        return commandName;
438      }
439    
440    
441    
442      /**
443       * Retrieves a description of the application or utility with which this
444       * command line argument parser is associated.
445       *
446       * @return  A description of the application or utility with which this
447       *          command line argument parser is associated.
448       */
449      public String getCommandDescription()
450      {
451        return commandDescription;
452      }
453    
454    
455    
456      /**
457       * Indicates whether this argument parser allows any unnamed trailing
458       * arguments to be provided.
459       *
460       * @return  {@code true} if at least one unnamed trailing argument may be
461       *          provided, or {@code false} if not.
462       */
463      public boolean allowsTrailingArguments()
464      {
465        return (maxTrailingArgs != 0);
466      }
467    
468    
469    
470      /**
471       * Indicates whether this argument parser requires at least unnamed trailing
472       * argument to be provided.
473       *
474       * @return  {@code true} if at least one unnamed trailing argument must be
475       *          provided, or {@code false} if the tool may be invoked without any
476       *          such arguments.
477       */
478      public boolean requiresTrailingArguments()
479      {
480        return (minTrailingArgs != 0);
481      }
482    
483    
484    
485      /**
486       * Retrieves the placeholder string that will be provided in usage information
487       * to indicate what may be included in the trailing arguments.
488       *
489       * @return  The placeholder string that will be provided in usage information
490       *          to indicate what may be included in the trailing arguments, or
491       *          {@code null} if unnamed trailing arguments are not allowed.
492       */
493      public String getTrailingArgumentsPlaceholder()
494      {
495        return trailingArgsPlaceholder;
496      }
497    
498    
499    
500      /**
501       * Retrieves the minimum number of unnamed trailing arguments that must be
502       * provided.
503       *
504       * @return  The minimum number of unnamed trailing arguments that must be
505       *          provided.
506       */
507      public int getMinTrailingArguments()
508      {
509        return minTrailingArgs;
510      }
511    
512    
513    
514      /**
515       * Retrieves the maximum number of unnamed trailing arguments that may be
516       * provided.
517       *
518       * @return  The maximum number of unnamed trailing arguments that may be
519       *          provided.
520       */
521      public int getMaxTrailingArguments()
522      {
523        return maxTrailingArgs;
524      }
525    
526    
527    
528      /**
529       * Updates this argument parser to enable support for a properties file that
530       * can be used to specify the default values for any properties that were not
531       * supplied via the command line.  This method should be invoked after the
532       * argument parser has been configured with all of the other arguments that it
533       * supports and before the {@link #parse} method is invoked.  In addition,
534       * after invoking the {@code parse} method, the caller must also invoke the
535       * {@link #getGeneratedPropertiesFile} method to determine if the only
536       * processing performed that should be performed is the generation of a
537       * properties file that will have already been performed.
538       * <BR><BR>
539       * This method will update the argument parser to add the following additional
540       * arguments:
541       * <UL>
542       *   <LI>
543       *     {@code propertiesFilePath} -- Specifies the path to the properties file
544       *     that should be used to obtain default values for any arguments not
545       *     provided on the command line.  If this is not specified and the
546       *     {@code noPropertiesFile} argument is not present, then the argument
547       *     parser may use a default properties file path specified using either
548       *     the {@code com.unboundid.util.args.ArgumentParser..propertiesFilePath}
549       *     system property or the {@code UNBOUNDID_TOOL_PROPERTIES_FILE_PATH}
550       *     environment variable.
551       *   </LI>
552       *   <LI>
553       *     {@code generatePropertiesFile} -- Indicates that the tool should
554       *     generate a properties file for this argument parser and write it to the
555       *     specified location.  The generated properties file will not have any
556       *     properties set, but will include comments that describe all of the
557       *     supported arguments, as well general information about the use of a
558       *     properties file.  If this argument is specified on the command line,
559       *     then no other arguments should be given.
560       *   </LI>
561       *   <LI>
562       *     {@code noPropertiesFile} -- Indicates that the tool should not use a
563       *     properties file to obtain default values for any arguments not provided
564       *     on the command line.
565       *   </LI>
566       * </UL>
567       *
568       * @throws  ArgumentException  If any of the arguments related to properties
569       *                             file processing conflicts with an argument that
570       *                             has already been added to the argument parser.
571       */
572      public void enablePropertiesFileSupport()
573             throws ArgumentException
574      {
575        final FileArgument propertiesFilePath = new FileArgument(null,
576             ARG_NAME_PROPERTIES_FILE_PATH, false, 1, null,
577             INFO_ARG_DESCRIPTION_PROP_FILE_PATH.get(), true, true, true, false);
578        propertiesFilePath.setUsageArgument(true);
579        propertiesFilePath.addLongIdentifier("properties-file-path");
580        addArgument(propertiesFilePath);
581    
582        final FileArgument generatePropertiesFile = new FileArgument(null,
583             ARG_NAME_GENERATE_PROPERTIES_FILE, false, 1, null,
584             INFO_ARG_DESCRIPTION_GEN_PROP_FILE.get(), false, true, true, false);
585        generatePropertiesFile.setUsageArgument(true);
586        generatePropertiesFile.addLongIdentifier("generate-properties-file");
587        addArgument(generatePropertiesFile);
588    
589        final BooleanArgument noPropertiesFile = new BooleanArgument(null,
590             ARG_NAME_NO_PROPERTIES_FILE, INFO_ARG_DESCRIPTION_NO_PROP_FILE.get());
591        noPropertiesFile.setUsageArgument(true);
592        noPropertiesFile.addLongIdentifier("no-properties-file");
593        addArgument(noPropertiesFile);
594    
595    
596        // The propertiesFilePath and noPropertiesFile arguments cannot be used
597        // together.
598        addExclusiveArgumentSet(propertiesFilePath, noPropertiesFile);
599      }
600    
601    
602    
603      /**
604       * Indicates whether this argument parser was used to generate a properties
605       * file.  If so, then the tool invoking the parser should return without
606       * performing any further processing.
607       *
608       * @return  A {@code File} object that represents the path to the properties
609       *          file that was generated, or {@code null} if no properties file was
610       *          generated.
611       */
612      public File getGeneratedPropertiesFile()
613      {
614        final Argument a = getNamedArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
615        if ((a == null) || (! a.isPresent()) || (! (a instanceof FileArgument)))
616        {
617          return null;
618        }
619    
620        return ((FileArgument) a).getValue();
621      }
622    
623    
624    
625      /**
626       * Retrieves the named argument with the specified short identifier.
627       *
628       * @param  shortIdentifier  The short identifier of the argument to retrieve.
629       *                          It must not be {@code null}.
630       *
631       * @return  The named argument with the specified short identifier, or
632       *          {@code null} if there is no such argument.
633       */
634      public Argument getNamedArgument(final Character shortIdentifier)
635      {
636        ensureNotNull(shortIdentifier);
637        return namedArgsByShortID.get(shortIdentifier);
638      }
639    
640    
641    
642      /**
643       * Retrieves the named argument with the specified identifier.
644       *
645       * @param  identifier  The identifier of the argument to retrieve.  It may be
646       *                     the long identifier without any dashes, the short
647       *                     identifier character preceded by a single dash, or the
648       *                     long identifier preceded by two dashes. It must not be
649       *                     {@code null}.
650       *
651       * @return  The named argument with the specified long identifier, or
652       *          {@code null} if there is no such argument.
653       */
654      public Argument getNamedArgument(final String identifier)
655      {
656        ensureNotNull(identifier);
657    
658        if (identifier.startsWith("--") && (identifier.length() > 2))
659        {
660          return namedArgsByLongID.get(toLowerCase(identifier.substring(2)));
661        }
662        else if (identifier.startsWith("-") && (identifier.length() == 2))
663        {
664          return namedArgsByShortID.get(identifier.charAt(1));
665        }
666        else
667        {
668          return namedArgsByLongID.get(toLowerCase(identifier));
669        }
670      }
671    
672    
673    
674      /**
675       * Retrieves the argument list argument with the specified identifier.
676       *
677       * @param  identifier  The identifier of the argument to retrieve.  It may be
678       *                     the long identifier without any dashes, the short
679       *                     identifier character preceded by a single dash, or the
680       *                     long identifier preceded by two dashes. It must not be
681       *                     {@code null}.
682       *
683       * @return  The argument list argument with the specified identifier, or
684       *          {@code null} if there is no such argument.
685       */
686      public ArgumentListArgument getArgumentListArgument(final String identifier)
687      {
688        final Argument a = getNamedArgument(identifier);
689        if (a == null)
690        {
691          return null;
692        }
693        else
694        {
695          return (ArgumentListArgument) a;
696        }
697      }
698    
699    
700    
701      /**
702       * Retrieves the Boolean argument with the specified identifier.
703       *
704       * @param  identifier  The identifier of the argument to retrieve.  It may be
705       *                     the long identifier without any dashes, the short
706       *                     identifier character preceded by a single dash, or the
707       *                     long identifier preceded by two dashes. It must not be
708       *                     {@code null}.
709       *
710       * @return  The Boolean argument with the specified identifier, or
711       *          {@code null} if there is no such argument.
712       */
713      public BooleanArgument getBooleanArgument(final String identifier)
714      {
715        final Argument a = getNamedArgument(identifier);
716        if (a == null)
717        {
718          return null;
719        }
720        else
721        {
722          return (BooleanArgument) a;
723        }
724      }
725    
726    
727    
728      /**
729       * Retrieves the Boolean value argument with the specified identifier.
730       *
731       * @param  identifier  The identifier of the argument to retrieve.  It may be
732       *                     the long identifier without any dashes, the short
733       *                     identifier character preceded by a single dash, or the
734       *                     long identifier preceded by two dashes. It must not be
735       *                     {@code null}.
736       *
737       * @return  The Boolean value argument with the specified identifier, or
738       *          {@code null} if there is no such argument.
739       */
740      public BooleanValueArgument getBooleanValueArgument(final String identifier)
741      {
742        final Argument a = getNamedArgument(identifier);
743        if (a == null)
744        {
745          return null;
746        }
747        else
748        {
749          return (BooleanValueArgument) a;
750        }
751      }
752    
753    
754    
755      /**
756       * Retrieves the control argument with the specified identifier.
757       *
758       * @param  identifier  The identifier of the argument to retrieve.  It may be
759       *                     the long identifier without any dashes, the short
760       *                     identifier character preceded by a single dash, or the
761       *                     long identifier preceded by two dashes. It must not be
762       *                     {@code null}.
763       *
764       * @return  The control argument with the specified identifier, or
765       *          {@code null} if there is no such argument.
766       */
767      public ControlArgument getControlArgument(final String identifier)
768      {
769        final Argument a = getNamedArgument(identifier);
770        if (a == null)
771        {
772          return null;
773        }
774        else
775        {
776          return (ControlArgument) a;
777        }
778      }
779    
780    
781    
782      /**
783       * Retrieves the DN argument with the specified identifier.
784       *
785       * @param  identifier  The identifier of the argument to retrieve.  It may be
786       *                     the long identifier without any dashes, the short
787       *                     identifier character preceded by a single dash, or the
788       *                     long identifier preceded by two dashes. It must not be
789       *                     {@code null}.
790       *
791       * @return  The DN argument with the specified identifier, or
792       *          {@code null} if there is no such argument.
793       */
794      public DNArgument getDNArgument(final String identifier)
795      {
796        final Argument a = getNamedArgument(identifier);
797        if (a == null)
798        {
799          return null;
800        }
801        else
802        {
803          return (DNArgument) a;
804        }
805      }
806    
807    
808    
809      /**
810       * Retrieves the duration argument with the specified identifier.
811       *
812       * @param  identifier  The identifier of the argument to retrieve.  It may be
813       *                     the long identifier without any dashes, the short
814       *                     identifier character preceded by a single dash, or the
815       *                     long identifier preceded by two dashes. It must not be
816       *                     {@code null}.
817       *
818       * @return  The duration argument with the specified identifier, or
819       *          {@code null} if there is no such argument.
820       */
821      public DurationArgument getDurationArgument(final String identifier)
822      {
823        final Argument a = getNamedArgument(identifier);
824        if (a == null)
825        {
826          return null;
827        }
828        else
829        {
830          return (DurationArgument) a;
831        }
832      }
833    
834    
835    
836      /**
837       * Retrieves the file argument with the specified identifier.
838       *
839       * @param  identifier  The identifier of the argument to retrieve.  It may be
840       *                     the long identifier without any dashes, the short
841       *                     identifier character preceded by a single dash, or the
842       *                     long identifier preceded by two dashes. It must not be
843       *                     {@code null}.
844       *
845       * @return  The file argument with the specified identifier, or
846       *          {@code null} if there is no such argument.
847       */
848      public FileArgument getFileArgument(final String identifier)
849      {
850        final Argument a = getNamedArgument(identifier);
851        if (a == null)
852        {
853          return null;
854        }
855        else
856        {
857          return (FileArgument) a;
858        }
859      }
860    
861    
862    
863      /**
864       * Retrieves the filter argument with the specified identifier.
865       *
866       * @param  identifier  The identifier of the argument to retrieve.  It may be
867       *                     the long identifier without any dashes, the short
868       *                     identifier character preceded by a single dash, or the
869       *                     long identifier preceded by two dashes. It must not be
870       *                     {@code null}.
871       *
872       * @return  The filter argument with the specified identifier, or
873       *          {@code null} if there is no such argument.
874       */
875      public FilterArgument getFilterArgument(final String identifier)
876      {
877        final Argument a = getNamedArgument(identifier);
878        if (a == null)
879        {
880          return null;
881        }
882        else
883        {
884          return (FilterArgument) a;
885        }
886      }
887    
888    
889    
890      /**
891       * Retrieves the integer argument with the specified identifier.
892       *
893       * @param  identifier  The identifier of the argument to retrieve.  It may be
894       *                     the long identifier without any dashes, the short
895       *                     identifier character preceded by a single dash, or the
896       *                     long identifier preceded by two dashes. It must not be
897       *                     {@code null}.
898       *
899       * @return  The integer argument with the specified identifier, or
900       *          {@code null} if there is no such argument.
901       */
902      public IntegerArgument getIntegerArgument(final String identifier)
903      {
904        final Argument a = getNamedArgument(identifier);
905        if (a == null)
906        {
907          return null;
908        }
909        else
910        {
911          return (IntegerArgument) a;
912        }
913      }
914    
915    
916    
917      /**
918       * Retrieves the scope argument with the specified identifier.
919       *
920       * @param  identifier  The identifier of the argument to retrieve.  It may be
921       *                     the long identifier without any dashes, the short
922       *                     identifier character preceded by a single dash, or the
923       *                     long identifier preceded by two dashes. It must not be
924       *                     {@code null}.
925       *
926       * @return  The scope argument with the specified identifier, or
927       *          {@code null} if there is no such argument.
928       */
929      public ScopeArgument getScopeArgument(final String identifier)
930      {
931        final Argument a = getNamedArgument(identifier);
932        if (a == null)
933        {
934          return null;
935        }
936        else
937        {
938          return (ScopeArgument) a;
939        }
940      }
941    
942    
943    
944      /**
945       * Retrieves the string argument with the specified identifier.
946       *
947       * @param  identifier  The identifier of the argument to retrieve.  It may be
948       *                     the long identifier without any dashes, the short
949       *                     identifier character preceded by a single dash, or the
950       *                     long identifier preceded by two dashes. It must not be
951       *                     {@code null}.
952       *
953       * @return  The string argument with the specified identifier, or
954       *          {@code null} if there is no such argument.
955       */
956      public StringArgument getStringArgument(final String identifier)
957      {
958        final Argument a = getNamedArgument(identifier);
959        if (a == null)
960        {
961          return null;
962        }
963        else
964        {
965          return (StringArgument) a;
966        }
967      }
968    
969    
970    
971      /**
972       * Retrieves the set of named arguments defined for use with this argument
973       * parser.
974       *
975       * @return  The set of named arguments defined for use with this argument
976       *          parser.
977       */
978      public List<Argument> getNamedArguments()
979      {
980        return Collections.unmodifiableList(namedArgs);
981      }
982    
983    
984    
985      /**
986       * Registers the provided argument with this argument parser.
987       *
988       * @param  argument  The argument to be registered.
989       *
990       * @throws  ArgumentException  If the provided argument conflicts with another
991       *                             argument already registered with this parser.
992       */
993      public void addArgument(final Argument argument)
994             throws ArgumentException
995      {
996        argument.setRegistered();
997        for (final Character c : argument.getShortIdentifiers())
998        {
999          if (namedArgsByShortID.containsKey(c))
1000          {
1001            throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
1002          }
1003        }
1004    
1005        for (final String s : argument.getLongIdentifiers())
1006        {
1007          if (namedArgsByLongID.containsKey(toLowerCase(s)))
1008          {
1009            throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
1010          }
1011        }
1012    
1013        for (final Character c : argument.getShortIdentifiers())
1014        {
1015          namedArgsByShortID.put(c, argument);
1016        }
1017    
1018        for (final String s : argument.getLongIdentifiers())
1019        {
1020          namedArgsByLongID.put(toLowerCase(s), argument);
1021        }
1022    
1023        namedArgs.add(argument);
1024      }
1025    
1026    
1027    
1028      /**
1029       * Retrieves the list of dependent argument sets for this argument parser.  If
1030       * an argument contained as the first object in the pair in a dependent
1031       * argument set is provided, then at least one of the arguments in the paired
1032       * set must also be provided.
1033       *
1034       * @return  The list of dependent argument sets for this argument parser.
1035       */
1036      public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
1037      {
1038        return Collections.unmodifiableList(dependentArgumentSets);
1039      }
1040    
1041    
1042    
1043      /**
1044       * Adds the provided collection of arguments as dependent upon the given
1045       * argument.
1046       *
1047       * @param  targetArgument      The argument whose presence indicates that at
1048       *                             least one of the dependent arguments must also
1049       *                             be present.  It must not be {@code null}.
1050       * @param  dependentArguments  The set of arguments from which at least one
1051       *                             argument must be present if the target argument
1052       *                             is present.  It must not be {@code null} or
1053       *                             empty.
1054       */
1055      public void addDependentArgumentSet(final Argument targetArgument,
1056                       final Collection<Argument> dependentArguments)
1057      {
1058        ensureNotNull(targetArgument, dependentArguments);
1059    
1060        final LinkedHashSet<Argument> argSet =
1061             new LinkedHashSet<Argument>(dependentArguments);
1062        dependentArgumentSets.add(
1063             new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1064      }
1065    
1066    
1067    
1068      /**
1069       * Adds the provided collection of arguments as dependent upon the given
1070       * argument.
1071       *
1072       * @param  targetArgument  The argument whose presence indicates that at least
1073       *                         one of the dependent arguments must also be
1074       *                         present.  It must not be {@code null}.
1075       * @param  dependentArg1   The first argument in the set of arguments in which
1076       *                         at least one argument must be present if the target
1077       *                         argument is present.  It must not be {@code null}.
1078       * @param  remaining       The remaining arguments in the set of arguments in
1079       *                         which at least one argument must be present if the
1080       *                         target argument is present.
1081       */
1082      public void addDependentArgumentSet(final Argument targetArgument,
1083                                          final Argument dependentArg1,
1084                                          final Argument... remaining)
1085      {
1086        ensureNotNull(targetArgument, dependentArg1);
1087    
1088        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1089        argSet.add(dependentArg1);
1090        argSet.addAll(Arrays.asList(remaining));
1091    
1092        dependentArgumentSets.add(
1093             new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
1094      }
1095    
1096    
1097    
1098      /**
1099       * Retrieves the list of exclusive argument sets for this argument parser.
1100       * If an argument contained in an exclusive argument set is provided, then
1101       * none of the other arguments in that set may be provided.  It is acceptable
1102       * for none of the arguments in the set to be provided, unless the same set
1103       * of arguments is also defined as a required argument set.
1104       *
1105       * @return  The list of exclusive argument sets for this argument parser.
1106       */
1107      public List<Set<Argument>> getExclusiveArgumentSets()
1108      {
1109        return Collections.unmodifiableList(exclusiveArgumentSets);
1110      }
1111    
1112    
1113    
1114      /**
1115       * Adds the provided collection of arguments as an exclusive argument set, in
1116       * which at most one of the arguments may be provided.
1117       *
1118       * @param  exclusiveArguments  The collection of arguments to form an
1119       *                             exclusive argument set.  It must not be
1120       *                             {@code null}.
1121       */
1122      public void addExclusiveArgumentSet(
1123                       final Collection<Argument> exclusiveArguments)
1124      {
1125        ensureNotNull(exclusiveArguments);
1126        final LinkedHashSet<Argument> argSet =
1127             new LinkedHashSet<Argument>(exclusiveArguments);
1128        exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1129      }
1130    
1131    
1132    
1133      /**
1134       * Adds the provided set of arguments as an exclusive argument set, in
1135       * which at most one of the arguments may be provided.
1136       *
1137       * @param  arg1       The first argument to include in the exclusive argument
1138       *                    set.  It must not be {@code null}.
1139       * @param  arg2       The second argument to include in the exclusive argument
1140       *                    set.  It must not be {@code null}.
1141       * @param  remaining  Any additional arguments to include in the exclusive
1142       *                    argument set.
1143       */
1144      public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
1145                                          final Argument... remaining)
1146      {
1147        ensureNotNull(arg1, arg2);
1148    
1149        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1150        argSet.add(arg1);
1151        argSet.add(arg2);
1152        argSet.addAll(Arrays.asList(remaining));
1153    
1154        exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
1155      }
1156    
1157    
1158    
1159      /**
1160       * Retrieves the list of required argument sets for this argument parser.  At
1161       * least one of the arguments contained in this set must be provided.  If this
1162       * same set is also defined as an exclusive argument set, then exactly one
1163       * of those arguments must be provided.
1164       *
1165       * @return  The list of required argument sets for this argument parser.
1166       */
1167      public List<Set<Argument>> getRequiredArgumentSets()
1168      {
1169        return Collections.unmodifiableList(requiredArgumentSets);
1170      }
1171    
1172    
1173    
1174      /**
1175       * Adds the provided collection of arguments as a required argument set, in
1176       * which at least one of the arguments must be provided.
1177       *
1178       * @param  requiredArguments  The collection of arguments to form an
1179       *                            required argument set.  It must not be
1180       *                            {@code null}.
1181       */
1182      public void addRequiredArgumentSet(
1183                       final Collection<Argument> requiredArguments)
1184      {
1185        ensureNotNull(requiredArguments);
1186        final LinkedHashSet<Argument> argSet =
1187             new LinkedHashSet<Argument>(requiredArguments);
1188        requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1189      }
1190    
1191    
1192    
1193      /**
1194       * Adds the provided set of arguments as a required argument set, in which
1195       * at least one of the arguments must be provided.
1196       *
1197       * @param  arg1       The first argument to include in the required argument
1198       *                    set.  It must not be {@code null}.
1199       * @param  arg2       The second argument to include in the required argument
1200       *                    set.  It must not be {@code null}.
1201       * @param  remaining  Any additional arguments to include in the required
1202       *                    argument set.
1203       */
1204      public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
1205                                         final Argument... remaining)
1206      {
1207        ensureNotNull(arg1, arg2);
1208    
1209        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
1210        argSet.add(arg1);
1211        argSet.add(arg2);
1212        argSet.addAll(Arrays.asList(remaining));
1213    
1214        requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
1215      }
1216    
1217    
1218    
1219      /**
1220       * Retrieves the set of unnamed trailing arguments in the provided command
1221       * line arguments.
1222       *
1223       * @return  The set of unnamed trailing arguments in the provided command line
1224       *          arguments, or an empty list if there were none.
1225       */
1226      public List<String> getTrailingArguments()
1227      {
1228        return Collections.unmodifiableList(trailingArgs);
1229      }
1230    
1231    
1232    
1233      /**
1234       * Clears the set of trailing arguments for this argument parser.
1235       */
1236      void resetTrailingArguments()
1237      {
1238        trailingArgs.clear();
1239      }
1240    
1241    
1242    
1243      /**
1244       * Adds the provided value to the set of trailing arguments.
1245       *
1246       * @param  value  The value to add to the set of trailing arguments.
1247       *
1248       * @throws  ArgumentException  If the parser already has the maximum allowed
1249       *                             number of trailing arguments.
1250       */
1251      void addTrailingArgument(final String value)
1252           throws ArgumentException
1253      {
1254        if ((maxTrailingArgs > 0) && (trailingArgs.size() >= maxTrailingArgs))
1255        {
1256          throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(value,
1257               commandName, maxTrailingArgs));
1258        }
1259    
1260        trailingArgs.add(value);
1261      }
1262    
1263    
1264    
1265      /**
1266       * Creates a copy of this argument parser that is "clean" and appears as if it
1267       * has not been used to parse an argument set.  The new parser will have all
1268       * of the same arguments and constraints as this parser.
1269       *
1270       * @return  The "clean" copy of this argument parser.
1271       */
1272      public ArgumentParser getCleanCopy()
1273      {
1274        return new ArgumentParser(this);
1275      }
1276    
1277    
1278    
1279      /**
1280       * Parses the provided set of arguments.
1281       *
1282       * @param  args  An array containing the argument information to parse.  It
1283       *               must not be {@code null}.
1284       *
1285       * @throws  ArgumentException  If a problem occurs while attempting to parse
1286       *                             the argument information.
1287       */
1288      public void parse(final String[] args)
1289             throws ArgumentException
1290      {
1291        // Iterate through the provided args strings and process them.
1292        boolean inTrailingArgs      = false;
1293        boolean skipFinalValidation = false;
1294        for (int i=0; i < args.length; i++)
1295        {
1296          final String s = args[i];
1297    
1298          if (inTrailingArgs)
1299          {
1300            if (maxTrailingArgs == 0)
1301            {
1302              throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1303                                               s, commandName));
1304            }
1305            else if (trailingArgs.size() >= maxTrailingArgs)
1306            {
1307              throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
1308                                               commandName, maxTrailingArgs));
1309            }
1310            else
1311            {
1312              trailingArgs.add(s);
1313            }
1314          }
1315          else if (s.equals("--"))
1316          {
1317            // This signifies the end of the named arguments and the beginning of
1318            // the trailing arguments.
1319            inTrailingArgs = true;
1320          }
1321          else if (s.startsWith("--"))
1322          {
1323            // There may be an equal sign to separate the name from the value.
1324            final String argName;
1325            final int equalPos = s.indexOf('=');
1326            if (equalPos > 0)
1327            {
1328              argName = s.substring(2, equalPos);
1329            }
1330            else
1331            {
1332              argName = s.substring(2);
1333            }
1334    
1335            final Argument a = namedArgsByLongID.get(toLowerCase(argName));
1336            if (a == null)
1337            {
1338              throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
1339            }
1340            else if (a.isUsageArgument())
1341            {
1342              skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1343            }
1344    
1345            a.incrementOccurrences();
1346            if (a.takesValue())
1347            {
1348              if (equalPos > 0)
1349              {
1350                a.addValue(s.substring(equalPos+1));
1351              }
1352              else
1353              {
1354                i++;
1355                if (i >= args.length)
1356                {
1357                  throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
1358                                                   argName));
1359                }
1360                else
1361                {
1362                  a.addValue(args[i]);
1363                }
1364              }
1365            }
1366            else
1367            {
1368              if (equalPos > 0)
1369              {
1370                throw new ArgumentException(
1371                               ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
1372              }
1373            }
1374          }
1375          else if (s.startsWith("-"))
1376          {
1377            if (s.length() == 1)
1378            {
1379              throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
1380            }
1381            else if (s.length() == 2)
1382            {
1383              final char c = s.charAt(1);
1384              final Argument a = namedArgsByShortID.get(c);
1385              if (a == null)
1386              {
1387                throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1388              }
1389              else if (a.isUsageArgument())
1390              {
1391                skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1392              }
1393    
1394              a.incrementOccurrences();
1395              if (a.takesValue())
1396              {
1397                i++;
1398                if (i >= args.length)
1399                {
1400                  throw new ArgumentException(
1401                                 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
1402                }
1403                else
1404                {
1405                  a.addValue(args[i]);
1406                }
1407              }
1408            }
1409            else
1410            {
1411              char c = s.charAt(1);
1412              Argument a = namedArgsByShortID.get(c);
1413              if (a == null)
1414              {
1415                throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
1416              }
1417              else if (a.isUsageArgument())
1418              {
1419                skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1420              }
1421    
1422              a.incrementOccurrences();
1423              if (a.takesValue())
1424              {
1425                a.addValue(s.substring(2));
1426              }
1427              else
1428              {
1429                // The rest of the characters in the string must also resolve to
1430                // arguments that don't take values.
1431                for (int j=2; j < s.length(); j++)
1432                {
1433                  c = s.charAt(j);
1434                  a = namedArgsByShortID.get(c);
1435                  if (a == null)
1436                  {
1437                    throw new ArgumentException(
1438                                   ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
1439                  }
1440                  else if (a.isUsageArgument())
1441                  {
1442                    skipFinalValidation |= skipFinalValidationBecauseOfArgument(a);
1443                  }
1444    
1445                  a.incrementOccurrences();
1446                  if (a.takesValue())
1447                  {
1448                    throw new ArgumentException(
1449                                   ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
1450                                        c, s));
1451                  }
1452                }
1453              }
1454            }
1455          }
1456          else
1457          {
1458            inTrailingArgs = true;
1459            if (maxTrailingArgs == 0)
1460            {
1461              throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
1462                                               s, commandName));
1463            }
1464            else
1465            {
1466              trailingArgs.add(s);
1467            }
1468          }
1469        }
1470    
1471    
1472        // Perform any appropriate processing related to the use of a properties
1473        // file.
1474        if (! handlePropertiesFile())
1475        {
1476          return;
1477        }
1478    
1479    
1480        // If a usage argument was provided, then no further validation should be
1481        // performed.
1482        if (skipFinalValidation)
1483        {
1484          return;
1485        }
1486    
1487    
1488        // Make sure that all required arguments have values.
1489        for (final Argument a : namedArgs)
1490        {
1491          if (a.isRequired() && (! a.isPresent()))
1492          {
1493            throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
1494                                             a.getIdentifierString()));
1495          }
1496        }
1497    
1498    
1499        // Make sure that at least the minimum number of trailing arguments were
1500        // provided.
1501        if (trailingArgs.size() < minTrailingArgs)
1502        {
1503          throw new ArgumentException(ERR_PARSER_NOT_ENOUGH_TRAILING_ARGS.get(
1504               commandName, minTrailingArgs, trailingArgsPlaceholder));
1505        }
1506    
1507    
1508        // Make sure that there are no dependent argument set conflicts.
1509        for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets)
1510        {
1511          final Argument targetArg = p.getFirst();
1512          if (targetArg.isPresent())
1513          {
1514            final Set<Argument> argSet = p.getSecond();
1515            boolean found = false;
1516            for (final Argument a : argSet)
1517            {
1518              if (a.isPresent())
1519              {
1520                found = true;
1521                break;
1522              }
1523            }
1524    
1525            if (! found)
1526            {
1527              if (argSet.size() == 1)
1528              {
1529                throw new ArgumentException(
1530                     ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
1531                          targetArg.getIdentifierString(),
1532                          argSet.iterator().next().getIdentifierString()));
1533              }
1534              else
1535              {
1536                boolean first = true;
1537                final StringBuilder buffer = new StringBuilder();
1538                for (final Argument a : argSet)
1539                {
1540                  if (first)
1541                  {
1542                    first = false;
1543                  }
1544                  else
1545                  {
1546                    buffer.append(", ");
1547                  }
1548                  buffer.append(a.getIdentifierString());
1549                }
1550                throw new ArgumentException(
1551                     ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
1552                          targetArg.getIdentifierString(), buffer.toString()));
1553              }
1554            }
1555          }
1556        }
1557    
1558    
1559        // Make sure that there are no exclusive argument set conflicts.
1560        for (final Set<Argument> argSet : exclusiveArgumentSets)
1561        {
1562          Argument setArg = null;
1563          for (final Argument a : argSet)
1564          {
1565            if (a.isPresent())
1566            {
1567              if (setArg == null)
1568              {
1569                setArg = a;
1570              }
1571              else
1572              {
1573                throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1574                                                 setArg.getIdentifierString(),
1575                                                 a.getIdentifierString()));
1576              }
1577            }
1578          }
1579        }
1580    
1581        // Make sure that there are no required argument set conflicts.
1582        for (final Set<Argument> argSet : requiredArgumentSets)
1583        {
1584          boolean found = false;
1585          for (final Argument a : argSet)
1586          {
1587            if (a.isPresent())
1588            {
1589              found = true;
1590              break;
1591            }
1592          }
1593    
1594          if (! found)
1595          {
1596            boolean first = true;
1597            final StringBuilder buffer = new StringBuilder();
1598            for (final Argument a : argSet)
1599            {
1600              if (first)
1601              {
1602                first = false;
1603              }
1604              else
1605              {
1606                buffer.append(", ");
1607              }
1608              buffer.append(a.getIdentifierString());
1609            }
1610            throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
1611                                             buffer.toString()));
1612          }
1613        }
1614      }
1615    
1616    
1617    
1618      /**
1619       * Indicates whether the provided argument is one that indicates that the
1620       * parser should skip all validation except that performed when assigning
1621       * values from command-line arguments.  Validation that will be skipped
1622       * includes ensuring that all required arguments have values, ensuring that
1623       * the minimum number of trailing arguments were provided, and ensuring that
1624       * there were no dependent/exclusive/required argument set conflicts.
1625       *
1626       * @param  a  The argument for which to make the determination.
1627       *
1628       * @return  {@code true} if the provided argument is one that indicates that
1629       *          final validation should be skipped, or {@code false} if not.
1630       */
1631      private static boolean skipFinalValidationBecauseOfArgument(final Argument a)
1632      {
1633        // We will skip final validation for all usage arguments except the
1634        // propertiesFilePath and noPropertiesFile arguments.
1635        if (ARG_NAME_PROPERTIES_FILE_PATH.equals(a.getLongIdentifier()) ||
1636            ARG_NAME_NO_PROPERTIES_FILE.equals(a.getLongIdentifier()))
1637        {
1638          return false;
1639        }
1640    
1641        return a.isUsageArgument();
1642      }
1643    
1644    
1645    
1646      /**
1647       * Performs any appropriate properties file processing for this argument
1648       * parser.
1649       *
1650       * @return  {@code true} if the tool should continue processing, or
1651       *          {@code false} if it should return immediately.
1652       *
1653       * @throws  ArgumentException  If a problem is encountered while attempting
1654       *                             to parse a properties file or update arguments
1655       *                             with the values contained in it.
1656       */
1657      private boolean handlePropertiesFile()
1658              throws ArgumentException
1659      {
1660        final BooleanArgument noPropertiesFile;
1661        final FileArgument generatePropertiesFile;
1662        final FileArgument propertiesFilePath;
1663        try
1664        {
1665          propertiesFilePath = getFileArgument(ARG_NAME_PROPERTIES_FILE_PATH);
1666          generatePropertiesFile =
1667               getFileArgument(ARG_NAME_GENERATE_PROPERTIES_FILE);
1668          noPropertiesFile = getBooleanArgument(ARG_NAME_NO_PROPERTIES_FILE);
1669        }
1670        catch (final Exception e)
1671        {
1672          Debug.debugException(e);
1673    
1674          // This should only ever happen if the argument parser has an argument
1675          // with a name that conflicts with one of the properties file arguments
1676          // but isn't of the right type.  In this case, we'll assume that no
1677          // properties file will be used.
1678          return true;
1679        }
1680    
1681    
1682        // If any of the properties file arguments isn't defined, then we'll assume
1683        // that no properties file will be used.
1684        if ((propertiesFilePath == null) || (generatePropertiesFile == null) ||
1685            (noPropertiesFile == null))
1686        {
1687          return true;
1688        }
1689    
1690    
1691        // If the noPropertiesFile argument is present, then don't do anything but
1692        // make sure that neither of the other arguments was specified.
1693        if (noPropertiesFile.isPresent())
1694        {
1695          if (propertiesFilePath.isPresent())
1696          {
1697            throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1698                 noPropertiesFile.getIdentifierString(),
1699                 propertiesFilePath.getIdentifierString()));
1700          }
1701          else if (generatePropertiesFile.isPresent())
1702          {
1703            throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1704                 noPropertiesFile.getIdentifierString(),
1705                 generatePropertiesFile.getIdentifierString()));
1706          }
1707          else
1708          {
1709            return true;
1710          }
1711        }
1712    
1713    
1714        // If the generatePropertiesFile argument is present, then make sure the
1715        // propertiesFilePath argument is not set and generate the output.
1716        if (generatePropertiesFile.isPresent())
1717        {
1718          if (propertiesFilePath.isPresent())
1719          {
1720            throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
1721                 generatePropertiesFile.getIdentifierString(),
1722                 propertiesFilePath.getIdentifierString()));
1723          }
1724          else
1725          {
1726            generatePropertiesFile(
1727                 generatePropertiesFile.getValue().getAbsolutePath());
1728            return false;
1729          }
1730        }
1731    
1732    
1733        // If the propertiesFilePath argument is present, then try to make use of
1734        // the specified file.
1735        if (propertiesFilePath.isPresent())
1736        {
1737          final File propertiesFile = propertiesFilePath.getValue();
1738          if (propertiesFile.exists() && propertiesFile.isFile())
1739          {
1740            handlePropertiesFile(propertiesFilePath.getValue());
1741          }
1742          else
1743          {
1744            throw new ArgumentException(
1745                 ERR_PARSER_NO_SUCH_PROPERTIES_FILE.get(
1746                      propertiesFilePath.getIdentifierString(),
1747                      propertiesFile.getAbsolutePath()));
1748          }
1749          return true;
1750        }
1751    
1752    
1753        // We may still use a properties file if the path was specified in either a
1754        // JVM property or an environment variable.  If both are defined, the JVM
1755        // property will take precedence.  If a property or environment variable
1756        // specifies an invalid value, then we'll just ignore it.
1757        String path = System.getProperty(PROPERTY_DEFAULT_PROPERTIES_FILE_PATH);
1758        if (path == null)
1759        {
1760          path = System.getenv(ENV_DEFAULT_PROPERTIES_FILE_PATH);
1761        }
1762    
1763        if (path != null)
1764        {
1765          final File propertiesFile = new File(path);
1766          if (propertiesFile.exists() && propertiesFile.isFile())
1767          {
1768            handlePropertiesFile(propertiesFile);
1769          }
1770        }
1771    
1772        return true;
1773      }
1774    
1775    
1776    
1777      /**
1778       * Write an empty properties file for this argument parser to the specified
1779       * path.
1780       *
1781       * @param  path  The path to the properties file to be written.
1782       *
1783       * @throws  ArgumentException  If a problem is encountered while writing the
1784       *                             properties file.
1785       */
1786      private void generatePropertiesFile(final String path)
1787              throws ArgumentException
1788      {
1789        final PrintWriter w;
1790        try
1791        {
1792          w = new PrintWriter(path);
1793        }
1794        catch (final Exception e)
1795        {
1796          Debug.debugException(e);
1797          throw new ArgumentException(
1798               ERR_PARSER_GEN_PROPS_CANNOT_OPEN_FILE.get(path,
1799                    getExceptionMessage(e)),
1800               e);
1801        }
1802    
1803        try
1804        {
1805          wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_1.get(commandName));
1806          w.println('#');
1807          wrapComment(w,
1808               INFO_PARSER_GEN_PROPS_HEADER_2.get(commandName,
1809                    ARG_NAME_PROPERTIES_FILE_PATH,
1810                    PROPERTY_DEFAULT_PROPERTIES_FILE_PATH,
1811                    ENV_DEFAULT_PROPERTIES_FILE_PATH, ARG_NAME_NO_PROPERTIES_FILE));
1812          w.println('#');
1813    
1814          for (final Argument a : getNamedArguments())
1815          {
1816            if (a.isUsageArgument() || a.isHidden())
1817            {
1818              continue;
1819            }
1820    
1821            final String argName = a.getLongIdentifier();
1822            if (argName != null)
1823            {
1824              wrapComment(w,
1825                   INFO_PARSER_GEN_PROPS_HEADER_3.get(commandName, argName));
1826              w.println('#');
1827              break;
1828            }
1829          }
1830    
1831          wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_4.get());
1832          w.println('#');
1833          wrapComment(w, INFO_PARSER_GEN_PROPS_HEADER_5.get(commandName));
1834    
1835          for (final Argument a : getNamedArguments())
1836          {
1837            if (a.isUsageArgument() || a.isHidden())
1838            {
1839              continue;
1840            }
1841    
1842            w.println();
1843            w.println();
1844            wrapComment(w, a.getDescription());
1845            w.println('#');
1846    
1847            final String constraints = a.getValueConstraints();
1848            if ((constraints != null) && (constraints.length() > 0) &&
1849                (! (a instanceof BooleanArgument)))
1850            {
1851              wrapComment(w, constraints);
1852              w.println('#');
1853            }
1854    
1855            final String identifier;
1856            if (a.getLongIdentifier() != null)
1857            {
1858              identifier = a.getLongIdentifier();
1859            }
1860            else
1861            {
1862              identifier = a.getIdentifierString();
1863            }
1864    
1865            String placeholder = a.getValuePlaceholder();
1866            if (placeholder == null)
1867            {
1868              if (a instanceof BooleanArgument)
1869              {
1870                placeholder = "{true|false}";
1871              }
1872              else
1873              {
1874                placeholder = "";
1875              }
1876            }
1877    
1878            final String propertyName = commandName + '.' + identifier;
1879            w.println("# " + propertyName + '=' + placeholder);
1880    
1881            if (a.isPresent())
1882            {
1883              for (final String s : a.getValueStringRepresentations(false))
1884              {
1885                w.println(propertyName + '=' + s);
1886              }
1887            }
1888          }
1889        }
1890        finally
1891        {
1892          w.close();
1893        }
1894      }
1895    
1896    
1897    
1898      /**
1899       * Wraps the given string and writes it as a comment to the provided writer.
1900       *
1901       * @param  w  The writer to use to write the wrapped and commented string.
1902       * @param  s  The string to be wrapped and written.
1903       */
1904      private static void wrapComment(final PrintWriter w, final String s)
1905      {
1906        for (final String line : wrapLine(s, 77))
1907        {
1908          w.println("# " + line);
1909        }
1910      }
1911    
1912    
1913    
1914      /**
1915       * Reads the contents of the specified properties file and updates the
1916       * configured arguments as appropriate.
1917       *
1918       * @param  propertiesFile  The properties file to process.
1919       *
1920       * @throws  ArgumentException  If a problem is encountered while examining the
1921       *                             properties file, or while trying to assign a
1922       *                             property value to a corresponding argument.
1923       */
1924      private void handlePropertiesFile(final File propertiesFile)
1925              throws ArgumentException
1926      {
1927        final BufferedReader reader;
1928        try
1929        {
1930          reader = new BufferedReader(new FileReader(propertiesFile));
1931        }
1932        catch (final Exception e)
1933        {
1934          Debug.debugException(e);
1935          throw new ArgumentException(
1936               ERR_PARSER_CANNOT_OPEN_PROP_FILE.get(
1937                    propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
1938               e);
1939        }
1940    
1941        try
1942        {
1943          // Read all of the lines of the file, ignoring comments and unwrapping
1944          // properties that span multiple lines.
1945          boolean lineIsContinued = false;
1946          int lineNumber = 0;
1947          final ArrayList<ObjectPair<Integer,StringBuilder>> propertyLines =
1948               new ArrayList<ObjectPair<Integer,StringBuilder>>(10);
1949          while (true)
1950          {
1951            String line;
1952            try
1953            {
1954              line = reader.readLine();
1955              lineNumber++;
1956            }
1957            catch (final Exception e)
1958            {
1959              Debug.debugException(e);
1960              throw new ArgumentException(
1961                   ERR_PARSER_ERROR_READING_PROP_FILE.get(
1962                        propertiesFile.getAbsolutePath(), getExceptionMessage(e)),
1963                   e);
1964            }
1965    
1966    
1967            // If the line is null, then we've reached the end of the file.  If we
1968            // expect a previous line to have been continued, then this is an error.
1969            if (line == null)
1970            {
1971              if (lineIsContinued)
1972              {
1973                throw new ArgumentException(
1974                     ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
1975                          (lineNumber-1), propertiesFile.getAbsolutePath()));
1976              }
1977              break;
1978            }
1979    
1980    
1981            // See if the line has any leading whitespace, and if so then trim it
1982            // off.  If there is leading whitespace, then make sure that we expect
1983            // the previous line to be continued.
1984            final int initialLength = line.length();
1985            line = trimLeading(line);
1986            final boolean hasLeadingWhitespace = (line.length() < initialLength);
1987            if (hasLeadingWhitespace && (! lineIsContinued))
1988            {
1989              throw new ArgumentException(
1990                   ERR_PARSER_PROP_FILE_UNEXPECTED_LEADING_SPACE.get(
1991                        propertiesFile.getAbsolutePath(), lineNumber));
1992            }
1993    
1994    
1995            // If the line is empty or starts with "#", then skip it.  But make sure
1996            // we didn't expect the previous line to be continued.
1997            if ((line.length() == 0) || line.startsWith("#"))
1998            {
1999              if (lineIsContinued)
2000              {
2001                throw new ArgumentException(
2002                     ERR_PARSER_PROP_FILE_MISSING_CONTINUATION.get(
2003                          (lineNumber-1), propertiesFile.getAbsolutePath()));
2004              }
2005              continue;
2006            }
2007    
2008    
2009            // See if the line ends with a backslash and if so then trim it off.
2010            final boolean hasTrailingBackslash = line.endsWith("\\");
2011            if (line.endsWith("\\"))
2012            {
2013              line = line.substring(0, (line.length() - 1));
2014            }
2015    
2016    
2017            // If the previous line needs to be continued, then append the new line
2018            // to it.  Otherwise, add it as a new line.
2019            if (lineIsContinued)
2020            {
2021              propertyLines.get(propertyLines.size() - 1).getSecond().append(line);
2022            }
2023            else
2024            {
2025              propertyLines.add(new ObjectPair<Integer,StringBuilder>(lineNumber,
2026                   new StringBuilder(line)));
2027            }
2028    
2029            lineIsContinued = hasTrailingBackslash;
2030          }
2031    
2032    
2033          // Parse all of the lines into a map of identifiers and their
2034          // corresponding values.
2035          if (propertyLines.isEmpty())
2036          {
2037            return;
2038          }
2039    
2040          final HashMap<String,ArrayList<String>> propertyMap =
2041               new HashMap<String,ArrayList<String>>(propertyLines.size());
2042          for (final ObjectPair<Integer,StringBuilder> p : propertyLines)
2043          {
2044            final String line = p.getSecond().toString();
2045            final int equalPos = line.indexOf('=');
2046            if (equalPos <= 0)
2047            {
2048              throw new ArgumentException(ERR_PARSER_MALFORMED_PROP_LINE.get(
2049                   propertiesFile.getAbsolutePath(), p.getFirst(), line));
2050            }
2051    
2052            final String propertyName = line.substring(0, equalPos).trim();
2053            final String propertyValue = line.substring(equalPos+1).trim();
2054            if (propertyValue.length() == 0)
2055            {
2056              // The property doesn't have a value, so we can ignore it.
2057              continue;
2058            }
2059    
2060    
2061            // An argument can have multiple identifiers, and we will allow any of
2062            // them to be used to reference it.  To deal with this, we'll map the
2063            // argument identifier to its corresponding argument and then use the
2064            // preferred identifier for that argument in the map.
2065            boolean prefixedWithToolName = false;
2066            Argument a = getNamedArgument(propertyName);
2067            if (a == null)
2068            {
2069              // It could be that the argument name was prefixed with the tool name.
2070              // Check to see if that was the case.
2071              if (propertyName.startsWith(commandName + '.'))
2072              {
2073                final String basePropertyName =
2074                     propertyName.substring(commandName.length()+1);
2075                a = getNamedArgument(basePropertyName);
2076                prefixedWithToolName = true;
2077              }
2078            }
2079    
2080            if (a == null)
2081            {
2082              // This could mean that there's a typo in the property name, but it's
2083              // more likely the case that the property is for a different tool.  In
2084              // either case, we'll ignore it.
2085              continue;
2086            }
2087    
2088            final String canonicalPropertyName;
2089            if (prefixedWithToolName)
2090            {
2091              canonicalPropertyName = commandName + '.' + a.getIdentifierString();
2092            }
2093            else
2094            {
2095              canonicalPropertyName = a.getIdentifierString();
2096            }
2097    
2098            ArrayList<String> valueList = propertyMap.get(canonicalPropertyName);
2099            if (valueList == null)
2100            {
2101              valueList = new ArrayList<String>(5);
2102              propertyMap.put(canonicalPropertyName, valueList);
2103            }
2104            valueList.add(propertyValue);
2105          }
2106    
2107    
2108          // Iterate through all of the named arguments for the argument parser and
2109          // see if we should use the properties to assign values to any of the
2110          // arguments that weren't provided on the command line.
2111          for (final Argument a : namedArgs)
2112          {
2113            if (a.getNumOccurrences() > 0)
2114            {
2115              // The argument was provided on the command line, and that will always
2116              // override anything that might be in the properties file.
2117              continue;
2118            }
2119    
2120    
2121            // See if the properties file had a property that is specific to the
2122            // tool.  If so, then try to assign its values to the argument.  If not,
2123            // then fall back to checking for a set of values that are generic to
2124            // any tool that has an argument with that name.
2125            List<String> values =
2126                 propertyMap.get(commandName + '.' + a.getIdentifierString());
2127            if (values == null)
2128            {
2129              values = propertyMap.get(a.getIdentifierString());
2130            }
2131    
2132            if (values != null)
2133            {
2134              for (final String value : values)
2135              {
2136                if (a instanceof BooleanArgument)
2137                {
2138                  // We'll treat this as a BooleanValueArgument.
2139                  final BooleanValueArgument bva = new BooleanValueArgument(
2140                       a.getShortIdentifier(), a.getLongIdentifier(), false, null,
2141                       a.getDescription());
2142                  bva.addValue(value);
2143                  if (bva.getValue())
2144                  {
2145                    a.incrementOccurrences();
2146                  }
2147                }
2148                else
2149                {
2150                  a.addValue(value);
2151                  a.incrementOccurrences();
2152                }
2153              }
2154            }
2155          }
2156        }
2157        finally
2158        {
2159          try
2160          {
2161            reader.close();
2162          }
2163          catch (final Exception e)
2164          {
2165            Debug.debugException(e);
2166          }
2167        }
2168      }
2169    
2170    
2171    
2172      /**
2173       * Retrieves lines that make up the usage information for this program,
2174       * optionally wrapping long lines.
2175       *
2176       * @param  maxWidth  The maximum line width to use for the output.  If this is
2177       *                   less than or equal to zero, then no wrapping will be
2178       *                   performed.
2179       *
2180       * @return  The lines that make up the usage information for this program.
2181       */
2182      public List<String> getUsage(final int maxWidth)
2183      {
2184        final ArrayList<String> lines = new ArrayList<String>(100);
2185    
2186        // First is a description of the command.
2187        lines.addAll(wrapLine(commandDescription, maxWidth));
2188        lines.add("");
2189    
2190        // Next comes the usage.  It may include neither, either, or both of the
2191        // set of options and trailing arguments.
2192        if (namedArgs.isEmpty())
2193        {
2194          if (maxTrailingArgs == 0)
2195          {
2196            lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
2197                                  maxWidth));
2198          }
2199          else
2200          {
2201            lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
2202                                       commandName, trailingArgsPlaceholder),
2203                                  maxWidth));
2204          }
2205        }
2206        else
2207        {
2208          if (maxTrailingArgs == 0)
2209          {
2210            lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
2211                                  maxWidth));
2212          }
2213          else
2214          {
2215            lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
2216                                       commandName, trailingArgsPlaceholder),
2217                                  maxWidth));
2218          }
2219    
2220          lines.add("");
2221          lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
2222    
2223    
2224          // If there are any argument groups, then collect the arguments in those
2225          // groups.
2226          boolean hasRequired = false;
2227          final LinkedHashMap<String,List<Argument>> argumentsByGroup =
2228               new LinkedHashMap<String,List<Argument>>(10);
2229          final ArrayList<Argument> argumentsWithoutGroup =
2230               new ArrayList<Argument>(namedArgs.size());
2231          final ArrayList<Argument> usageArguments =
2232               new ArrayList<Argument>(namedArgs.size());
2233          for (final Argument a : namedArgs)
2234          {
2235            if (a.isHidden())
2236            {
2237              // This argument shouldn't be included in the usage output.
2238              continue;
2239            }
2240    
2241            if (a.isRequired() && (! a.hasDefaultValue()))
2242            {
2243              hasRequired = true;
2244            }
2245    
2246            final String argumentGroup = a.getArgumentGroupName();
2247            if (argumentGroup == null)
2248            {
2249              if (a.isUsageArgument())
2250              {
2251                usageArguments.add(a);
2252              }
2253              else
2254              {
2255                argumentsWithoutGroup.add(a);
2256              }
2257            }
2258            else
2259            {
2260              List<Argument> groupArgs = argumentsByGroup.get(argumentGroup);
2261              if (groupArgs == null)
2262              {
2263                groupArgs = new ArrayList<Argument>(10);
2264                argumentsByGroup.put(argumentGroup, groupArgs);
2265              }
2266    
2267              groupArgs.add(a);
2268            }
2269          }
2270    
2271    
2272          // Iterate through the defined argument groups and display usage
2273          // information for each of them.
2274          for (final Map.Entry<String,List<Argument>> e :
2275               argumentsByGroup.entrySet())
2276          {
2277            lines.add("");
2278            lines.add("  " + e.getKey());
2279            lines.add("");
2280            for (final Argument a : e.getValue())
2281            {
2282              getArgUsage(a, lines, true, maxWidth);
2283            }
2284          }
2285    
2286          if (! argumentsWithoutGroup.isEmpty())
2287          {
2288            if (argumentsByGroup.isEmpty())
2289            {
2290              for (final Argument a : argumentsWithoutGroup)
2291              {
2292                getArgUsage(a, lines, false, maxWidth);
2293              }
2294            }
2295            else
2296            {
2297              lines.add("");
2298              lines.add("  " + INFO_USAGE_UNGROUPED_ARGS.get());
2299              lines.add("");
2300              for (final Argument a : argumentsWithoutGroup)
2301              {
2302                getArgUsage(a, lines, true, maxWidth);
2303              }
2304            }
2305          }
2306    
2307          if (! usageArguments.isEmpty())
2308          {
2309            if (argumentsByGroup.isEmpty())
2310            {
2311              for (final Argument a : usageArguments)
2312              {
2313                getArgUsage(a, lines, false, maxWidth);
2314              }
2315            }
2316            else
2317            {
2318              lines.add("");
2319              lines.add("  " + INFO_USAGE_USAGE_ARGS.get());
2320              lines.add("");
2321              for (final Argument a : usageArguments)
2322              {
2323                getArgUsage(a, lines, true, maxWidth);
2324              }
2325            }
2326          }
2327    
2328          if (hasRequired)
2329          {
2330            lines.add("");
2331            if (argumentsByGroup.isEmpty())
2332            {
2333              lines.add("* " + INFO_USAGE_ARG_IS_REQUIRED.get());
2334            }
2335            else
2336            {
2337              lines.add("  * " + INFO_USAGE_ARG_IS_REQUIRED.get());
2338            }
2339          }
2340        }
2341    
2342        return lines;
2343      }
2344    
2345    
2346    
2347      /**
2348       * Adds usage information for the provided argument to the given list.
2349       *
2350       * @param  a         The argument for which to get the usage information.
2351       * @param  lines     The list to which the resulting lines should be added.
2352       * @param  indent    Indicates whether to indent each line.
2353       * @param  maxWidth  The maximum width of each line, in characters.
2354       */
2355      private static void getArgUsage(final Argument a, final List<String> lines,
2356                                      final boolean indent, final int maxWidth)
2357      {
2358        final StringBuilder argLine = new StringBuilder();
2359        if (indent && (maxWidth > 10))
2360        {
2361          if (a.isRequired() && (! a.hasDefaultValue()))
2362          {
2363            argLine.append("  * ");
2364          }
2365          else
2366          {
2367            argLine.append("    ");
2368          }
2369        }
2370        else if (a.isRequired() && (! a.hasDefaultValue()))
2371        {
2372          argLine.append("* ");
2373        }
2374    
2375        boolean first = true;
2376        for (final Character c : a.getShortIdentifiers())
2377        {
2378          if (first)
2379          {
2380            argLine.append('-');
2381            first = false;
2382          }
2383          else
2384          {
2385            argLine.append(", -");
2386          }
2387          argLine.append(c);
2388        }
2389    
2390        for (final String s : a.getLongIdentifiers())
2391        {
2392          if (first)
2393          {
2394            argLine.append("--");
2395            first = false;
2396          }
2397          else
2398          {
2399            argLine.append(", --");
2400          }
2401          argLine.append(s);
2402        }
2403    
2404        final String valuePlaceholder = a.getValuePlaceholder();
2405        if (valuePlaceholder != null)
2406        {
2407          argLine.append(' ');
2408          argLine.append(valuePlaceholder);
2409        }
2410    
2411        // NOTE:  This line won't be wrapped.  That's intentional because I
2412        // think it would probably look bad no matter how we did it.
2413        lines.add(argLine.toString());
2414    
2415    
2416        // The description should be wrapped, if necessary.  We'll also want to
2417        // indent it (unless someone chose an absurdly small wrap width) to make
2418        // it stand out from the argument lines.
2419        final String description = a.getDescription();
2420        if (maxWidth > 10)
2421        {
2422          final String indentString;
2423          if (indent)
2424          {
2425            indentString = "        ";
2426          }
2427          else
2428          {
2429            indentString = "    ";
2430          }
2431    
2432          final List<String> descLines = wrapLine(description,
2433               (maxWidth-indentString.length()));
2434          for (final String s : descLines)
2435          {
2436            lines.add(indentString + s);
2437          }
2438        }
2439        else
2440        {
2441          lines.addAll(wrapLine(description, maxWidth));
2442        }
2443      }
2444    
2445    
2446    
2447      /**
2448       * Writes usage information for this program to the provided output stream
2449       * using the UTF-8 encoding, optionally wrapping long lines.
2450       *
2451       * @param  outputStream  The output stream to which the usage information
2452       *                       should be written.  It must not be {@code null}.
2453       * @param  maxWidth      The maximum line width to use for the output.  If
2454       *                       this is less than or equal to zero, then no wrapping
2455       *                       will be performed.
2456       *
2457       * @throws  IOException  If an error occurs while attempting to write to the
2458       *                       provided output stream.
2459       */
2460      public void getUsage(final OutputStream outputStream, final int maxWidth)
2461             throws IOException
2462      {
2463        final List<String> usageLines = getUsage(maxWidth);
2464        for (final String s : usageLines)
2465        {
2466          outputStream.write(getBytes(s));
2467          outputStream.write(EOL_BYTES);
2468        }
2469      }
2470    
2471    
2472    
2473      /**
2474       * Retrieves a string representation of the usage information.
2475       *
2476       * @param  maxWidth  The maximum line width to use for the output.  If this is
2477       *                   less than or equal to zero, then no wrapping will be
2478       *                   performed.
2479       *
2480       * @return  A string representation of the usage information
2481       */
2482      public String getUsageString(final int maxWidth)
2483      {
2484        final StringBuilder buffer = new StringBuilder();
2485        getUsageString(buffer, maxWidth);
2486        return buffer.toString();
2487      }
2488    
2489    
2490    
2491      /**
2492       * Appends a string representation of the usage information to the provided
2493       * buffer.
2494       *
2495       * @param  buffer    The buffer to which the information should be appended.
2496       * @param  maxWidth  The maximum line width to use for the output.  If this is
2497       *                   less than or equal to zero, then no wrapping will be
2498       *                   performed.
2499       */
2500      public void getUsageString(final StringBuilder buffer, final int maxWidth)
2501      {
2502        for (final String line : getUsage(maxWidth))
2503        {
2504          buffer.append(line);
2505          buffer.append(EOL);
2506        }
2507      }
2508    
2509    
2510    
2511      /**
2512       * Retrieves a string representation of this argument parser.
2513       *
2514       * @return  A string representation of this argument parser.
2515       */
2516      @Override()
2517      public String toString()
2518      {
2519        final StringBuilder buffer = new StringBuilder();
2520        toString(buffer);
2521        return buffer.toString();
2522      }
2523    
2524    
2525    
2526      /**
2527       * Appends a string representation of this argument parser to the provided
2528       * buffer.
2529       *
2530       * @param  buffer  The buffer to which the information should be appended.
2531       */
2532      public void toString(final StringBuilder buffer)
2533      {
2534        buffer.append("ArgumentParser(commandName='");
2535        buffer.append(commandName);
2536        buffer.append("', commandDescription='");
2537        buffer.append(commandDescription);
2538        buffer.append("', minTrailingArgs=");
2539        buffer.append(minTrailingArgs);
2540        buffer.append("', maxTrailingArgs=");
2541        buffer.append(maxTrailingArgs);
2542    
2543        if (trailingArgsPlaceholder != null)
2544        {
2545          buffer.append(", trailingArgsPlaceholder='");
2546          buffer.append(trailingArgsPlaceholder);
2547          buffer.append('\'');
2548        }
2549    
2550        buffer.append("namedArgs={");
2551    
2552        final Iterator<Argument> iterator = namedArgs.iterator();
2553        while (iterator.hasNext())
2554        {
2555          iterator.next().toString(buffer);
2556          if (iterator.hasNext())
2557          {
2558            buffer.append(", ");
2559          }
2560        }
2561    
2562        buffer.append("})");
2563      }
2564    }