@@ -22,6 +22,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more
2222import java .io .PrintWriter ;
2323import java .io .Serializable ;
2424import java .io .StringReader ;
25+ import java .io .UncheckedIOException ;
2526import java .util .ArrayList ;
2627import java .util .Arrays ;
2728import java .util .Collection ;
@@ -374,7 +375,6 @@ private void appendOptionGroup(final StringBuilder buff, final OptionGroup group
374375 if (!group .isRequired ()) {
375376 buff .append ("[" );
376377 }
377-
378378 final List <Option > optList = new ArrayList <>(group .getOptions ());
379379 if (getOptionComparator () != null ) {
380380 Collections .sort (optList , getOptionComparator ());
@@ -388,12 +388,135 @@ private void appendOptionGroup(final StringBuilder buff, final OptionGroup group
388388 buff .append (" | " );
389389 }
390390 }
391-
392391 if (!group .isRequired ()) {
393392 buff .append ("]" );
394393 }
395394 }
396395
396+ /**
397+ * Renders the specified Options and return the rendered Options in a StringBuffer.
398+ *
399+ * @param sb The StringBuffer to place the rendered Options into.
400+ * @param width The number of characters to display per line
401+ * @param options The command line Options
402+ * @param leftPad the number of characters of padding to be prefixed to each line
403+ * @param descPad the number of characters of padding to be prefixed to each description line
404+ * @return the StringBuffer with the rendered Options contents.
405+ * @throws IOException if an I/O error occurs.
406+ */
407+ <A extends Appendable > A appendOptions (final A sb , final int width , final Options options , final int leftPad , final int descPad ) throws IOException {
408+ final String lpad = createPadding (leftPad );
409+ final String dpad = createPadding (descPad );
410+ // first create list containing only <lpad>-a,--aaa where
411+ // -a is opt and --aaa is long opt; in parallel look for
412+ // the longest opt string this list will be then used to
413+ // sort options ascending
414+ int max = 0 ;
415+ final int maxSince = showSince ? determineMaxSinceLength (options ) + leftPad : 0 ;
416+ final List <StringBuilder > prefixList = new ArrayList <>();
417+ final List <Option > optList = options .helpOptions ();
418+ if (getOptionComparator () != null ) {
419+ Collections .sort (optList , getOptionComparator ());
420+ }
421+ for (final Option option : optList ) {
422+ final StringBuilder optBuf = new StringBuilder ();
423+ if (option .getOpt () == null ) {
424+ optBuf .append (lpad ).append (" " ).append (getLongOptPrefix ()).append (option .getLongOpt ());
425+ } else {
426+ optBuf .append (lpad ).append (getOptPrefix ()).append (option .getOpt ());
427+ if (option .hasLongOpt ()) {
428+ optBuf .append (',' ).append (getLongOptPrefix ()).append (option .getLongOpt ());
429+ }
430+ }
431+ if (option .hasArg ()) {
432+ final String argName = option .getArgName ();
433+ if (argName != null && argName .isEmpty ()) {
434+ // if the option has a blank argname
435+ optBuf .append (' ' );
436+ } else {
437+ optBuf .append (option .hasLongOpt () ? longOptSeparator : " " );
438+ optBuf .append ("<" ).append (argName != null ? option .getArgName () : getArgName ()).append (">" );
439+ }
440+ }
441+
442+ prefixList .add (optBuf );
443+ max = Math .max (optBuf .length () + maxSince , max );
444+ }
445+ final int nextLineTabStop = max + descPad ;
446+ if (showSince ) {
447+ final StringBuilder optHeader = new StringBuilder (HEADER_OPTIONS ).append (createPadding (max - maxSince - HEADER_OPTIONS .length () + leftPad ))
448+ .append (HEADER_SINCE );
449+ optHeader .append (createPadding (max - optHeader .length ())).append (lpad ).append (HEADER_DESCRIPTION );
450+ appendWrappedText (sb , width , nextLineTabStop , optHeader .toString ());
451+ sb .append (getNewLine ());
452+ }
453+
454+ int x = 0 ;
455+ for (final Iterator <Option > it = optList .iterator (); it .hasNext ();) {
456+ final Option option = it .next ();
457+ final StringBuilder optBuf = new StringBuilder (prefixList .get (x ++).toString ());
458+ if (optBuf .length () < max ) {
459+ optBuf .append (createPadding (max - maxSince - optBuf .length ()));
460+ if (showSince ) {
461+ optBuf .append (lpad ).append (option .getSince () == null ? "-" : option .getSince ());
462+ }
463+ optBuf .append (createPadding (max - optBuf .length ()));
464+ }
465+ optBuf .append (dpad );
466+
467+ if (deprecatedFormatFunc != null && option .isDeprecated ()) {
468+ optBuf .append (deprecatedFormatFunc .apply (option ).trim ());
469+ } else if (option .getDescription () != null ) {
470+ optBuf .append (option .getDescription ());
471+ }
472+ appendWrappedText (sb , width , nextLineTabStop , optBuf .toString ());
473+ if (it .hasNext ()) {
474+ sb .append (getNewLine ());
475+ }
476+ }
477+ return sb ;
478+ }
479+
480+ /**
481+ * Renders the specified text and return the rendered Options in a StringBuffer.
482+ *
483+ * @param <A> The Appendable implementation.
484+ * @param appendable The StringBuffer to place the rendered text into.
485+ * @param width The number of characters to display per line
486+ * @param nextLineTabStop The position on the next line for the first tab.
487+ * @param text The text to be rendered.
488+ * @return the StringBuffer with the rendered Options contents.
489+ * @throws IOException if an I/O error occurs.
490+ */
491+ <A extends Appendable > A appendWrappedText (final A appendable , final int width , final int nextLineTabStop , final String text ) throws IOException {
492+ String render = text ;
493+ int nextLineTabStopPos = nextLineTabStop ;
494+ int pos = findWrapPos (render , width , 0 );
495+ if (pos == -1 ) {
496+ appendable .append (rtrim (render ));
497+ return appendable ;
498+ }
499+ appendable .append (rtrim (render .substring (0 , pos ))).append (getNewLine ());
500+ if (nextLineTabStopPos >= width ) {
501+ // stops infinite loop happening
502+ nextLineTabStopPos = 1 ;
503+ }
504+ // all following lines must be padded with nextLineTabStop space characters
505+ final String padding = createPadding (nextLineTabStopPos );
506+ while (true ) {
507+ render = padding + render .substring (pos ).trim ();
508+ pos = findWrapPos (render , width , 0 );
509+ if (pos == -1 ) {
510+ appendable .append (render );
511+ return appendable ;
512+ }
513+ if (render .length () > width && pos == nextLineTabStopPos - 1 ) {
514+ pos = width ;
515+ }
516+ appendable .append (rtrim (render .substring (0 , pos ))).append (getNewLine ());
517+ }
518+ }
519+
397520 /**
398521 * Creates a String of padding of length {@code len}.
399522 *
@@ -407,6 +530,12 @@ protected String createPadding(final int len) {
407530 return new String (padding );
408531 }
409532
533+ private int determineMaxSinceLength (final Options options ) {
534+ final int minLen = HEADER_SINCE .length ();
535+ final int len = options .getOptions ().stream ().map (o -> o .getSince () == null ? minLen : o .getSince ().length ()).max (Integer ::compareTo ).orElse (minLen );
536+ return len < minLen ? minLen : len ;
537+ }
538+
410539 /**
411540 * Finds the next text wrap position after {@code startPos} for the text in {@code text} with the column width
412541 * {@code width}. The wrap point is the last position before startPos+width having a whitespace character (space,
@@ -443,7 +572,6 @@ protected int findWrapPos(final String text, final int width, final int startPos
443572 }
444573 // if we didn't find one, simply chop at startPos+width
445574 pos = startPos + width ;
446-
447575 return pos == text .length () ? -1 : pos ;
448576 }
449577
@@ -685,9 +813,12 @@ public void printHelp(final String cmdLineSyntax, final String header, final Opt
685813 * @param descPad the number of characters of padding to be prefixed to each description line
686814 */
687815 public void printOptions (final PrintWriter pw , final int width , final Options options , final int leftPad , final int descPad ) {
688- final StringBuffer sb = new StringBuffer ();
689- renderOptions (sb , width , options , leftPad , descPad );
690- pw .println (sb .toString ());
816+ try {
817+ pw .println (appendOptions (new StringBuilder (), width , options , leftPad , descPad ));
818+ } catch (final IOException e ) {
819+ // Cannot happen
820+ throw new UncheckedIOException (e );
821+ }
691822 }
692823
693824 /**
@@ -759,9 +890,7 @@ public void printUsage(final PrintWriter pw, final int width, final String app,
759890 * @param text The text to be written to the PrintWriter
760891 */
761892 public void printWrapped (final PrintWriter pw , final int width , final int nextLineTabStop , final String text ) {
762- final StringBuffer sb = new StringBuffer (text .length ());
763- renderWrappedTextBlock (sb , width , nextLineTabStop , text );
764- pw .println (sb .toString ());
893+ pw .println (renderWrappedTextBlock (new StringBuilder (text .length ()), width , nextLineTabStop , text ));
765894 }
766895
767896 /**
@@ -775,12 +904,6 @@ public void printWrapped(final PrintWriter pw, final int width, final String tex
775904 printWrapped (pw , width , 0 , text );
776905 }
777906
778- private int determineMaxSinceLength (final Options options ) {
779- int minLen = HEADER_SINCE .length ();
780- int len = options .getOptions ().stream ().map (o -> o .getSince () == null ? minLen : o .getSince ().length ()).max (Integer ::compareTo ).orElse (minLen );
781- return len < minLen ? minLen : len ;
782- }
783-
784907 /**
785908 * Renders the specified Options and return the rendered Options in a StringBuffer.
786909 *
@@ -793,76 +916,12 @@ private int determineMaxSinceLength(final Options options) {
793916 * @return the StringBuffer with the rendered Options contents.
794917 */
795918 protected StringBuffer renderOptions (final StringBuffer sb , final int width , final Options options , final int leftPad , final int descPad ) {
796- final String lpad = createPadding (leftPad );
797- final String dpad = createPadding (descPad );
798- // first create list containing only <lpad>-a,--aaa where
799- // -a is opt and --aaa is long opt; in parallel look for
800- // the longest opt string this list will be then used to
801- // sort options ascending
802- int max = 0 ;
803- int maxSince = showSince ? determineMaxSinceLength (options ) + leftPad : 0 ;
804- final List <StringBuilder > prefixList = new ArrayList <>();
805- final List <Option > optList = options .helpOptions ();
806- if (getOptionComparator () != null ) {
807- Collections .sort (optList , getOptionComparator ());
808- }
809- for (final Option option : optList ) {
810- final StringBuilder optBuf = new StringBuilder ();
811- if (option .getOpt () == null ) {
812- optBuf .append (lpad ).append (" " ).append (getLongOptPrefix ()).append (option .getLongOpt ());
813- } else {
814- optBuf .append (lpad ).append (getOptPrefix ()).append (option .getOpt ());
815- if (option .hasLongOpt ()) {
816- optBuf .append (',' ).append (getLongOptPrefix ()).append (option .getLongOpt ());
817- }
818- }
819- if (option .hasArg ()) {
820- final String argName = option .getArgName ();
821- if (argName != null && argName .isEmpty ()) {
822- // if the option has a blank argname
823- optBuf .append (' ' );
824- } else {
825- optBuf .append (option .hasLongOpt () ? longOptSeparator : " " );
826- optBuf .append ("<" ).append (argName != null ? option .getArgName () : getArgName ()).append (">" );
827- }
828- }
829-
830- prefixList .add (optBuf );
831- max = Math .max (optBuf .length () + maxSince , max );
832- }
833- final int nextLineTabStop = max + descPad ;
834- if (showSince ) {
835- StringBuilder optHeader = new StringBuilder (HEADER_OPTIONS ).append (createPadding (max - maxSince - HEADER_OPTIONS .length () + leftPad ))
836- .append (HEADER_SINCE );
837- optHeader .append (createPadding (max - optHeader .length ())).append (lpad ).append (HEADER_DESCRIPTION );
838- renderWrappedText (sb , width , nextLineTabStop , optHeader .toString ());
839- sb .append (getNewLine ());
840- }
841-
842- int x = 0 ;
843- for (final Iterator <Option > it = optList .iterator (); it .hasNext ();) {
844- final Option option = it .next ();
845- final StringBuilder optBuf = new StringBuilder (prefixList .get (x ++).toString ());
846- if (optBuf .length () < max ) {
847- optBuf .append (createPadding (max - maxSince - optBuf .length ()));
848- if (showSince ) {
849- optBuf .append (lpad ).append (option .getSince () == null ? "-" : option .getSince ());
850- }
851- optBuf .append (createPadding (max - optBuf .length ()));
852- }
853- optBuf .append (dpad );
854-
855- if (deprecatedFormatFunc != null && option .isDeprecated ()) {
856- optBuf .append (deprecatedFormatFunc .apply (option ).trim ());
857- } else if (option .getDescription () != null ) {
858- optBuf .append (option .getDescription ());
859- }
860- renderWrappedText (sb , width , nextLineTabStop , optBuf .toString ());
861- if (it .hasNext ()) {
862- sb .append (getNewLine ());
863- }
919+ try {
920+ return appendOptions (sb , width , options , leftPad , descPad );
921+ } catch (final IOException e ) {
922+ // Cannot happen
923+ throw new UncheckedIOException (e );
864924 }
865- return sb ;
866925 }
867926
868927 /**
@@ -876,31 +935,11 @@ protected StringBuffer renderOptions(final StringBuffer sb, final int width, fin
876935 * @return the StringBuffer with the rendered Options contents.
877936 */
878937 protected StringBuffer renderWrappedText (final StringBuffer sb , final int width , final int nextLineTabStop , final String text ) {
879- String render = text ;
880- int nextLineTabStopPos = nextLineTabStop ;
881- int pos = findWrapPos (render , width , 0 );
882- if (pos == -1 ) {
883- sb .append (rtrim (render ));
884- return sb ;
885- }
886- sb .append (rtrim (render .substring (0 , pos ))).append (getNewLine ());
887- if (nextLineTabStopPos >= width ) {
888- // stops infinite loop happening
889- nextLineTabStopPos = 1 ;
890- }
891- // all following lines must be padded with nextLineTabStop space characters
892- final String padding = createPadding (nextLineTabStopPos );
893- while (true ) {
894- render = padding + render .substring (pos ).trim ();
895- pos = findWrapPos (render , width , 0 );
896- if (pos == -1 ) {
897- sb .append (render );
898- return sb ;
899- }
900- if (render .length () > width && pos == nextLineTabStopPos - 1 ) {
901- pos = width ;
902- }
903- sb .append (rtrim (render .substring (0 , pos ))).append (getNewLine ());
938+ try {
939+ return appendWrappedText (sb , width , nextLineTabStop , text );
940+ } catch (final IOException e ) {
941+ // Cannot happen.
942+ throw new UncheckedIOException (e );
904943 }
905944 }
906945
@@ -913,7 +952,7 @@ protected StringBuffer renderWrappedText(final StringBuffer sb, final int width,
913952 * @param nextLineTabStop The position on the next line for the first tab.
914953 * @param text The text to be rendered.
915954 */
916- private Appendable renderWrappedTextBlock (final StringBuffer sb , final int width , final int nextLineTabStop , final String text ) {
955+ private < A extends Appendable > A renderWrappedTextBlock (final A sb , final int width , final int nextLineTabStop , final String text ) {
917956 try {
918957 final BufferedReader in = new BufferedReader (new StringReader (text ));
919958 String line ;
@@ -924,7 +963,7 @@ private Appendable renderWrappedTextBlock(final StringBuffer sb, final int width
924963 } else {
925964 firstLine = false ;
926965 }
927- renderWrappedText (sb , width , nextLineTabStop , line );
966+ appendWrappedText (sb , width , nextLineTabStop , line );
928967 }
929968 } catch (final IOException e ) { // NOPMD
930969 // cannot happen
0 commit comments