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    
030    
031    
032    import java.io.File;
033    import java.io.FileInputStream;
034    import java.io.IOException;
035    import java.io.OutputStream;
036    import java.util.ArrayList;
037    import java.util.Enumeration;
038    import java.util.HashMap;
039    import java.util.LinkedList;
040    import java.util.Properties;
041    import java.util.TreeSet;
042    import java.util.Set;
043    
044    import org.opends.server.core.DirectoryServer;
045    import org.opends.server.util.SetupUtils;
046    
047    import static org.opends.messages.UtilityMessages.*;
048    import static org.opends.server.util.ServerConstants.*;
049    import static org.opends.server.util.StaticUtils.*;
050    import static org.opends.server.tools.ToolConstants.*;
051    import static org.opends.messages.ToolMessages.*;
052    
053    import org.opends.messages.MessageBuilder;
054    
055    
056    /**
057     * This class defines a utility that can be used to deal with command-line
058     * arguments for applications in a CLIP-compliant manner using either short
059     * one-character or longer word-based arguments.  It is also integrated with the
060     * Directory Server message catalog so that it can display messages in an
061     * internationalizeable format, can automatically generate usage information,
062     * can detect conflicts between arguments, and can interact with a properties
063     * file to obtain default values for arguments there if they are not specified
064     * on the command-line.
065     */
066    public class ArgumentParser
067    {
068      /**
069       * The argument that will be used to indicate the file properties.
070       */
071      private StringArgument filePropertiesPathArgument;
072    
073      /**
074       * The argument that will be used to indicate that we'll not look for
075       * default properties file.
076       */
077      private BooleanArgument noPropertiesFileArgument;
078    
079      // The argument that will be used to trigger the display of usage information.
080      private Argument usageArgument;
081    
082      // The argument that will be used to trigger the display of the OpenDS
083      // version.
084      private Argument versionArgument;
085    
086      // The set of unnamed trailing arguments that were provided for this parser.
087      private ArrayList<String> trailingArguments;
088    
089      // Indicates whether this parser will allow additional unnamed arguments at
090      // the end of the list.
091      private boolean allowsTrailingArguments;
092    
093      // Indicates whether long arguments should be treated in a case-sensitive
094      // manner.
095      private boolean longArgumentsCaseSensitive;
096    
097      // Indicates whether the usage or version information has been displayed.
098      private boolean usageOrVersionDisplayed;
099    
100      // Indicates whether the version argument was provided.
101      private boolean versionPresent;
102    
103      // The set of arguments defined for this parser, referenced by short ID.
104      private HashMap<Character,Argument> shortIDMap;
105    
106      //  The set of arguments defined for this parser, referenced by argument name.
107      private HashMap<String,Argument> argumentMap;
108    
109      //  The set of arguments defined for this parser, referenced by long ID.
110      private HashMap<String,Argument> longIDMap;
111    
112      // The maximum number of unnamed trailing arguments that may be provided.
113      private int maxTrailingArguments;
114    
115      // The minimum number of unnamed trailing arguments that may be provided.
116      private int minTrailingArguments;
117    
118      // The total set of arguments defined for this parser.
119      private LinkedList<Argument> argumentList;
120    
121      // The output stream to which usage information should be printed.
122      private OutputStream usageOutputStream;
123    
124      // The fully-qualified name of the Java class that should be invoked to launch
125      // the program with which this argument parser is associated.
126      private String mainClassName;
127    
128      // A human-readable description for the tool, which will be included when
129      // displaying usage information.
130      private Message toolDescription;
131    
132      // The display name that will be used for the trailing arguments in the usage
133      // information.
134      private String trailingArgsDisplayName;
135    
136      // The raw set of command-line arguments that were provided.
137      private String[] rawArguments;
138    
139      /** Set of argument groups. */
140      protected Set<ArgumentGroup> argumentGroups;
141    
142    
143      /**
144       * Group for arguments that have not been explicitly grouped.
145       * These will appear at the top of the usage statement without
146       * a header.
147       */
148      protected ArgumentGroup defaultArgGroup = new ArgumentGroup(
149              Message.EMPTY, Integer.MAX_VALUE);
150    
151    
152      /**
153       * Group for arguments that are related to utility input/output like
154       * verbose, quite, no-prompt etc.  These will appear toward the bottom
155       * of the usage statement.
156       */
157      protected ArgumentGroup ldapArgGroup = new ArgumentGroup(
158              INFO_DESCRIPTION_LDAP_CONNECTION_ARGS.get(), Integer.MIN_VALUE + 2);
159    
160    
161      /**
162       * Group for arguments that are related to utility input/output like
163       * verbose, quite, no-prompt etc.  These will appear toward the bottom
164       * of the usage statement.
165       */
166      protected ArgumentGroup ioArgGroup = new ArgumentGroup(
167              INFO_DESCRIPTION_IO_ARGS.get(), Integer.MIN_VALUE + 1);
168    
169    
170      /**
171       * Group for arguments that are general like help, version etc.
172       * These will appear at the end of the usage statement.
173       */
174      protected ArgumentGroup generalArgGroup = new ArgumentGroup(
175              INFO_DESCRIPTION_GENERAL_ARGS.get(), Integer.MIN_VALUE);
176    
177    
178      private final static String INDENT = "    ";
179      private final static int MAX_LENGTH = SetupUtils.isWindows() ? 79 : 80;
180    
181      /**
182       * Creates a new instance of this argument parser with no arguments.
183       * Unnamed trailing arguments will not be allowed.
184       *
185       * @param  mainClassName               The fully-qualified name of the Java
186       *                                     class that should be invoked to launch
187       *                                     the program with which this argument
188       *                                     parser is associated.
189       * @param  toolDescription             A human-readable description for the
190       *                                     tool, which will be included when
191       *                                     displaying usage information.
192       * @param  longArgumentsCaseSensitive  Indicates whether long arguments should
193       *                                     be treated in a case-sensitive manner.
194       */
195      public ArgumentParser(String mainClassName, Message toolDescription,
196                            boolean longArgumentsCaseSensitive)
197      {
198        this.mainClassName              = mainClassName;
199        this.toolDescription            = toolDescription;
200        this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
201    
202        argumentList            = new LinkedList<Argument>();
203        argumentMap             = new HashMap<String,Argument>();
204        shortIDMap              = new HashMap<Character,Argument>();
205        longIDMap               = new HashMap<String,Argument>();
206        allowsTrailingArguments = false;
207        usageOrVersionDisplayed = false;
208        versionPresent         = false;
209        trailingArgsDisplayName = null;
210        maxTrailingArguments    = 0;
211        minTrailingArguments    = 0;
212        trailingArguments       = new ArrayList<String>();
213        rawArguments            = null;
214        usageArgument           = null;
215        filePropertiesPathArgument = null;
216        noPropertiesFileArgument = null;
217        usageOutputStream       = System.out;
218        initGroups();
219      }
220    
221    
222    
223    
224    
225      /**
226       * Creates a new instance of this argument parser with no arguments that may
227       * or may not be allowed to have unnamed trailing arguments.
228       *
229       * @param  mainClassName               The fully-qualified name of the Java
230       *                                     class that should be invoked to launch
231       *                                     the program with which this argument
232       *                                     parser is associated.
233       * @param  toolDescription             A human-readable description for the
234       *                                     tool, which will be included when
235       *                                     displaying usage information.
236       * @param  longArgumentsCaseSensitive  Indicates whether long arguments should
237       *                                     be treated in a case-sensitive manner.
238       * @param  allowsTrailingArguments     Indicates whether this parser allows
239       *                                     unnamed trailing arguments to be
240       *                                     provided.
241       * @param  minTrailingArguments        The minimum number of unnamed trailing
242       *                                     arguments that must be provided.  A
243       *                                     value less than or equal to zero
244       *                                     indicates that no minimum will be
245       *                                     enforced.
246       * @param  maxTrailingArguments        The maximum number of unnamed trailing
247       *                                     arguments that may be provided.  A
248       *                                     value less than or equal to zero
249       *                                     indicates that no maximum will be
250       *                                     enforced.
251       * @param  trailingArgsDisplayName     The display name that should be used
252       *                                     as a placeholder for unnamed trailing
253       *                                     arguments in the generated usage
254       *                                     information.
255       */
256      public ArgumentParser(String mainClassName, Message toolDescription,
257                            boolean longArgumentsCaseSensitive,
258                            boolean allowsTrailingArguments,
259                            int minTrailingArguments, int maxTrailingArguments,
260                            String trailingArgsDisplayName)
261      {
262        this.mainClassName              = mainClassName;
263        this.toolDescription            = toolDescription;
264        this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
265        this.allowsTrailingArguments    = allowsTrailingArguments;
266        this.minTrailingArguments       = minTrailingArguments;
267        this.maxTrailingArguments       = maxTrailingArguments;
268        this.trailingArgsDisplayName    = trailingArgsDisplayName;
269    
270        argumentList      = new LinkedList<Argument>();
271        argumentMap       = new HashMap<String,Argument>();
272        shortIDMap        = new HashMap<Character,Argument>();
273        longIDMap         = new HashMap<String,Argument>();
274        trailingArguments = new ArrayList<String>();
275        usageOrVersionDisplayed = false;
276        versionPresent   = false;
277        rawArguments      = null;
278        usageArgument     = null;
279        usageOutputStream = System.out;
280        initGroups();
281      }
282    
283    
284    
285      /**
286       * Retrieves the fully-qualified name of the Java class that should be invoked
287       * to launch the program with which this argument parser is associated.
288       *
289       * @return  The fully-qualified name of the Java class that should be invoked
290       *          to launch the program with which this argument parser is
291       *          associated.
292       */
293      public String getMainClassName()
294      {
295        return mainClassName;
296      }
297    
298    
299    
300      /**
301       * Retrieves a human-readable description for this tool, which should be
302       * included at the top of the command-line usage information.
303       *
304       * @return  A human-readable description for this tool, or {@code null} if
305       *          none is available.
306       */
307      public Message getToolDescription()
308      {
309        return toolDescription;
310      }
311    
312    
313    
314      /**
315       * Indicates whether this parser will allow unnamed trailing arguments.  These
316       * will be arguments at the end of the list that are not preceded by either a
317       * long or short identifier and will need to be manually parsed by the
318       * application using this parser.  Note that once an unnamed trailing argument
319       * has been identified, all remaining arguments will be classified as such.
320       *
321       * @return  <CODE>true</CODE> if this parser allows unnamed trailing
322       *          arguments, or <CODE>false</CODE> if it does not.
323       */
324      public boolean allowsTrailingArguments()
325      {
326        return allowsTrailingArguments;
327      }
328    
329    
330    
331      /**
332       * Retrieves the minimum number of unnamed trailing arguments that must be
333       * provided.
334       *
335       * @return  The minimum number of unnamed trailing arguments that must be
336       *          provided, or a value less than or equal to zero if no minimum will
337       *          be enforced.
338       */
339      public int getMinTrailingArguments()
340      {
341        return minTrailingArguments;
342      }
343    
344    
345    
346      /**
347       * Retrieves the maximum number of unnamed trailing arguments that may be
348       * provided.
349       *
350       * @return  The maximum number of unnamed trailing arguments that may be
351       *          provided, or a value less than or equal to zero if no maximum will
352       *          be enforced.
353       */
354      public int getMaxTrailingArguments()
355      {
356        return maxTrailingArguments;
357      }
358    
359    
360    
361      /**
362       * Retrieves the list of all arguments that have been defined for this
363       * argument parser.
364       *
365       * @return  The list of all arguments that have been defined for this argument
366       *          parser.
367       */
368      public LinkedList<Argument> getArgumentList()
369      {
370        return argumentList;
371      }
372    
373    
374    
375      /**
376       * Retrieves the argument with the specified name.
377       *
378       * @param  name  The name of the argument to retrieve.
379       *
380       * @return  The argument with the specified name, or <CODE>null</CODE> if
381       *          there is no such argument.
382       */
383      public Argument getArgument(String name)
384      {
385        return argumentMap.get(name);
386      }
387    
388    
389    
390      /**
391       * Retrieves the set of arguments mapped by the short identifier that may be
392       * used to reference them.  Note that arguments that do not have a short
393       * identifier will not be present in this list.
394       *
395       * @return  The set of arguments mapped by the short identifier that may be
396       *          used to reference them.
397       */
398      public HashMap<Character,Argument> getArgumentsByShortID()
399      {
400        return shortIDMap;
401      }
402    
403    
404    
405      /**
406       * Retrieves the argument with the specified short identifier.
407       *
408       * @param  shortID  The short ID for the argument to retrieve.
409       *
410       * @return  The argument with the specified short identifier, or
411       *          <CODE>null</CODE> if there is no such argument.
412       */
413      public Argument getArgumentForShortID(Character shortID)
414      {
415        return shortIDMap.get(shortID);
416      }
417    
418    
419    
420      /**
421       * Retrieves the set of arguments mapped by the long identifier that may be
422       * used to reference them.  Note that arguments that do not have a long
423       * identifier will not be present in this list.
424       *
425       * @return  The set of arguments mapped by the long identifier that may be
426       *          used to reference them.
427       */
428      public HashMap<String,Argument> getArgumentsByLongID()
429      {
430        return longIDMap;
431      }
432    
433    
434    
435      /**
436       * Retrieves the argument with the specified long identifier.
437       *
438       * @param  longID  The long identifier of the argument to retrieve.
439       *
440       * @return  The argument with the specified long identifier, or
441       *          <CODE>null</CODE> if there is no such argument.
442       */
443      public Argument getArgumentForLongID(String longID)
444      {
445        return longIDMap.get(longID);
446      }
447    
448    
449    
450      /**
451       * Retrieves the set of unnamed trailing arguments that were provided on the
452       * command line.
453       *
454       * @return  The set of unnamed trailing arguments that were provided on the
455       *          command line.
456       */
457      public ArrayList<String> getTrailingArguments()
458      {
459        return trailingArguments;
460      }
461    
462    
463    
464      /**
465       * Retrieves the raw set of arguments that were provided.
466       *
467       * @return  The raw set of arguments that were provided, or <CODE>null</CODE>
468       *          if the argument list has not yet been parsed.
469       */
470      public String[] getRawArguments()
471      {
472        return rawArguments;
473      }
474    
475    
476      /**
477       * Sets the usage group description for the default argument group.
478       *
479       * @param description for the default group
480       */
481      public void setDefaultArgumentGroupDescription(Message description)
482      {
483        this.defaultArgGroup.setDescription(description);
484      }
485    
486    
487      /**
488       * Sets the usage group description for the LDAP argument group.
489       *
490       * @param description for the LDAP group
491       */
492      public void setLdapArgumentGroupDescription(Message description)
493      {
494        this.ldapArgGroup.setDescription(description);
495      }
496    
497    
498      /**
499       * Sets the usage group description for the input/output argument group.
500       *
501       * @param description for the input/output group
502       */
503      public void setInputOutputArgumentGroupDescription(Message description)
504      {
505        this.ioArgGroup.setDescription(description);
506      }
507    
508    
509      /**
510       * Sets the usage group description for the general argument group.
511       *
512       * @param description for the general group
513       */
514      public void setGeneralArgumentGroupDescription(Message description)
515      {
516        this.generalArgGroup.setDescription(description);
517      }
518    
519    
520      /**
521       * Adds the provided argument to the set of arguments handled by this parser.
522       *
523       * @param  argument  The argument to be added.
524       *
525       * @throws  ArgumentException  If the provided argument conflicts with another
526       *                             argument that has already been defined.
527       */
528      public void addArgument(Argument argument)
529             throws ArgumentException
530      {
531        addArgument(argument, null);
532      }
533    
534      /**
535       * Adds the provided argument to the set of arguments handled by this parser
536       * and puts the arguement in the default group.
537       *
538       * @param  argument  The argument to be added.
539       *
540       * @throws  ArgumentException  If the provided argument conflicts with another
541       *                             argument that has already been defined.
542       */
543      public void addDefaultArgument(Argument argument)
544             throws ArgumentException
545      {
546        addArgument(argument, defaultArgGroup);
547      }
548    
549      /**
550       * Adds the provided argument to the set of arguments handled by this parser
551       * and puts the argument in the LDAP connection group.
552       *
553       * @param  argument  The argument to be added.
554       *
555       * @throws  ArgumentException  If the provided argument conflicts with another
556       *                             argument that has already been defined.
557       */
558      public void addLdapConnectionArgument(Argument argument)
559             throws ArgumentException
560      {
561        addArgument(argument, ldapArgGroup);
562      }
563    
564      /**
565       * Adds the provided argument to the set of arguments handled by this parser
566       * and puts the argument in the input/output group.
567       *
568       * @param  argument  The argument to be added.
569       *
570       * @throws  ArgumentException  If the provided argument conflicts with another
571       *                             argument that has already been defined.
572       */
573      public void addInputOutputArgument(Argument argument)
574             throws ArgumentException
575      {
576        addArgument(argument, ioArgGroup);
577      }
578    
579      /**
580       * Adds the provided argument to the set of arguments handled by this parser
581       * and puts the arguement in the general group.
582       *
583       * @param  argument  The argument to be added.
584       *
585       * @throws  ArgumentException  If the provided argument conflicts with another
586       *                             argument that has already been defined.
587       */
588      public void addGeneralArgument(Argument argument)
589             throws ArgumentException
590      {
591        addArgument(argument, generalArgGroup);
592      }
593    
594      /**
595       * Adds the provided argument to the set of arguments handled by this parser.
596       *
597       * @param  argument  The argument to be added.
598       * @param  group     The argument group to which the argument belongs.
599       *
600       * @throws  ArgumentException  If the provided argument conflicts with another
601       *                             argument that has already been defined.
602       */
603      public void addArgument(Argument argument, ArgumentGroup group)
604             throws ArgumentException
605      {
606    
607        Character shortID = argument.getShortIdentifier();
608        if ((shortID != null) && shortIDMap.containsKey(shortID))
609        {
610          String conflictingName = shortIDMap.get(shortID).getName();
611    
612          Message message = ERR_ARGPARSER_DUPLICATE_SHORT_ID.get(
613              argument.getName(), String.valueOf(shortID), conflictingName);
614          throw new ArgumentException(message);
615        }
616    
617        if (versionArgument != null)
618        {
619          if (shortID == versionArgument.getShortIdentifier())
620          {
621            // Update the version argument to not display its short identifier.
622            try {
623              versionArgument = new BooleanArgument(
624                      OPTION_LONG_PRODUCT_VERSION,
625                      null,
626                      OPTION_LONG_PRODUCT_VERSION,
627                      INFO_DESCRIPTION_PRODUCT_VERSION.get());
628              this.generalArgGroup.addArgument(versionArgument);
629            } catch (ArgumentException e) {
630              // ignore
631            }
632          }
633        }
634    
635        String longID = argument.getLongIdentifier();
636        if (longID != null)
637        {
638          if (! longArgumentsCaseSensitive)
639          {
640            longID = toLowerCase(longID);
641          }
642          if (longIDMap.containsKey(longID))
643          {
644            String conflictingName = longIDMap.get(longID).getName();
645    
646            Message message = ERR_ARGPARSER_DUPLICATE_LONG_ID.get(
647                argument.getName(), argument.getLongIdentifier(), conflictingName);
648            throw new ArgumentException(message);
649          }
650        }
651    
652        if (shortID != null)
653        {
654          shortIDMap.put(shortID, argument);
655        }
656    
657        if (longID != null)
658        {
659          longIDMap.put(longID, argument);
660        }
661    
662        argumentList.add(argument);
663    
664        if (group == null) {
665          group = getStandardGroup(argument);
666        }
667        group.addArgument(argument);
668        argumentGroups.add(group);
669      }
670    
671    
672    
673      /**
674       * Sets the provided argument as one which will automatically trigger the
675       * output of usage information if it is provided on the command line and no
676       * further argument validation will be performed.  Note that the caller will
677       * still need to add this argument to the parser with the
678       * <CODE>addArgument</CODE> method, and the argument should not be required
679       * and should not take a value.  Also, the caller will still need to check
680       * for the presence of the usage argument after calling
681       * <CODE>parseArguments</CODE> to know that no further processing will be
682       * required.
683       *
684       * @param  argument      The argument whose presence should automatically
685       *                       trigger the display of usage information.
686       */
687      public void setUsageArgument(Argument argument)
688      {
689        usageArgument     = argument;
690        usageOutputStream = System.out;
691      }
692    
693    
694    
695      /**
696       * Sets the provided argument as one which will automatically trigger the
697       * output of usage information if it is provided on the command line and no
698       * further argument validation will be performed.  Note that the caller will
699       * still need to add this argument to the parser with the
700       * <CODE>addArgument</CODE> method, and the argument should not be required
701       * and should not take a value.  Also, the caller will still need to check
702       * for the presence of the usage argument after calling
703       * <CODE>parseArguments</CODE> to know that no further processing will be
704       * required.
705       *
706       * @param  argument      The argument whose presence should automatically
707       *                       trigger the display of usage information.
708       * @param  outputStream  The output stream to which the usage information
709       *                       should be written.
710       */
711      public void setUsageArgument(Argument argument, OutputStream outputStream)
712      {
713        usageArgument     = argument;
714        usageOutputStream = outputStream;
715      }
716    
717    
718      /**
719       * Sets the provided argument which will be used to identify the
720       * file properties.
721       *
722       * @param argument
723       *          The argument which will be used to identify the file
724       *          properties.
725       */
726      public void setFilePropertiesArgument(StringArgument argument)
727      {
728        filePropertiesPathArgument= argument;
729      }
730    
731      /**
732       * Sets the provided argument which will be used to identify the
733       * file properties.
734       *
735       * @param argument
736       *          The argument which will be used to indicate if we have to
737       *          look for properties file.
738       */
739      public void setNoPropertiesFileArgument(BooleanArgument argument)
740      {
741        noPropertiesFileArgument = argument;
742      }
743    
744      /**
745       * Parses the provided set of arguments and updates the information associated
746       * with this parser accordingly.
747       *
748       * @param  rawArguments  The raw set of arguments to parse.
749       *
750       * @throws  ArgumentException  If a problem was encountered while parsing the
751       *                             provided arguments.
752       */
753      public void parseArguments(String[] rawArguments)
754             throws ArgumentException
755      {
756        parseArguments(rawArguments, null);
757      }
758    
759    
760    
761      /**
762       * Parses the provided set of arguments and updates the information associated
763       * with this parser accordingly.  Default values for unspecified arguments
764       * may be read from the specified properties file.
765       *
766       * @param  rawArguments           The set of raw arguments to parse.
767       * @param  propertiesFile         The path to the properties file to use to
768       *                                obtain default values for unspecified
769       *                                properties.
770       * @param  requirePropertiesFile  Indicates whether the parsing should fail if
771       *                                the provided properties file does not exist
772       *                                or is not accessible.
773       *
774       * @throws  ArgumentException  If a problem was encountered while parsing the
775       *                             provided arguments or interacting with the
776       *                             properties file.
777       */
778      public void parseArguments(String[] rawArguments, String propertiesFile,
779                                 boolean requirePropertiesFile)
780             throws ArgumentException
781      {
782        this.rawArguments = rawArguments;
783    
784        Properties argumentProperties = null;
785    
786        try
787        {
788          Properties p = new Properties();
789          FileInputStream fis = new FileInputStream(propertiesFile);
790          p.load(fis);
791          fis.close();
792          argumentProperties = p;
793        }
794        catch (Exception e)
795        {
796          if (requirePropertiesFile)
797          {
798            Message message = ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(
799                String.valueOf(propertiesFile), getExceptionMessage(e));
800            throw new ArgumentException(message, e);
801          }
802        }
803    
804        parseArguments(rawArguments, argumentProperties);
805      }
806    
807    
808    
809      /**
810       * Parses the provided set of arguments and updates the information associated
811       * with this parser accordingly.  Default values for unspecified arguments may
812       * be read from the specified properties if any are provided.
813       *
814       * @param  rawArguments        The set of raw arguments to parse.
815       * @param  argumentProperties  A set of properties that may be used to provide
816       *                             default values for arguments not included in
817       *                             the given raw arguments.
818       *
819       * @throws  ArgumentException  If a problem was encountered while parsing the
820       *                             provided arguments.
821       */
822      public void parseArguments(String[] rawArguments,
823                                 Properties argumentProperties)
824             throws ArgumentException
825      {
826        this.rawArguments = rawArguments;
827    
828        boolean inTrailingArgs = false;
829    
830        int numArguments = rawArguments.length;
831        for (int i=0; i < numArguments; i++)
832        {
833          String arg = rawArguments[i];
834    
835          if (inTrailingArgs)
836          {
837            trailingArguments.add(arg);
838            if ((maxTrailingArguments > 0) &&
839                (trailingArguments.size() > maxTrailingArguments))
840            {
841              Message message =
842                  ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS.get(maxTrailingArguments);
843              throw new ArgumentException(message);
844            }
845    
846            continue;
847          }
848    
849          if (arg.equals("--"))
850          {
851            // This is a special indicator that we have reached the end of the named
852            // arguments and that everything that follows after this should be
853            // considered trailing arguments.
854            inTrailingArgs = true;
855          }
856          else if (arg.startsWith("--"))
857          {
858            // This indicates that we are using the long name to reference the
859            // argument.  It may be in any of the following forms:
860            // --name
861            // --name value
862            // --name=value
863    
864            String argName  = arg.substring(2);
865            String argValue = null;
866            int    equalPos = argName.indexOf('=');
867            if (equalPos < 0)
868            {
869              // This is fine.  The value is not part of the argument name token.
870            }
871            else if (equalPos == 0)
872            {
873              // The argument starts with "--=", which is not acceptable.
874              Message message = ERR_ARGPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
875              throw new ArgumentException(message);
876            }
877            else
878            {
879              // The argument is in the form --name=value, so parse them both out.
880              argValue = argName.substring(equalPos+1);
881              argName  = argName.substring(0, equalPos);
882            }
883    
884            // If we're not case-sensitive, then convert the name to lowercase.
885            String origArgName = argName;
886            if (! longArgumentsCaseSensitive)
887            {
888              argName = toLowerCase(argName);
889            }
890    
891            // Get the argument with the specified name.
892            Argument a = longIDMap.get(argName);
893            if (a == null)
894            {
895              if (argName.equals(OPTION_LONG_HELP))
896              {
897                // "--help" will always be interpreted as requesting usage
898                // information.
899                try
900                {
901                  getUsage(usageOutputStream);
902                } catch (Exception e) {}
903    
904                return;
905              }
906              else
907              if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
908              {
909                // "--version" will always be interpreted as requesting version
910                // information.
911                usageOrVersionDisplayed = true;
912                versionPresent = true;
913                try
914                {
915                  DirectoryServer.printVersion(usageOutputStream);
916                } catch (Exception e) {}
917    
918                return;
919              }
920              else
921              {
922                // There is no such argument registered.
923                Message message =
924                    ERR_ARGPARSER_NO_ARGUMENT_WITH_LONG_ID.get(origArgName);
925                throw new ArgumentException(message);
926              }
927            }
928            else
929            {
930              a.setPresent(true);
931    
932              // If this is the usage argument, then immediately stop and print
933              // usage information.
934              if ((usageArgument != null) &&
935                  usageArgument.getName().equals(a.getName()))
936              {
937                try
938                {
939                  getUsage(usageOutputStream);
940                } catch (Exception e) {}
941    
942                return;
943              }
944            }
945    
946            // See if the argument takes a value.  If so, then make sure one was
947            // provided.  If not, then make sure none was provided.
948            if (a.needsValue())
949            {
950              if (argValue == null)
951              {
952                if ((i+1) == numArguments)
953                {
954                  Message message =
955                      ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID.get(
956                          origArgName);
957                  throw new ArgumentException(message);
958                }
959    
960                argValue = rawArguments[++i];
961              }
962    
963              MessageBuilder invalidReason = new MessageBuilder();
964              if (! a.valueIsAcceptable(argValue, invalidReason))
965              {
966                Message message = ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.get(
967                    argValue, origArgName, invalidReason.toString());
968                throw new ArgumentException(message);
969              }
970    
971              // If the argument already has a value, then make sure it is
972              // acceptable to have more than one.
973              if (a.hasValue() && (! a.isMultiValued()))
974              {
975                Message message =
976                    ERR_ARGPARSER_NOT_MULTIVALUED_FOR_LONG_ID.get(origArgName);
977                throw new ArgumentException(message);
978              }
979    
980              a.addValue(argValue);
981            }
982            else
983            {
984              if (argValue != null)
985              {
986                Message message =
987                    ERR_ARGPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE.get(
988                        origArgName);
989                throw new ArgumentException(message);
990              }
991            }
992          }
993          else if (arg.startsWith("-"))
994          {
995            // This indicates that we are using the 1-character name to reference
996            // the argument.  It may be in any of the following forms:
997            // -n
998            // -nvalue
999            // -n value
1000            if (arg.equals("-"))
1001            {
1002              Message message = ERR_ARGPARSER_INVALID_DASH_AS_ARGUMENT.get();
1003              throw new ArgumentException(message);
1004            }
1005    
1006            char argCharacter = arg.charAt(1);
1007            String argValue;
1008            if (arg.length() > 2)
1009            {
1010              argValue = arg.substring(2);
1011            }
1012            else
1013            {
1014              argValue = null;
1015            }
1016    
1017    
1018            // Get the argument with the specified short ID.
1019            Argument a = shortIDMap.get(argCharacter);
1020            if (a == null)
1021            {
1022              if (argCharacter == '?')
1023              {
1024                // "-?" will always be interpreted as requesting usage information.
1025                try
1026                {
1027                  getUsage(usageOutputStream);
1028                } catch (Exception e) {}
1029    
1030                return;
1031              }
1032              else
1033              if ( (argCharacter == OPTION_SHORT_PRODUCT_VERSION)
1034                   &&
1035                   ( ! shortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)))
1036              {
1037                // "-V" will always be interpreted as requesting
1038                // version information except if it's already defined (e.g in
1039                // ldap tools).
1040                usageOrVersionDisplayed = true ;
1041                versionPresent = true;
1042                try
1043                {
1044                  DirectoryServer.printVersion(usageOutputStream);
1045                } catch (Exception e) {}
1046                return;
1047              }
1048              else
1049              {
1050                // There is no such argument registered.
1051                Message message = ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(
1052                    String.valueOf(argCharacter));
1053                throw new ArgumentException(message);
1054              }
1055            }
1056            else
1057            {
1058              a.setPresent(true);
1059    
1060              // If this is the usage argument, then immediately stop and print
1061              // usage information.
1062              if ((usageArgument != null) &&
1063                  usageArgument.getName().equals(a.getName()))
1064              {
1065                try
1066                {
1067                  getUsage(usageOutputStream);
1068                } catch (Exception e) {}
1069    
1070                return;
1071              }
1072            }
1073    
1074            // See if the argument takes a value.  If so, then make sure one was
1075            // provided.  If not, then make sure none was provided.
1076            if (a.needsValue())
1077            {
1078              if (argValue == null)
1079              {
1080                if ((i+1) == numArguments)
1081                {
1082                  Message message =
1083                      ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID.
1084                        get(String.valueOf(argCharacter));
1085                  throw new ArgumentException(message);
1086                }
1087    
1088                argValue = rawArguments[++i];
1089              }
1090    
1091              MessageBuilder invalidReason = new MessageBuilder();
1092              if (! a.valueIsAcceptable(argValue, invalidReason))
1093              {
1094                Message message = ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.
1095                    get(argValue, String.valueOf(argCharacter),
1096                        invalidReason.toString());
1097                throw new ArgumentException(message);
1098              }
1099    
1100              // If the argument already has a value, then make sure it is
1101              // acceptable to have more than one.
1102              if (a.hasValue() && (! a.isMultiValued()))
1103              {
1104                Message message = ERR_ARGPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(
1105                    String.valueOf(argCharacter));
1106                throw new ArgumentException(message);
1107              }
1108    
1109              a.addValue(argValue);
1110            }
1111            else
1112            {
1113              if (argValue != null)
1114              {
1115                // If we've gotten here, then it means that we're in a scenario like
1116                // "-abc" where "a" is a valid argument that doesn't take a value.
1117                // However, this could still be valid if all remaining characters in
1118                // the value are also valid argument characters that don't take
1119                // values.
1120                int valueLength = argValue.length();
1121                for (int j=0; j < valueLength; j++)
1122                {
1123                  char c = argValue.charAt(j);
1124                  Argument b = shortIDMap.get(c);
1125                  if (b == null)
1126                  {
1127                    // There is no such argument registered.
1128                    Message message = ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(
1129                        String.valueOf(argCharacter));
1130                    throw new ArgumentException(message);
1131                  }
1132                  else if (b.needsValue())
1133                  {
1134                    // This means we're in a scenario like "-abc" where b is a
1135                    // valid argument that takes a value.  We don't support that.
1136                    Message message = ERR_ARGPARSER_CANT_MIX_ARGS_WITH_VALUES.get(
1137                        String.valueOf(argCharacter), argValue, String.valueOf(c));
1138                    throw new ArgumentException(message);
1139                  }
1140                  else
1141                  {
1142                    b.setPresent(true);
1143    
1144                    // If this is the usage argument, then immediately stop and
1145                    // print usage information.
1146                    if ((usageArgument != null) &&
1147                        usageArgument.getName().equals(b.getName()))
1148                    {
1149                      try
1150                      {
1151                        getUsage(usageOutputStream);
1152                      } catch (Exception e) {}
1153    
1154                      return;
1155                    }
1156                  }
1157                }
1158              }
1159            }
1160          }
1161          else if (allowsTrailingArguments)
1162          {
1163            // It doesn't start with a dash, so it must be a trailing argument if
1164            // that is acceptable.
1165            inTrailingArgs = true;
1166            trailingArguments.add(arg);
1167          }
1168          else
1169          {
1170            // It doesn't start with a dash and we don't allow trailing arguments,
1171            // so this is illegal.
1172            Message message = ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
1173            throw new ArgumentException(message);
1174          }
1175        }
1176    
1177    
1178        // If we allow trailing arguments and there is a minimum number, then make
1179        // sure at least that many were provided.
1180        if (allowsTrailingArguments && (minTrailingArguments > 0))
1181        {
1182          if (trailingArguments.size() < minTrailingArguments)
1183          {
1184            Message message =
1185                ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS.get(minTrailingArguments);
1186            throw new ArgumentException(message);
1187          }
1188        }
1189    
1190        // If we don't have the argumentProperties, try to load a properties file.
1191        if (argumentProperties == null)
1192        {
1193          argumentProperties = checkExternalProperties();
1194        }
1195    
1196        // Iterate through all of the arguments.  For any that were not provided on
1197        // the command line, see if there is an alternate default that can be used.
1198        // For cases where there is not, see that argument is required.
1199        for (Argument a : argumentList)
1200        {
1201          if (! a.isPresent())
1202          {
1203            // See if there is a value in the properties that can be used
1204            if ((argumentProperties != null) && (a.getPropertyName() != null))
1205            {
1206              String value = argumentProperties.getProperty(a.getPropertyName()
1207                  .toLowerCase());
1208              MessageBuilder invalidReason =  new MessageBuilder();
1209              if (value != null)
1210              {
1211                Boolean addValue = true;
1212                if (!( a instanceof BooleanArgument))
1213                {
1214                  addValue = a.valueIsAcceptable(value, invalidReason);
1215                }
1216                if (addValue)
1217                {
1218                  a.addValue(value);
1219                  if (a.needsValue())
1220                  {
1221                    a.setPresent(true);
1222                  }
1223                  a.setValueSetByProperty(true);
1224                }
1225              }
1226            }
1227          }
1228    
1229    
1230          if ((! a.isPresent()) && a.needsValue())
1231          {
1232            // See if the argument defines a default.
1233            if (a.getDefaultValue() != null)
1234            {
1235              a.addValue(a.getDefaultValue());
1236            }
1237    
1238            // If there is still no value and the argument is required, then that's
1239            // a problem.
1240            if ((! a.hasValue()) && a.isRequired())
1241            {
1242              Message message =
1243                  ERR_ARGPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
1244              throw new ArgumentException(message);
1245            }
1246          }
1247        }
1248      }
1249    
1250    
1251    
1252      /**
1253       * Check if we have a properties file.
1254       *
1255       * @return The properties found in the properties file or null.
1256       * @throws ArgumentException
1257       *           If a problem was encountered while parsing the provided
1258       *           arguments.
1259       */
1260      protected Properties checkExternalProperties()
1261          throws ArgumentException
1262      {
1263        // We don't look for properties file.
1264        if ((noPropertiesFileArgument != null)
1265            && (noPropertiesFileArgument.isPresent()))
1266        {
1267          return null;
1268        }
1269    
1270        // Check if we have a properties file argument
1271        if (filePropertiesPathArgument == null)
1272        {
1273          return null;
1274        }
1275    
1276        // check if the properties file argument has been set. If not
1277        // look for default location.
1278        String propertiesFilePath = null;
1279        if (filePropertiesPathArgument.isPresent())
1280        {
1281          propertiesFilePath = filePropertiesPathArgument.getValue();
1282        }
1283        else
1284        {
1285          // Check in "user home"/.opends directory
1286          String userDir = System.getProperty("user.home");
1287          propertiesFilePath = findPropertiesFile(userDir + File.separator
1288              + DEFAULT_OPENDS_CONFIG_DIR);
1289    
1290          if (propertiesFilePath == null)
1291          {
1292            // check "Opends instance"/config directory
1293            String instanceDir = DirectoryServer.getServerRoot();
1294            propertiesFilePath = findPropertiesFile(instanceDir+ File.separator
1295                + "config");
1296          }
1297        }
1298    
1299        // We don't have a properties file location
1300        if (propertiesFilePath == null)
1301        {
1302          return null;
1303        }
1304    
1305        // We have a location for the properties file.
1306        Properties argumentProperties = new Properties();
1307        String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1308        try
1309        {
1310          Properties p = new Properties();
1311          FileInputStream fis = new FileInputStream(propertiesFilePath);
1312          p.load(fis);
1313          fis.close();
1314    
1315          for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();)
1316          {
1317            String currentPropertyName = (String) e.nextElement();
1318            String propertyName = currentPropertyName;
1319    
1320            // Property name form <script name>.<property name> has the
1321            // precedence to <property name>
1322            if (scriptName != null)
1323            {
1324              if (currentPropertyName.startsWith(scriptName))
1325              {
1326               propertyName = currentPropertyName
1327                    .substring(scriptName.length() + 1);
1328              }
1329              else
1330              {
1331                if (p.containsKey(scriptName + "." + currentPropertyName ))
1332                {
1333                  continue;
1334                }
1335              }
1336            }
1337            argumentProperties.setProperty(propertyName.toLowerCase(), p
1338                .getProperty(currentPropertyName));
1339          }
1340        }
1341        catch (Exception e)
1342        {
1343          Message message = ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(String
1344              .valueOf(propertiesFilePath), getExceptionMessage(e));
1345          throw new ArgumentException(message, e);
1346        }
1347        return argumentProperties;
1348      }
1349    
1350    
1351      /**
1352       * Get the absolute path of the properties file.
1353       *
1354       * @param directory
1355       *          The location in which we should look for properties file
1356       * @return The absolute path of the properties file or null
1357       */
1358      private String findPropertiesFile(String directory)
1359      {
1360        // Look for the tools properties file
1361        File f = new File(directory,DEFAULT_OPENDS_PROPERTIES_FILE_NAME
1362            + DEFAULT_OPENDS_PROPERTIES_FILE_EXTENSION);
1363        if (f.exists() && f.canRead())
1364        {
1365          return f.getAbsolutePath();
1366        }
1367        else
1368        {
1369          return null;
1370        }
1371      }
1372    
1373      /**
1374       * Appends usage information based on the defined arguments to the
1375       * provided buffer.
1376       *
1377       * @param buffer
1378       *          The buffer to which the usage information should be
1379       *          appended.
1380       */
1381      public void getUsage(StringBuilder buffer)
1382      {
1383        usageOrVersionDisplayed = true;
1384        if ((toolDescription != null) && (toolDescription.length() > 0))
1385        {
1386          buffer.append(wrapText(toolDescription.toString(), MAX_LENGTH - 1));
1387          buffer.append(EOL);
1388          buffer.append(EOL);
1389        }
1390    
1391        String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
1392        if ((scriptName == null) || (scriptName.length() == 0))
1393        {
1394          buffer.append(INFO_ARGPARSER_USAGE_JAVA_CLASSNAME.get(mainClassName));
1395        }
1396        else
1397        {
1398          buffer.append(INFO_ARGPARSER_USAGE_JAVA_SCRIPTNAME.get(scriptName));
1399        }
1400    
1401        if (allowsTrailingArguments)
1402        {
1403          if (trailingArgsDisplayName == null)
1404          {
1405            buffer.append(" "+INFO_ARGPARSER_USAGE_TRAILINGARGS.get());
1406          }
1407          else
1408          {
1409            buffer.append(" ");
1410            buffer.append(trailingArgsDisplayName);
1411          }
1412        }
1413        buffer.append(EOL);
1414        buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
1415        buffer.append(EOL);
1416        buffer.append(EOL);
1417    
1418        Argument helpArgument = null ;
1419    
1420        boolean printHeaders = printUsageGroupHeaders();
1421        for (ArgumentGroup argGroup : argumentGroups)
1422        {
1423          if (argGroup.containsArguments() && printHeaders)
1424          {
1425            // Print the groups description if any
1426            Message groupDesc = argGroup.getDescription();
1427            if (groupDesc != null && !Message.EMPTY.equals(groupDesc)) {
1428              buffer.append(EOL);
1429              buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
1430              buffer.append(EOL);
1431              buffer.append(EOL);
1432            }
1433          }
1434    
1435          for (Argument a : argGroup.getArguments())
1436          {
1437            // If this argument is hidden, then skip it.
1438            if (a.isHidden())
1439            {
1440              continue;
1441            }
1442    
1443            // Help argument should be printed at the end
1444            if ((usageArgument != null) &&
1445                    usageArgument.getName().equals(a.getName()))
1446            {
1447              helpArgument = a ;
1448              continue ;
1449            }
1450            printArgumentUsage(a, buffer);
1451          }
1452        }
1453        if (helpArgument != null)
1454        {
1455          printArgumentUsage(helpArgument, buffer);
1456        }
1457        else
1458        {
1459          buffer.append(EOL);
1460          buffer.append("-?");
1461          buffer.append(EOL);
1462        }
1463      }
1464    
1465    
1466    
1467      /**
1468       * Retrieves a message containing usage information based on the defined
1469       * arguments.
1470       *
1471       * @return  A string containing usage information based on the defined
1472       *          arguments.
1473       */
1474      public Message getUsageMessage()
1475      {
1476        StringBuilder buffer = new StringBuilder();
1477        getUsage(buffer);
1478    
1479        // TODO: rework getUsage(OutputStream) to work with messages framework
1480        return Message.raw(buffer.toString());
1481      }
1482    
1483      /**
1484       * Retrieves a string containing usage information based on the defined
1485       * arguments.
1486       *
1487       * @return  A string containing usage information based on the defined
1488       *          arguments.
1489       */
1490      public String getUsage()
1491      {
1492        StringBuilder buffer = new StringBuilder();
1493        getUsage(buffer);
1494    
1495        return buffer.toString();
1496      }
1497    
1498    
1499    
1500      /**
1501       * Writes usage information based on the defined arguments to the provided
1502       * output stream.
1503       *
1504       * @param  outputStream  The output stream to which the usage information
1505       *                       should be written.
1506       *
1507       * @throws  IOException  If a problem occurs while attempting to write the
1508       *                       usage information to the provided output stream.
1509       */
1510      public void getUsage(OutputStream outputStream)
1511             throws IOException
1512      {
1513        StringBuilder buffer = new StringBuilder();
1514        getUsage(buffer);
1515    
1516        outputStream.write(getBytes(buffer.toString()));
1517      }
1518    
1519    
1520    
1521      /**
1522       * Indicates whether the version or the usage information has been
1523       * displayed to the end user either by an explicit argument like
1524       * "-H" or "--help", or by a built-in argument like "-?".
1525       *
1526       * @return {@code true} if the usage information has been displayed,
1527       *         or {@code false} if not.
1528       */
1529      public boolean usageOrVersionDisplayed()
1530      {
1531        return usageOrVersionDisplayed;
1532      }
1533    
1534      /**
1535      * Appends argument usage information to the provided buffer.
1536      *
1537      * @param a The argument to handle.
1538      * @param buffer
1539      *          The buffer to which the usage information should be
1540      *          appended.
1541      */
1542       private void printArgumentUsage(Argument a, StringBuilder buffer)
1543      {
1544        // Write a line with the short and/or long identifiers that may be
1545        // used
1546        // for the argument.
1547        final int indentLength = INDENT.length();
1548        Character shortID = a.getShortIdentifier();
1549        String longID = a.getLongIdentifier();
1550        if (shortID != null)
1551        {
1552          int currentLength = buffer.length();
1553    
1554          if (usageArgument.getName().equals(a.getName()))
1555          {
1556            buffer.append("-?, ");
1557          }
1558    
1559          buffer.append("-");
1560          buffer.append(shortID.charValue());
1561    
1562          if (a.needsValue() && longID == null)
1563          {
1564            buffer.append(" ");
1565            buffer.append(a.getValuePlaceholder());
1566          }
1567    
1568          if (longID != null)
1569          {
1570            StringBuilder newBuffer = new StringBuilder();
1571            newBuffer.append(", --");
1572            newBuffer.append(longID);
1573    
1574            if (a.needsValue())
1575            {
1576              newBuffer.append(" ");
1577              newBuffer.append(a.getValuePlaceholder());
1578            }
1579    
1580            int lineLength = (buffer.length() - currentLength) +
1581                             newBuffer.length();
1582            if (lineLength > MAX_LENGTH)
1583            {
1584              buffer.append(EOL);
1585              buffer.append(newBuffer.toString());
1586            }
1587            else
1588            {
1589              buffer.append(newBuffer.toString());
1590            }
1591          }
1592    
1593          buffer.append(EOL);
1594        }
1595        else
1596        {
1597          if (longID != null)
1598          {
1599            if (usageArgument.getName().equals(a.getName()))
1600            {
1601              buffer.append("-?, ");
1602            }
1603            buffer.append("--");
1604            buffer.append(longID);
1605    
1606            if (a.needsValue())
1607            {
1608              buffer.append(" ");
1609              buffer.append(a.getValuePlaceholder());
1610            }
1611    
1612            buffer.append(EOL);
1613          }
1614        }
1615    
1616    
1617        // Write one or more lines with the description of the argument.
1618        // We will
1619        // indent the description five characters and try our best to wrap
1620        // at or
1621        // before column 79 so it will be friendly to 80-column displays.
1622        Message description = a.getDescription();
1623        int descMaxLength = MAX_LENGTH - indentLength - 1;
1624        if (description.length() <= descMaxLength)
1625        {
1626          buffer.append(INDENT);
1627          buffer.append(description);
1628          buffer.append(EOL);
1629        }
1630        else
1631        {
1632          String s = description.toString();
1633          while (s.length() > descMaxLength)
1634          {
1635            int spacePos = s.lastIndexOf(' ', descMaxLength);
1636            if (spacePos > 0)
1637            {
1638              buffer.append(INDENT);
1639              buffer.append(s.substring(0, spacePos).trim());
1640              s = s.substring(spacePos+1).trim();
1641              buffer.append(EOL);
1642            }
1643            else
1644            {
1645              // There are no spaces in the first 74 columns. See if there
1646              // is one
1647              // after that point. If so, then break there. If not, then
1648              // don't
1649              // break at all.
1650              spacePos = s.indexOf(' ');
1651              if (spacePos > 0)
1652              {
1653                buffer.append(INDENT);
1654                buffer.append(s.substring(0, spacePos).trim());
1655                s = s.substring(spacePos+1).trim();
1656                buffer.append(EOL);
1657              }
1658              else
1659              {
1660                buffer.append(INDENT);
1661                buffer.append(s);
1662                s = "";
1663                buffer.append(EOL);
1664              }
1665            }
1666          }
1667    
1668          if (s.length() > 0)
1669          {
1670            buffer.append(INDENT);
1671            buffer.append(s);
1672            buffer.append(EOL);
1673          }
1674        }
1675      }
1676    
1677      /**
1678       * Given an argument, returns an appropriate group.  Arguments may
1679       * be part of one of the special groups or the default group.
1680       *
1681       * @param argument for which a group is requested
1682       * @return argument group appropriate for <code>argument</code>
1683       */
1684      protected ArgumentGroup getStandardGroup(Argument argument) {
1685        ArgumentGroup group;
1686        if (isInputOutputArgument(argument)) {
1687          group = ioArgGroup;
1688        } else if (isGeneralArgument(argument)) {
1689          group = generalArgGroup;
1690        } else if (isLdapConnectionArgument(argument)) {
1691          group = ldapArgGroup;
1692        } else {
1693          group = defaultArgGroup;
1694        }
1695        return group;
1696      }
1697    
1698      /**
1699       * Indicates whether or not argument group description headers
1700       * should be printed.
1701       *
1702       * @return boolean where true means print the descriptions
1703       */
1704      protected boolean printUsageGroupHeaders() {
1705        // If there is only a single group then we won't print them.
1706        int groupsContainingArgs = 0;
1707        for (ArgumentGroup argGroup : argumentGroups)
1708        {
1709          if (argGroup.containsNonHiddenArguments())
1710          {
1711            groupsContainingArgs++;
1712          }
1713        }
1714        return groupsContainingArgs > 1;
1715      }
1716    
1717      private void initGroups() {
1718        this.argumentGroups = new TreeSet<ArgumentGroup>();
1719        this.argumentGroups.add(defaultArgGroup);
1720        this.argumentGroups.add(ldapArgGroup);
1721        this.argumentGroups.add(generalArgGroup);
1722        this.argumentGroups.add(ioArgGroup);
1723    
1724        try {
1725          versionArgument = new BooleanArgument(
1726                  OPTION_LONG_PRODUCT_VERSION,
1727                  OPTION_SHORT_PRODUCT_VERSION,
1728                  OPTION_LONG_PRODUCT_VERSION,
1729                  INFO_DESCRIPTION_PRODUCT_VERSION.get());
1730          this.generalArgGroup.addArgument(versionArgument);
1731        } catch (ArgumentException e) {
1732          // ignore
1733        }
1734      }
1735    
1736    
1737      private boolean isInputOutputArgument(Argument arg) {
1738        boolean io = false;
1739        if (arg != null) {
1740          String longId = arg.getLongIdentifier();
1741          io = OPTION_LONG_VERBOSE.equals(longId) ||
1742                  OPTION_LONG_QUIET.equals(longId) ||
1743                  OPTION_LONG_NO_PROMPT.equals(longId) ||
1744                  OPTION_LONG_PROP_FILE_PATH.equals(longId) ||
1745                  OPTION_LONG_NO_PROP_FILE.equals(longId) ||
1746                  OPTION_LONG_SCRIPT_FRIENDLY.equals(longId) ||
1747                  OPTION_LONG_DONT_WRAP.equals(longId) ||
1748                  OPTION_LONG_ENCODING.equals(longId) ||
1749                  OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT.equals(longId) ||
1750                  OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH.equals(longId);
1751        }
1752        return io;
1753      }
1754    
1755      private boolean isLdapConnectionArgument(Argument arg) {
1756        boolean ldap = false;
1757        if (arg != null) {
1758          String longId = arg.getLongIdentifier();
1759          ldap = OPTION_LONG_USE_SSL.equals(longId) ||
1760                  OPTION_LONG_START_TLS.equals(longId) ||
1761                  OPTION_LONG_HOST.equals(longId) ||
1762                  OPTION_LONG_PORT.equals(longId) ||
1763                  OPTION_LONG_BINDDN.equals(longId) ||
1764                  OPTION_LONG_BINDPWD.equals(longId) ||
1765                  OPTION_LONG_BINDPWD_FILE.equals(longId) ||
1766                  OPTION_LONG_SASLOPTION.equals(longId) ||
1767                  OPTION_LONG_TRUSTALL.equals(longId) ||
1768                  OPTION_LONG_TRUSTSTOREPATH.equals(longId) ||
1769                  OPTION_LONG_TRUSTSTORE_PWD.equals(longId) ||
1770                  OPTION_LONG_TRUSTSTORE_PWD_FILE.equals(longId) ||
1771                  OPTION_LONG_KEYSTOREPATH.equals(longId) ||
1772                  OPTION_LONG_KEYSTORE_PWD.equals(longId) ||
1773                  OPTION_LONG_KEYSTORE_PWD_FILE.equals(longId) ||
1774                  OPTION_LONG_CERT_NICKNAME.equals(longId) ||
1775                  OPTION_LONG_REFERENCED_HOST_NAME.equals(longId) ||
1776                  OPTION_LONG_ADMIN_UID.equals(longId) ||
1777                  OPTION_LONG_REPORT_AUTHZ_ID.equals(longId) ||
1778                  OPTION_LONG_USE_PW_POLICY_CTL.equals(longId) ||
1779                  OPTION_LONG_USE_SASL_EXTERNAL.equals(longId) ||
1780                  OPTION_LONG_PROTOCOL_VERSION.equals(longId);
1781        }
1782        return ldap;
1783      }
1784    
1785    
1786      private boolean isGeneralArgument(Argument arg) {
1787        boolean general = false;
1788        if (arg != null) {
1789          String longId = arg.getLongIdentifier();
1790          general = OPTION_LONG_HELP.equals(longId) ||
1791                        OPTION_LONG_PRODUCT_VERSION.equals(longId);
1792        }
1793        return general;
1794      }
1795    
1796      /**
1797       * Returns whether the usage argument was provided or not.  This method
1798       * should be called after a call to parseArguments.
1799       * @return <CODE>true</CODE> if the usage argument was provided and
1800       * <CODE>false</CODE> otherwise.
1801       */
1802      public boolean isUsageArgumentPresent()
1803      {
1804        boolean isUsageArgumentPresent = false;
1805        if (usageArgument != null)
1806        {
1807          isUsageArgumentPresent = usageArgument.isPresent();
1808        }
1809        return isUsageArgumentPresent;
1810      }
1811    
1812      /**
1813       * Returns whether the version argument was provided or not.  This method
1814       * should be called after a call to parseArguments.
1815       * @return <CODE>true</CODE> if the version argument was provided and
1816       * <CODE>false</CODE> otherwise.
1817       */
1818      public boolean isVersionArgumentPresent()
1819      {
1820        return versionPresent;
1821      }
1822    }
1823