001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.util.args;
028    import org.opends.messages.Message;
029    import org.opends.messages.MessageBuilder;
030    
031    import static org.opends.messages.UtilityMessages.*;
032    import static org.opends.server.tools.ToolConstants.*;
033    import static org.opends.server.util.ServerConstants.*;
034    import static org.opends.server.util.StaticUtils.*;
035    
036    import java.io.FileInputStream;
037    import java.io.IOException;
038    import java.io.OutputStream;
039    import java.util.ArrayList;
040    import java.util.Collection;
041    import java.util.Collections;
042    import java.util.HashMap;
043    import java.util.LinkedList;
044    import java.util.Map;
045    import java.util.Properties;
046    import java.util.SortedMap;
047    import java.util.TreeMap;
048    
049    import org.opends.server.core.DirectoryServer;
050    import org.opends.server.util.SetupUtils;
051    
052    
053    
054    /**
055     * This class defines a variant of the argument parser that can be used with
056     * applications that use subcommands to customize their behavior and that have a
057     * different set of options per subcommand (e.g, "cvs checkout" takes different
058     * options than "cvs commit").  This parser also has the ability to use global
059     * options that will always be applicable regardless of the subcommand in
060     * addition to the subcommand-specific arguments.  There must not be any
061     * conflicts between the global options and the option for any subcommand, but
062     * it is allowed to re-use subcommand-specific options for different purposes
063     * between different subcommands.
064     */
065    public class SubCommandArgumentParser extends ArgumentParser
066    {
067      // The argument that will be used to trigger the display of usage information.
068      private Argument usageArgument;
069    
070      // The arguments that will be used to trigger the display of usage
071      // information for groups of sub-commands.
072      private Map<Argument, Collection<SubCommand>> usageGroupArguments;
073    
074      // The set of unnamed trailing arguments that were provided for this parser.
075      private ArrayList<String> trailingArguments;
076    
077      // Indicates whether subcommand and long argument names should be treated in a
078      // case-sensitive manner.
079      private boolean longArgumentsCaseSensitive;
080    
081      // Indicates whether the usage information has been displayed.
082      private boolean usageOrVersionDisplayed;
083    
084      // The set of global arguments defined for this parser, referenced by short
085      // ID.
086      private HashMap<Character,Argument> globalShortIDMap;
087    
088      //  The set of global arguments defined for this parser, referenced by
089      // argument name.
090      private HashMap<String,Argument> globalArgumentMap;
091    
092      //  The set of global arguments defined for this parser, referenced by long
093      // ID.
094      private HashMap<String,Argument> globalLongIDMap;
095    
096      // The set of subcommands defined for this parser, referenced by subcommand
097      // name.
098      private SortedMap<String,SubCommand> subCommands;
099    
100      // The total set of global arguments defined for this parser.
101      private LinkedList<Argument> globalArgumentList;
102    
103      // The output stream to which usage information should be printed.
104      private OutputStream usageOutputStream;
105    
106      // The fully-qualified name of the Java class that should be invoked to launch
107      // the program with which this argument parser is associated.
108      private String mainClassName;
109    
110      // A human-readable description for the tool, which will be included when
111      // displaying usage information.
112      private Message toolDescription;
113    
114      // The raw set of command-line arguments that were provided.
115      private String[] rawArguments;
116    
117      // The subcommand requested by the user as part of the command-line arguments.
118      private SubCommand subCommand;
119    
120      //Indicates whether the version argument was provided.
121      private boolean versionPresent;
122    
123      private final static String INDENT = "    ";
124      private final static int MAX_LENGTH = SetupUtils.isWindows() ? 79 : 80;
125    
126    
127      /**
128       * Creates a new instance of this subcommand argument parser with no
129       * arguments.
130       *
131       * @param  mainClassName               The fully-qualified name of the Java
132       *                                     class that should be invoked to launch
133       *                                     the program with which this argument
134       *                                     parser is associated.
135       * @param  toolDescription             A human-readable description for the
136       *                                     tool, which will be included when
137       *                                     displaying usage information.
138       * @param  longArgumentsCaseSensitive  Indicates whether subcommand and long
139       *                                     argument names should be treated in a
140       *                                     case-sensitive manner.
141       */
142      public SubCommandArgumentParser(String mainClassName, Message toolDescription,
143                                      boolean longArgumentsCaseSensitive)
144      {
145        super(mainClassName, toolDescription, longArgumentsCaseSensitive);
146        this.mainClassName              = mainClassName;
147        this.toolDescription            = toolDescription;
148        this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
149    
150        trailingArguments   = new ArrayList<String>();
151        globalArgumentList  = new LinkedList<Argument>();
152        globalArgumentMap   = new HashMap<String,Argument>();
153        globalShortIDMap    = new HashMap<Character,Argument>();
154        globalLongIDMap     = new HashMap<String,Argument>();
155        usageGroupArguments = new HashMap<Argument, Collection<SubCommand>>();
156        subCommands         = new TreeMap<String,SubCommand>();
157        usageOrVersionDisplayed = false;
158        rawArguments        = null;
159        subCommand          = null;
160        usageArgument       = null;
161        usageOutputStream   = null;
162      }
163    
164    
165    
166      /**
167       * Retrieves the fully-qualified name of the Java class that should be invoked
168       * to launch the program with which this argument parser is associated.
169       *
170       * @return  The fully-qualified name of the Java class that should be invoked
171       *          to launch the program with which this argument parser is
172       *          associated.
173       */
174      public String getMainClassName()
175      {
176        return mainClassName;
177      }
178    
179    
180    
181      /**
182       * Retrieves a human-readable description for this tool, which should be
183       * included at the top of the command-line usage information.
184       *
185       * @return  A human-readable description for this tool, or {@code null} if
186       *          none is available.
187       */
188      public Message getToolDescription()
189      {
190        return toolDescription;
191      }
192    
193    
194    
195      /**
196       * Indicates whether subcommand names and long argument strings should be
197       * treated in a case-sensitive manner.
198       *
199       * @return  <CODE>true</CODE> if subcommand names and long argument strings
200       *          should be treated in a case-sensitive manner, or
201       *          <CODE>false</CODE> if they should not.
202       */
203      public boolean longArgumentsCaseSensitive()
204      {
205        return longArgumentsCaseSensitive;
206      }
207    
208    
209    
210      /**
211       * Retrieves the list of all global arguments that have been defined for this
212       * argument parser.
213       *
214       * @return  The list of all global arguments that have been defined for this
215       *          argument parser.
216       */
217      public LinkedList<Argument> getGlobalArgumentList()
218      {
219        return globalArgumentList;
220      }
221    
222    
223    
224      /**
225       * Indicates whether this argument parser contains a global argument with the
226       * specified name.
227       *
228       * @param  argumentName  The name for which to make the determination.
229       *
230       * @return  <CODE>true</CODE> if a global argument exists with the specified
231       *          name, or <CODE>false</CODE> if not.
232       */
233      public boolean hasGlobalArgument(String argumentName)
234      {
235        return globalArgumentMap.containsKey(argumentName);
236      }
237    
238    
239    
240      /**
241       * Retrieves the global argument with the specified name.
242       *
243       * @param  name  The name of the global argument to retrieve.
244       *
245       * @return  The global argument with the specified name, or <CODE>null</CODE>
246       *          if there is no such argument.
247       */
248      public Argument getGlobalArgument(String name)
249      {
250        return globalArgumentMap.get(name);
251      }
252    
253    
254    
255      /**
256       * Retrieves the set of global arguments mapped by the short identifier that
257       * may be used to reference them.  Note that arguments that do not have a
258       * short identifier will not be present in this list.
259       *
260       * @return  The set of global arguments mapped by the short identifier that
261       *          may be used to reference them.
262       */
263      public HashMap<Character,Argument> getGlobalArgumentsByShortID()
264      {
265        return globalShortIDMap;
266      }
267    
268    
269    
270      /**
271       * Indicates whether this argument parser has a global argument with the
272       * specified short ID.
273       *
274       * @param  shortID  The short ID character for which to make the
275       *                  determination.
276       *
277       * @return  <CODE>true</CODE> if a global argument exists with the specified
278       *          short ID, or <CODE>false</CODE> if not.
279       */
280      public boolean hasGlobalArgumentWithShortID(Character shortID)
281      {
282        return globalShortIDMap.containsKey(shortID);
283      }
284    
285    
286    
287      /**
288       * Retrieves the global argument with the specified short identifier.
289       *
290       * @param  shortID  The short identifier for the global argument to retrieve.
291       *
292       * @return  The global argument with the specified short identifier, or
293       *          <CODE>null</CODE> if there is no such argument.
294       */
295      public Argument getGlobalArgumentForShortID(Character shortID)
296      {
297        return globalShortIDMap.get(shortID);
298      }
299    
300    
301    
302      /**
303       * Retrieves the set of global arguments mapped by the long identifier that
304       * may be used to reference them.  Note that arguments that do not have a long
305       * identifier will not be present in this list.
306       *
307       * @return  The set of global arguments mapped by the long identifier that may
308       *          be used to reference them.
309       */
310      public HashMap<String,Argument> getGlobalArgumentsByLongID()
311      {
312        return globalLongIDMap;
313      }
314    
315    
316    
317      /**
318       * Indicates whether this argument parser has a global argument with the
319       * specified long ID.
320       *
321       * @param  longID  The long ID string for which to make the determination.
322       *
323       * @return  <CODE>true</CODE> if a global argument exists with the specified
324       *          long ID, or <CODE>false</CODE> if not.
325       */
326      public boolean hasGlobalArgumentWithLongID(String longID)
327      {
328        return globalLongIDMap.containsKey(longID);
329      }
330    
331    
332    
333      /**
334       * Retrieves the global argument with the specified long identifier.
335       *
336       * @param  longID  The long identifier for the global argument to retrieve.
337       *
338       * @return  The global argument with the specified long identifier, or
339       *          <CODE>null</CODE> if there is no such argument.
340       */
341      public Argument getGlobalArgumentForLongID(String longID)
342      {
343        return globalLongIDMap.get(longID);
344      }
345    
346    
347    
348      /**
349       * Retrieves the set of subcommands defined for this argument parser,
350       * referenced by subcommand name.
351       *
352       * @return  The set of subcommands defined for this argument parser,
353       *          referenced by subcommand name.
354       */
355      public SortedMap<String,SubCommand> getSubCommands()
356      {
357        return subCommands;
358      }
359    
360    
361    
362      /**
363       * Indicates whether this argument parser has a subcommand with the specified
364       * name.
365       *
366       * @param  name  The subcommand name for which to make the determination.
367       *
368       * @return  <CODE>true</CODE> if this argument parser has a subcommand with
369       *          the specified name, or <CODE>false</CODE> if it does not.
370       */
371      public boolean hasSubCommand(String name)
372      {
373        return subCommands.containsKey(name);
374      }
375    
376    
377    
378      /**
379       * Retrieves the subcommand with the specified name.
380       *
381       * @param  name  The name of the subcommand to retrieve.
382       *
383       * @return  The subcommand with the specified name, or <CODE>null</CODE> if no
384       *          such subcommand is defined.
385       */
386      public SubCommand getSubCommand(String name)
387      {
388        return subCommands.get(name);
389      }
390    
391    
392    
393      /**
394       * Retrieves the subcommand that was selected in the set of command-line
395       * arguments.
396       *
397       * @return  The subcommand that was selected in the set of command-line
398       *          arguments, or <CODE>null</CODE> if none was selected.
399       */
400      public SubCommand getSubCommand()
401      {
402        return subCommand;
403      }
404    
405    
406    
407      /**
408       * Retrieves the raw set of arguments that were provided.
409       *
410       * @return  The raw set of arguments that were provided, or <CODE>null</CODE>
411       *          if the argument list has not yet been parsed.
412       */
413      public String[] getRawArguments()
414      {
415        return rawArguments;
416      }
417    
418    
419    
420      /**
421       * Adds the provided argument to the set of global arguments handled by this
422       * parser.
423       *
424       * @param  argument  The argument to be added.
425       *
426       * @throws  ArgumentException  If the provided argument conflicts with another
427       *                             global or subcommand argument that has already
428       *                             been defined.
429       */
430      public void addGlobalArgument(Argument argument)
431             throws ArgumentException
432      {
433        addGlobalArgument(argument, null);
434      }
435    
436    
437      /**
438       * Adds the provided argument to the set of global arguments handled by this
439       * parser.
440       *
441       * @param  argument  The argument to be added.
442       * @param  group     The argument group to which the argument belongs.
443       * @throws  ArgumentException  If the provided argument conflicts with another
444       *                             global or subcommand argument that has already
445       *                             been defined.
446       */
447      public void addGlobalArgument(Argument argument, ArgumentGroup group)
448             throws ArgumentException
449      {
450    
451        String argumentName = argument.getName();
452        if (globalArgumentMap.containsKey(argumentName))
453        {
454          Message message =
455              ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_NAME.get(argumentName);
456          throw new ArgumentException(message);
457        }
458        for (SubCommand s : subCommands.values())
459        {
460          if (s.getArgumentForName(argumentName) != null)
461          {
462            Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_NAME_SUBCMD_CONFLICT.get(
463                argumentName, s.getName());
464            throw new ArgumentException(message);
465          }
466        }
467    
468    
469        Character shortID = argument.getShortIdentifier();
470        if (shortID != null)
471        {
472          if (globalShortIDMap.containsKey(shortID))
473          {
474            String name = globalShortIDMap.get(shortID).getName();
475    
476            Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_SHORT_ID.get(
477                String.valueOf(shortID), argumentName, name);
478            throw new ArgumentException(message);
479          }
480    
481          for (SubCommand s : subCommands.values())
482          {
483            if (s.getArgument(shortID) != null)
484            {
485              String cmdName = s.getName();
486              String name    = s.getArgument(shortID).getName();
487    
488              Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_SHORT_ID_CONFLICT.get(
489                  String.valueOf(shortID), argumentName, name, cmdName);
490              throw new ArgumentException(message);
491            }
492          }
493        }
494    
495    
496        String longID = argument.getLongIdentifier();
497        if (longID != null)
498        {
499          if (! longArgumentsCaseSensitive)
500          {
501            longID = toLowerCase(longID);
502          }
503    
504          if (globalLongIDMap.containsKey(longID))
505          {
506            String name = globalLongIDMap.get(longID).getName();
507    
508            Message message = ERR_SUBCMDPARSER_DUPLICATE_GLOBAL_ARG_LONG_ID.get(
509                argument.getLongIdentifier(), argumentName, name);
510            throw new ArgumentException(message);
511          }
512    
513          for (SubCommand s : subCommands.values())
514          {
515            if (s.getArgument(longID) != null)
516            {
517              String cmdName = s.getName();
518              String name    = s.getArgument(longID).getName();
519    
520              Message message = ERR_SUBCMDPARSER_GLOBAL_ARG_LONG_ID_CONFLICT.get(
521                  argument.getLongIdentifier(), argumentName, name, cmdName);
522              throw new ArgumentException(message);
523            }
524          }
525        }
526    
527    
528        if (shortID != null)
529        {
530          globalShortIDMap.put(shortID, argument);
531        }
532    
533        if (longID != null)
534        {
535          globalLongIDMap.put(longID, argument);
536        }
537    
538        globalArgumentList.add(argument);
539    
540        if (group == null) {
541          group = getStandardGroup(argument);
542        }
543        group.addArgument(argument);
544        argumentGroups.add(group);
545      }
546    
547      /**
548       * Removes the provided argument from the set of global arguments handled by
549       * this parser.
550       *
551       * @param  argument  The argument to be removed.
552       */
553      protected void removeGlobalArgument(Argument argument)
554      {
555        String argumentName = argument.getName();
556        globalArgumentMap.remove(argumentName);
557    
558        Character shortID = argument.getShortIdentifier();
559        if (shortID != null)
560        {
561          globalShortIDMap.remove(shortID);
562        }
563    
564        String longID = argument.getLongIdentifier();
565        if (longID != null)
566        {
567          if (! longArgumentsCaseSensitive)
568          {
569            longID = toLowerCase(longID);
570          }
571    
572          globalLongIDMap.remove(longID);
573        }
574    
575        globalArgumentList.remove(argument);
576      }
577    
578    
579      /**
580       * Sets the provided argument as one which will automatically
581       * trigger the output of full usage information if it is provided on
582       * the command line and no further argument validation will be
583       * performed.
584       * <p>
585       * If sub-command groups are defined using the
586       * {@link #setUsageGroupArgument(Argument, Collection)} method, then
587       * this usage argument, when specified, will result in usage
588       * information being displayed which does not include information on
589       * sub-commands.
590       * <p>
591       * Note that the caller will still need to add this argument to the
592       * parser with the {@link #addGlobalArgument(Argument)} method, and
593       * the argument should not be required and should not take a value.
594       * Also, the caller will still need to check for the presence of the
595       * usage argument after calling {@link #parseArguments(String[])} to
596       * know that no further processing will be required.
597       *
598       * @param argument
599       *          The argument whose presence should automatically trigger
600       *          the display of full usage information.
601       * @param outputStream
602       *          The output stream to which the usage information should
603       *          be written.
604       */
605      public void setUsageArgument(Argument argument, OutputStream outputStream) {
606        usageArgument = argument;
607        usageOutputStream = outputStream;
608    
609        usageGroupArguments.put(argument, Collections.<SubCommand>emptySet());
610      }
611    
612    
613    
614      /**
615       * Sets the provided argument as one which will automatically
616       * trigger the output of partial usage information if it is provided
617       * on the command line and no further argument validation will be
618       * performed.
619       * <p>
620       * Partial usage information will include a usage synopsis, a
621       * summary of each of the sub-commands listed in the provided
622       * sub-commands collection, and a summary of the global options.
623       * <p>
624       * Note that the caller will still need to add this argument to the
625       * parser with the {@link #addGlobalArgument(Argument)} method, and
626       * the argument should not be required and should not take a value.
627       * Also, the caller will still need to check for the presence of the
628       * usage argument after calling {@link #parseArguments(String[])} to
629       * know that no further processing will be required.
630       *
631       * @param argument
632       *          The argument whose presence should automatically trigger
633       *          the display of partial usage information.
634       * @param subCommands
635       *          The list of sub-commands which should have their usage
636       *          displayed.
637       */
638      public void setUsageGroupArgument(Argument argument,
639          Collection<SubCommand> subCommands) {
640        usageGroupArguments.put(argument, subCommands);
641      }
642    
643    
644      /**
645       * Parses the provided set of arguments and updates the information associated
646       * with this parser accordingly.
647       *
648       * @param  rawArguments  The raw set of arguments to parse.
649       *
650       * @throws  ArgumentException  If a problem was encountered while parsing the
651       *                             provided arguments.
652       */
653      public void parseArguments(String[] rawArguments)
654             throws ArgumentException
655      {
656        parseArguments(rawArguments, null);
657      }
658    
659    
660    
661      /**
662       * Parses the provided set of arguments and updates the information associated
663       * with this parser accordingly.  Default values for unspecified arguments
664       * may be read from the specified properties file.
665       *
666       * @param  rawArguments           The set of raw arguments to parse.
667       * @param  propertiesFile         The path to the properties file to use to
668       *                                obtain default values for unspecified
669       *                                properties.
670       * @param  requirePropertiesFile  Indicates whether the parsing should fail if
671       *                                the provided properties file does not exist
672       *                                or is not accessible.
673       *
674       * @throws  ArgumentException  If a problem was encountered while parsing the
675       *                             provided arguments or interacting with the
676       *                             properties file.
677       */
678      public void parseArguments(String[] rawArguments, String propertiesFile,
679                                 boolean requirePropertiesFile)
680             throws ArgumentException
681      {
682        this.rawArguments = rawArguments;
683    
684        Properties argumentProperties = null;
685    
686        try
687        {
688          Properties p = new Properties();
689          FileInputStream fis = new FileInputStream(propertiesFile);
690          p.load(fis);
691          fis.close();
692          argumentProperties = p;
693        }
694        catch (Exception e)
695        {
696          if (requirePropertiesFile)
697          {
698            Message message = ERR_SUBCMDPARSER_CANNOT_READ_PROPERTIES_FILE.get(
699                String.valueOf(propertiesFile), getExceptionMessage(e));
700            throw new ArgumentException(message, e);
701          }
702        }
703    
704        parseArguments(rawArguments, argumentProperties);
705      }
706    
707    
708    
709      /**
710       * Parses the provided set of arguments and updates the information associated
711       * with this parser accordingly.  Default values for unspecified arguments may
712       * be read from the specified properties if any are provided.
713       *
714       * @param  rawArguments        The set of raw arguments to parse.
715       * @param  argumentProperties  A set of properties that may be used to provide
716       *                             default values for arguments not included in
717       *                             the given raw arguments.
718       *
719       * @throws  ArgumentException  If a problem was encountered while parsing the
720       *                             provided arguments.
721       */
722      public void parseArguments(String[] rawArguments,
723                                 Properties argumentProperties)
724             throws ArgumentException
725      {
726        this.rawArguments = rawArguments;
727        this.subCommand = null;
728        this.trailingArguments = new ArrayList<String>();
729        this.usageOrVersionDisplayed = false;
730    
731        boolean inTrailingArgs = false;
732    
733        int numArguments = rawArguments.length;
734        for (int i=0; i < numArguments; i++)
735        {
736          String arg = rawArguments[i];
737    
738          if (inTrailingArgs)
739          {
740            trailingArguments.add(arg);
741            if ((subCommand.getMaxTrailingArguments() > 0) &&
742                (trailingArguments.size() > subCommand.getMaxTrailingArguments()))
743            {
744              Message message = ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS.get(
745                  subCommand.getMaxTrailingArguments());
746              throw new ArgumentException(message);
747            }
748    
749            continue;
750          }
751    
752          if (arg.equals("--"))
753          {
754            inTrailingArgs = true;
755          }
756          else if (arg.startsWith("--"))
757          {
758            // This indicates that we are using the long name to reference the
759            // argument.  It may be in any of the following forms:
760            // --name
761            // --name value
762            // --name=value
763    
764            String argName  = arg.substring(2);
765            String argValue = null;
766            int    equalPos = argName.indexOf('=');
767            if (equalPos < 0)
768            {
769              // This is fine.  The value is not part of the argument name token.
770            }
771            else if (equalPos == 0)
772            {
773              // The argument starts with "--=", which is not acceptable.
774              Message message = ERR_SUBCMDPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
775              throw new ArgumentException(message);
776            }
777            else
778            {
779              // The argument is in the form --name=value, so parse them both out.
780              argValue = argName.substring(equalPos+1);
781              argName  = argName.substring(0, equalPos);
782            }
783    
784            // If we're not case-sensitive, then convert the name to lowercase.
785            String origArgName = argName;
786            if (! longArgumentsCaseSensitive)
787            {
788              argName = toLowerCase(argName);
789            }
790    
791            // See if the specified name references a global argument.  If not, then
792            // see if it references a subcommand argument.
793            Argument a = globalLongIDMap.get(argName);
794            if (a == null)
795            {
796              if (subCommand == null)
797              {
798                if (argName.equals("help"))
799                {
800                  // "--help" will always be interpreted as requesting usage
801                  // information.
802                  try
803                  {
804                    getUsage(usageOutputStream);
805                  } catch (Exception e) {}
806    
807                  return;
808                }
809                else
810                if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
811                {
812                  // "--version" will always be interpreted as requesting usage
813                  // information.
814                  try
815                  {
816                    versionPresent = true;
817                    DirectoryServer.printVersion(usageOutputStream);
818                    usageOrVersionDisplayed = true ;
819                  } catch (Exception e) {}
820    
821                  return;
822                }
823                else
824                {
825                  // There is no such global argument.
826                  Message message =
827                      ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_LONG_ID.get(
828                          origArgName);
829                  throw new ArgumentException(message);
830                }
831              }
832              else
833              {
834                a = subCommand.getArgument(argName);
835                if (a == null)
836                {
837                  if (argName.equals("help"))
838                  {
839                    // "--help" will always be interpreted as requesting usage
840                    // information.
841                    try
842                    {
843                      getUsage(usageOutputStream);
844                    } catch (Exception e) {}
845    
846                    return;
847                  }
848                  else
849                  if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
850                  {
851                    // "--version" will always be interpreted as requesting usage
852                    // information.
853                    try
854                    {
855                      versionPresent = true;
856                      DirectoryServer.printVersion(usageOutputStream);
857                      usageOrVersionDisplayed = true ;
858                    } catch (Exception e) {}
859    
860                    return;
861                  }
862                  else
863                  {
864                    // There is no such global or subcommand argument.
865                    Message message =
866                        ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_LONG_ID.get(origArgName);
867                    throw new ArgumentException(message);
868                  }
869                }
870              }
871            }
872    
873            a.setPresent(true);
874    
875            // If this is a usage argument, then immediately stop and print
876            // usage information.
877            if (usageGroupArguments.containsKey(a))
878            {
879              try
880              {
881                getUsage(a, usageOutputStream);
882              } catch (Exception e) {}
883    
884              return;
885            }
886    
887            // See if the argument takes a value.  If so, then make sure one was
888            // provided.  If not, then make sure none was provided.
889            if (a.needsValue())
890            {
891              if (argValue == null)
892              {
893                if ((i+1) == numArguments)
894                {
895                  Message message =
896                      ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID.
897                        get(argName);
898                  throw new ArgumentException(message);
899                }
900    
901                argValue = rawArguments[++i];
902              }
903    
904              MessageBuilder invalidReason = new MessageBuilder();
905              if (! a.valueIsAcceptable(argValue, invalidReason))
906              {
907                Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.
908                    get(argValue, argName, invalidReason.toString());
909                throw new ArgumentException(message);
910              }
911    
912              // If the argument already has a value, then make sure it is
913              // acceptable to have more than one.
914              if (a.hasValue() && (! a.isMultiValued()))
915              {
916                Message message =
917                    ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_LONG_ID.get(origArgName);
918                throw new ArgumentException(message);
919              }
920    
921              a.addValue(argValue);
922            }
923            else
924            {
925              if (argValue != null)
926              {
927                Message message =
928                    ERR_SUBCMDPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE.get(
929                        origArgName);
930                throw new ArgumentException(message);
931              }
932            }
933          }
934          else if (arg.startsWith("-"))
935          {
936            // This indicates that we are using the 1-character name to reference
937            // the argument.  It may be in any of the following forms:
938            // -n
939            // -nvalue
940            // -n value
941            if (arg.equals("-"))
942            {
943              Message message = ERR_SUBCMDPARSER_INVALID_DASH_AS_ARGUMENT.get();
944              throw new ArgumentException(message);
945            }
946    
947            char argCharacter = arg.charAt(1);
948            String argValue;
949            if (arg.length() > 2)
950            {
951              argValue = arg.substring(2);
952            }
953            else
954            {
955              argValue = null;
956            }
957    
958    
959            // Get the argument with the specified short ID.  It may be either a
960            // global argument or a subcommand-specific argument.
961            Argument a = globalShortIDMap.get(argCharacter);
962            if (a == null)
963            {
964              if (subCommand == null)
965              {
966                if (argCharacter == '?')
967                {
968                  // "-?" will always be interpreted as requesting usage.
969                  try
970                  {
971                    getUsage(usageOutputStream);
972                    if (usageArgument != null)
973                    {
974                      usageArgument.setPresent(true);
975                    }
976                  } catch (Exception e) {}
977    
978                  return;
979                }
980                else
981                if (argCharacter == OPTION_SHORT_PRODUCT_VERSION)
982                {
983                  //  "-V" will always be interpreted as requesting
984                  // version information except if it's already defined.
985                  boolean dashVAccepted = true;
986                  if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION))
987                  {
988                    dashVAccepted = false;
989                  }
990                  else
991                  {
992                    for (SubCommand subCmd : subCommands.values())
993                    {
994                      if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION) != null)
995                      {
996                        dashVAccepted = false;
997                        break;
998                      }
999                    }
1000                  }
1001                  if (dashVAccepted)
1002                  {
1003                    usageOrVersionDisplayed = true;
1004                    versionPresent = true;
1005                    try
1006                    {
1007                      DirectoryServer.printVersion(usageOutputStream);
1008                    }
1009                    catch (Exception e)
1010                    {
1011                    }
1012                    return;
1013                  }
1014                  else
1015                  {
1016                    // -V is defined in another suncommand, so we can
1017                    // accepted it as the version information argument
1018                    Message message =
1019                        ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1020                          get(String.valueOf(argCharacter));
1021                    throw new ArgumentException(message);
1022                  }
1023                }
1024                else
1025                {
1026                  // There is no such argument registered.
1027                  Message message =
1028                      ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1029                        get(String.valueOf(argCharacter));
1030                  throw new ArgumentException(message);
1031                }
1032              }
1033              else
1034              {
1035                a = subCommand.getArgument(argCharacter);
1036                if (a == null)
1037                {
1038                  if (argCharacter == '?')
1039                  {
1040                    // "-?" will always be interpreted as requesting usage.
1041                    try
1042                    {
1043                      getUsage(usageOutputStream);
1044                    } catch (Exception e) {}
1045    
1046                    return;
1047                  }
1048                  else
1049                  if (argCharacter == OPTION_SHORT_PRODUCT_VERSION)
1050                  {
1051                      // "-V" will always be interpreted as requesting
1052                      // version information except if it's already defined.
1053                    boolean dashVAccepted = true;
1054                    if (globalShortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION))
1055                    {
1056                      dashVAccepted = false;
1057                    }
1058                    else
1059                    {
1060                      for (SubCommand subCmd : subCommands.values())
1061                      {
1062                        if (subCmd.getArgument(OPTION_SHORT_PRODUCT_VERSION)!=null)
1063                        {
1064                          dashVAccepted = false;
1065                          break;
1066                        }
1067                      }
1068                    }
1069                    if (dashVAccepted)
1070                    {
1071                      usageOrVersionDisplayed = true;
1072                      versionPresent = true;
1073                      try
1074                      {
1075                        DirectoryServer.printVersion(usageOutputStream);
1076                      }
1077                      catch (Exception e)
1078                      {
1079                      }
1080                      return;
1081                    }
1082                  }
1083                  else
1084                  {
1085                    // There is no such argument registered.
1086                    Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.get(
1087                        String.valueOf(argCharacter));
1088                    throw new ArgumentException(message);
1089                  }
1090                }
1091              }
1092            }
1093    
1094            a.setPresent(true);
1095    
1096            // If this is the usage argument, then immediately stop and print
1097            // usage information.
1098            if (usageGroupArguments.containsKey(a))
1099            {
1100              try
1101              {
1102                getUsage(a, usageOutputStream);
1103              } catch (Exception e) {}
1104    
1105              return;
1106            }
1107    
1108            // See if the argument takes a value.  If so, then make sure one was
1109            // provided.  If not, then make sure none was provided.
1110            if (a.needsValue())
1111            {
1112              if (argValue == null)
1113              {
1114                if ((i+1) == numArguments)
1115                {
1116                  Message message =
1117                      ERR_SUBCMDPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID.
1118                        get(String.valueOf(argCharacter));
1119                  throw new ArgumentException(message);
1120                }
1121    
1122                argValue = rawArguments[++i];
1123              }
1124    
1125              MessageBuilder invalidReason = new MessageBuilder();
1126              if (! a.valueIsAcceptable(argValue, invalidReason))
1127              {
1128                Message message = ERR_SUBCMDPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.
1129                    get(argValue, String.valueOf(argCharacter),
1130                        invalidReason.toString());
1131                throw new ArgumentException(message);
1132              }
1133    
1134              // If the argument already has a value, then make sure it is
1135              // acceptable to have more than one.
1136              if (a.hasValue() && (! a.isMultiValued()))
1137              {
1138                Message message = ERR_SUBCMDPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(
1139                    String.valueOf(argCharacter));
1140                throw new ArgumentException(message);
1141              }
1142    
1143              a.addValue(argValue);
1144            }
1145            else
1146            {
1147              if (argValue != null)
1148              {
1149                // If we've gotten here, then it means that we're in a scenario like
1150                // "-abc" where "a" is a valid argument that doesn't take a value.
1151                // However, this could still be valid if all remaining characters in
1152                // the value are also valid argument characters that don't take
1153                // values.
1154                int valueLength = argValue.length();
1155                for (int j=0; j < valueLength; j++)
1156                {
1157                  char c = argValue.charAt(j);
1158                  Argument b = globalShortIDMap.get(c);
1159                  if (b == null)
1160                  {
1161                    if (subCommand == null)
1162                    {
1163                      Message message =
1164                          ERR_SUBCMDPARSER_NO_GLOBAL_ARGUMENT_FOR_SHORT_ID.
1165                            get(String.valueOf(argCharacter));
1166                      throw new ArgumentException(message);
1167                    }
1168                    else
1169                    {
1170                      b = subCommand.getArgument(c);
1171                      if (b == null)
1172                      {
1173                        Message message = ERR_SUBCMDPARSER_NO_ARGUMENT_FOR_SHORT_ID.
1174                            get(String.valueOf(argCharacter));
1175                        throw new ArgumentException(message);
1176                      }
1177                    }
1178                  }
1179    
1180                  if (b.needsValue())
1181                  {
1182                    // This means we're in a scenario like "-abc" where b is a
1183                    // valid argument that takes a value.  We don't support that.
1184                    Message message = ERR_SUBCMDPARSER_CANT_MIX_ARGS_WITH_VALUES.
1185                        get(String.valueOf(argCharacter), argValue,
1186                            String.valueOf(c));
1187                    throw new ArgumentException(message);
1188                  }
1189                  else
1190                  {
1191                    b.setPresent(true);
1192    
1193                    // If this is the usage argument, then immediately stop and
1194                    // print usage information.
1195                    if (usageGroupArguments.containsKey(b))
1196                    {
1197                      try
1198                      {
1199                        getUsage(b, usageOutputStream);
1200                      } catch (Exception e) {}
1201    
1202                      return;
1203                    }
1204                  }
1205                }
1206              }
1207            }
1208          }
1209          else if (subCommand != null)
1210          {
1211            // It's not a short or long identifier and the sub-command has
1212            // already been specified, so it must be the first trailing argument.
1213            if (subCommand.allowsTrailingArguments())
1214            {
1215              trailingArguments.add(arg);
1216              inTrailingArgs = true;
1217            }
1218            else
1219            {
1220              // Trailing arguments are not allowed for this sub-command.
1221              Message message = ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
1222              throw new ArgumentException(message);
1223            }
1224          }
1225          else
1226          {
1227            // It must be the sub-command.
1228            String nameToCheck = arg;
1229            if (! longArgumentsCaseSensitive)
1230            {
1231              nameToCheck = toLowerCase(arg);
1232            }
1233    
1234            SubCommand sc = subCommands.get(nameToCheck);
1235            if (sc == null)
1236            {
1237              Message message = ERR_SUBCMDPARSER_INVALID_ARGUMENT.get(arg);
1238              throw new ArgumentException(message);
1239            }
1240            else
1241            {
1242              subCommand = sc;
1243            }
1244          }
1245        }
1246    
1247        // If we have a sub-command and it allows trailing arguments and
1248        // there is a minimum number, then make sure at least that many
1249        // were provided.
1250        if (subCommand != null)
1251        {
1252          int minTrailingArguments = subCommand.getMinTrailingArguments();
1253          if (subCommand.allowsTrailingArguments() && (minTrailingArguments > 0))
1254          {
1255            if (trailingArguments.size() < minTrailingArguments)
1256            {
1257              Message message = ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS.get(
1258                  minTrailingArguments);
1259              throw new ArgumentException(message);
1260            }
1261          }
1262        }
1263    
1264        // If we don't have the argumentProperties, try to load a properties file.
1265        if (argumentProperties == null)
1266        {
1267          argumentProperties = checkExternalProperties();
1268        }
1269    
1270        // Iterate through all the global arguments and make sure that they have
1271        // values or a suitable default is available.
1272        for (Argument a : globalArgumentList)
1273        {
1274          if (! a.isPresent())
1275          {
1276            // See if there is a value in the properties that can be used
1277            if ((argumentProperties != null) && (a.getPropertyName() != null))
1278            {
1279              String value = argumentProperties.getProperty(a.getPropertyName()
1280                  .toLowerCase());
1281              MessageBuilder invalidReason =  new MessageBuilder();
1282              if (value != null)
1283              {
1284                Boolean addValue = true;
1285                if (!( a instanceof BooleanArgument))
1286                {
1287                  addValue = a.valueIsAcceptable(value, invalidReason);
1288                }
1289                if (addValue)
1290                {
1291                  a.addValue(value);
1292                  if (a.needsValue())
1293                  {
1294                    a.setPresent(true);
1295                  }
1296                  a.setValueSetByProperty(true);
1297                }
1298              }
1299            }
1300          }
1301    
1302          if ((! a.isPresent()) && a.needsValue())
1303          {
1304            // ISee if the argument defines a default.
1305            if (a.getDefaultValue() != null)
1306            {
1307              a.addValue(a.getDefaultValue());
1308            }
1309    
1310            // If there is still no value and the argument is required, then that's
1311            // a problem.
1312            if ((! a.hasValue()) && a.isRequired())
1313            {
1314              Message message =
1315                  ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
1316              throw new ArgumentException(message);
1317            }
1318          }
1319        }
1320    
1321    
1322        // Iterate through all the subcommand-specific arguments and make sure that
1323        // they have values or a suitable default is available.
1324        if (subCommand != null)
1325        {
1326          for (Argument a : subCommand.getArguments())
1327          {
1328            if (! a.isPresent())
1329            {
1330              // See if there is a value in the properties that can be used
1331              if ((argumentProperties != null) && (a.getPropertyName() != null))
1332              {
1333                String value = argumentProperties.getProperty(a.getPropertyName()
1334                    .toLowerCase());
1335                MessageBuilder invalidReason =  new MessageBuilder();
1336                if (value != null)
1337                {
1338                  Boolean addValue = true;
1339                  if (!( a instanceof BooleanArgument))
1340                  {
1341                    addValue = a.valueIsAcceptable(value, invalidReason);
1342                  }
1343                  if (addValue)
1344                  {
1345                    a.addValue(value);
1346                    if (a.needsValue())
1347                    {
1348                      a.setPresent(true);
1349                    }
1350                    a.setValueSetByProperty(true);
1351                  }
1352                }
1353              }
1354            }
1355    
1356            if ((! a.isPresent()) && a.needsValue())
1357            {
1358              // See if the argument defines a default.
1359              if (a.getDefaultValue() != null)
1360              {
1361                a.addValue(a.getDefaultValue());
1362              }
1363    
1364              // If there is still no value and the argument is required, then
1365              // that's a problem.
1366              if ((! a.hasValue()) && a.isRequired())
1367              {
1368                Message message =
1369                    ERR_SUBCMDPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
1370                throw new ArgumentException(message);
1371              }
1372            }
1373          }
1374        }
1375      }
1376    
1377    
1378    
1379      /**
1380       * Appends usage information for the specified subcommand to the provided
1381       * buffer.
1382       *
1383       * @param  buffer      The buffer to which the usage information should be
1384       *                     appended.
1385       * @param  subCommand  The subcommand for which to display the usage
1386       *                     information.
1387       */
1388      public void getSubCommandUsage(MessageBuilder buffer, SubCommand subCommand)
1389      {
1390        usageOrVersionDisplayed = true;
1391        String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1392        if ((scriptName == null) || (scriptName.length() == 0))
1393        {
1394          scriptName = "java " + mainClassName;
1395        }
1396        buffer.append(INFO_ARGPARSER_USAGE.get());
1397        buffer.append("  ");
1398        buffer.append(scriptName);
1399    
1400        buffer.append(" ");
1401        buffer.append(subCommand.getName());
1402        buffer.append(" ").append(INFO_SUBCMDPARSER_OPTIONS.get());
1403        if (subCommand.allowsTrailingArguments()) {
1404          buffer.append(' ');
1405          buffer.append(subCommand.getTrailingArgumentsDisplayName());
1406        }
1407        buffer.append(EOL);
1408        buffer.append(subCommand.getDescription());
1409        buffer.append(EOL);
1410    
1411        if ( ! globalArgumentList.isEmpty())
1412        {
1413          buffer.append(EOL);
1414          buffer.append(INFO_GLOBAL_OPTIONS.get());
1415          buffer.append(EOL);
1416          buffer.append("    ");
1417          buffer.append(INFO_GLOBAL_OPTIONS_REFERENCE.get(scriptName));
1418          buffer.append(EOL);
1419        }
1420    
1421        if ( ! subCommand.getArguments().isEmpty() )
1422        {
1423          buffer.append(EOL);
1424          buffer.append(INFO_SUBCMD_OPTIONS.get());
1425          buffer.append(EOL);
1426        }
1427        for (Argument a : subCommand.getArguments())
1428        {
1429          // If this argument is hidden, then skip it.
1430          if (a.isHidden())
1431          {
1432            continue;
1433          }
1434    
1435    
1436          // Write a line with the short and/or long identifiers that may be used
1437          // for the argument.
1438          Character shortID = a.getShortIdentifier();
1439          String longID = a.getLongIdentifier();
1440          if (shortID != null)
1441          {
1442            int currentLength = buffer.length();
1443    
1444            if (a.equals(usageArgument))
1445            {
1446              buffer.append("-?, ");
1447            }
1448    
1449            buffer.append("-");
1450            buffer.append(shortID.charValue());
1451    
1452            if (a.needsValue() && longID == null)
1453            {
1454              buffer.append(" ");
1455              buffer.append(a.getValuePlaceholder());
1456            }
1457    
1458            if (longID != null)
1459            {
1460              StringBuilder newBuffer = new StringBuilder();
1461              newBuffer.append(", --");
1462              newBuffer.append(longID);
1463    
1464              if (a.needsValue())
1465              {
1466                newBuffer.append(" ");
1467                newBuffer.append(a.getValuePlaceholder());
1468              }
1469    
1470              int lineLength = (buffer.length() - currentLength) +
1471                               newBuffer.length();
1472              if (lineLength > MAX_LENGTH)
1473              {
1474                buffer.append(EOL);
1475                buffer.append(newBuffer.toString());
1476              }
1477              else
1478              {
1479                buffer.append(newBuffer.toString());
1480              }
1481            }
1482    
1483            buffer.append(EOL);
1484          }
1485          else
1486          {
1487            if (longID != null)
1488            {
1489              if (a.equals(usageArgument))
1490              {
1491                buffer.append("-?, ");
1492              }
1493              buffer.append("--");
1494              buffer.append(longID);
1495    
1496              if (a.needsValue())
1497              {
1498                buffer.append(" ");
1499                buffer.append(a.getValuePlaceholder());
1500              }
1501    
1502              buffer.append(EOL);
1503            }
1504          }
1505    
1506    
1507          // Write one or more lines with the description of the argument.  We will
1508          // indent the description five characters and try our best to wrap at or
1509          // before column 79 so it will be friendly to 80-column displays.
1510          Message description = a.getDescription();
1511          int maxLength = MAX_LENGTH - INDENT.length() - 1;
1512          if (description.length() <= maxLength)
1513          {
1514            buffer.append(INDENT);
1515            buffer.append(description);
1516            buffer.append(EOL);
1517          }
1518          else
1519          {
1520            String s = description.toString();
1521            while (s.length() > maxLength)
1522            {
1523              int spacePos = s.lastIndexOf(' ', maxLength);
1524              if (spacePos > 0)
1525              {
1526                buffer.append(INDENT);
1527                buffer.append(s.substring(0, spacePos).trim());
1528                s = s.substring(spacePos+1).trim();
1529                buffer.append(EOL);
1530              }
1531              else
1532              {
1533                // There are no spaces in the first 74 columns.  See if there is one
1534                // after that point.  If so, then break there.  If not, then don't
1535                // break at all.
1536                spacePos = s.indexOf(' ');
1537                if (spacePos > 0)
1538                {
1539                  buffer.append(INDENT);
1540                  buffer.append(s.substring(0, spacePos).trim());
1541                  s = s.substring(spacePos+1).trim();
1542                  buffer.append(EOL);
1543                }
1544                else
1545                {
1546                  buffer.append(INDENT);
1547                  buffer.append(s);
1548                  s = "";
1549                  buffer.append(EOL);
1550                }
1551              }
1552            }
1553    
1554            if (s.length() > 0)
1555            {
1556              buffer.append("    ");
1557              buffer.append(s);
1558              buffer.append(EOL);
1559            }
1560          }
1561        }
1562      }
1563    
1564    
1565    
1566      /**
1567       * Retrieves a string containing usage information based on the defined
1568       * arguments.
1569       *
1570       * @return  A string containing usage information based on the defined
1571       *          arguments.
1572       */
1573      public String getUsage()
1574      {
1575        MessageBuilder buffer = new MessageBuilder();
1576    
1577        if (subCommand == null) {
1578          if (usageGroupArguments.size() > 1) {
1579            // We have sub-command groups, so don't display any
1580            // sub-commands by default.
1581            getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
1582          } else {
1583            // No grouping, so display all sub-commands.
1584            getFullUsage(subCommands.values(), true, buffer);
1585          }
1586        } else {
1587          getSubCommandUsage(buffer, subCommand);
1588        }
1589    
1590        return buffer.toMessage().toString();
1591      }
1592    
1593    
1594    
1595      /**
1596       * Retrieves a string describing how the user can get more help.
1597       *
1598       * @return A string describing how the user can get more help.
1599       */
1600      public Message getHelpUsageReference()
1601      {
1602        usageOrVersionDisplayed = true;
1603        String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1604        if ((scriptName == null) || (scriptName.length() == 0))
1605        {
1606          scriptName = "java " + mainClassName;
1607        }
1608    
1609        MessageBuilder buffer = new MessageBuilder();
1610        buffer.append(INFO_GLOBAL_HELP_REFERENCE.get(scriptName));
1611        buffer.append(EOL);
1612        return buffer.toMessage();
1613      }
1614    
1615    
1616    
1617      /**
1618       * Retrieves the set of unnamed trailing arguments that were
1619       * provided on the command line.
1620       *
1621       * @return The set of unnamed trailing arguments that were provided
1622       *         on the command line.
1623       */
1624      public ArrayList<String> getTrailingArguments()
1625      {
1626        return trailingArguments;
1627      }
1628    
1629    
1630    
1631      /**
1632       * Indicates whether the usage information has been displayed to the end user
1633       * either by an explicit argument like "-H" or "--help", or by a built-in
1634       * argument like "-?".
1635       *
1636       * @return  {@code true} if the usage information has been displayed, or
1637       *          {@code false} if not.
1638       */
1639      public boolean usageOrVersionDisplayed()
1640      {
1641        return usageOrVersionDisplayed;
1642      }
1643    
1644    
1645    
1646      /**
1647       * Adds the provided subcommand to this argument parser.  This is only
1648       * intended for use by the <CODE>SubCommand</CODE> constructor and does not
1649       * do any validation of its own to ensure that there are no conflicts with the
1650       * subcommand or any of its arguments.
1651       *
1652       * @param  subCommand  The subcommand to add to this argument parser.
1653       */
1654      void addSubCommand(SubCommand subCommand)
1655      {
1656        subCommands.put(toLowerCase(subCommand.getName()), subCommand);
1657      }
1658    
1659    
1660    
1661      // Get usage for a specific usage argument.
1662      private void getUsage(Argument a, OutputStream outputStream)
1663          throws IOException {
1664        MessageBuilder buffer = new MessageBuilder();
1665    
1666        if (a.equals(usageArgument) && subCommand != null) {
1667          getSubCommandUsage(buffer, subCommand);
1668        } else if (a.equals(usageArgument) && usageGroupArguments.size() <= 1) {
1669          // No groups - so display all sub-commands.
1670          getFullUsage(subCommands.values(), true, buffer);
1671        } else if (a.equals(usageArgument)) {
1672          // Using groups - so display all sub-commands group help.
1673          getFullUsage(Collections.<SubCommand> emptySet(), true, buffer);
1674        } else {
1675          // Requested help on specific group - don't display global
1676          // options.
1677          getFullUsage(usageGroupArguments.get(a), false, buffer);
1678        }
1679    
1680        outputStream.write(getBytes(buffer.toString()));
1681      }
1682    
1683    
1684      /**
1685       * {@inheritDoc}
1686       */
1687      public void getUsage(OutputStream outputStream)
1688          throws IOException {
1689        outputStream.write(getBytes(String.valueOf(getUsage())));
1690      }
1691    
1692    
1693    
1694      // Appends complete usage information for the specified set of
1695      // sub-commands.
1696      private void getFullUsage(Collection<SubCommand> c,
1697          boolean showGlobalOptions, MessageBuilder buffer) {
1698        usageOrVersionDisplayed = true;
1699        if ((toolDescription != null) && (toolDescription.length() > 0))
1700        {
1701          buffer.append(wrapText(toolDescription, MAX_LENGTH - 1));
1702          buffer.append(EOL);
1703          buffer.append(EOL);
1704        }
1705    
1706        String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1707        if ((scriptName == null) || (scriptName.length() == 0))
1708        {
1709          scriptName = "java " + mainClassName;
1710        }
1711        buffer.append(INFO_ARGPARSER_USAGE.get());
1712        buffer.append("  ");
1713        buffer.append(scriptName);
1714    
1715        if (subCommands.isEmpty())
1716        {
1717          buffer.append(" "+INFO_SUBCMDPARSER_OPTIONS.get());
1718        }
1719        else
1720        {
1721          buffer.append(" "+INFO_SUBCMDPARSER_SUBCMD_AND_OPTIONS.get());
1722        }
1723    
1724        if (!subCommands.isEmpty())
1725        {
1726          buffer.append(EOL);
1727          buffer.append(EOL);
1728          if (c.isEmpty())
1729          {
1730            buffer.append(INFO_SUBCMDPARSER_SUBCMD_HELP_HEADING.get());
1731          }
1732          else
1733          {
1734            buffer.append(INFO_SUBCMDPARSER_SUBCMD_HEADING.get());
1735          }
1736          buffer.append(EOL);
1737        }
1738    
1739        if (c.isEmpty()) {
1740          // Display usage arguments (except the default one).
1741          for (Argument a : globalArgumentList) {
1742            if (a.isHidden()) {
1743              continue;
1744            }
1745    
1746            if (usageGroupArguments.containsKey(a)) {
1747              if (!a.equals(usageArgument)) {
1748                printArgumentUsage(a, buffer);
1749              }
1750            }
1751          }
1752        } else {
1753          boolean isFirst = true;
1754          for (SubCommand sc : c) {
1755            if (sc.isHidden()) {
1756              continue;
1757            }
1758            if (isFirst)
1759            {
1760              buffer.append(EOL);
1761            }
1762            buffer.append(sc.getName());
1763            buffer.append(EOL);
1764            indentAndWrap(Message.raw(INDENT), sc.getDescription(), buffer);
1765            buffer.append(EOL);
1766            isFirst = false;
1767          }
1768        }
1769    
1770        buffer.append(EOL);
1771    
1772        if (showGlobalOptions) {
1773          if (subCommands.isEmpty())
1774          {
1775            buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
1776            buffer.append(EOL);
1777          }
1778          else
1779          {
1780            buffer.append(INFO_SUBCMDPARSER_GLOBAL_HEADING.get());
1781            buffer.append(EOL);
1782          }
1783          buffer.append(EOL);
1784    
1785          boolean printGroupHeaders = printUsageGroupHeaders();
1786    
1787          // Display non-usage arguments.
1788          for (ArgumentGroup argGroup : argumentGroups)
1789          {
1790            if (argGroup.containsArguments() && printGroupHeaders)
1791            {
1792              // Print the groups description if any
1793              Message groupDesc = argGroup.getDescription();
1794              if (groupDesc != null && !Message.EMPTY.equals(groupDesc)) {
1795                buffer.append(EOL);
1796                buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
1797                buffer.append(EOL);
1798                buffer.append(EOL);
1799              }
1800            }
1801    
1802            for (Argument a : argGroup.getArguments()) {
1803              if (a.isHidden()) {
1804                continue;
1805              }
1806    
1807              if (!usageGroupArguments.containsKey(a)) {
1808                printArgumentUsage(a, buffer);
1809              }
1810            }
1811          }
1812    
1813          // Finally print default usage argument.
1814          if (usageArgument != null) {
1815            printArgumentUsage(usageArgument, buffer);
1816          } else {
1817            buffer.append("-?");
1818          }
1819          buffer.append(EOL);
1820        }
1821      }
1822    
1823    
1824    
1825      /**
1826       * Appends argument usage information to the provided buffer.
1827       *
1828       * @param a
1829       *          The argument to handle.
1830       * @param buffer
1831       *          The buffer to which the usage information should be
1832       *          appended.
1833       */
1834      private void printArgumentUsage(Argument a, MessageBuilder buffer) {
1835        String value;
1836        if (a.needsValue())
1837        {
1838          Message pHolder = a.getValuePlaceholder();
1839          if (pHolder == null)
1840          {
1841            value = " {value}";
1842          }
1843          else
1844          {
1845            value = " " + pHolder;
1846          }
1847        }
1848        else
1849        {
1850          value = "";
1851        }
1852    
1853        Character shortIDChar = a.getShortIdentifier();
1854        if (shortIDChar != null)
1855        {
1856          if (a.equals(usageArgument))
1857          {
1858            buffer.append("-?, ");
1859          }
1860          buffer.append("-");
1861          buffer.append(shortIDChar);
1862    
1863          String longIDString = a.getLongIdentifier();
1864          if (longIDString != null)
1865          {
1866            buffer.append(", --");
1867            buffer.append(longIDString);
1868          }
1869          buffer.append(value);
1870        }
1871        else
1872        {
1873          String longIDString = a.getLongIdentifier();
1874          if (longIDString != null)
1875          {
1876            if (a.equals(usageArgument))
1877            {
1878              buffer.append("-?, ");
1879            }
1880            buffer.append("--");
1881            buffer.append(longIDString);
1882            buffer.append(value);
1883          }
1884        }
1885    
1886        buffer.append(EOL);
1887        indentAndWrap(Message.raw(INDENT), a.getDescription(), buffer);
1888      }
1889    
1890    
1891    
1892      /**
1893       * Write one or more lines with the description of the argument.  We will
1894       * indent the description five characters and try our best to wrap at or
1895       * before column 79 so it will be friendly to 80-column displays.
1896       */
1897      private void indentAndWrap(Message indent, Message text,
1898                                 MessageBuilder buffer)
1899      {
1900        int actualSize = MAX_LENGTH - indent.length();
1901        if (text.length() <= actualSize)
1902        {
1903          buffer.append(indent);
1904          buffer.append(text);
1905          buffer.append(EOL);
1906        }
1907        else
1908        {
1909          String s = text.toString();
1910          while (s.length() > actualSize)
1911          {
1912            int spacePos = s.lastIndexOf(' ', actualSize);
1913            if (spacePos > 0)
1914            {
1915              buffer.append(indent);
1916              buffer.append(s.substring(0, spacePos).trim());
1917              s = s.substring(spacePos + 1).trim();
1918              buffer.append(EOL);
1919            }
1920            else
1921            {
1922              // There are no spaces in the first actualSize -1 columns. See
1923              // if there is one after that point. If so, then break there.
1924              // If not, then don't break at all.
1925              spacePos = s.indexOf(' ');
1926              if (spacePos > 0)
1927              {
1928                buffer.append(indent);
1929                buffer.append(s.substring(0, spacePos).trim());
1930                s = s.substring(spacePos + 1).trim();
1931                buffer.append(EOL);
1932              }
1933              else
1934              {
1935                buffer.append(indent);
1936                buffer.append(s);
1937                s = "";
1938                buffer.append(EOL);
1939              }
1940            }
1941          }
1942    
1943          if (s.length() > 0)
1944          {
1945            buffer.append(indent);
1946            buffer.append(s);
1947            buffer.append(EOL);
1948          }
1949        }
1950      }
1951    
1952      /**
1953       * Returns whether the usage argument was provided or not.  This method
1954       * should be called after a call to parseArguments.
1955       * @return <CODE>true</CODE> if the usage argument was provided and
1956       * <CODE>false</CODE> otherwise.
1957       */
1958      public boolean isUsageArgumentPresent()
1959      {
1960        boolean isUsageArgumentPresent = false;
1961        if (usageArgument != null)
1962        {
1963          isUsageArgumentPresent = usageArgument.isPresent();
1964        }
1965        return isUsageArgumentPresent;
1966      }
1967    
1968      /**
1969       * Returns whether the version argument was provided or not.  This method
1970       * should be called after a call to parseArguments.
1971       * @return <CODE>true</CODE> if the version argument was provided and
1972       * <CODE>false</CODE> otherwise.
1973       */
1974      public boolean isVersionArgumentPresent()
1975      {
1976        boolean isPresent;
1977        if (!super.isVersionArgumentPresent())
1978        {
1979          isPresent = versionPresent;
1980        }
1981        else
1982        {
1983          isPresent = true;
1984        }
1985        return isPresent;
1986      }
1987    }
1988