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