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