1/* 2 * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26/* 27 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * The MIT License 33 * 34 * Copyright (c) 2004-2014 Paul R. Holser, Jr. 35 * 36 * Permission is hereby granted, free of charge, to any person obtaining 37 * a copy of this software and associated documentation files (the 38 * "Software"), to deal in the Software without restriction, including 39 * without limitation the rights to use, copy, modify, merge, publish, 40 * distribute, sublicense, and/or sell copies of the Software, and to 41 * permit persons to whom the Software is furnished to do so, subject to 42 * the following conditions: 43 * 44 * The above copyright notice and this permission notice shall be 45 * included in all copies or substantial portions of the Software. 46 * 47 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 48 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 49 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 50 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 51 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 52 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 53 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 54 */ 55 56package jdk.internal.joptsimple; 57 58import java.util.ArrayList; 59import java.util.Collection; 60import java.util.List; 61import java.util.StringTokenizer; 62 63import static java.util.Collections.*; 64 65import static jdk.internal.joptsimple.internal.Objects.*; 66import static jdk.internal.joptsimple.internal.Reflection.*; 67import static jdk.internal.joptsimple.internal.Strings.*; 68 69/** 70 * <p>Specification of an option that accepts an argument.</p> 71 * 72 * <p>Instances are returned from {@link OptionSpecBuilder} methods to allow the formation of parser directives as 73 * sentences in a "fluent interface" language. For example:</p> 74 * 75 * <pre> 76 * <code> 77 * OptionParser parser = new OptionParser(); 78 * parser.accepts( "c" ).withRequiredArg().<strong>ofType( Integer.class )</strong>; 79 * </code> 80 * </pre> 81 * 82 * <p>If no methods are invoked on an instance of this class, then that instance's option will treat its argument as 83 * a {@link String}.</p> 84 * 85 * @param <V> represents the type of the arguments this option accepts 86 * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a> 87 */ 88public abstract class ArgumentAcceptingOptionSpec<V> extends AbstractOptionSpec<V> { 89 private static final char NIL_VALUE_SEPARATOR = '\u0000'; 90 91 private boolean optionRequired; 92 private final boolean argumentRequired; 93 private ValueConverter<V> converter; 94 private String argumentDescription = ""; 95 private String valueSeparator = String.valueOf( NIL_VALUE_SEPARATOR ); 96 private final List<V> defaultValues = new ArrayList<V>(); 97 98 ArgumentAcceptingOptionSpec( String option, boolean argumentRequired ) { 99 super( option ); 100 101 this.argumentRequired = argumentRequired; 102 } 103 104 ArgumentAcceptingOptionSpec( Collection<String> options, boolean argumentRequired, String description ) { 105 super( options, description ); 106 107 this.argumentRequired = argumentRequired; 108 } 109 110 /** 111 * <p>Specifies a type to which arguments of this spec's option are to be converted.</p> 112 * 113 * <p>JOpt Simple accepts types that have either:</p> 114 * 115 * <ol> 116 * <li>a public static method called {@code valueOf} which accepts a single argument of type {@link String} 117 * and whose return type is the same as the class on which the method is declared. The {@code java.lang} 118 * primitive wrapper classes have such methods.</li> 119 * 120 * <li>a public constructor which accepts a single argument of type {@link String}.</li> 121 * </ol> 122 * 123 * <p>This class converts arguments using those methods in that order; that is, {@code valueOf} would be invoked 124 * before a one-{@link String}-arg constructor would.</p> 125 * 126 * <p>Invoking this method will trump any previous calls to this method or to 127 * {@link #withValuesConvertedBy(ValueConverter)}.</p> 128 * 129 * @param <T> represents the runtime class of the desired option argument type 130 * @param argumentType desired type of arguments to this spec's option 131 * @return self, so that the caller can add clauses to the fluent interface sentence 132 * @throws NullPointerException if the type is {@code null} 133 * @throws IllegalArgumentException if the type does not have the standard conversion methods 134 */ 135 public final <T> ArgumentAcceptingOptionSpec<T> ofType( Class<T> argumentType ) { 136 return withValuesConvertedBy( findConverter( argumentType ) ); 137 } 138 139 /** 140 * <p>Specifies a converter to use to translate arguments of this spec's option into Java objects. This is useful 141 * when converting to types that do not have the requisite factory method or constructor for 142 * {@link #ofType(Class)}.</p> 143 * 144 * <p>Invoking this method will trump any previous calls to this method or to {@link #ofType(Class)}. 145 * 146 * @param <T> represents the runtime class of the desired option argument type 147 * @param aConverter the converter to use 148 * @return self, so that the caller can add clauses to the fluent interface sentence 149 * @throws NullPointerException if the converter is {@code null} 150 */ 151 @SuppressWarnings( "unchecked" ) 152 public final <T> ArgumentAcceptingOptionSpec<T> withValuesConvertedBy( ValueConverter<T> aConverter ) { 153 if ( aConverter == null ) 154 throw new NullPointerException( "illegal null converter" ); 155 156 converter = (ValueConverter<V>) aConverter; 157 return (ArgumentAcceptingOptionSpec<T>) this; 158 } 159 160 /** 161 * <p>Specifies a description for the argument of the option that this spec represents. This description is used 162 * when generating help information about the parser.</p> 163 * 164 * @param description describes the nature of the argument of this spec's option 165 * @return self, so that the caller can add clauses to the fluent interface sentence 166 */ 167 public final ArgumentAcceptingOptionSpec<V> describedAs( String description ) { 168 argumentDescription = description; 169 return this; 170 } 171 172 /** 173 * <p>Specifies a value separator for the argument of the option that this spec represents. This allows a single 174 * option argument to represent multiple values for the option. For example:</p> 175 * 176 * <pre> 177 * <code> 178 * parser.accepts( "z" ).withRequiredArg() 179 * .<strong>withValuesSeparatedBy( ',' )</strong>; 180 * OptionSet options = parser.parse( new String[] { "-z", "foo,bar,baz", "-z", 181 * "fizz", "-z", "buzz" } ); 182 * </code> 183 * </pre> 184 * 185 * <p>Then {@code options.valuesOf( "z" )} would yield the list {@code [foo, bar, baz, fizz, buzz]}.</p> 186 * 187 * <p>You cannot use Unicode U+0000 as the separator.</p> 188 * 189 * @param separator a character separator 190 * @return self, so that the caller can add clauses to the fluent interface sentence 191 * @throws IllegalArgumentException if the separator is Unicode U+0000 192 */ 193 public final ArgumentAcceptingOptionSpec<V> withValuesSeparatedBy( char separator ) { 194 if ( separator == NIL_VALUE_SEPARATOR ) 195 throw new IllegalArgumentException( "cannot use U+0000 as separator" ); 196 197 valueSeparator = String.valueOf( separator ); 198 return this; 199 } 200 201 /** 202 * <p>Specifies a value separator for the argument of the option that this spec represents. This allows a single 203 * option argument to represent multiple values for the option. For example:</p> 204 * 205 * <pre> 206 * <code> 207 * parser.accepts( "z" ).withRequiredArg() 208 * .<strong>withValuesSeparatedBy( ":::" )</strong>; 209 * OptionSet options = parser.parse( new String[] { "-z", "foo:::bar:::baz", "-z", 210 * "fizz", "-z", "buzz" } ); 211 * </code> 212 * </pre> 213 * 214 * <p>Then {@code options.valuesOf( "z" )} would yield the list {@code [foo, bar, baz, fizz, buzz]}.</p> 215 * 216 * <p>You cannot use Unicode U+0000 in the separator.</p> 217 * 218 * @param separator a string separator 219 * @return self, so that the caller can add clauses to the fluent interface sentence 220 * @throws IllegalArgumentException if the separator contains Unicode U+0000 221 */ 222 public final ArgumentAcceptingOptionSpec<V> withValuesSeparatedBy( String separator ) { 223 if ( separator.indexOf( NIL_VALUE_SEPARATOR ) != -1 ) 224 throw new IllegalArgumentException( "cannot use U+0000 in separator" ); 225 226 valueSeparator = separator; 227 return this; 228 } 229 230 /** 231 * Specifies a set of default values for the argument of the option that this spec represents. 232 * 233 * @param value the first in the set of default argument values for this spec's option 234 * @param values the (optional) remainder of the set of default argument values for this spec's option 235 * @return self, so that the caller can add clauses to the fluent interface sentence 236 * @throws NullPointerException if {@code value}, {@code values}, or any elements of {@code values} are 237 * {@code null} 238 */ 239 @SuppressWarnings("unchecked") 240 public ArgumentAcceptingOptionSpec<V> defaultsTo( V value, V... values ) { 241 addDefaultValue( value ); 242 defaultsTo( values ); 243 244 return this; 245 } 246 247 /** 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 option 251 * @return self, so that the caller can add clauses to the fluent interface sentence 252 * @throws NullPointerException if {@code values} or any elements of {@code values} are {@code null} 253 */ 254 public ArgumentAcceptingOptionSpec<V> defaultsTo( V[] values ) { 255 for ( V each : values ) 256 addDefaultValue( each ); 257 258 return this; 259 } 260 261 /** 262 * Marks this option as required. An {@link OptionException} will be thrown when 263 * {@link OptionParser#parse(java.lang.String...)} is called, if an option is marked as required and not specified 264 * on the command line. 265 * 266 * @return self, so that the caller can add clauses to the fluent interface sentence 267 */ 268 public ArgumentAcceptingOptionSpec<V> required() { 269 optionRequired = true; 270 return this; 271 } 272 273 public boolean isRequired() { 274 return optionRequired; 275 } 276 277 private void addDefaultValue( V value ) { 278 ensureNotNull( value ); 279 defaultValues.add( value ); 280 } 281 282 @Override 283 final void handleOption( OptionParser parser, ArgumentList arguments, OptionSet detectedOptions, 284 String detectedArgument ) { 285 286 if ( isNullOrEmpty( detectedArgument ) ) 287 detectOptionArgument( parser, arguments, detectedOptions ); 288 else 289 addArguments( detectedOptions, detectedArgument ); 290 } 291 292 protected void addArguments( OptionSet detectedOptions, String detectedArgument ) { 293 StringTokenizer lexer = new StringTokenizer( detectedArgument, valueSeparator ); 294 if ( !lexer.hasMoreTokens() ) 295 detectedOptions.addWithArgument( this, detectedArgument ); 296 else { 297 while ( lexer.hasMoreTokens() ) 298 detectedOptions.addWithArgument( this, lexer.nextToken() ); 299 } 300 } 301 302 protected abstract void detectOptionArgument( OptionParser parser, ArgumentList arguments, 303 OptionSet detectedOptions ); 304 305 @Override 306 protected final V convert( String argument ) { 307 return convertWith( converter, argument ); 308 } 309 310 protected boolean canConvertArgument( String argument ) { 311 StringTokenizer lexer = new StringTokenizer( argument, valueSeparator ); 312 313 try { 314 while ( lexer.hasMoreTokens() ) 315 convert( lexer.nextToken() ); 316 return true; 317 } 318 catch ( OptionException ignored ) { 319 return false; 320 } 321 } 322 323 protected boolean isArgumentOfNumberType() { 324 return converter != null && Number.class.isAssignableFrom( converter.valueType() ); 325 } 326 327 public boolean acceptsArguments() { 328 return true; 329 } 330 331 public boolean requiresArgument() { 332 return argumentRequired; 333 } 334 335 public String argumentDescription() { 336 return argumentDescription; 337 } 338 339 public String argumentTypeIndicator() { 340 return argumentTypeIndicatorFrom( converter ); 341 } 342 343 public List<V> defaultValues() { 344 return unmodifiableList( defaultValues ); 345 } 346 347 @Override 348 public boolean equals( Object that ) { 349 if ( !super.equals( that ) ) 350 return false; 351 352 ArgumentAcceptingOptionSpec<?> other = (ArgumentAcceptingOptionSpec<?>) that; 353 return requiresArgument() == other.requiresArgument(); 354 } 355 356 @Override 357 public int hashCode() { 358 return super.hashCode() ^ ( argumentRequired ? 0 : 1 ); 359 } 360} 361