001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.cli;
019
020import java.io.BufferedReader;
021import java.io.IOException;
022import java.io.PrintWriter;
023import java.io.Serializable;
024import java.io.StringReader;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.Iterator;
031import java.util.List;
032
033/**
034 * A formatter of help messages for command line options.
035 *
036 * <p>Example:</p>
037 * 
038 * <pre>
039 * Options options = new Options();
040 * options.addOption(OptionBuilder.withLongOpt("file")
041 *                                .withDescription("The file to be processed")
042 *                                .hasArg()
043 *                                .withArgName("FILE")
044 *                                .isRequired()
045 *                                .create('f'));
046 * options.addOption(OptionBuilder.withLongOpt("version")
047 *                                .withDescription("Print the version of the application")
048 *                                .create('v'));
049 * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
050 * 
051 * String header = "Do something useful with an input file\n\n";
052 * String footer = "\nPlease report issues at http://example.com/issues";
053 * 
054 * HelpFormatter formatter = new HelpFormatter();
055 * formatter.printHelp("myapp", header, options, footer, true);
056 * </pre>
057 * 
058 * This produces the following output:
059 * 
060 * <pre>
061 * usage: myapp -f <FILE> [-h] [-v]
062 * Do something useful with an input file
063 * 
064 *  -f,--file &lt;FILE>   The file to be processed
065 *  -h,--help
066 *  -v,--version       Print the version of the application
067 * 
068 * Please report issues at http://example.com/issues
069 * </pre>
070 * 
071 * @version $Id: HelpFormatter.java 1444961 2013-02-11 21:03:51Z tn $
072 */
073public class HelpFormatter
074{
075    // --------------------------------------------------------------- Constants
076
077    /** default number of characters per line */
078    public static final int DEFAULT_WIDTH = 74;
079
080    /** default padding to the left of each line */
081    public static final int DEFAULT_LEFT_PAD = 1;
082
083    /** number of space characters to be prefixed to each description line */
084    public static final int DEFAULT_DESC_PAD = 3;
085
086    /** the string to display at the beginning of the usage statement */
087    public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
088
089    /** default prefix for shortOpts */
090    public static final String DEFAULT_OPT_PREFIX = "-";
091
092    /** default prefix for long Option */
093    public static final String DEFAULT_LONG_OPT_PREFIX = "--";
094
095    /** default separator displayed between a long Option and its value */
096    public static final String DEFAULT_LONG_OPT_SEPARATOR = " ";
097
098    /** default name for an argument */
099    public static final String DEFAULT_ARG_NAME = "arg";
100
101    // -------------------------------------------------------------- Attributes
102
103    /**
104     * number of characters per line
105     *
106     * @deprecated Scope will be made private for next major version
107     * - use get/setWidth methods instead.
108     */
109    @Deprecated
110    public int defaultWidth = DEFAULT_WIDTH;
111
112    /**
113     * amount of padding to the left of each line
114     *
115     * @deprecated Scope will be made private for next major version
116     * - use get/setLeftPadding methods instead.
117     */
118    @Deprecated
119    public int defaultLeftPad = DEFAULT_LEFT_PAD;
120
121    /**
122     * the number of characters of padding to be prefixed
123     * to each description line
124     *
125     * @deprecated Scope will be made private for next major version
126     * - use get/setDescPadding methods instead.
127     */
128    @Deprecated
129    public int defaultDescPad = DEFAULT_DESC_PAD;
130
131    /**
132     * the string to display at the beginning of the usage statement
133     *
134     * @deprecated Scope will be made private for next major version
135     * - use get/setSyntaxPrefix methods instead.
136     */
137    @Deprecated
138    public String defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX;
139
140    /**
141     * the new line string
142     *
143     * @deprecated Scope will be made private for next major version
144     * - use get/setNewLine methods instead.
145     */
146    @Deprecated
147    public String defaultNewLine = System.getProperty("line.separator");
148
149    /**
150     * the shortOpt prefix
151     *
152     * @deprecated Scope will be made private for next major version
153     * - use get/setOptPrefix methods instead.
154     */
155    @Deprecated
156    public String defaultOptPrefix = DEFAULT_OPT_PREFIX;
157
158    /**
159     * the long Opt prefix
160     *
161     * @deprecated Scope will be made private for next major version
162     * - use get/setLongOptPrefix methods instead.
163     */
164    @Deprecated
165    public String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX;
166
167    /**
168     * the name of the argument
169     *
170     * @deprecated Scope will be made private for next major version
171     * - use get/setArgName methods instead.
172     */
173    @Deprecated
174    public String defaultArgName = DEFAULT_ARG_NAME;
175
176    /**
177     * Comparator used to sort the options when they output in help text
178     * 
179     * Defaults to case-insensitive alphabetical sorting by option key
180     */
181    protected Comparator<Option> optionComparator = new OptionComparator();
182
183    /** The separator displayed between the long option and its value. */
184    private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR;
185
186    /**
187     * Sets the 'width'.
188     *
189     * @param width the new value of 'width'
190     */
191    public void setWidth(int width)
192    {
193        this.defaultWidth = width;
194    }
195
196    /**
197     * Returns the 'width'.
198     *
199     * @return the 'width'
200     */
201    public int getWidth()
202    {
203        return defaultWidth;
204    }
205
206    /**
207     * Sets the 'leftPadding'.
208     *
209     * @param padding the new value of 'leftPadding'
210     */
211    public void setLeftPadding(int padding)
212    {
213        this.defaultLeftPad = padding;
214    }
215
216    /**
217     * Returns the 'leftPadding'.
218     *
219     * @return the 'leftPadding'
220     */
221    public int getLeftPadding()
222    {
223        return defaultLeftPad;
224    }
225
226    /**
227     * Sets the 'descPadding'.
228     *
229     * @param padding the new value of 'descPadding'
230     */
231    public void setDescPadding(int padding)
232    {
233        this.defaultDescPad = padding;
234    }
235
236    /**
237     * Returns the 'descPadding'.
238     *
239     * @return the 'descPadding'
240     */
241    public int getDescPadding()
242    {
243        return defaultDescPad;
244    }
245
246    /**
247     * Sets the 'syntaxPrefix'.
248     *
249     * @param prefix the new value of 'syntaxPrefix'
250     */
251    public void setSyntaxPrefix(String prefix)
252    {
253        this.defaultSyntaxPrefix = prefix;
254    }
255
256    /**
257     * Returns the 'syntaxPrefix'.
258     *
259     * @return the 'syntaxPrefix'
260     */
261    public String getSyntaxPrefix()
262    {
263        return defaultSyntaxPrefix;
264    }
265
266    /**
267     * Sets the 'newLine'.
268     *
269     * @param newline the new value of 'newLine'
270     */
271    public void setNewLine(String newline)
272    {
273        this.defaultNewLine = newline;
274    }
275
276    /**
277     * Returns the 'newLine'.
278     *
279     * @return the 'newLine'
280     */
281    public String getNewLine()
282    {
283        return defaultNewLine;
284    }
285
286    /**
287     * Sets the 'optPrefix'.
288     *
289     * @param prefix the new value of 'optPrefix'
290     */
291    public void setOptPrefix(String prefix)
292    {
293        this.defaultOptPrefix = prefix;
294    }
295
296    /**
297     * Returns the 'optPrefix'.
298     *
299     * @return the 'optPrefix'
300     */
301    public String getOptPrefix()
302    {
303        return defaultOptPrefix;
304    }
305
306    /**
307     * Sets the 'longOptPrefix'.
308     *
309     * @param prefix the new value of 'longOptPrefix'
310     */
311    public void setLongOptPrefix(String prefix)
312    {
313        this.defaultLongOptPrefix = prefix;
314    }
315
316    /**
317     * Returns the 'longOptPrefix'.
318     *
319     * @return the 'longOptPrefix'
320     */
321    public String getLongOptPrefix()
322    {
323        return defaultLongOptPrefix;
324    }
325
326    /**
327     * Set the separator displayed between a long option and its value.
328     * Ensure that the separator specified is supported by the parser used,
329     * typically ' ' or '='.
330     * 
331     * @param longOptSeparator the separator, typically ' ' or '='.
332     * @since 1.3
333     */
334    public void setLongOptSeparator(String longOptSeparator)
335    {
336        this.longOptSeparator = longOptSeparator;
337    }
338
339    /**
340     * Returns the separator displayed between a long option and its value.
341     * 
342     * @return the separator
343     * @since 1.3
344     */
345    public String getLongOptSeparator()
346    {
347        return longOptSeparator;
348    }
349
350    /**
351     * Sets the 'argName'.
352     *
353     * @param name the new value of 'argName'
354     */
355    public void setArgName(String name)
356    {
357        this.defaultArgName = name;
358    }
359
360    /**
361     * Returns the 'argName'.
362     *
363     * @return the 'argName'
364     */
365    public String getArgName()
366    {
367        return defaultArgName;
368    }
369
370    /**
371     * Comparator used to sort the options when they output in help text.
372     * Defaults to case-insensitive alphabetical sorting by option key.
373     *
374     * @return the {@link Comparator} currently in use to sort the options
375     * @since 1.2
376     */
377    public Comparator<Option> getOptionComparator()
378    {
379        return optionComparator;
380    }
381
382    /**
383     * Set the comparator used to sort the options when they output in help text.
384     * Passing in a null comparator will keep the options in the order they were declared.
385     *
386     * @param comparator the {@link Comparator} to use for sorting the options
387     * @since 1.2
388     */
389    public void setOptionComparator(Comparator<Option> comparator)
390    {
391        this.optionComparator = comparator;
392    }
393
394    /**
395     * Print the help for <code>options</code> with the specified
396     * command line syntax.  This method prints help information to
397     * System.out.
398     *
399     * @param cmdLineSyntax the syntax for this application
400     * @param options the Options instance
401     */
402    public void printHelp(String cmdLineSyntax, Options options)
403    {
404        printHelp(getWidth(), cmdLineSyntax, null, options, null, false);
405    }
406
407    /**
408     * Print the help for <code>options</code> with the specified
409     * command line syntax.  This method prints help information to 
410     * System.out.
411     *
412     * @param cmdLineSyntax the syntax for this application
413     * @param options the Options instance
414     * @param autoUsage whether to print an automatically generated
415     * usage statement
416     */
417    public void printHelp(String cmdLineSyntax, Options options, boolean autoUsage)
418    {
419        printHelp(getWidth(), cmdLineSyntax, null, options, null, autoUsage);
420    }
421
422    /**
423     * Print the help for <code>options</code> with the specified
424     * command line syntax.  This method prints help information to
425     * System.out.
426     *
427     * @param cmdLineSyntax the syntax for this application
428     * @param header the banner to display at the beginning of the help
429     * @param options the Options instance
430     * @param footer the banner to display at the end of the help
431     */
432    public void printHelp(String cmdLineSyntax, String header, Options options, String footer)
433    {
434        printHelp(cmdLineSyntax, header, options, footer, false);
435    }
436
437    /**
438     * Print the help for <code>options</code> with the specified
439     * command line syntax.  This method prints help information to 
440     * System.out.
441     *
442     * @param cmdLineSyntax the syntax for this application
443     * @param header the banner to display at the beginning of the help
444     * @param options the Options instance
445     * @param footer the banner to display at the end of the help
446     * @param autoUsage whether to print an automatically generated
447     * usage statement
448     */
449    public void printHelp(String cmdLineSyntax, String header, Options options, String footer, boolean autoUsage)
450    {
451        printHelp(getWidth(), cmdLineSyntax, header, options, footer, autoUsage);
452    }
453
454    /**
455     * Print the help for <code>options</code> with the specified
456     * command line syntax.  This method prints help information to
457     * System.out.
458     *
459     * @param width the number of characters to be displayed on each line
460     * @param cmdLineSyntax the syntax for this application
461     * @param header the banner to display at the beginning of the help
462     * @param options the Options instance
463     * @param footer the banner to display at the end of the help
464     */
465    public void printHelp(int width, String cmdLineSyntax, String header, Options options, String footer)
466    {
467        printHelp(width, cmdLineSyntax, header, options, footer, false);
468    }
469
470    /**
471     * Print the help for <code>options</code> with the specified
472     * command line syntax.  This method prints help information to
473     * System.out.
474     *
475     * @param width the number of characters to be displayed on each line
476     * @param cmdLineSyntax the syntax for this application
477     * @param header the banner to display at the beginning of the help
478     * @param options the Options instance
479     * @param footer the banner to display at the end of the help
480     * @param autoUsage whether to print an automatically generated 
481     * usage statement
482     */
483    public void printHelp(int width, String cmdLineSyntax, String header,
484                          Options options, String footer, boolean autoUsage)
485    {
486        PrintWriter pw = new PrintWriter(System.out);
487
488        printHelp(pw, width, cmdLineSyntax, header, options, getLeftPadding(), getDescPadding(), footer, autoUsage);
489        pw.flush();
490    }
491
492    /**
493     * Print the help for <code>options</code> with the specified
494     * command line syntax.
495     *
496     * @param pw the writer to which the help will be written
497     * @param width the number of characters to be displayed on each line
498     * @param cmdLineSyntax the syntax for this application
499     * @param header the banner to display at the beginning of the help
500     * @param options the Options instance
501     * @param leftPad the number of characters of padding to be prefixed
502     * to each line
503     * @param descPad the number of characters of padding to be prefixed
504     * to each description line
505     * @param footer the banner to display at the end of the help
506     *
507     * @throws IllegalStateException if there is no room to print a line
508     */
509    public void printHelp(PrintWriter pw, int width, String cmdLineSyntax, 
510                          String header, Options options, int leftPad, 
511                          int descPad, String footer)
512    {
513        printHelp(pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false);
514    }
515
516
517    /**
518     * Print the help for <code>options</code> with the specified
519     * command line syntax.
520     *
521     * @param pw the writer to which the help will be written
522     * @param width the number of characters to be displayed on each line
523     * @param cmdLineSyntax the syntax for this application
524     * @param header the banner to display at the beginning of the help
525     * @param options the Options instance
526     * @param leftPad the number of characters of padding to be prefixed
527     * to each line
528     * @param descPad the number of characters of padding to be prefixed
529     * to each description line
530     * @param footer the banner to display at the end of the help
531     * @param autoUsage whether to print an automatically generated
532     * usage statement
533     *
534     * @throws IllegalStateException if there is no room to print a line
535     */
536    public void printHelp(PrintWriter pw, int width, String cmdLineSyntax,
537                          String header, Options options, int leftPad,
538                          int descPad, String footer, boolean autoUsage)
539    {
540        if ((cmdLineSyntax == null) || (cmdLineSyntax.length() == 0))
541        {
542            throw new IllegalArgumentException("cmdLineSyntax not provided");
543        }
544
545        if (autoUsage)
546        {
547            printUsage(pw, width, cmdLineSyntax, options);
548        }
549        else
550        {
551            printUsage(pw, width, cmdLineSyntax);
552        }
553
554        if ((header != null) && (header.trim().length() > 0))
555        {
556            printWrapped(pw, width, header);
557        }
558
559        printOptions(pw, width, options, leftPad, descPad);
560
561        if ((footer != null) && (footer.trim().length() > 0))
562        {
563            printWrapped(pw, width, footer);
564        }
565    }
566
567    /**
568     * Prints the usage statement for the specified application.
569     *
570     * @param pw The PrintWriter to print the usage statement 
571     * @param width The number of characters to display per line
572     * @param app The application name
573     * @param options The command line Options
574     */
575    public void printUsage(PrintWriter pw, int width, String app, Options options)
576    {
577        // initialise the string buffer
578        StringBuffer buff = new StringBuffer(getSyntaxPrefix()).append(app).append(" ");
579
580        // create a list for processed option groups
581        Collection<OptionGroup> processedGroups = new ArrayList<OptionGroup>();
582
583        List<Option> optList = new ArrayList<Option>(options.getOptions());
584        if (getOptionComparator() != null)
585        {
586            Collections.sort(optList, getOptionComparator());
587        }
588        // iterate over the options
589        for (Iterator<Option> it = optList.iterator(); it.hasNext();)
590        {
591            // get the next Option
592            Option option = it.next();
593
594            // check if the option is part of an OptionGroup
595            OptionGroup group = options.getOptionGroup(option);
596
597            // if the option is part of a group 
598            if (group != null)
599            {
600                // and if the group has not already been processed
601                if (!processedGroups.contains(group))
602                {
603                    // add the group to the processed list
604                    processedGroups.add(group);
605
606
607                    // add the usage clause
608                    appendOptionGroup(buff, group);
609                }
610
611                // otherwise the option was displayed in the group
612                // previously so ignore it.
613            }
614
615            // if the Option is not part of an OptionGroup
616            else
617            {
618                appendOption(buff, option, option.isRequired());
619            }
620
621            if (it.hasNext())
622            {
623                buff.append(" ");
624            }
625        }
626
627
628        // call printWrapped
629        printWrapped(pw, width, buff.toString().indexOf(' ') + 1, buff.toString());
630    }
631
632    /**
633     * Appends the usage clause for an OptionGroup to a StringBuffer.  
634     * The clause is wrapped in square brackets if the group is required.
635     * The display of the options is handled by appendOption
636     * @param buff the StringBuffer to append to
637     * @param group the group to append
638     * @see #appendOption(StringBuffer,Option,boolean)
639     */
640    private void appendOptionGroup(StringBuffer buff, OptionGroup group)
641    {
642        if (!group.isRequired())
643        {
644            buff.append("[");
645        }
646
647        List<Option> optList = new ArrayList<Option>(group.getOptions());
648        if (getOptionComparator() != null)
649        {
650            Collections.sort(optList, getOptionComparator());
651        }
652        // for each option in the OptionGroup
653        for (Iterator<Option> it = optList.iterator(); it.hasNext();)
654        {
655            // whether the option is required or not is handled at group level
656            appendOption(buff, it.next(), true);
657
658            if (it.hasNext())
659            {
660                buff.append(" | ");
661            }
662        }
663
664        if (!group.isRequired())
665        {
666            buff.append("]");
667        }
668    }
669
670    /**
671     * Appends the usage clause for an Option to a StringBuffer.  
672     *
673     * @param buff the StringBuffer to append to
674     * @param option the Option to append
675     * @param required whether the Option is required or not
676     */
677    private void appendOption(StringBuffer buff, Option option, boolean required)
678    {
679        if (!required)
680        {
681            buff.append("[");
682        }
683
684        if (option.getOpt() != null)
685        {
686            buff.append("-").append(option.getOpt());
687        }
688        else
689        {
690            buff.append("--").append(option.getLongOpt());
691        }
692        
693        // if the Option has a value and a non blank argname
694        if (option.hasArg() && (option.getArgName() == null || option.getArgName().length() != 0))
695        {
696            buff.append(option.getOpt() == null ? longOptSeparator : " ");
697            buff.append("<").append(option.getArgName() != null ? option.getArgName() : getArgName()).append(">");
698        }
699        
700        // if the Option is not a required option
701        if (!required)
702        {
703            buff.append("]");
704        }
705    }
706
707    /**
708     * Print the cmdLineSyntax to the specified writer, using the
709     * specified width.
710     *
711     * @param pw The printWriter to write the help to
712     * @param width The number of characters per line for the usage statement.
713     * @param cmdLineSyntax The usage statement.
714     */
715    public void printUsage(PrintWriter pw, int width, String cmdLineSyntax)
716    {
717        int argPos = cmdLineSyntax.indexOf(' ') + 1;
718
719        printWrapped(pw, width, getSyntaxPrefix().length() + argPos, getSyntaxPrefix() + cmdLineSyntax);
720    }
721
722    /**
723     * Print the help for the specified Options to the specified writer, 
724     * using the specified width, left padding and description padding.
725     *
726     * @param pw The printWriter to write the help to
727     * @param width The number of characters to display per line
728     * @param options The command line Options
729     * @param leftPad the number of characters of padding to be prefixed
730     * to each line
731     * @param descPad the number of characters of padding to be prefixed
732     * to each description line
733     */
734    public void printOptions(PrintWriter pw, int width, Options options, 
735                             int leftPad, int descPad)
736    {
737        StringBuffer sb = new StringBuffer();
738
739        renderOptions(sb, width, options, leftPad, descPad);
740        pw.println(sb.toString());
741    }
742
743    /**
744     * Print the specified text to the specified PrintWriter.
745     *
746     * @param pw The printWriter to write the help to
747     * @param width The number of characters to display per line
748     * @param text The text to be written to the PrintWriter
749     */
750    public void printWrapped(PrintWriter pw, int width, String text)
751    {
752        printWrapped(pw, width, 0, text);
753    }
754
755    /**
756     * Print the specified text to the specified PrintWriter.
757     *
758     * @param pw The printWriter to write the help to
759     * @param width The number of characters to display per line
760     * @param nextLineTabStop The position on the next line for the first tab.
761     * @param text The text to be written to the PrintWriter
762     */
763    public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text)
764    {
765        StringBuffer sb = new StringBuffer(text.length());
766
767        renderWrappedTextBlock(sb, width, nextLineTabStop, text);
768        pw.println(sb.toString());
769    }
770
771    // --------------------------------------------------------------- Protected
772
773    /**
774     * Render the specified Options and return the rendered Options
775     * in a StringBuffer.
776     *
777     * @param sb The StringBuffer to place the rendered Options into.
778     * @param width The number of characters to display per line
779     * @param options The command line Options
780     * @param leftPad the number of characters of padding to be prefixed
781     * to each line
782     * @param descPad the number of characters of padding to be prefixed
783     * to each description line
784     *
785     * @return the StringBuffer with the rendered Options contents.
786     */
787    protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, int descPad)
788    {
789        final String lpad = createPadding(leftPad);
790        final String dpad = createPadding(descPad);
791
792        // first create list containing only <lpad>-a,--aaa where 
793        // -a is opt and --aaa is long opt; in parallel look for 
794        // the longest opt string this list will be then used to 
795        // sort options ascending
796        int max = 0;
797        List<StringBuffer> prefixList = new ArrayList<StringBuffer>();
798
799        List<Option> optList = options.helpOptions();
800
801        if (getOptionComparator() != null)
802        {
803            Collections.sort(optList, getOptionComparator());
804        }
805
806        for (Option option : optList)
807        {
808            StringBuffer optBuf = new StringBuffer();
809
810            if (option.getOpt() == null)
811            {
812                optBuf.append(lpad).append("   ").append(getLongOptPrefix()).append(option.getLongOpt());
813            }
814            else
815            {
816                optBuf.append(lpad).append(getOptPrefix()).append(option.getOpt());
817
818                if (option.hasLongOpt())
819                {
820                    optBuf.append(',').append(getLongOptPrefix()).append(option.getLongOpt());
821                }
822            }
823
824            if (option.hasArg())
825            {
826                String argName = option.getArgName();
827                if (argName != null && argName.length() == 0)
828                {
829                    // if the option has a blank argname
830                    optBuf.append(' ');
831                }
832                else
833                {
834                    optBuf.append(option.hasLongOpt() ? longOptSeparator : " ");
835                    optBuf.append("<").append(argName != null ? option.getArgName() : getArgName()).append(">");
836                }
837            }
838
839            prefixList.add(optBuf);
840            max = (optBuf.length() > max) ? optBuf.length() : max;
841        }
842
843        int x = 0;
844
845        for (Iterator<Option> it = optList.iterator(); it.hasNext();)
846        {
847            Option option = it.next();
848            StringBuilder optBuf = new StringBuilder(prefixList.get(x++).toString());
849
850            if (optBuf.length() < max)
851            {
852                optBuf.append(createPadding(max - optBuf.length()));
853            }
854
855            optBuf.append(dpad);
856
857            int nextLineTabStop = max + descPad;
858
859            if (option.getDescription() != null)
860            {
861                optBuf.append(option.getDescription());
862            }
863
864            renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
865
866            if (it.hasNext())
867            {
868                sb.append(getNewLine());
869            }
870        }
871
872        return sb;
873    }
874
875    /**
876     * Render the specified text and return the rendered Options
877     * in a StringBuffer.
878     *
879     * @param sb The StringBuffer to place the rendered text into.
880     * @param width The number of characters to display per line
881     * @param nextLineTabStop The position on the next line for the first tab.
882     * @param text The text to be rendered.
883     *
884     * @return the StringBuffer with the rendered Options contents.
885     */
886    protected StringBuffer renderWrappedText(StringBuffer sb, int width, 
887                                             int nextLineTabStop, String text)
888    {
889        int pos = findWrapPos(text, width, 0);
890
891        if (pos == -1)
892        {
893            sb.append(rtrim(text));
894
895            return sb;
896        }
897        sb.append(rtrim(text.substring(0, pos))).append(getNewLine());
898
899        if (nextLineTabStop >= width)
900        {
901            // stops infinite loop happening
902            nextLineTabStop = 1;
903        }
904
905        // all following lines must be padded with nextLineTabStop space characters
906        final String padding = createPadding(nextLineTabStop);
907
908        while (true)
909        {
910            text = padding + text.substring(pos).trim();
911            pos = findWrapPos(text, width, 0);
912
913            if (pos == -1)
914            {
915                sb.append(text);
916
917                return sb;
918            }
919
920            if ((text.length() > width) && (pos == nextLineTabStop - 1))
921            {
922                pos = width;
923            }
924
925            sb.append(rtrim(text.substring(0, pos))).append(getNewLine());
926        }
927    }
928
929    /**
930     * Render the specified text width a maximum width. This method differs
931     * from renderWrappedText by not removing leading spaces after a new line.
932     *
933     * @param sb The StringBuffer to place the rendered text into.
934     * @param width The number of characters to display per line
935     * @param nextLineTabStop The position on the next line for the first tab.
936     * @param text The text to be rendered.
937     */
938    private Appendable renderWrappedTextBlock(StringBuffer sb, int width, int nextLineTabStop, String text)
939    {
940        try
941        {
942            BufferedReader in = new BufferedReader(new StringReader(text));
943            String line;
944            boolean firstLine = true;
945            while ((line = in.readLine()) != null)
946            {
947                if (!firstLine)
948                {
949                    sb.append(getNewLine());
950                }
951                else
952                {
953                    firstLine = false;
954                }
955                renderWrappedText(sb, width, nextLineTabStop, line);
956            }
957        }
958        catch (IOException e) //NOPMD
959        {
960            // cannot happen
961        }
962
963        return sb;
964    }
965
966    /**
967     * Finds the next text wrap position after <code>startPos</code> for the
968     * text in <code>text</code> with the column width <code>width</code>.
969     * The wrap point is the last position before startPos+width having a 
970     * whitespace character (space, \n, \r). If there is no whitespace character
971     * before startPos+width, it will return startPos+width.
972     *
973     * @param text The text being searched for the wrap position
974     * @param width width of the wrapped text
975     * @param startPos position from which to start the lookup whitespace
976     * character
977     * @return position on which the text must be wrapped or -1 if the wrap
978     * position is at the end of the text
979     */
980    protected int findWrapPos(String text, int width, int startPos)
981    {
982        // the line ends before the max wrap pos or a new line char found
983        int pos = text.indexOf('\n', startPos);
984        if (pos != -1 && pos <= width)
985        {
986            return pos + 1;
987        }
988
989        pos = text.indexOf('\t', startPos);
990        if (pos != -1 && pos <= width)
991        {
992            return pos + 1;
993        }
994
995        if (startPos + width >= text.length())
996        {
997            return -1;
998        }
999
1000        // look for the last whitespace character before startPos+width
1001        for (pos = startPos + width; pos >= startPos; --pos)
1002        {
1003            final char c = text.charAt(pos);
1004            if (c == ' ' || c == '\n' || c == '\r')
1005            {
1006                break;
1007            }
1008        }
1009
1010        // if we found it - just return
1011        if (pos > startPos)
1012        {
1013            return pos;
1014        }
1015
1016        // if we didn't find one, simply chop at startPos+width
1017        pos = startPos + width;
1018
1019        return pos == text.length() ? -1 : pos;
1020    }
1021
1022    /**
1023     * Return a String of padding of length <code>len</code>.
1024     *
1025     * @param len The length of the String of padding to create.
1026     *
1027     * @return The String of padding
1028     */
1029    protected String createPadding(int len)
1030    {
1031        char[] padding = new char[len];
1032        Arrays.fill(padding, ' ');
1033
1034        return new String(padding);
1035    }
1036
1037    /**
1038     * Remove the trailing whitespace from the specified String.
1039     *
1040     * @param s The String to remove the trailing padding from.
1041     *
1042     * @return The String of without the trailing padding
1043     */
1044    protected String rtrim(String s)
1045    {
1046        if ((s == null) || (s.length() == 0))
1047        {
1048            return s;
1049        }
1050
1051        int pos = s.length();
1052
1053        while ((pos > 0) && Character.isWhitespace(s.charAt(pos - 1)))
1054        {
1055            --pos;
1056        }
1057
1058        return s.substring(0, pos);
1059    }
1060
1061    // ------------------------------------------------------ Package protected
1062    // ---------------------------------------------------------------- Private
1063    // ---------------------------------------------------------- Inner classes
1064    /**
1065     * This class implements the <code>Comparator</code> interface
1066     * for comparing Options.
1067     */
1068    private static class OptionComparator implements Comparator<Option>, Serializable
1069    {
1070        /** The serial version UID. */
1071        private static final long serialVersionUID = 5305467873966684014L;
1072
1073        /**
1074         * Compares its two arguments for order. Returns a negative
1075         * integer, zero, or a positive integer as the first argument
1076         * is less than, equal to, or greater than the second.
1077         *
1078         * @param opt1 The first Option to be compared.
1079         * @param opt2 The second Option to be compared.
1080         * @return a negative integer, zero, or a positive integer as
1081         *         the first argument is less than, equal to, or greater than the
1082         *         second.
1083         */
1084        public int compare(Option opt1, Option opt2)
1085        {
1086            return opt1.getKey().compareToIgnoreCase(opt2.getKey());
1087        }
1088    }
1089
1090}