001/*
002 * Copyright 2008-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.args;
022
023
024
025import java.io.BufferedReader;
026import java.io.File;
027import java.io.FileReader;
028import java.io.IOException;
029import java.io.OutputStream;
030import java.io.PrintWriter;
031import java.io.Serializable;
032import java.util.ArrayList;
033import java.util.Arrays;
034import java.util.Collection;
035import java.util.Collections;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.LinkedHashSet;
039import java.util.LinkedHashMap;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044import com.unboundid.util.Debug;
045import com.unboundid.util.ObjectPair;
046import com.unboundid.util.ThreadSafety;
047import com.unboundid.util.ThreadSafetyLevel;
048
049import static com.unboundid.util.StaticUtils.*;
050import static com.unboundid.util.Validator.*;
051import static com.unboundid.util.args.ArgsMessages.*;
052
053
054
055/**
056 * This class provides an argument parser, which may be used to process command
057 * line arguments provided to Java applications.  See the package-level Javadoc
058 * documentation for details regarding the capabilities of the argument parser.
059 */
060@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
061public final class ArgumentParser
062       implements Serializable
063{
064  /**
065   * The name of the system property that can be used to specify the default
066   * properties file that should be used to obtain the default values for
067   * arguments not specified via the command line.
068   */
069  public static final String PROPERTY_DEFAULT_PROPERTIES_FILE_PATH =
070       ArgumentParser.class.getName() + ".propertiesFilePath";
071
072
073
074  /**
075   * The name of an environment variable that can be used to specify the default
076   * properties file that should be used to obtain the default values for
077   * arguments not specified via the command line.
078   */
079  public static final String ENV_DEFAULT_PROPERTIES_FILE_PATH =
080       "UNBOUNDID_TOOL_PROPERTIES_FILE_PATH";
081
082
083
084  /**
085   * The name of the argument used to specify the path to a 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}