/* * 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 jdk.internal.joptsimple.internal.AbbreviationMap; import jdk.internal.joptsimple.util.KeyValuePair; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import static java.util.Collections.*; import static jdk.internal.joptsimple.OptionException.*; import static jdk.internal.joptsimple.OptionParserState.*; import static jdk.internal.joptsimple.ParserRules.*; /** * <p>Parses command line arguments, using a syntax that attempts to take from the best of POSIX {@code getopt()} * and GNU {@code getopt_long()}.</p> * * <p>This parser supports short options and long options.</p> * * <ul> * <li><dfn>Short options</dfn> begin with a single hyphen ("<kbd>-</kbd>") followed by a single letter or digit, * or question mark ("<kbd>?</kbd>"), or dot ("<kbd>.</kbd>").</li> * * <li>Short options can accept single arguments. The argument can be made required or optional. The option's * argument can occur: * <ul> * <li>in the slot after the option, as in <kbd>-d /tmp</kbd></li> * <li>right up against the option, as in <kbd>-d/tmp</kbd></li> * <li>right up against the option separated by an equals sign (<kbd>"="</kbd>), as in <kbd>-d=/tmp</kbd></li> * </ul> * To specify <em>n</em> arguments for an option, specify the option <em>n</em> times, once for each argument, * as in <kbd>-d /tmp -d /var -d /opt</kbd>; or, when using the * {@linkplain ArgumentAcceptingOptionSpec#withValuesSeparatedBy(char) "separated values"} clause of the "fluent * interface" (see below), give multiple values separated by a given character as a single argument to the * option.</li> * * <li>Short options can be clustered, so that <kbd>-abc</kbd> is treated as <kbd>-a -b -c</kbd>. If a short option * in the cluster can accept an argument, the remaining characters are interpreted as the argument for that * option.</li> * * <li>An argument consisting only of two hyphens (<kbd>"--"</kbd>) signals that the remaining arguments are to be * treated as non-options.</li> * * <li>An argument consisting only of a single hyphen is considered a non-option argument (though it can be an * argument of an option). Many Unix programs treat single hyphens as stand-ins for the standard input or standard * output streams.</li> * * <li><dfn>Long options</dfn> begin with two hyphens (<kbd>"--"</kbd>), followed by multiple letters, digits, * hyphens, question marks, or dots. A hyphen cannot be the first character of a long option specification when * configuring the parser.</li> * * <li>You can abbreviate long options, so long as the abbreviation is unique.</li> * * <li>Long options can accept single arguments. The argument can be made required or optional. The option's * argument can occur: * <ul> * <li>in the slot after the option, as in <kbd>--directory /tmp</kbd></li> * <li>right up against the option separated by an equals sign (<kbd>"="</kbd>), as in * <kbd>--directory=/tmp</kbd> * </ul> * Specify multiple arguments for a long option in the same manner as for short options (see above).</li> * * <li>You can use a single hyphen (<kbd>"-"</kbd>) instead of a double hyphen (<kbd>"--"</kbd>) for a long * option.</li> * * <li>The option <kbd>-W</kbd> is reserved. If you tell the parser to {@linkplain * #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then it will treat, for example, * <kbd>-W foo=bar</kbd> as the long option <kbd>foo</kbd> with argument <kbd>bar</kbd>, as though you had written * <kbd>--foo=bar</kbd>.</li> * * <li>You can specify <kbd>-W</kbd> as a valid short option, or use it as an abbreviation for a long option, but * {@linkplain #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will always supersede * this behavior.</li> * * <li>You can specify a given short or long option multiple times on a single command line. The parser collects * any arguments specified for those options as a list.</li> * * <li>If the parser detects an option whose argument is optional, and the next argument "looks like" an option, * that argument is not treated as the argument to the option, but as a potentially valid option. If, on the other * hand, the optional argument is typed as a derivative of {@link Number}, then that argument is treated as the * negative number argument of the option, even if the parser recognizes the corresponding numeric option. * For example: * <pre><code> * OptionParser parser = new OptionParser(); * parser.accepts( "a" ).withOptionalArg().ofType( Integer.class ); * parser.accepts( "2" ); * OptionSet options = parser.parse( "-a", "-2" ); * </code></pre> * In this case, the option set contains <kbd>"a"</kbd> with argument <kbd>-2</kbd>, not both <kbd>"a"</kbd> and * <kbd>"2"</kbd>. Swapping the elements in the <em>args</em> array gives the latter.</li> * </ul> * * <p>There are two ways to tell the parser what options to recognize:</p> * * <ol> * <li>A "fluent interface"-style API for specifying options, available since version 2. Sentences in this fluent * interface language begin with a call to {@link #accepts(String) accepts} or {@link #acceptsAll(Collection) * acceptsAll} methods; calls on the ensuing chain of objects describe whether the options can take an argument, * whether the argument is required or optional, to what type arguments of the options should be converted if any, * etc. Since version 3, these calls return an instance of {@link OptionSpec}, which can subsequently be used to * retrieve the arguments of the associated option in a type-safe manner.</li> * * <li>Since version 1, a more concise way of specifying short options has been to use the special {@linkplain * #OptionParser(String) constructor}. Arguments of options specified in this manner will be of type {@link String}. * Here are the rules for the format of the specification strings this constructor accepts: * * <ul> * <li>Any letter or digit is treated as an option character.</li> * * <li>An option character can be immediately followed by an asterisk (*) to indicate that the option is a * "help" option.</li> * * <li>If an option character (with possible trailing asterisk) is followed by a single colon (<kbd>":"</kbd>), * then the option requires an argument.</li> * * <li>If an option character (with possible trailing asterisk) is followed by two colons (<kbd>"::"</kbd>), * then the option accepts an optional argument.</li> * * <li>Otherwise, the option character accepts no argument.</li> * * <li>If the option specification string begins with a plus sign (<kbd>"+"</kbd>), the parser will behave * "POSIX-ly correct".</li> * * <li>If the option specification string contains the sequence <kbd>"W;"</kbd> (capital W followed by a * semicolon), the parser will recognize the alternative form of long options.</li> * </ul> * </li> * </ol> * * <p>Each of the options in a list of options given to {@link #acceptsAll(Collection) acceptsAll} is treated as a * synonym of the others. For example: * <pre> * <code> * OptionParser parser = new OptionParser(); * parser.acceptsAll( asList( "w", "interactive", "confirmation" ) ); * OptionSet options = parser.parse( "-w" ); * </code> * </pre> * In this case, <code>options.{@link OptionSet#has(String) has}</code> would answer {@code true} when given arguments * <kbd>"w"</kbd>, <kbd>"interactive"</kbd>, and <kbd>"confirmation"</kbd>. The {@link OptionSet} would give the same * responses to these arguments for its other methods as well.</p> * * <p>By default, as with GNU {@code getopt()}, the parser allows intermixing of options and non-options. If, however, * the parser has been created to be "POSIX-ly correct", then the first argument that does not look lexically like an * option, and is not a required argument of a preceding option, signals the end of options. You can still bind * optional arguments to their options using the abutting (for short options) or <kbd>=</kbd> syntax.</p> * * <p>Unlike GNU {@code getopt()}, this parser does not honor the environment variable {@code POSIXLY_CORRECT}. * "POSIX-ly correct" parsers are configured by either:</p> * * <ol> * <li>using the method {@link #posixlyCorrect(boolean)}, or</li> * * <li>using the {@linkplain #OptionParser(String) constructor} with an argument whose first character is a plus sign * (<kbd>"+"</kbd>)</li> * </ol> * * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a> * @see <a href="http://www.gnu.org/software/libc/manual">The GNU C Library</a> */ public class OptionParser implements OptionDeclarer { private final AbbreviationMap<AbstractOptionSpec<?>> recognizedOptions; private final Map<Collection<String>, Set<OptionSpec<?>>> requiredIf; private final Map<Collection<String>, Set<OptionSpec<?>>> requiredUnless; private OptionParserState state; private boolean posixlyCorrect; private boolean allowsUnrecognizedOptions; private HelpFormatter helpFormatter = new BuiltinHelpFormatter(); /** * Creates an option parser that initially recognizes no options, and does not exhibit "POSIX-ly correct" * behavior. */ public OptionParser() { recognizedOptions = new AbbreviationMap<AbstractOptionSpec<?>>(); requiredIf = new HashMap<Collection<String>, Set<OptionSpec<?>>>(); requiredUnless = new HashMap<Collection<String>, Set<OptionSpec<?>>>(); state = moreOptions( false ); recognize( new NonOptionArgumentSpec<String>() ); } /** * Creates an option parser and configures it to recognize the short options specified in the given string. * * Arguments of options specified this way will be of type {@link String}. * * @param optionSpecification an option specification * @throws NullPointerException if {@code optionSpecification} is {@code null} * @throws OptionException if the option specification contains illegal characters or otherwise cannot be * recognized */ public OptionParser( String optionSpecification ) { this(); new OptionSpecTokenizer( optionSpecification ).configure( this ); } public OptionSpecBuilder accepts( String option ) { return acceptsAll( singletonList( option ) ); } public OptionSpecBuilder accepts( String option, String description ) { return acceptsAll( singletonList( option ), description ); } public OptionSpecBuilder acceptsAll( Collection<String> options ) { return acceptsAll( options, "" ); } public OptionSpecBuilder acceptsAll( Collection<String> options, String description ) { if ( options.isEmpty() ) throw new IllegalArgumentException( "need at least one option" ); ensureLegalOptions( options ); return new OptionSpecBuilder( this, options, description ); } public NonOptionArgumentSpec<String> nonOptions() { NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec<String>(); recognize( spec ); return spec; } public NonOptionArgumentSpec<String> nonOptions( String description ) { NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec<String>( description ); recognize( spec ); return spec; } public void posixlyCorrect( boolean setting ) { posixlyCorrect = setting; state = moreOptions( setting ); } boolean posixlyCorrect() { return posixlyCorrect; } public void allowsUnrecognizedOptions() { allowsUnrecognizedOptions = true; } boolean doesAllowsUnrecognizedOptions() { return allowsUnrecognizedOptions; } public void recognizeAlternativeLongOptions( boolean recognize ) { if ( recognize ) recognize( new AlternativeLongOptionSpec() ); else recognizedOptions.remove( String.valueOf( RESERVED_FOR_EXTENSIONS ) ); } void recognize( AbstractOptionSpec<?> spec ) { recognizedOptions.putAll( spec.options(), spec ); } /** * Writes information about the options this parser recognizes to the given output sink. * * The output sink is flushed, but not closed. * * @param sink the sink to write information to * @throws IOException if there is a problem writing to the sink * @throws NullPointerException if {@code sink} is {@code null} * @see #printHelpOn(Writer) */ public void printHelpOn( OutputStream sink ) throws IOException { printHelpOn( new OutputStreamWriter( sink ) ); } /** * Writes information about the options this parser recognizes to the given output sink. * * The output sink is flushed, but not closed. * * @param sink the sink to write information to * @throws IOException if there is a problem writing to the sink * @throws NullPointerException if {@code sink} is {@code null} * @see #printHelpOn(OutputStream) */ public void printHelpOn( Writer sink ) throws IOException { sink.write( helpFormatter.format( recognizedOptions.toJavaUtilMap() ) ); sink.flush(); } /** * Tells the parser to use the given formatter when asked to {@linkplain #printHelpOn(java.io.Writer) print help}. * * @param formatter the formatter to use for printing help * @throws NullPointerException if the formatter is {@code null} */ public void formatHelpWith( HelpFormatter formatter ) { if ( formatter == null ) throw new NullPointerException(); helpFormatter = formatter; } /** * Retrieves all the options which have been configured for the parser. * * @return a {@link Map} containing all the configured options and their corresponding {@link OptionSpec} */ public Map<String, OptionSpec<?>> recognizedOptions() { return new HashMap<String, OptionSpec<?>>( recognizedOptions.toJavaUtilMap() ); } /** * Parses the given command line arguments according to the option specifications given to the parser. * * @param arguments arguments to parse * @return an {@link OptionSet} describing the parsed options, their arguments, and any non-option arguments found * @throws OptionException if problems are detected while parsing * @throws NullPointerException if the argument list is {@code null} */ public OptionSet parse( String... arguments ) { ArgumentList argumentList = new ArgumentList( arguments ); OptionSet detected = new OptionSet( recognizedOptions.toJavaUtilMap() ); detected.add( recognizedOptions.get( NonOptionArgumentSpec.NAME ) ); while ( argumentList.hasMore() ) state.handleArgument( this, argumentList, detected ); reset(); ensureRequiredOptions( detected ); return detected; } private void ensureRequiredOptions( OptionSet options ) { Collection<String> missingRequiredOptions = missingRequiredOptions( options ); boolean helpOptionPresent = isHelpOptionPresent( options ); if ( !missingRequiredOptions.isEmpty() && !helpOptionPresent ) throw new MissingRequiredOptionException( missingRequiredOptions ); } private Collection<String> missingRequiredOptions( OptionSet options ) { Collection<String> missingRequiredOptions = new HashSet<String>(); for ( AbstractOptionSpec<?> each : recognizedOptions.toJavaUtilMap().values() ) { if ( each.isRequired() && !options.has( each ) ) missingRequiredOptions.addAll( each.options() ); } for ( Map.Entry<Collection<String>, Set<OptionSpec<?>>> eachEntry : requiredIf.entrySet() ) { AbstractOptionSpec<?> required = specFor( eachEntry.getKey().iterator().next() ); if ( optionsHasAnyOf( options, eachEntry.getValue() ) && !options.has( required ) ) { missingRequiredOptions.addAll( required.options() ); } } for ( Map.Entry<Collection<String>, Set<OptionSpec<?>>> eachEntry : requiredUnless.entrySet() ) { AbstractOptionSpec<?> required = specFor( eachEntry.getKey().iterator().next() ); if ( !optionsHasAnyOf( options, eachEntry.getValue() ) && !options.has( required ) ) { missingRequiredOptions.addAll( required.options() ); } } return missingRequiredOptions; } private boolean optionsHasAnyOf( OptionSet options, Collection<OptionSpec<?>> specs ) { for ( OptionSpec<?> each : specs ) { if ( options.has( each ) ) return true; } return false; } private boolean isHelpOptionPresent( OptionSet options ) { boolean helpOptionPresent = false; for ( AbstractOptionSpec<?> each : recognizedOptions.toJavaUtilMap().values() ) { if ( each.isForHelp() && options.has( each ) ) { helpOptionPresent = true; break; } } return helpOptionPresent; } void handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate ); if ( !isRecognized( optionAndArgument.key ) ) throw unrecognizedOption( optionAndArgument.key ); AbstractOptionSpec<?> optionSpec = specFor( optionAndArgument.key ); optionSpec.handleOption( this, arguments, detected, optionAndArgument.value ); } void handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate ); if ( isRecognized( optionAndArgument.key ) ) { specFor( optionAndArgument.key ).handleOption( this, arguments, detected, optionAndArgument.value ); } else handleShortOptionCluster( candidate, arguments, detected ); } private void handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected ) { char[] options = extractShortOptionsFrom( candidate ); validateOptionCharacters( options ); for ( int i = 0; i < options.length; i++ ) { AbstractOptionSpec<?> optionSpec = specFor( options[ i ] ); if ( optionSpec.acceptsArguments() && options.length > i + 1 ) { String detectedArgument = String.valueOf( options, i + 1, options.length - 1 - i ); optionSpec.handleOption( this, arguments, detected, detectedArgument ); break; } optionSpec.handleOption( this, arguments, detected, null ); } } void handleNonOptionArgument( String candidate, ArgumentList arguments, OptionSet detectedOptions ) { specFor( NonOptionArgumentSpec.NAME ).handleOption( this, arguments, detectedOptions, candidate ); } void noMoreOptions() { state = OptionParserState.noMoreOptions(); } boolean looksLikeAnOption( String argument ) { return isShortOptionToken( argument ) || isLongOptionToken( argument ); } boolean isRecognized( String option ) { return recognizedOptions.contains( option ); } void requiredIf( Collection<String> precedentSynonyms, String required ) { requiredIf( precedentSynonyms, specFor( required ) ); } void requiredIf( Collection<String> precedentSynonyms, OptionSpec<?> required ) { putRequiredOption( precedentSynonyms, required, requiredIf ); } void requiredUnless( Collection<String> precedentSynonyms, String required ) { requiredUnless( precedentSynonyms, specFor( required ) ); } void requiredUnless( Collection<String> precedentSynonyms, OptionSpec<?> required ) { putRequiredOption( precedentSynonyms, required, requiredUnless ); } private void putRequiredOption( Collection<String> precedentSynonyms, OptionSpec<?> required, Map<Collection<String>, Set<OptionSpec<?>>> target ) { for ( String each : precedentSynonyms ) { AbstractOptionSpec<?> spec = specFor( each ); if ( spec == null ) throw new UnconfiguredOptionException( precedentSynonyms ); } Set<OptionSpec<?>> associated = target.get( precedentSynonyms ); if ( associated == null ) { associated = new HashSet<OptionSpec<?>>(); target.put( precedentSynonyms, associated ); } associated.add( required ); } private AbstractOptionSpec<?> specFor( char option ) { return specFor( String.valueOf( option ) ); } private AbstractOptionSpec<?> specFor( String option ) { return recognizedOptions.get( option ); } private void reset() { state = moreOptions( posixlyCorrect ); } private static char[] extractShortOptionsFrom( String argument ) { char[] options = new char[ argument.length() - 1 ]; argument.getChars( 1, argument.length(), options, 0 ); return options; } private void validateOptionCharacters( char[] options ) { for ( char each : options ) { String option = String.valueOf( each ); if ( !isRecognized( option ) ) throw unrecognizedOption( option ); if ( specFor( option ).acceptsArguments() ) return; } } private static KeyValuePair parseLongOptionWithArgument( String argument ) { return KeyValuePair.valueOf( argument.substring( 2 ) ); } private static KeyValuePair parseShortOptionWithArgument( String argument ) { return KeyValuePair.valueOf( argument.substring( 1 ) ); } }