001/*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 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.IOException;
026import java.io.OutputStream;
027import java.io.Serializable;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.Iterator;
033import java.util.LinkedHashSet;
034import java.util.LinkedHashMap;
035import java.util.List;
036import java.util.Set;
037
038import com.unboundid.util.Debug;
039import com.unboundid.util.ObjectPair;
040import com.unboundid.util.ThreadSafety;
041import com.unboundid.util.ThreadSafetyLevel;
042
043import static com.unboundid.util.StaticUtils.*;
044import static com.unboundid.util.Validator.*;
045import 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)
055public 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}