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 <FILE> [-h] [-v]
62   * Do something useful with an input file
63   * 
64   *  -f,--file &lt;FILE>   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 1444961 2013-02-11 21:03:51Z tn $
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      /** default separator displayed between a long Option and its value */
96      public static final String DEFAULT_LONG_OPT_SEPARATOR = " ";
97  
98      /** default name for an argument */
99      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 }