Path: blob/master/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/BuiltinHelpFormatter.java
40948 views
/*1* Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* This file is available under and governed by the GNU General Public27* License version 2 only, as published by the Free Software Foundation.28* However, the following notice accompanied the original version of this29* file:30*31* The MIT License32*33* Copyright (c) 2004-2015 Paul R. Holser, Jr.34*35* Permission is hereby granted, free of charge, to any person obtaining36* a copy of this software and associated documentation files (the37* "Software"), to deal in the Software without restriction, including38* without limitation the rights to use, copy, modify, merge, publish,39* distribute, sublicense, and/or sell copies of the Software, and to40* permit persons to whom the Software is furnished to do so, subject to41* the following conditions:42*43* The above copyright notice and this permission notice shall be44* included in all copies or substantial portions of the Software.45*46* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,47* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF48* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND49* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE50* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION51* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION52* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.53*/5455package jdk.internal.joptsimple;5657import java.util.*;5859import jdk.internal.joptsimple.internal.Messages;60import jdk.internal.joptsimple.internal.Rows;61import jdk.internal.joptsimple.internal.Strings;6263import static jdk.internal.joptsimple.ParserRules.*;64import static jdk.internal.joptsimple.internal.Classes.*;65import static jdk.internal.joptsimple.internal.Strings.*;6667/**68* <p>A help formatter that allows configuration of overall row width and column separator width.</p>69*70* <p>The formatter produces output in two sections: one for the options, and one for non-option arguments.</p>71*72* <p>The options section has two columns: the left column for the options, and the right column for their73* descriptions. The formatter will allow as much space as possible for the descriptions, by minimizing the option74* column's width, no greater than slightly less than half the overall desired width.</p>75*76* <p>The non-option arguments section is one column, occupying as much width as it can.</p>77*78* <p>Subclasses are free to override bits of this implementation as they see fit. Inspect the code79* carefully to understand the flow of control that this implementation guarantees.</p>80*81* @author <a href="mailto:[email protected]">Paul Holser</a>82*/83public class BuiltinHelpFormatter implements HelpFormatter {84private final Rows nonOptionRows;85private final Rows optionRows;8687/**88* Makes a formatter with a pre-configured overall row width and column separator width.89*/90BuiltinHelpFormatter() {91this( 80, 2 );92}9394/**95* Makes a formatter with a given overall row width and column separator width.96*97* @param desiredOverallWidth how many characters wide to make the overall help display98* @param desiredColumnSeparatorWidth how many characters wide to make the separation between option column and99* description column100*/101public BuiltinHelpFormatter( int desiredOverallWidth, int desiredColumnSeparatorWidth ) {102nonOptionRows = new Rows( desiredOverallWidth * 2, 0 );103optionRows = new Rows( desiredOverallWidth, desiredColumnSeparatorWidth );104}105106/**107* {@inheritDoc}108*109* <p>This implementation:</p>110* <ul>111* <li>Sorts the given descriptors by their first elements of {@link OptionDescriptor#options()}</li>112* <li>Passes the resulting sorted set to {@link #addRows(java.util.Collection)}</li>113* <li>Returns the result of {@link #formattedHelpOutput()}</li>114* </ul>115*/116public String format( Map<String, ? extends OptionDescriptor> options ) {117optionRows.reset();118nonOptionRows.reset();119120Comparator<OptionDescriptor> comparator =121new Comparator<OptionDescriptor>() {122public int compare( OptionDescriptor first, OptionDescriptor second ) {123return first.options().iterator().next().compareTo( second.options().iterator().next() );124}125};126127Set<OptionDescriptor> sorted = new TreeSet<>( comparator );128sorted.addAll( options.values() );129130addRows( sorted );131132return formattedHelpOutput();133}134135/**136* Adds a row of option help output in the left column, with empty space in the right column.137*138* @param single text to put in the left column139*/140protected void addOptionRow( String single ) {141addOptionRow( single, "" );142}143144/**145* Adds a row of option help output in the left and right columns.146*147* @param left text to put in the left column148* @param right text to put in the right column149*/150protected void addOptionRow( String left, String right ) {151optionRows.add( left, right );152}153154/**155* Adds a single row of non-option argument help.156*157* @param single single row of non-option argument help text158*/159protected void addNonOptionRow( String single ) {160nonOptionRows.add( single, "" );161}162163/**164* Resizes the columns of all the rows to be no wider than the widest element in that column.165*/166protected void fitRowsToWidth() {167nonOptionRows.fitToWidth();168optionRows.fitToWidth();169}170171/**172* Produces non-option argument help.173*174* @return non-option argument help175*/176protected String nonOptionOutput() {177return nonOptionRows.render();178}179180/**181* Produces help for options and their descriptions.182*183* @return option help184*/185protected String optionOutput() {186return optionRows.render();187}188189/**190* <p>Produces help output for an entire set of options and non-option arguments.</p>191*192* <p>This implementation concatenates:</p>193* <ul>194* <li>the result of {@link #nonOptionOutput()}</li>195* <li>if there is non-option output, a line separator</li>196* <li>the result of {@link #optionOutput()}</li>197* </ul>198*199* @return help output for entire set of options and non-option arguments200*/201protected String formattedHelpOutput() {202StringBuilder formatted = new StringBuilder();203String nonOptionDisplay = nonOptionOutput();204if ( !Strings.isNullOrEmpty( nonOptionDisplay ) )205formatted.append( nonOptionDisplay ).append( LINE_SEPARATOR );206formatted.append( optionOutput() );207208return formatted.toString();209}210211/**212* <p>Adds rows of help output for the given options.</p>213*214* <p>This implementation:</p>215* <ul>216* <li>Calls {@link #addNonOptionsDescription(java.util.Collection)} with the options as the argument</li>217* <li>If there are no options, calls {@link #addOptionRow(String)} with an argument that indicates218* that no options are specified.</li>219* <li>Otherwise, calls {@link #addHeaders(java.util.Collection)} with the options as the argument,220* followed by {@link #addOptions(java.util.Collection)} with the options as the argument.</li>221* <li>Calls {@link #fitRowsToWidth()}.</li>222* </ul>223*224* @param options descriptors for the configured options of a parser225*/226protected void addRows( Collection<? extends OptionDescriptor> options ) {227addNonOptionsDescription( options );228229if ( options.isEmpty() )230addOptionRow( message( "no.options.specified" ) );231else {232addHeaders( options );233addOptions( options );234}235236fitRowsToWidth();237}238239/**240* <p>Adds non-option arguments descriptions to the help output.</p>241*242* <p>This implementation:</p>243* <ul>244* <li>{@linkplain #findAndRemoveNonOptionsSpec(java.util.Collection) Finds and removes the non-option245* arguments descriptor}</li>246* <li>{@linkplain #shouldShowNonOptionArgumentDisplay(OptionDescriptor) Decides whether there is247* anything to show for non-option arguments}</li>248* <li>If there is, {@linkplain #addNonOptionRow(String) adds a header row} and249* {@linkplain #addNonOptionRow(String) adds a}250* {@linkplain #createNonOptionArgumentsDisplay(OptionDescriptor) non-option arguments description} </li>251* </ul>252*253* @param options descriptors for the configured options of a parser254*/255protected void addNonOptionsDescription( Collection<? extends OptionDescriptor> options ) {256OptionDescriptor nonOptions = findAndRemoveNonOptionsSpec( options );257if ( shouldShowNonOptionArgumentDisplay( nonOptions ) ) {258addNonOptionRow( message( "non.option.arguments.header" ) );259addNonOptionRow( createNonOptionArgumentsDisplay( nonOptions ) );260}261}262263/**264* <p>Decides whether or not to show a non-option arguments help.</p>265*266* <p>This implementation responds with {@code true} if the non-option descriptor has a non-{@code null},267* non-empty value for any of {@link OptionDescriptor#description()},268* {@link OptionDescriptor#argumentTypeIndicator()}, or {@link OptionDescriptor#argumentDescription()}.</p>269*270* @param nonOptionDescriptor non-option argument descriptor271* @return {@code true} if non-options argument help should be shown272*/273protected boolean shouldShowNonOptionArgumentDisplay( OptionDescriptor nonOptionDescriptor ) {274return !Strings.isNullOrEmpty( nonOptionDescriptor.description() )275|| !Strings.isNullOrEmpty( nonOptionDescriptor.argumentTypeIndicator() )276|| !Strings.isNullOrEmpty( nonOptionDescriptor.argumentDescription() );277}278279/**280* <p>Creates a non-options argument help string.</p>281*282* <p>This implementation creates an empty string buffer and calls283* {@link #maybeAppendOptionInfo(StringBuilder, OptionDescriptor)}284* and {@link #maybeAppendNonOptionsDescription(StringBuilder, OptionDescriptor)}, passing them the285* buffer and the non-option arguments descriptor.</p>286*287* @param nonOptionDescriptor non-option argument descriptor288* @return help string for non-options289*/290protected String createNonOptionArgumentsDisplay( OptionDescriptor nonOptionDescriptor ) {291StringBuilder buffer = new StringBuilder();292maybeAppendOptionInfo( buffer, nonOptionDescriptor );293maybeAppendNonOptionsDescription( buffer, nonOptionDescriptor );294295return buffer.toString();296}297298/**299* <p>Appends help for the given non-option arguments descriptor to the given buffer.</p>300*301* <p>This implementation appends {@code " -- "} if the buffer has text in it and the non-option arguments302* descriptor has a {@link OptionDescriptor#description()}; followed by the303* {@link OptionDescriptor#description()}.</p>304*305* @param buffer string buffer306* @param nonOptions non-option arguments descriptor307*/308protected void maybeAppendNonOptionsDescription( StringBuilder buffer, OptionDescriptor nonOptions ) {309buffer.append( buffer.length() > 0 && !Strings.isNullOrEmpty( nonOptions.description() ) ? " -- " : "" )310.append( nonOptions.description() );311}312313/**314* Finds the non-option arguments descriptor in the given collection, removes it, and returns it.315*316* @param options descriptors for the configured options of a parser317* @return the non-option arguments descriptor318*/319protected OptionDescriptor findAndRemoveNonOptionsSpec( Collection<? extends OptionDescriptor> options ) {320for ( Iterator<? extends OptionDescriptor> it = options.iterator(); it.hasNext(); ) {321OptionDescriptor next = it.next();322if ( next.representsNonOptions() ) {323it.remove();324return next;325}326}327328throw new AssertionError( "no non-options argument spec" );329}330331/**332* <p>Adds help row headers for option help columns.</p>333*334* <p>This implementation uses the headers {@code "Option"} and {@code "Description"}. If the options contain335* a "required" option, the {@code "Option"} header looks like {@code "Option (* = required)}. Both headers336* are "underlined" using {@code "-"}.</p>337*338* @param options descriptors for the configured options of a parser339*/340protected void addHeaders( Collection<? extends OptionDescriptor> options ) {341if ( hasRequiredOption( options ) ) {342addOptionRow( message( "option.header.with.required.indicator" ), message( "description.header" ) );343addOptionRow( message( "option.divider.with.required.indicator" ), message( "description.divider" ) );344} else {345addOptionRow( message( "option.header" ), message( "description.header" ) );346addOptionRow( message( "option.divider" ), message( "description.divider" ) );347}348}349350/**351* Tells whether the given option descriptors contain a "required" option.352*353* @param options descriptors for the configured options of a parser354* @return {@code true} if at least one of the options is "required"355*/356protected final boolean hasRequiredOption( Collection<? extends OptionDescriptor> options ) {357for ( OptionDescriptor each : options ) {358if ( each.isRequired() )359return true;360}361362return false;363}364365/**366* <p>Adds help rows for the given options.</p>367*368* <p>This implementation loops over the given options, and for each, calls {@link #addOptionRow(String, String)}369* using the results of {@link #createOptionDisplay(OptionDescriptor)} and370* {@link #createDescriptionDisplay(OptionDescriptor)}, respectively, as arguments.</p>371*372* @param options descriptors for the configured options of a parser373*/374protected void addOptions( Collection<? extends OptionDescriptor> options ) {375for ( OptionDescriptor each : options ) {376if ( !each.representsNonOptions() )377addOptionRow( createOptionDisplay( each ), createDescriptionDisplay( each ) );378}379}380381/**382* <p>Creates a string for how the given option descriptor is to be represented in help.</p>383*384* <p>This implementation gives a string consisting of the concatenation of:</p>385* <ul>386* <li>{@code "* "} for "required" options, otherwise {@code ""}</li>387* <li>For each of the {@link OptionDescriptor#options()} of the descriptor, separated by {@code ", "}:388* <ul>389* <li>{@link #optionLeader(String)} of the option</li>390* <li>the option</li>391* </ul>392* </li>393* <li>the result of {@link #maybeAppendOptionInfo(StringBuilder, OptionDescriptor)}</li>394* </ul>395*396* @param descriptor a descriptor for a configured option of a parser397* @return help string398*/399protected String createOptionDisplay( OptionDescriptor descriptor ) {400StringBuilder buffer = new StringBuilder( descriptor.isRequired() ? "* " : "" );401402for ( Iterator<String> i = descriptor.options().iterator(); i.hasNext(); ) {403String option = i.next();404buffer.append( optionLeader( option ) );405buffer.append( option );406407if ( i.hasNext() )408buffer.append( ", " );409}410411maybeAppendOptionInfo( buffer, descriptor );412413return buffer.toString();414}415416/**417* <p>Gives a string that represents the given option's "option leader" in help.</p>418*419* <p>This implementation answers with {@code "--"} for options of length greater than one; otherwise answers420* with {@code "-"}.</p>421*422* @param option a string option423* @return an "option leader" string424*/425protected String optionLeader( String option ) {426return option.length() > 1 ? DOUBLE_HYPHEN : HYPHEN;427}428429/**430* <p>Appends additional info about the given option to the given buffer.</p>431*432* <p>This implementation:</p>433* <ul>434* <li>calls {@link #extractTypeIndicator(OptionDescriptor)} for the descriptor</li>435* <li>calls {@link jdk.internal.joptsimple.OptionDescriptor#argumentDescription()} for the descriptor</li>436* <li>if either of the above is present, calls437* {@link #appendOptionHelp(StringBuilder, String, String, boolean)}</li>438* </ul>439*440* @param buffer string buffer441* @param descriptor a descriptor for a configured option of a parser442*/443protected void maybeAppendOptionInfo( StringBuilder buffer, OptionDescriptor descriptor ) {444String indicator = extractTypeIndicator( descriptor );445String description = descriptor.argumentDescription();446if ( descriptor.acceptsArguments()447|| !isNullOrEmpty( description )448|| descriptor.representsNonOptions() ) {449450appendOptionHelp( buffer, indicator, description, descriptor.requiresArgument() );451}452}453454/**455* <p>Gives an indicator of the type of arguments of the option described by the given descriptor,456* for use in help.</p>457*458* <p>This implementation asks for the {@link OptionDescriptor#argumentTypeIndicator()} of the given459* descriptor, and if it is present and not {@code "java.lang.String"}, parses it as a fully qualified460* class name and returns the base name of that class; otherwise returns {@code "String"}.</p>461*462* @param descriptor a descriptor for a configured option of a parser463* @return type indicator text464*/465protected String extractTypeIndicator( OptionDescriptor descriptor ) {466String indicator = descriptor.argumentTypeIndicator();467468if ( !isNullOrEmpty( indicator ) && !String.class.getName().equals( indicator ) )469return shortNameOf( indicator );470471return "String";472}473474/**475* <p>Appends info about an option's argument to the given buffer.</p>476*477* <p>This implementation calls {@link #appendTypeIndicator(StringBuilder, String, String, char, char)} with478* the surrounding characters {@code '<'} and {@code '>'} for options with {@code required} arguments, and479* with the surrounding characters {@code '['} and {@code ']'} for options with optional arguments.</p>480*481* @param buffer string buffer482* @param typeIndicator type indicator483* @param description type description484* @param required indicator of "required"-ness of the argument of the option485*/486protected void appendOptionHelp( StringBuilder buffer, String typeIndicator, String description,487boolean required ) {488if ( required )489appendTypeIndicator( buffer, typeIndicator, description, '<', '>' );490else491appendTypeIndicator( buffer, typeIndicator, description, '[', ']' );492}493494/**495* <p>Appends a type indicator for an option's argument to the given buffer.</p>496*497* <p>This implementation appends, in order:</p>498* <ul>499* <li>{@code ' '}</li>500* <li>{@code start}</li>501* <li>the type indicator, if not {@code null}</li>502* <li>if the description is present, then {@code ": "} plus the description if the type indicator is503* present; otherwise the description only</li>504* <li>{@code end}</li>505* </ul>506*507* @param buffer string buffer508* @param typeIndicator type indicator509* @param description type description510* @param start starting character511* @param end ending character512*/513protected void appendTypeIndicator( StringBuilder buffer, String typeIndicator, String description,514char start, char end ) {515buffer.append( ' ' ).append( start );516if ( typeIndicator != null )517buffer.append( typeIndicator );518519if ( !Strings.isNullOrEmpty( description ) ) {520if ( typeIndicator != null )521buffer.append( ": " );522523buffer.append( description );524}525526buffer.append( end );527}528529/**530* <p>Gives a string representing a description of the option with the given descriptor.</p>531*532* <p>This implementation:</p>533* <ul>534* <li>Asks for the descriptor's {@link OptionDescriptor#defaultValues()}</li>535* <li>If they're not present, answers the descriptor's {@link OptionDescriptor#description()}.</li>536* <li>If they are present, concatenates and returns:537* <ul>538* <li>the descriptor's {@link OptionDescriptor#description()}</li>539* <li>{@code ' '}</li>540* <li>{@code "default: "} plus the result of {@link #createDefaultValuesDisplay(java.util.List)},541* surrounded by parentheses</li>542* </ul>543* </li>544* </ul>545*546* @param descriptor a descriptor for a configured option of a parser547* @return display text for the option's description548*/549protected String createDescriptionDisplay( OptionDescriptor descriptor ) {550List<?> defaultValues = descriptor.defaultValues();551if ( defaultValues.isEmpty() )552return descriptor.description();553554String defaultValuesDisplay = createDefaultValuesDisplay( defaultValues );555return ( descriptor.description()556+ ' '557+ surround( message( "default.value.header" ) + ' ' + defaultValuesDisplay, '(', ')' )558).trim();559}560561/**562* <p>Gives a display string for the default values of an option's argument.</p>563*564* <p>This implementation gives the {@link Object#toString()} of the first value if there is only one value,565* otherwise gives the {@link Object#toString()} of the whole list.</p>566*567* @param defaultValues some default values for a given option's argument568* @return a display string for those default values569*/570protected String createDefaultValuesDisplay( List<?> defaultValues ) {571return defaultValues.size() == 1 ? defaultValues.get( 0 ).toString() : defaultValues.toString();572}573574/**575* <p>Looks up and gives a resource bundle message.</p>576*577* <p>This implementation looks in the bundle {@code "jdk.internal.joptsimple.HelpFormatterMessages"} in the default578* locale, using a key that is the concatenation of this class's fully qualified name, {@code '.'},579* and the given key suffix, formats the corresponding value using the given arguments, and returns580* the result.</p>581*582* @param keySuffix suffix to use when looking up the bundle message583* @param args arguments to fill in the message template with584* @return a formatted localized message585*/586protected String message( String keySuffix, Object... args ) {587return Messages.message(588Locale.getDefault(),589"jdk.internal.joptsimple.HelpFormatterMessages",590BuiltinHelpFormatter.class,591keySuffix,592args );593}594}595596597