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-2015 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 56 package jdk.internal.joptsimple; 57 58 import java.io.IOException; 59 import java.io.OutputStream; 60 import java.io.OutputStreamWriter; 61 import java.io.Writer; 62 import java.util.*; 63 64 import jdk.internal.joptsimple.internal.AbbreviationMap; 65 import jdk.internal.joptsimple.internal.SimpleOptionNameMap; 66 import jdk.internal.joptsimple.internal.OptionNameMap; 67 import jdk.internal.joptsimple.util.KeyValuePair; 68 69 import static java.util.Collections.*; 70 import static jdk.internal.joptsimple.OptionException.*; 71 import static jdk.internal.joptsimple.OptionParserState.*; 72 import static jdk.internal.joptsimple.ParserRules.*; 73 74 /** 75 * <p>Parses command line arguments, using a syntax that attempts to take from the best of POSIX {@code getopt()} 76 * and GNU {@code getopt_long()}.</p> 77 * 78 * <p>This parser supports short options and long options.</p> 79 * 80 * <ul> 81 * <li><dfn>Short options</dfn> begin with a single hyphen ("{@code -}") followed by a single letter or digit, 82 * or question mark ("{@code ?}"), or dot ("{@code .}"), or underscore ("{@code _}").</li> 83 * 84 * <li>Short options can accept single arguments. The argument can be made required or optional. The option's 85 * argument can occur: 86 * <ul> 87 * <li>in the slot after the option, as in {@code -d /tmp}</li> 88 * <li>right up against the option, as in {@code -d/tmp}</li> 89 * <li>right up against the option separated by an equals sign ({@code "="}), as in {@code -d=/tmp}</li> 90 * </ul> 91 * To specify <em>n</em> arguments for an option, specify the option <em>n</em> times, once for each argument, 92 * as in {@code -d /tmp -d /var -d /opt}; or, when using the 93 * {@linkplain ArgumentAcceptingOptionSpec#withValuesSeparatedBy(char) "separated values"} clause of the "fluent 94 * interface" (see below), give multiple values separated by a given character as a single argument to the 95 * option.</li> 96 * 97 * <li>Short options can be clustered, so that {@code -abc} is treated as {@code -a -b -c}. If a short option 98 * in the cluster can accept an argument, the remaining characters are interpreted as the argument for that 99 * option.</li> 100 * 101 * <li>An argument consisting only of two hyphens ({@code "--"}) signals that the remaining arguments are to be 102 * treated as non-options.</li> 103 * 104 * <li>An argument consisting only of a single hyphen is considered a non-option argument (though it can be an 105 * argument of an option). Many Unix programs treat single hyphens as stand-ins for the standard input or standard 106 * output streams.</li> 107 * 108 * <li><dfn>Long options</dfn> begin with two hyphens ({@code "--"}), followed by multiple letters, digits, 109 * hyphens, question marks, or dots. A hyphen cannot be the first character of a long option specification when 110 * configuring the parser.</li> 111 * 112 * <li>You can abbreviate long options, so long as the abbreviation is unique. Suppress this behavior if 113 * you wish using {@linkplain OptionParser#OptionParser(boolean) this constructor}.</li> 114 * 115 * <li>Long options can accept single arguments. The argument can be made required or optional. The option's 116 * argument can occur: 117 * <ul> 118 * <li>in the slot after the option, as in {@code --directory /tmp}</li> 119 * <li>right up against the option separated by an equals sign ({@code "="}), as in 120 * {@code --directory=/tmp} 121 * </ul> 122 * Specify multiple arguments for a long option in the same manner as for short options (see above).</li> 123 * 124 * <li>You can use a single hyphen ({@code "-"}) instead of a double hyphen ({@code "--"}) for a long 125 * option.</li> 126 * 127 * <li>The option {@code -W} is reserved. If you tell the parser to {@linkplain 128 * #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then it will treat, for example, 129 * {@code -W foo=bar} as the long option {@code foo} with argument {@code bar}, as though you had written 130 * {@code --foo=bar}.</li> 131 * 132 * <li>You can specify {@code -W} as a valid short option, or use it as an abbreviation for a long option, but 133 * {@linkplain #recognizeAlternativeLongOptions(boolean) recognizing alternative long options} will always supersede 134 * this behavior.</li> 135 * 136 * <li>You can specify a given short or long option multiple times on a single command line. The parser collects 137 * any arguments specified for those options as a list.</li> 138 * 139 * <li>If the parser detects an option whose argument is optional, and the next argument "looks like" an option, 140 * that argument is not treated as the argument to the option, but as a potentially valid option. If, on the other 141 * hand, the optional argument is typed as a derivative of {@link Number}, then that argument is treated as the 142 * negative number argument of the option, even if the parser recognizes the corresponding numeric option. 143 * For example: 144 * <pre><code> 145 * OptionParser parser = new OptionParser(); 146 * parser.accepts( "a" ).withOptionalArg().ofType( Integer.class ); 147 * parser.accepts( "2" ); 148 * OptionSet options = parser.parse( "-a", "-2" ); 149 * </code></pre> 150 * In this case, the option set contains {@code "a"} with argument {@code -2}, not both {@code "a"} and 151 * {@code "2"}. Swapping the elements in the <em>args</em> array gives the latter.</li> 152 * </ul> 153 * 154 * <p>There are two ways to tell the parser what options to recognize:</p> 155 * 156 * <ol> 157 * <li>A "fluent interface"-style API for specifying options, available since version 2. Sentences in this fluent 158 * interface language begin with a call to {@link #accepts(String) accepts} or {@link #acceptsAll(List) 159 * acceptsAll} methods; calls on the ensuing chain of objects describe whether the options can take an argument, 160 * whether the argument is required or optional, to what type arguments of the options should be converted if any, 161 * etc. Since version 3, these calls return an instance of {@link OptionSpec}, which can subsequently be used to 162 * retrieve the arguments of the associated option in a type-safe manner.</li> 163 * 164 * <li>Since version 1, a more concise way of specifying short options has been to use the special {@linkplain 165 * #OptionParser(String) constructor}. Arguments of options specified in this manner will be of type {@link String}. 166 * Here are the rules for the format of the specification strings this constructor accepts: 167 * 168 * <ul> 169 * <li>Any letter or digit is treated as an option character.</li> 170 * 171 * <li>An option character can be immediately followed by an asterisk ({@code *)} to indicate that 172 * the option is a "help" option.</li> 173 * 174 * <li>If an option character (with possible trailing asterisk) is followed by a single colon ({@code ":"}), 175 * then the option requires an argument.</li> 176 * 177 * <li>If an option character (with possible trailing asterisk) is followed by two colons ({@code "::"}), 178 * then the option accepts an optional argument.</li> 179 * 180 * <li>Otherwise, the option character accepts no argument.</li> 181 * 182 * <li>If the option specification string begins with a plus sign ({@code "+" }), the parser will behave 183 * "POSIX-ly correct".</li> 184 * 185 * <li>If the option specification string contains the sequence {@code "W;"} (capital W followed by a 186 * semicolon), the parser will recognize the alternative form of long options.</li> 187 * </ul> 188 * </li> 189 * </ol> 190 * 191 * <p>Each of the options in a list of options given to {@link #acceptsAll(List) acceptsAll} is treated as a 192 * synonym of the others. For example:</p> 193 * <pre> 194 * <code> 195 * OptionParser parser = new OptionParser(); 196 * parser.acceptsAll( asList( "w", "interactive", "confirmation" ) ); 197 * OptionSet options = parser.parse( "-w" ); 198 * </code> 199 * </pre> 200 * <p>In this case, <code>options.{@link OptionSet#has(String) has}</code> would answer {@code true} when given arguments 201 * {@code "w"}, {@code "interactive"}, and {@code "confirmation"}. The {@link OptionSet} would give the same 202 * responses to these arguments for its other methods as well.</p> 203 * 204 * <p>By default, as with GNU {@code getopt()}, the parser allows intermixing of options and non-options. If, however, 205 * the parser has been created to be "POSIX-ly correct", then the first argument that does not look lexically like an 206 * option, and is not a required argument of a preceding option, signals the end of options. You can still bind 207 * optional arguments to their options using the abutting (for short options) or {@code =} syntax.</p> 208 * 209 * <p>Unlike GNU {@code getopt()}, this parser does not honor the environment variable {@code POSIXLY_CORRECT}. 210 * "POSIX-ly correct" parsers are configured by either:</p> 211 * 212 * <ol> 213 * <li>using the method {@link #posixlyCorrect(boolean)}, or</li> 214 * 215 * <li>using the {@linkplain #OptionParser(String) constructor} with an argument whose first character is a plus sign 216 * ({@code "+"})</li> 217 * </ol> 218 * 219 * @author <a href="mailto:pholser@alumni.rice.edu">Paul Holser</a> 220 * @see <a href="http://www.gnu.org/software/libc/manual">The GNU C Library</a> 221 */ 222 public class OptionParser implements OptionDeclarer { 223 private final OptionNameMap<AbstractOptionSpec<?>> recognizedOptions; 224 private final ArrayList<AbstractOptionSpec<?>> trainingOrder; 225 private final Map<List<String>, Set<OptionSpec<?>>> requiredIf; 226 private final Map<List<String>, Set<OptionSpec<?>>> requiredUnless; 227 private final Map<List<String>, Set<OptionSpec<?>>> availableIf; 228 private final Map<List<String>, Set<OptionSpec<?>>> availableUnless; 229 230 private OptionParserState state; 231 private boolean posixlyCorrect; 232 private boolean allowsUnrecognizedOptions; 233 private HelpFormatter helpFormatter = new BuiltinHelpFormatter(); 234 235 /** 236 * Creates an option parser that initially recognizes no options, and does not exhibit "POSIX-ly correct" 237 * behavior. 238 */ OptionParser()239 public OptionParser() { 240 this(true); 241 } 242 243 /** 244 * Creates an option parser that initially recognizes no options, and does not exhibit "POSIX-ly correct" 245 * behavior. 246 * 247 * @param allowAbbreviations whether unambiguous abbreviations of long options should be recognized 248 * by the parser 249 */ OptionParser( boolean allowAbbreviations )250 public OptionParser( boolean allowAbbreviations ) { 251 trainingOrder = new ArrayList<>(); 252 requiredIf = new HashMap<>(); 253 requiredUnless = new HashMap<>(); 254 availableIf = new HashMap<>(); 255 availableUnless = new HashMap<>(); 256 state = moreOptions( false ); 257 258 recognizedOptions = allowAbbreviations 259 ? new AbbreviationMap<AbstractOptionSpec<?>>() 260 : new SimpleOptionNameMap<AbstractOptionSpec<?>>(); 261 262 recognize( new NonOptionArgumentSpec<String>() ); 263 } 264 265 /** 266 * Creates an option parser and configures it to recognize the short options specified in the given string. 267 * 268 * Arguments of options specified this way will be of type {@link String}. 269 * 270 * @param optionSpecification an option specification 271 * @throws NullPointerException if {@code optionSpecification} is {@code null} 272 * @throws OptionException if the option specification contains illegal characters or otherwise cannot be 273 * recognized 274 */ OptionParser( String optionSpecification )275 public OptionParser( String optionSpecification ) { 276 this(); 277 278 new OptionSpecTokenizer( optionSpecification ).configure( this ); 279 } 280 accepts( String option )281 public OptionSpecBuilder accepts( String option ) { 282 return acceptsAll( singletonList( option ) ); 283 } 284 accepts( String option, String description )285 public OptionSpecBuilder accepts( String option, String description ) { 286 return acceptsAll( singletonList( option ), description ); 287 } 288 acceptsAll( List<String> options )289 public OptionSpecBuilder acceptsAll( List<String> options ) { 290 return acceptsAll( options, "" ); 291 } 292 acceptsAll( List<String> options, String description )293 public OptionSpecBuilder acceptsAll( List<String> options, String description ) { 294 if ( options.isEmpty() ) 295 throw new IllegalArgumentException( "need at least one option" ); 296 297 ensureLegalOptions( options ); 298 299 return new OptionSpecBuilder( this, options, description ); 300 } 301 nonOptions()302 public NonOptionArgumentSpec<String> nonOptions() { 303 NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec<>(); 304 305 recognize( spec ); 306 307 return spec; 308 } 309 nonOptions( String description )310 public NonOptionArgumentSpec<String> nonOptions( String description ) { 311 NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec<>( description ); 312 313 recognize( spec ); 314 315 return spec; 316 } 317 posixlyCorrect( boolean setting )318 public void posixlyCorrect( boolean setting ) { 319 posixlyCorrect = setting; 320 state = moreOptions( setting ); 321 } 322 posixlyCorrect()323 boolean posixlyCorrect() { 324 return posixlyCorrect; 325 } 326 allowsUnrecognizedOptions()327 public void allowsUnrecognizedOptions() { 328 allowsUnrecognizedOptions = true; 329 } 330 doesAllowsUnrecognizedOptions()331 boolean doesAllowsUnrecognizedOptions() { 332 return allowsUnrecognizedOptions; 333 } 334 recognizeAlternativeLongOptions( boolean recognize )335 public void recognizeAlternativeLongOptions( boolean recognize ) { 336 if ( recognize ) 337 recognize( new AlternativeLongOptionSpec() ); 338 else 339 recognizedOptions.remove( String.valueOf( RESERVED_FOR_EXTENSIONS ) ); 340 } 341 recognize( AbstractOptionSpec<?> spec )342 void recognize( AbstractOptionSpec<?> spec ) { 343 recognizedOptions.putAll( spec.options(), spec ); 344 trainingOrder.add( spec ); 345 } 346 347 /** 348 * Writes information about the options this parser recognizes to the given output sink. 349 * 350 * The output sink is flushed, but not closed. 351 * 352 * @param sink the sink to write information to 353 * @throws IOException if there is a problem writing to the sink 354 * @throws NullPointerException if {@code sink} is {@code null} 355 * @see #printHelpOn(Writer) 356 */ printHelpOn( OutputStream sink )357 public void printHelpOn( OutputStream sink ) throws IOException { 358 printHelpOn( new OutputStreamWriter( sink ) ); 359 } 360 361 /** 362 * Writes information about the options this parser recognizes to the given output sink. 363 * 364 * The output sink is flushed, but not closed. 365 * 366 * @param sink the sink to write information to 367 * @throws IOException if there is a problem writing to the sink 368 * @throws NullPointerException if {@code sink} is {@code null} 369 * @see #printHelpOn(OutputStream) 370 */ printHelpOn( Writer sink )371 public void printHelpOn( Writer sink ) throws IOException { 372 sink.write( helpFormatter.format( _recognizedOptions() ) ); 373 sink.flush(); 374 } 375 376 /** 377 * Tells the parser to use the given formatter when asked to {@linkplain #printHelpOn(java.io.Writer) print help}. 378 * 379 * @param formatter the formatter to use for printing help 380 * @throws NullPointerException if the formatter is {@code null} 381 */ formatHelpWith( HelpFormatter formatter )382 public void formatHelpWith( HelpFormatter formatter ) { 383 if ( formatter == null ) 384 throw new NullPointerException(); 385 386 helpFormatter = formatter; 387 } 388 389 /** 390 * Retrieves all options-spec pairings which have been configured for the parser in the same order as declared 391 * during training. Option flags for specs are alphabetized by {@link OptionSpec#options()}; only the order of the 392 * specs is preserved. 393 * 394 * (Note: prior to 4.7 the order was alphabetical across all options regardless of spec.) 395 * 396 * @return a map containing all the configured options and their corresponding {@link OptionSpec} 397 * @since 4.6 398 */ recognizedOptions()399 public Map<String, OptionSpec<?>> recognizedOptions() { 400 return new LinkedHashMap<String, OptionSpec<?>>( _recognizedOptions() ); 401 } 402 _recognizedOptions()403 private Map<String, AbstractOptionSpec<?>> _recognizedOptions() { 404 Map<String, AbstractOptionSpec<?>> options = new LinkedHashMap<>(); 405 for ( AbstractOptionSpec<?> spec : trainingOrder ) { 406 for ( String option : spec.options() ) 407 options.put( option, spec ); 408 } 409 return options; 410 } 411 412 /** 413 * Parses the given command line arguments according to the option specifications given to the parser. 414 * 415 * @param arguments arguments to parse 416 * @return an {@link OptionSet} describing the parsed options, their arguments, and any non-option arguments found 417 * @throws OptionException if problems are detected while parsing 418 * @throws NullPointerException if the argument list is {@code null} 419 */ parse( String... arguments )420 public OptionSet parse( String... arguments ) { 421 ArgumentList argumentList = new ArgumentList( arguments ); 422 OptionSet detected = new OptionSet( recognizedOptions.toJavaUtilMap() ); 423 detected.add( recognizedOptions.get( NonOptionArgumentSpec.NAME ) ); 424 425 while ( argumentList.hasMore() ) 426 state.handleArgument( this, argumentList, detected ); 427 428 reset(); 429 430 ensureRequiredOptions( detected ); 431 ensureAllowedOptions( detected ); 432 433 return detected; 434 } 435 436 /** 437 * Mandates mutual exclusiveness for the options built by the specified builders. 438 * 439 * @param specs descriptors for options that should be mutually exclusive on a command line. 440 * @throws NullPointerException if {@code specs} is {@code null} 441 */ mutuallyExclusive( OptionSpecBuilder... specs )442 public void mutuallyExclusive( OptionSpecBuilder... specs ) { 443 for ( int i = 0; i < specs.length; i++ ) { 444 for ( int j = 0; j < specs.length; j++ ) { 445 if ( i != j ) 446 specs[i].availableUnless( specs[j] ); 447 } 448 } 449 } 450 ensureRequiredOptions( OptionSet options )451 private void ensureRequiredOptions( OptionSet options ) { 452 List<AbstractOptionSpec<?>> missingRequiredOptions = missingRequiredOptions(options); 453 boolean helpOptionPresent = isHelpOptionPresent( options ); 454 455 if ( !missingRequiredOptions.isEmpty() && !helpOptionPresent ) 456 throw new MissingRequiredOptionsException( missingRequiredOptions ); 457 } 458 ensureAllowedOptions( OptionSet options )459 private void ensureAllowedOptions( OptionSet options ) { 460 List<AbstractOptionSpec<?>> forbiddenOptions = unavailableOptions( options ); 461 boolean helpOptionPresent = isHelpOptionPresent( options ); 462 463 if ( !forbiddenOptions.isEmpty() && !helpOptionPresent ) 464 throw new UnavailableOptionException( forbiddenOptions ); 465 } 466 missingRequiredOptions( OptionSet options )467 private List<AbstractOptionSpec<?>> missingRequiredOptions( OptionSet options ) { 468 List<AbstractOptionSpec<?>> missingRequiredOptions = new ArrayList<>(); 469 470 for ( AbstractOptionSpec<?> each : recognizedOptions.toJavaUtilMap().values() ) { 471 if ( each.isRequired() && !options.has( each ) ) 472 missingRequiredOptions.add(each); 473 } 474 475 for ( Map.Entry<List<String>, Set<OptionSpec<?>>> each : requiredIf.entrySet() ) { 476 AbstractOptionSpec<?> required = specFor( each.getKey().iterator().next() ); 477 478 if ( optionsHasAnyOf( options, each.getValue() ) && !options.has( required ) ) 479 missingRequiredOptions.add( required ); 480 } 481 482 for ( Map.Entry<List<String>, Set<OptionSpec<?>>> each : requiredUnless.entrySet() ) { 483 AbstractOptionSpec<?> required = specFor(each.getKey().iterator().next()); 484 485 if ( !optionsHasAnyOf( options, each.getValue() ) && !options.has( required ) ) 486 missingRequiredOptions.add( required ); 487 } 488 489 return missingRequiredOptions; 490 } 491 unavailableOptions(OptionSet options)492 private List<AbstractOptionSpec<?>> unavailableOptions(OptionSet options) { 493 List<AbstractOptionSpec<?>> unavailableOptions = new ArrayList<>(); 494 495 for ( Map.Entry<List<String>, Set<OptionSpec<?>>> eachEntry : availableIf.entrySet() ) { 496 AbstractOptionSpec<?> forbidden = specFor( eachEntry.getKey().iterator().next() ); 497 498 if ( !optionsHasAnyOf( options, eachEntry.getValue() ) && options.has( forbidden ) ) { 499 unavailableOptions.add(forbidden); 500 } 501 } 502 503 for ( Map.Entry<List<String>, Set<OptionSpec<?>>> eachEntry : availableUnless.entrySet() ) { 504 AbstractOptionSpec<?> forbidden = specFor( eachEntry.getKey().iterator().next() ); 505 506 if ( optionsHasAnyOf( options, eachEntry.getValue() ) && options.has( forbidden ) ) { 507 unavailableOptions.add(forbidden); 508 } 509 } 510 511 return unavailableOptions; 512 } 513 optionsHasAnyOf( OptionSet options, Collection<OptionSpec<?>> specs )514 private boolean optionsHasAnyOf( OptionSet options, Collection<OptionSpec<?>> specs ) { 515 for ( OptionSpec<?> each : specs ) { 516 if ( options.has( each ) ) 517 return true; 518 } 519 520 return false; 521 } 522 isHelpOptionPresent( OptionSet options )523 private boolean isHelpOptionPresent( OptionSet options ) { 524 boolean helpOptionPresent = false; 525 526 for ( AbstractOptionSpec<?> each : recognizedOptions.toJavaUtilMap().values() ) { 527 if ( each.isForHelp() && options.has( each ) ) { 528 helpOptionPresent = true; 529 break; 530 } 531 } 532 533 return helpOptionPresent; 534 } 535 handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected )536 void handleLongOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { 537 KeyValuePair optionAndArgument = parseLongOptionWithArgument( candidate ); 538 539 if ( !isRecognized( optionAndArgument.key ) ) 540 throw unrecognizedOption( optionAndArgument.key ); 541 542 AbstractOptionSpec<?> optionSpec = specFor( optionAndArgument.key ); 543 optionSpec.handleOption( this, arguments, detected, optionAndArgument.value ); 544 } 545 handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected )546 void handleShortOptionToken( String candidate, ArgumentList arguments, OptionSet detected ) { 547 KeyValuePair optionAndArgument = parseShortOptionWithArgument( candidate ); 548 549 if ( isRecognized( optionAndArgument.key ) ) { 550 specFor( optionAndArgument.key ).handleOption( this, arguments, detected, optionAndArgument.value ); 551 } 552 else 553 handleShortOptionCluster( candidate, arguments, detected ); 554 } 555 handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected )556 private void handleShortOptionCluster( String candidate, ArgumentList arguments, OptionSet detected ) { 557 char[] options = extractShortOptionsFrom( candidate ); 558 validateOptionCharacters( options ); 559 560 for ( int i = 0; i < options.length; i++ ) { 561 AbstractOptionSpec<?> optionSpec = specFor( options[ i ] ); 562 563 if ( optionSpec.acceptsArguments() && options.length > i + 1 ) { 564 String detectedArgument = String.valueOf( options, i + 1, options.length - 1 - i ); 565 optionSpec.handleOption( this, arguments, detected, detectedArgument ); 566 break; 567 } 568 569 optionSpec.handleOption( this, arguments, detected, null ); 570 } 571 } 572 handleNonOptionArgument( String candidate, ArgumentList arguments, OptionSet detectedOptions )573 void handleNonOptionArgument( String candidate, ArgumentList arguments, OptionSet detectedOptions ) { 574 specFor( NonOptionArgumentSpec.NAME ).handleOption( this, arguments, detectedOptions, candidate ); 575 } 576 noMoreOptions()577 void noMoreOptions() { 578 state = OptionParserState.noMoreOptions(); 579 } 580 looksLikeAnOption( String argument )581 boolean looksLikeAnOption( String argument ) { 582 return isShortOptionToken( argument ) || isLongOptionToken( argument ); 583 } 584 isRecognized( String option )585 boolean isRecognized( String option ) { 586 return recognizedOptions.contains( option ); 587 } 588 requiredIf( List<String> precedentSynonyms, String required )589 void requiredIf( List<String> precedentSynonyms, String required ) { 590 requiredIf( precedentSynonyms, specFor( required ) ); 591 } 592 requiredIf( List<String> precedentSynonyms, OptionSpec<?> required )593 void requiredIf( List<String> precedentSynonyms, OptionSpec<?> required ) { 594 putDependentOption( precedentSynonyms, required, requiredIf ); 595 } 596 requiredUnless( List<String> precedentSynonyms, String required )597 void requiredUnless( List<String> precedentSynonyms, String required ) { 598 requiredUnless( precedentSynonyms, specFor( required ) ); 599 } 600 requiredUnless( List<String> precedentSynonyms, OptionSpec<?> required )601 void requiredUnless( List<String> precedentSynonyms, OptionSpec<?> required ) { 602 putDependentOption( precedentSynonyms, required, requiredUnless ); 603 } 604 availableIf( List<String> precedentSynonyms, String available )605 void availableIf( List<String> precedentSynonyms, String available ) { 606 availableIf( precedentSynonyms, specFor( available ) ); 607 } 608 availableIf( List<String> precedentSynonyms, OptionSpec<?> available)609 void availableIf( List<String> precedentSynonyms, OptionSpec<?> available) { 610 putDependentOption( precedentSynonyms, available, availableIf ); 611 } 612 availableUnless( List<String> precedentSynonyms, String available )613 void availableUnless( List<String> precedentSynonyms, String available ) { 614 availableUnless( precedentSynonyms, specFor( available ) ); 615 } 616 availableUnless( List<String> precedentSynonyms, OptionSpec<?> available )617 void availableUnless( List<String> precedentSynonyms, OptionSpec<?> available ) { 618 putDependentOption( precedentSynonyms, available, availableUnless ); 619 } 620 putDependentOption( List<String> precedentSynonyms, OptionSpec<?> required, Map<List<String>, Set<OptionSpec<?>>> target )621 private void putDependentOption( List<String> precedentSynonyms, OptionSpec<?> required, 622 Map<List<String>, Set<OptionSpec<?>>> target ) { 623 624 for ( String each : precedentSynonyms ) { 625 AbstractOptionSpec<?> spec = specFor( each ); 626 if ( spec == null ) 627 throw new UnconfiguredOptionException( precedentSynonyms ); 628 } 629 630 Set<OptionSpec<?>> associated = target.get( precedentSynonyms ); 631 if ( associated == null ) { 632 associated = new HashSet<>(); 633 target.put( precedentSynonyms, associated ); 634 } 635 636 associated.add( required ); 637 } 638 specFor( char option )639 private AbstractOptionSpec<?> specFor( char option ) { 640 return specFor( String.valueOf( option ) ); 641 } 642 specFor( String option )643 private AbstractOptionSpec<?> specFor( String option ) { 644 return recognizedOptions.get( option ); 645 } 646 reset()647 private void reset() { 648 state = moreOptions( posixlyCorrect ); 649 } 650 extractShortOptionsFrom( String argument )651 private static char[] extractShortOptionsFrom( String argument ) { 652 char[] options = new char[ argument.length() - 1 ]; 653 argument.getChars( 1, argument.length(), options, 0 ); 654 655 return options; 656 } 657 validateOptionCharacters( char[] options )658 private void validateOptionCharacters( char[] options ) { 659 for ( char each : options ) { 660 String option = String.valueOf( each ); 661 662 if ( !isRecognized( option ) ) 663 throw unrecognizedOption( option ); 664 665 if ( specFor( option ).acceptsArguments() ) 666 return; 667 } 668 } 669 parseLongOptionWithArgument( String argument )670 private static KeyValuePair parseLongOptionWithArgument( String argument ) { 671 return KeyValuePair.valueOf( argument.substring( 2 ) ); 672 } 673 parseShortOptionWithArgument( String argument )674 private static KeyValuePair parseShortOptionWithArgument( String argument ) { 675 return KeyValuePair.valueOf( argument.substring( 1 ) ); 676 } 677 } 678