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