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