jdk/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/BuiltinHelpFormatter.java
changeset 35377 462f93ab37f2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/BuiltinHelpFormatter.java	Tue Jan 26 09:18:51 2016 +0000
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * This file is available under and governed by the GNU General Public
+ * License version 2 only, as published by the Free Software Foundation.
+ * However, the following notice accompanied the original version of this
+ * file:
+ *
+ * The MIT License
+ *
+ * Copyright (c) 2004-2014 Paul R. Holser, Jr.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package jdk.internal.joptsimple;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import jdk.internal.joptsimple.internal.Rows;
+import jdk.internal.joptsimple.internal.Strings;
+
+import static jdk.internal.joptsimple.ParserRules.*;
+import static jdk.internal.joptsimple.internal.Classes.*;
+import static jdk.internal.joptsimple.internal.Strings.*;
+
+/**
+ * <p>A help formatter that allows configuration of overall row width and column separator width.</p>
+ *
+ * <p>The formatter produces a two-column output. The left column is for the options, and the right column for their
+ * descriptions. The formatter will allow as much space as possible for the descriptions, by minimizing the option
+ * column's width, no greater than slightly less than half the overall desired width.</p>
+ *
+ * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a>
+ */
+public class BuiltinHelpFormatter implements HelpFormatter {
+    private final Rows nonOptionRows;
+    private final Rows optionRows;
+
+    /**
+     * Makes a formatter with a pre-configured overall row width and column separator width.
+     */
+    BuiltinHelpFormatter() {
+        this( 80, 2 );
+    }
+
+    /**
+     * Makes a formatter with a given overall row width and column separator width.
+     *
+     * @param desiredOverallWidth how many characters wide to make the overall help display
+     * @param desiredColumnSeparatorWidth how many characters wide to make the separation between option column and
+     * description column
+     */
+    public BuiltinHelpFormatter( int desiredOverallWidth, int desiredColumnSeparatorWidth ) {
+        nonOptionRows = new Rows( desiredOverallWidth * 2, 0 );
+        optionRows = new Rows( desiredOverallWidth, desiredColumnSeparatorWidth );
+    }
+
+    public String format( Map<String, ? extends OptionDescriptor> options ) {
+        Comparator<OptionDescriptor> comparator =
+            new Comparator<OptionDescriptor>() {
+                public int compare( OptionDescriptor first, OptionDescriptor second ) {
+                    return first.options().iterator().next().compareTo( second.options().iterator().next() );
+                }
+            };
+
+        Set<OptionDescriptor> sorted = new TreeSet<OptionDescriptor>( comparator );
+        sorted.addAll( options.values() );
+
+        addRows( sorted );
+
+        return formattedHelpOutput();
+    }
+
+    private String formattedHelpOutput() {
+        StringBuilder formatted = new StringBuilder();
+        String nonOptionDisplay = nonOptionRows.render();
+        if ( !Strings.isNullOrEmpty( nonOptionDisplay ) )
+            formatted.append( nonOptionDisplay ).append( LINE_SEPARATOR );
+        formatted.append( optionRows.render() );
+
+        return formatted.toString();
+    }
+
+    private void addRows( Collection<? extends OptionDescriptor> options ) {
+        addNonOptionsDescription( options );
+
+        if ( options.isEmpty() )
+            optionRows.add( "No options specified", "" );
+        else {
+            addHeaders( options );
+            addOptions( options );
+        }
+
+        fitRowsToWidth();
+    }
+
+    private void addNonOptionsDescription( Collection<? extends OptionDescriptor> options ) {
+        OptionDescriptor nonOptions = findAndRemoveNonOptionsSpec( options );
+        if ( shouldShowNonOptionArgumentDisplay( nonOptions ) ) {
+            nonOptionRows.add( "Non-option arguments:", "" );
+            nonOptionRows.add(createNonOptionArgumentsDisplay(nonOptions), "");
+        }
+    }
+
+    private boolean shouldShowNonOptionArgumentDisplay( OptionDescriptor nonOptions ) {
+        return !Strings.isNullOrEmpty( nonOptions.description() )
+            || !Strings.isNullOrEmpty( nonOptions.argumentTypeIndicator() )
+            || !Strings.isNullOrEmpty( nonOptions.argumentDescription() );
+    }
+
+    private String createNonOptionArgumentsDisplay(OptionDescriptor nonOptions) {
+        StringBuilder buffer = new StringBuilder();
+        maybeAppendOptionInfo( buffer, nonOptions );
+        maybeAppendNonOptionsDescription( buffer, nonOptions );
+
+        return buffer.toString();
+    }
+
+    private void maybeAppendNonOptionsDescription( StringBuilder buffer, OptionDescriptor nonOptions ) {
+        buffer.append( buffer.length() > 0 && !Strings.isNullOrEmpty( nonOptions.description() ) ? " -- " : "" )
+            .append( nonOptions.description() );
+    }
+
+    private OptionDescriptor findAndRemoveNonOptionsSpec( Collection<? extends OptionDescriptor> options ) {
+        for ( Iterator<? extends OptionDescriptor> it = options.iterator(); it.hasNext(); ) {
+            OptionDescriptor next = it.next();
+            if ( next.representsNonOptions() ) {
+                it.remove();
+                return next;
+            }
+        }
+
+        throw new AssertionError( "no non-options argument spec" );
+    }
+
+    private void addHeaders( Collection<? extends OptionDescriptor> options ) {
+        if ( hasRequiredOption( options ) ) {
+            optionRows.add("Option (* = required)", "Description");
+            optionRows.add("---------------------", "-----------");
+        } else {
+            optionRows.add("Option", "Description");
+            optionRows.add("------", "-----------");
+        }
+    }
+
+    private boolean hasRequiredOption( Collection<? extends OptionDescriptor> options ) {
+        for ( OptionDescriptor each : options ) {
+            if ( each.isRequired() )
+                return true;
+        }
+
+        return false;
+    }
+
+    private void addOptions( Collection<? extends OptionDescriptor> options ) {
+        for ( OptionDescriptor each : options ) {
+            if ( !each.representsNonOptions() )
+                optionRows.add( createOptionDisplay( each ), createDescriptionDisplay( each ) );
+        }
+    }
+
+    private String createOptionDisplay( OptionDescriptor descriptor ) {
+        StringBuilder buffer = new StringBuilder( descriptor.isRequired() ? "* " : "" );
+
+        for ( Iterator<String> i = descriptor.options().iterator(); i.hasNext(); ) {
+            String option = i.next();
+            buffer.append( option.length() > 1 ? DOUBLE_HYPHEN : HYPHEN );
+            buffer.append( option );
+
+            if ( i.hasNext() )
+                buffer.append( ", " );
+        }
+
+        maybeAppendOptionInfo( buffer, descriptor );
+
+        return buffer.toString();
+    }
+
+    private void maybeAppendOptionInfo( StringBuilder buffer, OptionDescriptor descriptor ) {
+        String indicator = extractTypeIndicator( descriptor );
+        String description = descriptor.argumentDescription();
+        if ( indicator != null || !isNullOrEmpty( description ) )
+            appendOptionHelp( buffer, indicator, description, descriptor.requiresArgument() );
+    }
+
+    private String extractTypeIndicator( OptionDescriptor descriptor ) {
+        String indicator = descriptor.argumentTypeIndicator();
+
+        if ( !isNullOrEmpty( indicator ) && !String.class.getName().equals( indicator ) )
+            return shortNameOf( indicator );
+
+        return null;
+    }
+
+    private void appendOptionHelp( StringBuilder buffer, String typeIndicator, String description, boolean required ) {
+        if ( required )
+            appendTypeIndicator( buffer, typeIndicator, description, '<', '>' );
+        else
+            appendTypeIndicator( buffer, typeIndicator, description, '[', ']' );
+    }
+
+    private void appendTypeIndicator( StringBuilder buffer, String typeIndicator, String description,
+                                      char start, char end ) {
+        buffer.append( ' ' ).append( start );
+        if ( typeIndicator != null )
+            buffer.append( typeIndicator );
+
+        if ( !Strings.isNullOrEmpty( description ) ) {
+            if ( typeIndicator != null )
+                buffer.append( ": " );
+
+            buffer.append( description );
+        }
+
+        buffer.append( end );
+    }
+
+    private String createDescriptionDisplay( OptionDescriptor descriptor ) {
+        List<?> defaultValues = descriptor.defaultValues();
+        if ( defaultValues.isEmpty() )
+            return descriptor.description();
+
+        String defaultValuesDisplay = createDefaultValuesDisplay( defaultValues );
+        return ( descriptor.description() + ' ' + surround( "default: " + defaultValuesDisplay, '(', ')' ) ).trim();
+    }
+
+    private String createDefaultValuesDisplay( List<?> defaultValues ) {
+        return defaultValues.size() == 1 ? defaultValues.get( 0 ).toString() : defaultValues.toString();
+    }
+
+    private void fitRowsToWidth() {
+        nonOptionRows.fitToWidth();
+        optionRows.fitToWidth();
+    }
+}