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