Path: blob/master/src/jdk.internal.opt/share/classes/jdk/internal/joptsimple/ArgumentAcceptingOptionSpec.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.ArrayList;58import java.util.List;59import java.util.StringTokenizer;6061import static java.util.Collections.*;62import static java.util.Objects.*;6364import static jdk.internal.joptsimple.internal.Reflection.*;65import static jdk.internal.joptsimple.internal.Strings.*;6667/**68* <p>Specification of an option that accepts an argument.</p>69*70* <p>Instances are returned from {@link OptionSpecBuilder} methods to allow the formation of parser directives as71* sentences in a "fluent interface" language. For example:</p>72*73* <pre>74* <code>75* OptionParser parser = new OptionParser();76* parser.accepts( "c" ).withRequiredArg().<strong>ofType( Integer.class )</strong>;77* </code>78* </pre>79*80* <p>If no methods are invoked on an instance of this class, then that instance's option will treat its argument as81* a {@link String}.</p>82*83* @param <V> represents the type of the arguments this option accepts84* @author <a href="mailto:[email protected]">Paul Holser</a>85*/86public abstract class ArgumentAcceptingOptionSpec<V> extends AbstractOptionSpec<V> {87private static final char NIL_VALUE_SEPARATOR = '\u0000';8889private final boolean argumentRequired;90private final List<V> defaultValues = new ArrayList<>();9192private boolean optionRequired;93private ValueConverter<V> converter;94private String argumentDescription = "";95private String valueSeparator = String.valueOf( NIL_VALUE_SEPARATOR );9697ArgumentAcceptingOptionSpec( String option, boolean argumentRequired ) {98super( option );99100this.argumentRequired = argumentRequired;101}102103ArgumentAcceptingOptionSpec( List<String> options, boolean argumentRequired, String description ) {104super( options, description );105106this.argumentRequired = argumentRequired;107}108109/**110* <p>Specifies a type to which arguments of this spec's option are to be converted.</p>111*112* <p>JOpt Simple accepts types that have either:</p>113*114* <ol>115* <li>a public static method called {@code valueOf} which accepts a single argument of type {@link String}116* and whose return type is the same as the class on which the method is declared. The {@code java.lang}117* primitive wrapper classes have such methods.</li>118*119* <li>a public constructor which accepts a single argument of type {@link String}.</li>120* </ol>121*122* <p>This class converts arguments using those methods in that order; that is, {@code valueOf} would be invoked123* before a one-{@link String}-arg constructor would.</p>124*125* <p>Invoking this method will trump any previous calls to this method or to126* {@link #withValuesConvertedBy(ValueConverter)}.</p>127*128* @param <T> represents the runtime class of the desired option argument type129* @param argumentType desired type of arguments to this spec's option130* @return self, so that the caller can add clauses to the fluent interface sentence131* @throws NullPointerException if the type is {@code null}132* @throws IllegalArgumentException if the type does not have the standard conversion methods133*/134public final <T> ArgumentAcceptingOptionSpec<T> ofType( Class<T> argumentType ) {135return withValuesConvertedBy( findConverter( argumentType ) );136}137138/**139* <p>Specifies a converter to use to translate arguments of this spec's option into Java objects. This is useful140* when converting to types that do not have the requisite factory method or constructor for141* {@link #ofType(Class)}.</p>142*143* <p>Invoking this method will trump any previous calls to this method or to {@link #ofType(Class)}.144*145* @param <T> represents the runtime class of the desired option argument type146* @param aConverter the converter to use147* @return self, so that the caller can add clauses to the fluent interface sentence148* @throws NullPointerException if the converter is {@code null}149*/150@SuppressWarnings( "unchecked" )151public final <T> ArgumentAcceptingOptionSpec<T> withValuesConvertedBy( ValueConverter<T> aConverter ) {152if ( aConverter == null )153throw new NullPointerException( "illegal null converter" );154155converter = (ValueConverter<V>) aConverter;156return (ArgumentAcceptingOptionSpec<T>) this;157}158159/**160* <p>Specifies a description for the argument of the option that this spec represents. This description is used161* when generating help information about the parser.</p>162*163* @param description describes the nature of the argument of this spec's option164* @return self, so that the caller can add clauses to the fluent interface sentence165*/166public final ArgumentAcceptingOptionSpec<V> describedAs( String description ) {167argumentDescription = description;168return this;169}170171/**172* <p>Specifies a value separator for the argument of the option that this spec represents. This allows a single173* option argument to represent multiple values for the option. For example:</p>174*175* <pre>176* <code>177* parser.accepts( "z" ).withRequiredArg()178* .<strong>withValuesSeparatedBy( ',' )</strong>;179* OptionSet options = parser.parse( new String[] { "-z", "foo,bar,baz", "-z",180* "fizz", "-z", "buzz" } );181* </code>182* </pre>183*184* <p>Then <code>options.valuesOf( "z" )</code> would yield the list {@code [foo, bar, baz, fizz, buzz]}.</p>185*186* <p>You cannot use Unicode U+0000 as the separator.</p>187*188* @param separator a character separator189* @return self, so that the caller can add clauses to the fluent interface sentence190* @throws IllegalArgumentException if the separator is Unicode U+0000191*/192public final ArgumentAcceptingOptionSpec<V> withValuesSeparatedBy( char separator ) {193if ( separator == NIL_VALUE_SEPARATOR )194throw new IllegalArgumentException( "cannot use U+0000 as separator" );195196valueSeparator = String.valueOf( separator );197return this;198}199200/**201* <p>Specifies a value separator for the argument of the option that this spec represents. This allows a single202* option argument to represent multiple values for the option. For example:</p>203*204* <pre>205* <code>206* parser.accepts( "z" ).withRequiredArg()207* .<strong>withValuesSeparatedBy( ":::" )</strong>;208* OptionSet options = parser.parse( new String[] { "-z", "foo:::bar:::baz", "-z",209* "fizz", "-z", "buzz" } );210* </code>211* </pre>212*213* <p>Then <code>options.valuesOf( "z" )</code> would yield the list {@code [foo, bar, baz, fizz, buzz]}.</p>214*215* <p>You cannot use Unicode U+0000 in the separator.</p>216*217* @param separator a string separator218* @return self, so that the caller can add clauses to the fluent interface sentence219* @throws IllegalArgumentException if the separator contains Unicode U+0000220*/221public final ArgumentAcceptingOptionSpec<V> withValuesSeparatedBy( String separator ) {222if ( separator.indexOf( NIL_VALUE_SEPARATOR ) != -1 )223throw new IllegalArgumentException( "cannot use U+0000 in separator" );224225valueSeparator = separator;226return this;227}228229/**230* Specifies a set of default values for the argument of the option that this spec represents.231*232* @param value the first in the set of default argument values for this spec's option233* @param values the (optional) remainder of the set of default argument values for this spec's option234* @return self, so that the caller can add clauses to the fluent interface sentence235* @throws NullPointerException if {@code value}, {@code values}, or any elements of {@code values} are236* {@code null}237*/238@SafeVarargs239@SuppressWarnings("varargs")240public final ArgumentAcceptingOptionSpec<V> defaultsTo( V value, V... values ) {241addDefaultValue( value );242defaultsTo( values );243244return this;245}246247/**248* Specifies a set of default values for the argument of the option that this spec represents.249*250* @param values the set of default argument values for this spec's option251* @return self, so that the caller can add clauses to the fluent interface sentence252* @throws NullPointerException if {@code values} or any elements of {@code values} are {@code null}253*/254public ArgumentAcceptingOptionSpec<V> defaultsTo( V[] values ) {255for ( V each : values )256addDefaultValue( each );257258return this;259}260261/**262* Marks this option as required. An {@link OptionException} will be thrown when263* {@link OptionParser#parse(java.lang.String...)} is called, if an option is marked as required and not specified264* on the command line.265*266* @return self, so that the caller can add clauses to the fluent interface sentence267*/268public ArgumentAcceptingOptionSpec<V> required() {269optionRequired = true;270return this;271}272273public boolean isRequired() {274return optionRequired;275}276277private void addDefaultValue( V value ) {278requireNonNull( value );279defaultValues.add( value );280}281282@Override283final void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions,284String detectedArgument ) {285286if ( detectedArgument == null )287detectOptionArgument( parser, arguments, detectedOptions );288else289addArguments( detectedOptions, detectedArgument );290}291292protected void addArguments( OptionSet detectedOptions, String detectedArgument ) {293StringTokenizer lexer = new StringTokenizer( detectedArgument, valueSeparator );294if ( !lexer.hasMoreTokens() )295detectedOptions.addWithArgument( this, detectedArgument );296else {297while ( lexer.hasMoreTokens() )298detectedOptions.addWithArgument( this, lexer.nextToken() );299}300}301302protected abstract void detectOptionArgument( OptionParser parser, ArgumentList arguments,303OptionSet detectedOptions );304305@Override306protected final V convert( String argument ) {307return convertWith( converter, argument );308}309310protected boolean canConvertArgument( String argument ) {311StringTokenizer lexer = new StringTokenizer( argument, valueSeparator );312313try {314while ( lexer.hasMoreTokens() )315convert( lexer.nextToken() );316return true;317} catch ( OptionException ignored ) {318return false;319}320}321322protected boolean isArgumentOfNumberType() {323return converter != null && Number.class.isAssignableFrom( converter.valueType() );324}325326public boolean acceptsArguments() {327return true;328}329330public boolean requiresArgument() {331return argumentRequired;332}333334public String argumentDescription() {335return argumentDescription;336}337338public String argumentTypeIndicator() {339return argumentTypeIndicatorFrom( converter );340}341342public List<V> defaultValues() {343return unmodifiableList( defaultValues );344}345346@Override347public boolean equals( Object that ) {348if ( !super.equals( that ) )349return false;350351ArgumentAcceptingOptionSpec<?> other = (ArgumentAcceptingOptionSpec<?>) that;352return requiresArgument() == other.requiresArgument();353}354355@Override356public int hashCode() {357return super.hashCode() ^ ( argumentRequired ? 0 : 1 );358}359}360361362