View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import java.io.BufferedReader;
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.Serializable;
24  import java.io.StringReader;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  /**
34   * A formatter of help messages for command line options.
35   *
36   * <p>Example:</p>
37   * 
38   * <pre>
39   * Options options = new Options();
40   * options.addOption(OptionBuilder.withLongOpt("file")
41   *                                .withDescription("The file to be processed")
42   *                                .hasArg()
43   *                                .withArgName("FILE")
44   *                                .isRequired()
45   *                                .create('f'));
46   * options.addOption(OptionBuilder.withLongOpt("version")
47   *                                .withDescription("Print the version of the application")
48   *                                .create('v'));
49   * options.addOption(OptionBuilder.withLongOpt("help").create('h'));
50   * 
51   * String header = "Do something useful with an input file\n\n";
52   * String footer = "\nPlease report issues at http://example.com/issues";
53   * 
54   * HelpFormatter formatter = new HelpFormatter();
55   * formatter.printHelp("myapp", header, options, footer, true);
56   * </pre>
57   * 
58   * This produces the following output:
59   * 
60   * <pre>
61   * usage: myapp -f &lt;FILE&gt; [-h] [-v]
62   * Do something useful with an input file
63   * 
64   *  -f,--file &lt;FILE&gt;   The file to be processed
65   *  -h,--help
66   *  -v,--version       Print the version of the application
67   * 
68   * Please report issues at http://example.com/issues
69   * </pre>
70   * 
71   * @version $Id: HelpFormatter.java 1677407 2015-05-03 14:31:12Z britter $
72   */
73  public class HelpFormatter
74  {
75      // --------------------------------------------------------------- Constants
76  
77      /** default number of characters per line */
78      public static final int DEFAULT_WIDTH = 74;
79  
80      /** default padding to the left of each line */
81      public static final int DEFAULT_LEFT_PAD = 1;
82  
83      /** number of space characters to be prefixed to each description line */
84      public static final int DEFAULT_DESC_PAD = 3;
85  
86      /** the string to display at the beginning of the usage statement */
87      public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
88  
89      /** default prefix for shortOpts */
90      public static final String DEFAULT_OPT_PREFIX = "-";
91  
92      /** default prefix for long Option */
93      public static final String DEFAULT_LONG_OPT_PREFIX = "--";
94  
95      /** 
96       * default separator displayed between a long Option and its value
97       * 
98       * @since 1.3
99       **/
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 }