001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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.IOException;
026    import java.io.OutputStream;
027    import java.io.Serializable;
028    import java.util.ArrayList;
029    import java.util.Arrays;
030    import java.util.Collection;
031    import java.util.Collections;
032    import java.util.Iterator;
033    import java.util.LinkedHashSet;
034    import java.util.LinkedHashMap;
035    import java.util.List;
036    import java.util.Set;
037    
038    import com.unboundid.util.Debug;
039    import com.unboundid.util.ObjectPair;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    
043    import static com.unboundid.util.StaticUtils.*;
044    import static com.unboundid.util.Validator.*;
045    import static com.unboundid.util.args.ArgsMessages.*;
046    
047    
048    
049    /**
050     * This class provides an argument parser, which may be used to process command
051     * line arguments provided to Java applications.  See the package-level Javadoc
052     * documentation for details regarding the capabilities of the argument parser.
053     */
054    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
055    public final class ArgumentParser
056           implements Serializable
057    {
058      /**
059       * The serial version UID for this serializable class.
060       */
061      private static final long serialVersionUID = 361008526269946465L;
062    
063    
064    
065      // The maximum number of trailing arguments allowed to be provided.
066      private final int maxTrailingArgs;
067    
068      // The set of named arguments associated with this parser, indexed by short
069      // identifier.
070      private final LinkedHashMap<Character,Argument> namedArgsByShortID;
071    
072      // The set of named arguments associated with this parser, indexed by long
073      // identifier.
074      private final LinkedHashMap<String,Argument> namedArgsByLongID;
075    
076      // The full set of named arguments associated with this parser.
077      private final List<Argument> namedArgs;
078    
079      // Sets of arguments in which if the key argument is provided, then at least
080      // one of the value arguments must also be provided.
081      private final List<ObjectPair<Argument,Set<Argument>>> dependentArgumentSets;
082    
083      // Sets of arguments in which at most one argument in the list is allowed to
084      // be present.
085      private final List<Set<Argument>> exclusiveArgumentSets;
086    
087      // Sets of arguments in which at least one argument in the list is required to
088      // be present.
089      private final List<Set<Argument>> requiredArgumentSets;
090    
091      // The list of trailing arguments provided on the command line.
092      private final List<String> trailingArgs;
093    
094      // The description for the associated command.
095      private final String commandDescription;
096    
097      // The name for the associated command.
098      private final String commandName;
099    
100      // The placeholder string for the trailing arguments.
101      private final String trailingArgsPlaceholder;
102    
103    
104    
105      /**
106       * Creates a new instance of this argument parser with the provided
107       * information.  It will not allow unnamed trailing arguments.
108       *
109       * @param  commandName         The name of the application or utility with
110       *                             which this argument parser is associated.  It
111       *                             must not be {@code null}.
112       * @param  commandDescription  A description of the application or utility
113       *                             with which this argument parser is associated.
114       *                             It will be included in generated usage
115       *                             information.  It must not be {@code null}.
116       *
117       * @throws  ArgumentException  If either the command name or command
118       *                             description is {@code null},
119       */
120      public ArgumentParser(final String commandName,
121                            final String commandDescription)
122             throws ArgumentException
123      {
124        this(commandName, commandDescription, 0, null);
125      }
126    
127    
128    
129      /**
130       * Creates a new instance of this argument parser with the provided
131       * information.
132       *
133       * @param  commandName              The name of the application or utility
134       *                                  with which this argument parser is
135       *                                  associated.  It must not be {@code null}.
136       * @param  commandDescription       A description of the application or
137       *                                  utility with which this argument parser is
138       *                                  associated.  It will be included in
139       *                                  generated usage information.  It must not
140       *                                  be {@code null}.
141       * @param  maxTrailingArgs          The maximum number of trailing arguments
142       *                                  that may be provided to this command.  A
143       *                                  value of zero indicates that no trailing
144       *                                  arguments will be allowed.  A value less
145       *                                  than zero will indicate that there is no
146       *                                  limit on the number of trailing arguments
147       *                                  allowed.
148       * @param  trailingArgsPlaceholder  A placeholder string that will be included
149       *                                  in usage output to indicate what trailing
150       *                                  arguments may be provided.  It must not be
151       *                                  {@code null} if {@code maxTrailingArgs} is
152       *                                  anything other than zero.
153       *
154       * @throws  ArgumentException  If either the command name or command
155       *                             description is {@code null}, or if the maximum
156       *                             number of trailing arguments is non-zero and
157       *                             the trailing arguments placeholder is
158       *                             {@code null}.
159       */
160      public ArgumentParser(final String commandName,
161                            final String commandDescription,
162                            final int maxTrailingArgs,
163                            final String trailingArgsPlaceholder)
164             throws ArgumentException
165      {
166        if (commandName == null)
167        {
168          throw new ArgumentException(ERR_PARSER_COMMAND_NAME_NULL.get());
169        }
170    
171        if (commandDescription == null)
172        {
173          throw new ArgumentException(ERR_PARSER_COMMAND_DESCRIPTION_NULL.get());
174        }
175    
176        if ((maxTrailingArgs != 0) && (trailingArgsPlaceholder == null))
177        {
178          throw new ArgumentException(
179                         ERR_PARSER_TRAILING_ARGS_PLACEHOLDER_NULL.get());
180        }
181    
182        this.commandName             = commandName;
183        this.commandDescription      = commandDescription;
184        this.trailingArgsPlaceholder = trailingArgsPlaceholder;
185    
186        if (maxTrailingArgs >= 0)
187        {
188          this.maxTrailingArgs = maxTrailingArgs;
189        }
190        else
191        {
192          this.maxTrailingArgs = Integer.MAX_VALUE;
193        }
194    
195        namedArgsByShortID    = new LinkedHashMap<Character,Argument>();
196        namedArgsByLongID     = new LinkedHashMap<String,Argument>();
197        namedArgs             = new ArrayList<Argument>();
198        trailingArgs          = new ArrayList<String>();
199        dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>();
200        exclusiveArgumentSets = new ArrayList<Set<Argument>>();
201        requiredArgumentSets  = new ArrayList<Set<Argument>>();
202      }
203    
204    
205    
206      /**
207       * Creates a new argument parser that is a "clean" copy of the provided source
208       * argument parser.
209       *
210       * @param  source  The source argument parser to use for this argument parser.
211       */
212      private ArgumentParser(final ArgumentParser source)
213      {
214        commandName             = source.commandName;
215        commandDescription      = source.commandDescription;
216        maxTrailingArgs         = source.maxTrailingArgs;
217        trailingArgsPlaceholder = source.trailingArgsPlaceholder;
218    
219        trailingArgs = new ArrayList<String>();
220    
221        namedArgs = new ArrayList<Argument>(source.namedArgs.size());
222        namedArgsByLongID =
223             new LinkedHashMap<String,Argument>(source.namedArgsByLongID.size());
224        namedArgsByShortID = new LinkedHashMap<Character,Argument>(
225             source.namedArgsByShortID.size());
226    
227        final LinkedHashMap<String,Argument> argsByID =
228             new LinkedHashMap<String,Argument>(source.namedArgs.size());
229        for (final Argument sourceArg : source.namedArgs)
230        {
231          final Argument a = sourceArg.getCleanCopy();
232    
233          try
234          {
235            a.setRegistered();
236          }
237          catch (final ArgumentException ae)
238          {
239            // This should never happen.
240            Debug.debugException(ae);
241          }
242    
243          namedArgs.add(a);
244          argsByID.put(a.getIdentifierString(), a);
245    
246          for (final Character c : a.getShortIdentifiers())
247          {
248            namedArgsByShortID.put(c, a);
249          }
250    
251          for (final String s : a.getLongIdentifiers())
252          {
253            namedArgsByLongID.put(toLowerCase(s), a);
254          }
255        }
256    
257        dependentArgumentSets = new ArrayList<ObjectPair<Argument,Set<Argument>>>(
258             source.dependentArgumentSets.size());
259        for (final ObjectPair<Argument,Set<Argument>> p :
260             source.dependentArgumentSets)
261        {
262          final Set<Argument> sourceSet = p.getSecond();
263          final LinkedHashSet<Argument> newSet =
264               new LinkedHashSet<Argument>(sourceSet.size());
265          for (final Argument a : sourceSet)
266          {
267            newSet.add(argsByID.get(a.getIdentifierString()));
268          }
269    
270          final Argument sourceFirst = p.getFirst();
271          final Argument newFirst = argsByID.get(sourceFirst.getIdentifierString());
272          dependentArgumentSets.add(
273               new ObjectPair<Argument, Set<Argument>>(newFirst, newSet));
274        }
275    
276        exclusiveArgumentSets =
277             new ArrayList<Set<Argument>>(source.exclusiveArgumentSets.size());
278        for (final Set<Argument> sourceSet : source.exclusiveArgumentSets)
279        {
280          final LinkedHashSet<Argument> newSet =
281               new LinkedHashSet<Argument>(sourceSet.size());
282          for (final Argument a : sourceSet)
283          {
284            newSet.add(argsByID.get(a.getIdentifierString()));
285          }
286    
287          exclusiveArgumentSets.add(newSet);
288        }
289    
290        requiredArgumentSets =
291             new ArrayList<Set<Argument>>(source.requiredArgumentSets.size());
292        for (final Set<Argument> sourceSet : source.requiredArgumentSets)
293        {
294          final LinkedHashSet<Argument> newSet =
295               new LinkedHashSet<Argument>(sourceSet.size());
296          for (final Argument a : sourceSet)
297          {
298            newSet.add(argsByID.get(a.getIdentifierString()));
299          }
300          requiredArgumentSets.add(newSet);
301        }
302      }
303    
304    
305    
306      /**
307       * Retrieves the name of the application or utility with which this command
308       * line argument parser is associated.
309       *
310       * @return  The name of the application or utility with which this command
311       *          line argument parser is associated.
312       */
313      public String getCommandName()
314      {
315        return commandName;
316      }
317    
318    
319    
320      /**
321       * Retrieves a description of the application or utility with which this
322       * command line argument parser is associated.
323       *
324       * @return  A description of the application or utility with which this
325       *          command line argument parser is associated.
326       */
327      public String getCommandDescription()
328      {
329        return commandDescription;
330      }
331    
332    
333    
334      /**
335       * Indicates whether this argument parser allows any unnamed trailing
336       * arguments to be provided.
337       *
338       * @return  {@code true} if at least one unnamed trailing argument may be
339       *          provided, or {@code false} if not.
340       */
341      public boolean allowsTrailingArguments()
342      {
343        return (maxTrailingArgs != 0);
344      }
345    
346    
347    
348      /**
349       * Retrieves the placeholder string that will be provided in usage information
350       * to indicate what may be included in the trailing arguments.
351       *
352       * @return  The placeholder string that will be provided in usage information
353       *          to indicate what may be included in the trailing arguments, or
354       *          {@code null} if unnamed trailing arguments are not allowed.
355       */
356      public String getTrailingArgumentsPlaceholder()
357      {
358        return trailingArgsPlaceholder;
359      }
360    
361    
362    
363      /**
364       * Retrieves the maximum number of unnamed trailing arguments that may be
365       * provided.
366       *
367       * @return  The maximum number of unnamed trailing arguments that may be
368       *          provided.
369       */
370      public int getMaxTrailingArguments()
371      {
372        return maxTrailingArgs;
373      }
374    
375    
376    
377      /**
378       * Retrieves the named argument with the specified short identifier.
379       *
380       * @param  shortIdentifier  The short identifier of the argument to retrieve.
381       *                          It must not be {@code null}.
382       *
383       * @return  The named argument with the specified short identifier, or
384       *          {@code null} if there is no such argument.
385       */
386      public Argument getNamedArgument(final Character shortIdentifier)
387      {
388        ensureNotNull(shortIdentifier);
389        return namedArgsByShortID.get(shortIdentifier);
390      }
391    
392    
393    
394      /**
395       * Retrieves the named argument with the specified long identifier.
396       *
397       * @param  longIdentifier  The long identifier of the argument to retrieve.
398       *                         It must not be {@code null}.
399       *
400       * @return  The named argument with the specified long identifier, or
401       *          {@code null} if there is no such argument.
402       */
403      public Argument getNamedArgument(final String longIdentifier)
404      {
405        ensureNotNull(longIdentifier);
406        return namedArgsByLongID.get(toLowerCase(longIdentifier));
407      }
408    
409    
410    
411      /**
412       * Retrieves the set of named arguments defined for use with this argument
413       * parser.
414       *
415       * @return  The set of named arguments defined for use with this argument
416       *          parser.
417       */
418      public List<Argument> getNamedArguments()
419      {
420        return Collections.unmodifiableList(namedArgs);
421      }
422    
423    
424    
425      /**
426       * Registers the provided argument with this argument parser.
427       *
428       * @param  argument  The argument to be registered.
429       *
430       * @throws  ArgumentException  If the provided argument conflicts with another
431       *                             argument already registered with this parser.
432       */
433      public void addArgument(final Argument argument)
434             throws ArgumentException
435      {
436        argument.setRegistered();
437        for (final Character c : argument.getShortIdentifiers())
438        {
439          if (namedArgsByShortID.containsKey(c))
440          {
441            throw new ArgumentException(ERR_PARSER_SHORT_ID_CONFLICT.get(c));
442          }
443        }
444    
445        for (final String s : argument.getLongIdentifiers())
446        {
447          if (namedArgsByLongID.containsKey(toLowerCase(s)))
448          {
449            throw new ArgumentException(ERR_PARSER_LONG_ID_CONFLICT.get(s));
450          }
451        }
452    
453        for (final Character c : argument.getShortIdentifiers())
454        {
455          namedArgsByShortID.put(c, argument);
456        }
457    
458        for (final String s : argument.getLongIdentifiers())
459        {
460          namedArgsByLongID.put(toLowerCase(s), argument);
461        }
462    
463        namedArgs.add(argument);
464      }
465    
466    
467    
468      /**
469       * Retrieves the list of dependent argument sets for this argument parser.  If
470       * an argument contained as the first object in the pair in a dependent
471       * argument set is provided, then at least one of the arguments in the paired
472       * set must also be provided.
473       *
474       * @return  The list of dependent argument sets for this argument parser.
475       */
476      public List<ObjectPair<Argument,Set<Argument>>> getDependentArgumentSets()
477      {
478        return Collections.unmodifiableList(dependentArgumentSets);
479      }
480    
481    
482    
483      /**
484       * Adds the provided collection of arguments as dependent upon the given
485       * argument.
486       *
487       * @param  targetArgument      The argument whose presence indicates that at
488       *                             least one of the dependent arguments must also
489       *                             be present.  It must not be {@code null}.
490       * @param  dependentArguments  The set of arguments from which at least one
491       *                             argument must be present if the target argument
492       *                             is present.  It must not be {@code null} or
493       *                             empty.
494       */
495      public void addDependentArgumentSet(final Argument targetArgument,
496                       final Collection<Argument> dependentArguments)
497      {
498        ensureNotNull(targetArgument, dependentArguments);
499    
500        final LinkedHashSet<Argument> argSet =
501             new LinkedHashSet<Argument>(dependentArguments);
502        dependentArgumentSets.add(
503             new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
504      }
505    
506    
507    
508      /**
509       * Adds the provided collection of arguments as dependent upon the given
510       * argument.
511       *
512       * @param  targetArgument  The argument whose presence indicates that at least
513       *                         one of the dependent arguments must also be
514       *                         present.  It must not be {@code null}.
515       * @param  dependentArg1   The first argument in the set of arguments in which
516       *                         at least one argument must be present if the target
517       *                         argument is present.  It must not be {@code null}.
518       * @param  remaining       The remaining arguments in the set of arguments in
519       *                         which at least one argument must be present if the
520       *                         target argument is present.
521       */
522      public void addDependentArgumentSet(final Argument targetArgument,
523                                          final Argument dependentArg1,
524                                          final Argument... remaining)
525      {
526        ensureNotNull(targetArgument, dependentArg1);
527    
528        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
529        argSet.add(dependentArg1);
530        argSet.addAll(Arrays.asList(remaining));
531    
532        dependentArgumentSets.add(
533             new ObjectPair<Argument,Set<Argument>>(targetArgument, argSet));
534      }
535    
536    
537    
538      /**
539       * Retrieves the list of exclusive argument sets for this argument parser.
540       * If an argument contained in an exclusive argument set is provided, then
541       * none of the other arguments in that set may be provided.  It is acceptable
542       * for none of the arguments in the set to be provided, unless the same set
543       * of arguments is also defined as a required argument set.
544       *
545       * @return  The list of exclusive argument sets for this argument parser.
546       */
547      public List<Set<Argument>> getExclusiveArgumentSets()
548      {
549        return Collections.unmodifiableList(exclusiveArgumentSets);
550      }
551    
552    
553    
554      /**
555       * Adds the provided collection of arguments as an exclusive argument set, in
556       * which at most one of the arguments may be provided.
557       *
558       * @param  exclusiveArguments  The collection of arguments to form an
559       *                             exclusive argument set.  It must not be
560       *                             {@code null}.
561       */
562      public void addExclusiveArgumentSet(
563                       final Collection<Argument> exclusiveArguments)
564      {
565        ensureNotNull(exclusiveArguments);
566        final LinkedHashSet<Argument> argSet =
567             new LinkedHashSet<Argument>(exclusiveArguments);
568        exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
569      }
570    
571    
572    
573      /**
574       * Adds the provided set of arguments as an exclusive argument set, in
575       * which at most one of the arguments may be provided.
576       *
577       * @param  arg1       The first argument to include in the exclusive argument
578       *                    set.  It must not be {@code null}.
579       * @param  arg2       The second argument to include in the exclusive argument
580       *                    set.  It must not be {@code null}.
581       * @param  remaining  Any additional arguments to include in the exclusive
582       *                    argument set.
583       */
584      public void addExclusiveArgumentSet(final Argument arg1, final Argument arg2,
585                                          final Argument... remaining)
586      {
587        ensureNotNull(arg1, arg2);
588    
589        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
590        argSet.add(arg1);
591        argSet.add(arg2);
592        argSet.addAll(Arrays.asList(remaining));
593    
594        exclusiveArgumentSets.add(Collections.unmodifiableSet(argSet));
595      }
596    
597    
598    
599      /**
600       * Retrieves the list of required argument sets for this argument parser.  At
601       * least one of the arguments contained in this set must be provided.  If this
602       * same set is also defined as an exclusive argument set, then exactly one
603       * of those arguments must be provided.
604       *
605       * @return  The list of required argument sets for this argument parser.
606       */
607      public List<Set<Argument>> getRequiredArgumentSets()
608      {
609        return Collections.unmodifiableList(requiredArgumentSets);
610      }
611    
612    
613    
614      /**
615       * Adds the provided collection of arguments as a required argument set, in
616       * which at least one of the arguments must be provided.
617       *
618       * @param  requiredArguments  The collection of arguments to form an
619       *                            required argument set.  It must not be
620       *                            {@code null}.
621       */
622      public void addRequiredArgumentSet(
623                       final Collection<Argument> requiredArguments)
624      {
625        ensureNotNull(requiredArguments);
626        final LinkedHashSet<Argument> argSet =
627             new LinkedHashSet<Argument>(requiredArguments);
628        requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
629      }
630    
631    
632    
633      /**
634       * Adds the provided set of arguments as a required argument set, in which
635       * at least one of the arguments must be provided.
636       *
637       * @param  arg1       The first argument to include in the required argument
638       *                    set.  It must not be {@code null}.
639       * @param  arg2       The second argument to include in the required argument
640       *                    set.  It must not be {@code null}.
641       * @param  remaining  Any additional arguments to include in the required
642       *                    argument set.
643       */
644      public void addRequiredArgumentSet(final Argument arg1, final Argument arg2,
645                                         final Argument... remaining)
646      {
647        ensureNotNull(arg1, arg2);
648    
649        final LinkedHashSet<Argument> argSet = new LinkedHashSet<Argument>();
650        argSet.add(arg1);
651        argSet.add(arg2);
652        argSet.addAll(Arrays.asList(remaining));
653    
654        requiredArgumentSets.add(Collections.unmodifiableSet(argSet));
655      }
656    
657    
658    
659      /**
660       * Retrieves the set of unnamed trailing arguments in the provided command
661       * line arguments.
662       *
663       * @return  The set of unnamed trailing arguments in the provided command line
664       *          arguments, or an empty list if there were none.
665       */
666      public List<String> getTrailingArguments()
667      {
668        return Collections.unmodifiableList(trailingArgs);
669      }
670    
671    
672    
673      /**
674       * Creates a copy of this argument parser that is "clean" and appears as if it
675       * has not been used to parse an argument set.  The new parser will have all
676       * of the same arguments and constraints as this parser.
677       *
678       * @return  The "clean" copy of this argument parser.
679       */
680      public ArgumentParser getCleanCopy()
681      {
682        return new ArgumentParser(this);
683      }
684    
685    
686    
687      /**
688       * Parses the provided set of arguments.
689       *
690       * @param  args  An array containing the argument information to parse.  It
691       *               must not be {@code null}.
692       *
693       * @throws  ArgumentException  If a problem occurs while attempting to parse
694       *                             the argument information.
695       */
696      public void parse(final String[] args)
697             throws ArgumentException
698      {
699        // Iterate through the provided args strings and process them.
700        boolean inTrailingArgs   = false;
701        boolean usageArgProvided = false;
702        for (int i=0; i < args.length; i++)
703        {
704          final String s = args[i];
705    
706          if (inTrailingArgs)
707          {
708            if (maxTrailingArgs == 0)
709            {
710              throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
711                                               s, commandName));
712            }
713            else if (trailingArgs.size() >= maxTrailingArgs)
714            {
715              throw new ArgumentException(ERR_PARSER_TOO_MANY_TRAILING_ARGS.get(s,
716                                               commandName, maxTrailingArgs));
717            }
718            else
719            {
720              trailingArgs.add(s);
721            }
722          }
723          else if (s.equals("--"))
724          {
725            // This signifies the end of the named arguments and the beginning of
726            // the trailing arguments.
727            inTrailingArgs = true;
728          }
729          else if (s.startsWith("--"))
730          {
731            // There may be an equal sign to separate the name from the value.
732            final String argName;
733            final int equalPos = s.indexOf('=');
734            if (equalPos > 0)
735            {
736              argName = s.substring(2, equalPos);
737            }
738            else
739            {
740              argName = s.substring(2);
741            }
742    
743            final Argument a = namedArgsByLongID.get(toLowerCase(argName));
744            if (a == null)
745            {
746              throw new ArgumentException(ERR_PARSER_NO_SUCH_LONG_ID.get(argName));
747            }
748            else if(a.isUsageArgument())
749            {
750              usageArgProvided = true;
751            }
752    
753            a.incrementOccurrences();
754            if (a.takesValue())
755            {
756              if (equalPos > 0)
757              {
758                a.addValue(s.substring(equalPos+1));
759              }
760              else
761              {
762                i++;
763                if (i >= args.length)
764                {
765                  throw new ArgumentException(ERR_PARSER_LONG_ARG_MISSING_VALUE.get(
766                                                   argName));
767                }
768                else
769                {
770                  a.addValue(args[i]);
771                }
772              }
773            }
774            else
775            {
776              if (equalPos > 0)
777              {
778                throw new ArgumentException(
779                               ERR_PARSER_LONG_ARG_DOESNT_TAKE_VALUE.get(argName));
780              }
781            }
782          }
783          else if (s.startsWith("-"))
784          {
785            if (s.length() == 1)
786            {
787              throw new ArgumentException(ERR_PARSER_UNEXPECTED_DASH.get());
788            }
789            else if (s.length() == 2)
790            {
791              final char c = s.charAt(1);
792              final Argument a = namedArgsByShortID.get(c);
793              if (a == null)
794              {
795                throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
796              }
797              else if(a.isUsageArgument())
798              {
799                usageArgProvided = true;
800              }
801    
802              a.incrementOccurrences();
803              if (a.takesValue())
804              {
805                i++;
806                if (i >= args.length)
807                {
808                  throw new ArgumentException(
809                                 ERR_PARSER_SHORT_ARG_MISSING_VALUE.get(c));
810                }
811                else
812                {
813                  a.addValue(args[i]);
814                }
815              }
816            }
817            else
818            {
819              char c = s.charAt(1);
820              Argument a = namedArgsByShortID.get(c);
821              if (a == null)
822              {
823                throw new ArgumentException(ERR_PARSER_NO_SUCH_SHORT_ID.get(c));
824              }
825              else if(a.isUsageArgument())
826              {
827                usageArgProvided = true;
828              }
829    
830              a.incrementOccurrences();
831              if (a.takesValue())
832              {
833                a.addValue(s.substring(2));
834              }
835              else
836              {
837                // The rest of the characters in the string must also resolve to
838                // arguments that don't take values.
839                for (int j=2; j < s.length(); j++)
840                {
841                  c = s.charAt(j);
842                  a = namedArgsByShortID.get(c);
843                  if (a == null)
844                  {
845                    throw new ArgumentException(
846                                   ERR_PARSER_NO_SUBSEQUENT_SHORT_ARG.get(c, s));
847                  }
848                  else if(a.isUsageArgument())
849                  {
850                    usageArgProvided = true;
851                  }
852    
853                  a.incrementOccurrences();
854                  if (a.takesValue())
855                  {
856                    throw new ArgumentException(
857                                   ERR_PARSER_SUBSEQUENT_SHORT_ARG_TAKES_VALUE.get(
858                                        c, s));
859                  }
860                }
861              }
862            }
863          }
864          else
865          {
866            inTrailingArgs = true;
867            if (maxTrailingArgs == 0)
868            {
869              throw new ArgumentException(ERR_PARSER_TRAILING_ARGS_NOT_ALLOWED.get(
870                                               s, commandName));
871            }
872            else
873            {
874              trailingArgs.add(s);
875            }
876          }
877        }
878    
879    
880        // If a usage argument was provided, then no further validation should be
881        // performed.
882        if (usageArgProvided)
883        {
884          return;
885        }
886    
887    
888        // Make sure that all required arguments have values.
889        for (final Argument a : namedArgs)
890        {
891          if (a.isRequired() && (! a.isPresent()))
892          {
893            throw new ArgumentException(ERR_PARSER_MISSING_REQUIRED_ARG.get(
894                                             a.getIdentifierString()));
895          }
896        }
897    
898    
899        // Make sure that there are no dependent argument set conflicts.
900        for (final ObjectPair<Argument,Set<Argument>> p : dependentArgumentSets)
901        {
902          final Argument targetArg = p.getFirst();
903          if (targetArg.isPresent())
904          {
905            final Set<Argument> argSet = p.getSecond();
906            boolean found = false;
907            for (final Argument a : argSet)
908            {
909              if (a.isPresent())
910              {
911                found = true;
912                break;
913              }
914            }
915    
916            if (! found)
917            {
918              if (argSet.size() == 1)
919              {
920                throw new ArgumentException(
921                     ERR_PARSER_DEPENDENT_CONFLICT_SINGLE.get(
922                          targetArg.getIdentifierString(),
923                          argSet.iterator().next().getIdentifierString()));
924              }
925              else
926              {
927                boolean first = true;
928                final StringBuilder buffer = new StringBuilder();
929                for (final Argument a : argSet)
930                {
931                  if (first)
932                  {
933                    first = false;
934                  }
935                  else
936                  {
937                    buffer.append(", ");
938                  }
939                  buffer.append(a.getIdentifierString());
940                }
941                throw new ArgumentException(
942                     ERR_PARSER_DEPENDENT_CONFLICT_MULTIPLE.get(
943                          targetArg.getIdentifierString(), buffer.toString()));
944              }
945            }
946          }
947        }
948    
949    
950        // Make sure that there are no exclusive argument set conflicts.
951        for (final Set<Argument> argSet : exclusiveArgumentSets)
952        {
953          Argument setArg = null;
954          for (final Argument a : argSet)
955          {
956            if (a.isPresent())
957            {
958              if (setArg == null)
959              {
960                setArg = a;
961              }
962              else
963              {
964                throw new ArgumentException(ERR_PARSER_EXCLUSIVE_CONFLICT.get(
965                                                 setArg.getIdentifierString(),
966                                                 a.getIdentifierString()));
967              }
968            }
969          }
970        }
971    
972        // Make sure that there are no required argument set conflicts.
973        for (final Set<Argument> argSet : requiredArgumentSets)
974        {
975          boolean found = false;
976          for (final Argument a : argSet)
977          {
978            if (a.isPresent())
979            {
980              found = true;
981              break;
982            }
983          }
984    
985          if (! found)
986          {
987            boolean first = true;
988            final StringBuilder buffer = new StringBuilder();
989            for (final Argument a : argSet)
990            {
991              if (first)
992              {
993                first = false;
994              }
995              else
996              {
997                buffer.append(", ");
998              }
999              buffer.append(a.getIdentifierString());
1000            }
1001            throw new ArgumentException(ERR_PARSER_REQUIRED_CONFLICT.get(
1002                                             buffer.toString()));
1003          }
1004        }
1005      }
1006    
1007    
1008    
1009      /**
1010       * Retrieves lines that make up the usage information for this program,
1011       * optionally wrapping long lines.
1012       *
1013       * @param  maxWidth  The maximum line width to use for the output.  If this is
1014       *                   less than or equal to zero, then no wrapping will be
1015       *                   performed.
1016       *
1017       * @return  The lines that make up the usage information for this program.
1018       */
1019      public List<String> getUsage(final int maxWidth)
1020      {
1021        final ArrayList<String> lines = new ArrayList<String>(100);
1022    
1023        // First is a description of the command.
1024        lines.addAll(wrapLine(commandDescription, maxWidth));
1025        lines.add("");
1026    
1027        // Next comes the usage.  It may include neither, either, or both of the
1028        // set of options and trailing arguments.
1029        if (namedArgs.isEmpty())
1030        {
1031          if (maxTrailingArgs == 0)
1032          {
1033            lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_NOTRAILING.get(commandName),
1034                                  maxWidth));
1035          }
1036          else
1037          {
1038            lines.addAll(wrapLine(INFO_USAGE_NOOPTIONS_TRAILING.get(
1039                                       commandName, trailingArgsPlaceholder),
1040                                  maxWidth));
1041          }
1042        }
1043        else
1044        {
1045          if (maxTrailingArgs == 0)
1046          {
1047            lines.addAll(wrapLine(INFO_USAGE_OPTIONS_NOTRAILING.get(commandName),
1048                                  maxWidth));
1049          }
1050          else
1051          {
1052            lines.addAll(wrapLine(INFO_USAGE_OPTIONS_TRAILING.get(
1053                                       commandName, trailingArgsPlaceholder),
1054                                  maxWidth));
1055          }
1056    
1057          lines.add("");
1058          lines.add(INFO_USAGE_OPTIONS_INCLUDE.get());
1059    
1060    
1061          // We know that there are named arguments, so we'll want to display them
1062          // and their descriptions.
1063          for (final Argument a : namedArgs)
1064          {
1065            if (a.isHidden())
1066            {
1067              // This shouldn't be included in the usage output.
1068              continue;
1069            }
1070    
1071            final StringBuilder argLine = new StringBuilder();
1072            boolean first = true;
1073            for (final Character c : a.getShortIdentifiers())
1074            {
1075              if (first)
1076              {
1077                argLine.append('-');
1078                first = false;
1079              }
1080              else
1081              {
1082                argLine.append(", -");
1083              }
1084              argLine.append(c);
1085            }
1086    
1087            for (final String s : a.getLongIdentifiers())
1088            {
1089              if (first)
1090              {
1091                argLine.append("--");
1092              }
1093              else
1094              {
1095                argLine.append(", --");
1096              }
1097              argLine.append(s);
1098            }
1099    
1100            final String valuePlaceholder = a.getValuePlaceholder();
1101            if (valuePlaceholder != null)
1102            {
1103              argLine.append(' ');
1104              argLine.append(valuePlaceholder);
1105            }
1106    
1107            // NOTE:  This line won't be wrapped.  That's intentional because I
1108            // think it would probably look bad no matter how we did it.
1109            lines.add(argLine.toString());
1110    
1111            // The description should be wrapped, if necessary.  We'll also want to
1112            // indent it (unless someone chose an absurdly small wrap width) to make
1113            // it stand out from the argument lines.
1114            final String description = a.getDescription();
1115            if (maxWidth > 10)
1116            {
1117              final List<String> descLines = wrapLine(description, (maxWidth-4));
1118              for (final String s : descLines)
1119              {
1120                lines.add("    " + s);
1121              }
1122            }
1123            else
1124            {
1125              lines.addAll(wrapLine(description, maxWidth));
1126            }
1127          }
1128        }
1129    
1130        return lines;
1131      }
1132    
1133    
1134    
1135      /**
1136       * Writes usage information for this program to the provided output stream
1137       * using the UTF-8 encoding, optionally wrapping long lines.
1138       *
1139       * @param  outputStream  The output stream to which the usage information
1140       *                       should be written.  It must not be {@code null}.
1141       * @param  maxWidth      The maximum line width to use for the output.  If
1142       *                       this is less than or equal to zero, then no wrapping
1143       *                       will be performed.
1144       *
1145       * @throws  IOException  If an error occurs while attempting to write to the
1146       *                       provided output stream.
1147       */
1148      public void getUsage(final OutputStream outputStream, final int maxWidth)
1149             throws IOException
1150      {
1151        final List<String> usageLines = getUsage(maxWidth);
1152        for (final String s : usageLines)
1153        {
1154          outputStream.write(getBytes(s));
1155          outputStream.write(EOL_BYTES);
1156        }
1157      }
1158    
1159    
1160    
1161      /**
1162       * Retrieves a string representation of the usage information.
1163       *
1164       * @param  maxWidth  The maximum line width to use for the output.  If this is
1165       *                   less than or equal to zero, then no wrapping will be
1166       *                   performed.
1167       *
1168       * @return  A string representation of the usage information
1169       */
1170      public String getUsageString(final int maxWidth)
1171      {
1172        final StringBuilder buffer = new StringBuilder();
1173        getUsageString(buffer, maxWidth);
1174        return buffer.toString();
1175      }
1176    
1177    
1178    
1179      /**
1180       * Appends a string representation of the usage information to the provided
1181       * buffer.
1182       *
1183       * @param  buffer    The buffer to which the information should be appended.
1184       * @param  maxWidth  The maximum line width to use for the output.  If this is
1185       *                   less than or equal to zero, then no wrapping will be
1186       *                   performed.
1187       */
1188      public void getUsageString(final StringBuilder buffer, final int maxWidth)
1189      {
1190        for (final String line : getUsage(maxWidth))
1191        {
1192          buffer.append(line);
1193          buffer.append(EOL);
1194        }
1195      }
1196    
1197    
1198    
1199      /**
1200       * Retrieves a string representation of this argument parser.
1201       *
1202       * @return  A string representation of this argument parser.
1203       */
1204      @Override()
1205      public String toString()
1206      {
1207        final StringBuilder buffer = new StringBuilder();
1208        toString(buffer);
1209        return buffer.toString();
1210      }
1211    
1212    
1213    
1214      /**
1215       * Appends a string representation of this argument parser to the provided
1216       * buffer.
1217       *
1218       * @param  buffer  The buffer to which the information should be appended.
1219       */
1220      public void toString(final StringBuilder buffer)
1221      {
1222        buffer.append("ArgumentParser(commandName='");
1223        buffer.append(commandName);
1224        buffer.append("', commandDescription='");
1225        buffer.append(commandDescription);
1226        buffer.append("', maxTrailingArgs=");
1227        buffer.append(maxTrailingArgs);
1228    
1229        if (trailingArgsPlaceholder != null)
1230        {
1231          buffer.append(", trailingArgsPlaceholder='");
1232          buffer.append(trailingArgsPlaceholder);
1233          buffer.append('\'');
1234        }
1235    
1236        buffer.append("namedArgs={");
1237    
1238        final Iterator<Argument> iterator = namedArgs.iterator();
1239        while (iterator.hasNext())
1240        {
1241          iterator.next().toString(buffer);
1242          if (iterator.hasNext())
1243          {
1244            buffer.append(", ");
1245          }
1246        }
1247    
1248        buffer.append("})");
1249      }
1250    }