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