aboutsummaryrefslogtreecommitdiffhomepage
path: root/third_party/java/jopt-simple/src/main/java/joptsimple/OptionParser.java
blob: d141f623c0a99e6384c4f27e7a00b9d0d0791ce1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
/*
 The MIT License

 Copyright (c) 2004-2015 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 joptsimple;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.*;

import joptsimple.internal.AbbreviationMap;
import joptsimple.internal.SimpleOptionNameMap;
import joptsimple.internal.OptionNameMap;
import joptsimple.util.KeyValuePair;

import static java.util.Collections.*;
import static joptsimple.OptionException.*;
import static joptsimple.OptionParserState.*;
import static 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 ("{@code -}") followed by a single letter or digit,
 *   or question mark ("{@code ?}"), or dot ("{@code .}"), or underscore ("{@code _}").</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 {@code -d /tmp}</li>
 *       <li>right up against the option, as in {@code -d/tmp}</li>
 *       <li>right up against the option separated by an equals sign ({@code "="}), as in {@code -d=/tmp}</li>
 *     </ul>
 *   To specify <em>n</em> arguments for an option, specify the option <em>n</em> times, once for each argument,
 *   as in {@code -d /tmp -d /var -d /opt}; 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 {@code -abc} is treated as {@code -a -b -c}. 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 ({@code "--"}) 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 ({@code "--"}), 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. Suppress this behavior if
 *   you wish using {@linkplain OptionParser#OptionParser(boolean) this constructor}.</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 {@code --directory /tmp}</li>
 *       <li>right up against the option separated by an equals sign ({@code "="}), as in
 *       {@code --directory=/tmp}
 *     </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 ({@code "-"}) instead of a double hyphen ({@code "--"}) for a long
 *   option.</li>
 *
 *   <li>The option {@code -W} is reserved.  If you tell the parser to {@linkplain
 *   #recognizeAlternativeLongOptions(boolean) recognize alternative long options}, then it will treat, for example,
 *   {@code -W foo=bar} as the long option {@code foo} with argument {@code bar}, as though you had written
 *   {@code --foo=bar}.</li>
 *
 *   <li>You can specify {@code -W} 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 {@code "a"} with argument {@code -2}, not both {@code "a"} and
 *   {@code "2"}. 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(List)
 *   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 ({@code *)} 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 ({@code ":"}),
 *       then the option requires an argument.</li>
 *
 *       <li>If an option character (with possible trailing asterisk) is followed by two colons ({@code "::"}),
 *       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 ({@code "+" }), the parser will behave
 *       "POSIX-ly correct".</li>
 *
 *       <li>If the option specification string contains the sequence {@code "W;"} (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(List) acceptsAll} is treated as a
 * synonym of the others.  For example:</p>
 *   <pre>
 *     <code>
 *     OptionParser parser = new OptionParser();
 *     parser.acceptsAll( asList( "w", "interactive", "confirmation" ) );
 *     OptionSet options = parser.parse( "-w" );
 *     </code>
 *   </pre>
 * <p>In this case, <code>options.{@link OptionSet#has(String) has}</code> would answer {@code true} when given arguments
 * {@code "w"}, {@code "interactive"}, and {@code "confirmation"}. 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 {@code =} 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
 *   ({@code "+"})</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 OptionNameMap<AbstractOptionSpec<?>> recognizedOptions;
    private final ArrayList<AbstractOptionSpec<?>> trainingOrder;
    private final Map<List<String>, Set<OptionSpec<?>>> requiredIf;
    private final Map<List<String>, Set<OptionSpec<?>>> requiredUnless;
    private final Map<List<String>, Set<OptionSpec<?>>> availableIf;
    private final Map<List<String>, Set<OptionSpec<?>>> availableUnless;

    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() {
        this(true);
    }

    /**
     * Creates an option parser that initially recognizes no options, and does not exhibit "POSIX-ly correct"
     * behavior.
     *
     * @param allowAbbreviations whether unambiguous abbreviations of long options should be recognized
     * by the parser
     */
    public OptionParser( boolean allowAbbreviations ) {
        trainingOrder = new ArrayList<>();
        requiredIf = new HashMap<>();
        requiredUnless = new HashMap<>();
        availableIf = new HashMap<>();
        availableUnless = new HashMap<>();
        state = moreOptions( false );

        recognizedOptions = allowAbbreviations
            ? new AbbreviationMap<AbstractOptionSpec<?>>()
            : new SimpleOptionNameMap<AbstractOptionSpec<?>>();

        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( List<String> options ) {
        return acceptsAll( options, "" );
    }

    public OptionSpecBuilder acceptsAll( List<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<>();

        recognize( spec );

        return spec;
    }

    public NonOptionArgumentSpec<String> nonOptions( String description ) {
        NonOptionArgumentSpec<String> spec = new NonOptionArgumentSpec<>( 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 );
        trainingOrder.add( 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() ) );
        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 options-spec pairings which have been configured for the parser in the same order as declared
     * during training. Option flags for specs are alphabetized by {@link OptionSpec#options()}; only the order of the
     * specs is preserved.
     *
     * (Note: prior to 4.7 the order was alphabetical across all options regardless of spec.)
     *
     * @return a map containing all the configured options and their corresponding {@link OptionSpec}
     * @since 4.6
     */
    public Map<String, OptionSpec<?>> recognizedOptions() {
        return new LinkedHashMap<String, OptionSpec<?>>( _recognizedOptions() );
    }

    private Map<String, AbstractOptionSpec<?>> _recognizedOptions() {
        Map<String, AbstractOptionSpec<?>> options = new LinkedHashMap<>();
        for ( AbstractOptionSpec<?> spec : trainingOrder ) {
            for ( String option : spec.options() )
                options.put( option, spec );
        }
        return options;
    }

   /**
     * 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 );
        ensureAllowedOptions( detected );

        return detected;
    }

    /**
     * Mandates mutual exclusiveness for the options built by the specified builders.
     *
     * @param specs descriptors for options that should be mutually exclusive on a command line.
     * @throws NullPointerException if {@code specs} is {@code null}
     */
    public void mutuallyExclusive( OptionSpecBuilder... specs ) {
        for ( int i = 0; i < specs.length; i++ ) {
            for ( int j = 0; j < specs.length; j++ ) {
                if ( i != j )
                    specs[i].availableUnless( specs[j] );
            }
        }
    }

    private void ensureRequiredOptions( OptionSet options ) {
        List<AbstractOptionSpec<?>> missingRequiredOptions = missingRequiredOptions(options);
        boolean helpOptionPresent = isHelpOptionPresent( options );

        if ( !missingRequiredOptions.isEmpty() && !helpOptionPresent )
            throw new MissingRequiredOptionsException( missingRequiredOptions );
    }

    private void ensureAllowedOptions( OptionSet options ) {
        List<AbstractOptionSpec<?>> forbiddenOptions = unavailableOptions( options );
        boolean helpOptionPresent = isHelpOptionPresent( options );

        if ( !forbiddenOptions.isEmpty() && !helpOptionPresent )
            throw new UnavailableOptionException( forbiddenOptions );
    }

    private List<AbstractOptionSpec<?>> missingRequiredOptions( OptionSet options ) {
        List<AbstractOptionSpec<?>> missingRequiredOptions = new ArrayList<>();

        for ( AbstractOptionSpec<?> each : recognizedOptions.toJavaUtilMap().values() ) {
            if ( each.isRequired() && !options.has( each ) )
                missingRequiredOptions.add(each);
        }

        for ( Map.Entry<List<String>, Set<OptionSpec<?>>> each : requiredIf.entrySet() ) {
            AbstractOptionSpec<?> required = specFor( each.getKey().iterator().next() );

            if ( optionsHasAnyOf( options, each.getValue() ) && !options.has( required ) )
                missingRequiredOptions.add( required );
        }

        for ( Map.Entry<List<String>, Set<OptionSpec<?>>> each : requiredUnless.entrySet() ) {
            AbstractOptionSpec<?> required = specFor(each.getKey().iterator().next());

            if ( !optionsHasAnyOf( options, each.getValue() ) && !options.has( required ) )
                missingRequiredOptions.add( required );
        }

        return missingRequiredOptions;
    }

    private List<AbstractOptionSpec<?>> unavailableOptions(OptionSet options) {
        List<AbstractOptionSpec<?>> unavailableOptions = new ArrayList<>();

        for ( Map.Entry<List<String>, Set<OptionSpec<?>>> eachEntry : availableIf.entrySet() ) {
            AbstractOptionSpec<?> forbidden = specFor( eachEntry.getKey().iterator().next() );

            if ( !optionsHasAnyOf( options, eachEntry.getValue() ) && options.has( forbidden ) ) {
                unavailableOptions.add(forbidden);
            }
        }

        for ( Map.Entry<List<String>, Set<OptionSpec<?>>> eachEntry : availableUnless.entrySet() ) {
            AbstractOptionSpec<?> forbidden = specFor( eachEntry.getKey().iterator().next() );

            if ( optionsHasAnyOf( options, eachEntry.getValue() ) && options.has( forbidden ) ) {
                unavailableOptions.add(forbidden);
            }
        }

        return unavailableOptions;
    }

    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( List<String> precedentSynonyms, String required ) {
        requiredIf( precedentSynonyms, specFor( required ) );
    }

    void requiredIf( List<String> precedentSynonyms, OptionSpec<?> required ) {
        putDependentOption( precedentSynonyms, required, requiredIf );
    }

    void requiredUnless( List<String> precedentSynonyms, String required ) {
        requiredUnless( precedentSynonyms, specFor( required ) );
    }

    void requiredUnless( List<String> precedentSynonyms, OptionSpec<?> required ) {
        putDependentOption( precedentSynonyms, required, requiredUnless );
    }

    void availableIf( List<String> precedentSynonyms, String available ) {
        availableIf( precedentSynonyms, specFor( available ) );
    }

    void availableIf( List<String> precedentSynonyms, OptionSpec<?> available) {
        putDependentOption( precedentSynonyms, available, availableIf );
    }

    void availableUnless( List<String> precedentSynonyms, String available ) {
        availableUnless( precedentSynonyms, specFor( available ) );
    }

    void availableUnless( List<String> precedentSynonyms, OptionSpec<?> available ) {
        putDependentOption( precedentSynonyms, available, availableUnless );
    }

    private void putDependentOption( List<String> precedentSynonyms, OptionSpec<?> required,
        Map<List<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<>();
            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 ) );
    }
}