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