001/*
002  Licensed to the Apache Software Foundation (ASF) under one or more
003  contributor license agreements.  See the NOTICE file distributed with
004  this work for additional information regarding copyright ownership.
005  The ASF licenses this file to You under the Apache License, Version 2.0
006  (the "License"); you may not use this file except in compliance with
007  the License.  You may obtain a copy of the License at
008
009      http://www.apache.org/licenses/LICENSE-2.0
010
011  Unless required by applicable law or agreed to in writing, software
012  distributed under the License is distributed on an "AS IS" BASIS,
013  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  See the License for the specific language governing permissions and
015  limitations under the License.
016 */
017
018package org.apache.commons.cli;
019
020import java.util.ArrayList;
021import java.util.Enumeration;
022import java.util.List;
023import java.util.Properties;
024
025/**
026 * Default parser.
027 *
028 * @since 1.3
029 */
030public class DefaultParser implements CommandLineParser {
031
032    /**
033     * A nested builder class to create {@code DefaultParser} instances
034     * using descriptive methods.
035     *
036     * Example usage:
037     * <pre>
038     * DefaultParser parser = Option.builder()
039     *     .setAllowPartialMatching(false)
040     *     .setStripLeadingAndTrailingQuotes(false)
041     *     .build();
042     * </pre>
043     *
044     * @since 1.5.0
045     */
046    public static final class Builder {
047
048        /** Flag indicating if partial matching of long options is supported. */
049        private boolean allowPartialMatching = true;
050
051        /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments. */
052        private Boolean stripLeadingAndTrailingQuotes;
053
054        /**
055         * Constructs a new {@code Builder} for a {@code DefaultParser} instance.
056         *
057         * Both allowPartialMatching and stripLeadingAndTrailingQuotes are true by default,
058         * mimicking the argument-less constructor.
059         */
060        private Builder() {
061        }
062
063        /**
064         * Builds an DefaultParser with the values declared by this {@link Builder}.
065         *
066         * @return the new {@link DefaultParser}
067         * @since 1.5.0
068         */
069        public DefaultParser build() {
070            return new DefaultParser(allowPartialMatching, stripLeadingAndTrailingQuotes);
071        }
072
073        /**
074         * Sets if partial matching of long options is supported.
075         *
076         * By "partial matching" we mean that given the following code:
077         *
078         * <pre>
079         * {
080         *     &#64;code
081         *     final Options options = new Options();
082         *     options.addOption(new Option("d", "debug", false, "Turn on debug."));
083         *     options.addOption(new Option("e", "extract", false, "Turn on extract."));
084         *     options.addOption(new Option("o", "option", true, "Turn on option with argument."));
085         * }
086         * </pre>
087         *
088         * If "partial matching" is turned on, {@code -de} only matches the {@code "debug"} option. However, with
089         * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract}
090         *
091         * @param allowPartialMatching whether to allow partial matching of long options
092         * @return this builder, to allow method chaining
093         * @since 1.5.0
094         */
095        public Builder setAllowPartialMatching(final boolean allowPartialMatching) {
096            this.allowPartialMatching = allowPartialMatching;
097            return this;
098        }
099
100        /**
101         * Sets if balanced leading and trailing double quotes should be stripped from option arguments.
102         *
103         * If "stripping of balanced leading and trailing double quotes from option arguments" is true,
104         * the outermost balanced double quotes of option arguments values will be removed.
105         * For example, {@code -o '"x"'} getValue() will return {@code x}, instead of {@code "x"}
106         *
107         * If "stripping of balanced leading and trailing double quotes from option arguments" is null,
108         * then quotes will be stripped from option values separated by space from the option, but
109         * kept in other cases, which is the historic behaviour.
110         *
111         * @param stripLeadingAndTrailingQuotes whether balanced leading and trailing double quotes should be stripped from option arguments.
112         * @return this builder, to allow method chaining
113         * @since 1.5.0
114         */
115        public Builder setStripLeadingAndTrailingQuotes(final Boolean stripLeadingAndTrailingQuotes) {
116            this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
117            return this;
118        }
119    }
120
121    /**
122     * Creates a new {@link Builder} to create an {@link DefaultParser} using descriptive
123     * methods.
124     *
125     * @return a new {@link Builder} instance
126     * @since 1.5.0
127     */
128    public static Builder builder() {
129        return new Builder();
130    }
131
132    /** The command-line instance. */
133    protected CommandLine cmd;
134
135    /** The current options. */
136    protected Options options;
137
138    /**
139     * Flag indicating how unrecognized tokens are handled. {@code true} to stop the parsing and add the remaining
140     * tokens to the args list. {@code false} to throw an exception.
141     */
142    protected boolean stopAtNonOption;
143
144    /** The token currently processed. */
145    protected String currentToken;
146
147    /** The last option parsed. */
148    protected Option currentOption;
149
150    /** Flag indicating if tokens should no longer be analyzed and simply added as arguments of the command line. */
151    protected boolean skipParsing;
152
153    /** The required options and groups expected to be found when parsing the command line. */
154    protected List expectedOpts;
155
156    /** Flag indicating if partial matching of long options is supported. */
157    private final boolean allowPartialMatching;
158
159    /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments.
160     * null represents the historic arbitrary behaviour */
161    private final Boolean stripLeadingAndTrailingQuotes;
162
163    /**
164     * Creates a new DefaultParser instance with partial matching enabled.
165     *
166     * By "partial matching" we mean that given the following code:
167     *
168     * <pre>
169     * {
170     *     &#64;code
171     *     final Options options = new Options();
172     *     options.addOption(new Option("d", "debug", false, "Turn on debug."));
173     *     options.addOption(new Option("e", "extract", false, "Turn on extract."));
174     *     options.addOption(new Option("o", "option", true, "Turn on option with argument."));
175     * }
176     * </pre>
177     *
178     * with "partial matching" turned on, {@code -de} only matches the {@code "debug"} option. However, with
179     * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract}
180     * options.
181     */
182    public DefaultParser() {
183        this.allowPartialMatching = true;
184        this.stripLeadingAndTrailingQuotes = null;
185    }
186
187    /**
188     * Create a new DefaultParser instance with the specified partial matching policy.
189     *
190     * By "partial matching" we mean that given the following code:
191     *
192     * <pre>
193     * {
194     *     &#64;code
195     *     final Options options = new Options();
196     *     options.addOption(new Option("d", "debug", false, "Turn on debug."));
197     *     options.addOption(new Option("e", "extract", false, "Turn on extract."));
198     *     options.addOption(new Option("o", "option", true, "Turn on option with argument."));
199     * }
200     * </pre>
201     *
202     * with "partial matching" turned on, {@code -de} only matches the {@code "debug"} option. However, with
203     * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract}
204     * options.
205     *
206     * @param allowPartialMatching if partial matching of long options shall be enabled
207     */
208    public DefaultParser(final boolean allowPartialMatching) {
209        this.allowPartialMatching = allowPartialMatching;
210        this.stripLeadingAndTrailingQuotes = null;
211    }
212
213    /**
214     * Creates a new DefaultParser instance with the specified partial matching and quote
215     * stripping policy.
216     *
217     * @param allowPartialMatching if partial matching of long options shall be enabled
218     * @param stripLeadingAndTrailingQuotes if balanced outer double quoutes should be stripped
219     */
220    private DefaultParser(final boolean allowPartialMatching,
221            final Boolean stripLeadingAndTrailingQuotes) {
222        this.allowPartialMatching = allowPartialMatching;
223        this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
224    }
225
226    /**
227     * Throws a {@link MissingArgumentException} if the current option didn't receive the number of arguments expected.
228     */
229    private void checkRequiredArgs() throws ParseException {
230        if (currentOption != null && currentOption.requiresArg()) {
231            throw new MissingArgumentException(currentOption);
232        }
233    }
234
235    /**
236     * Throws a {@link MissingOptionException} if all of the required options are not present.
237     *
238     * @throws MissingOptionException if any of the required Options are not present.
239     */
240    protected void checkRequiredOptions() throws MissingOptionException {
241        // if there are required options that have not been processed
242        if (!expectedOpts.isEmpty()) {
243            throw new MissingOptionException(expectedOpts);
244        }
245    }
246
247    /**
248     * Searches for a prefix that is the long name of an option (-Xmx512m)
249     *
250     * @param token
251     */
252    private String getLongPrefix(final String token) {
253        final String t = Util.stripLeadingHyphens(token);
254
255        int i;
256        String opt = null;
257        for (i = t.length() - 2; i > 1; i--) {
258            final String prefix = t.substring(0, i);
259            if (options.hasLongOption(prefix)) {
260                opt = prefix;
261                break;
262            }
263        }
264
265        return opt;
266    }
267
268    /**
269     * Gets a list of matching option strings for the given token, depending on the selected partial matching policy.
270     *
271     * @param token the token (may contain leading dashes)
272     * @return the list of matching option strings or an empty list if no matching option could be found
273     */
274    private List<String> getMatchingLongOptions(final String token) {
275        if (allowPartialMatching) {
276            return options.getMatchingOptions(token);
277        }
278        final List<String> matches = new ArrayList<>(1);
279        if (options.hasLongOption(token)) {
280            final Option option = options.getOption(token);
281            matches.add(option.getLongOpt());
282        }
283
284        return matches;
285    }
286
287    /**
288     * Breaks {@code token} into its constituent parts using the following algorithm.
289     *
290     * <ul>
291     * <li>ignore the first character ("<b>-</b>")</li>
292     * <li>for each remaining character check if an {@link Option} exists with that id.</li>
293     * <li>if an {@link Option} does exist then add that character prepended with "<b>-</b>" to the list of processed
294     * tokens.</li>
295     * <li>if the {@link Option} can have an argument value and there are remaining characters in the token then add the
296     * remaining characters as a token to the list of processed tokens.</li>
297     * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS</b> set then add the
298     * special token "<b>--</b>" followed by the remaining characters and also the remaining tokens directly to the
299     * processed tokens list.</li>
300     * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> {@code stopAtNonOption} <b>IS NOT</b> set then add
301     * that character prepended with "<b>-</b>".</li>
302     * </ul>
303     *
304     * @param token The current token to be <b>burst</b> at the first non-Option encountered.
305     * @throws ParseException if there are any problems encountered while parsing the command line token.
306     */
307    protected void handleConcatenatedOptions(final String token) throws ParseException {
308        for (int i = 1; i < token.length(); i++) {
309            final String ch = String.valueOf(token.charAt(i));
310
311            if (!options.hasOption(ch)) {
312                handleUnknownToken(stopAtNonOption && i > 1 ? token.substring(i) : token);
313                break;
314            }
315            handleOption(options.getOption(ch));
316
317            if (currentOption != null && token.length() != i + 1) {
318                // add the trail as an argument of the option
319                currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(token.substring(i + 1)));
320                break;
321            }
322        }
323    }
324
325    /**
326     * Handles the following tokens:
327     *
328     * --L --L=V --L V --l
329     *
330     * @param token the command line token to handle
331     */
332    private void handleLongOption(final String token) throws ParseException {
333        if (token.indexOf('=') == -1) {
334            handleLongOptionWithoutEqual(token);
335        } else {
336            handleLongOptionWithEqual(token);
337        }
338    }
339
340    /**
341     * Handles the following tokens:
342     *
343     * --L=V -L=V --l=V -l=V
344     *
345     * @param token the command line token to handle
346     */
347    private void handleLongOptionWithEqual(final String token) throws ParseException {
348        final int pos = token.indexOf('=');
349
350        final String value = token.substring(pos + 1);
351
352        final String opt = token.substring(0, pos);
353
354        final List<String> matchingOpts = getMatchingLongOptions(opt);
355        if (matchingOpts.isEmpty()) {
356            handleUnknownToken(currentToken);
357        } else if (matchingOpts.size() > 1 && !options.hasLongOption(opt)) {
358            throw new AmbiguousOptionException(opt, matchingOpts);
359        } else {
360            final String key = options.hasLongOption(opt) ? opt : matchingOpts.get(0);
361            final Option option = options.getOption(key);
362
363            if (option.acceptsArg()) {
364                handleOption(option);
365                currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(value));
366                currentOption = null;
367            } else {
368                handleUnknownToken(currentToken);
369            }
370        }
371    }
372
373    /**
374     * Handles the following tokens:
375     *
376     * --L -L --l -l
377     *
378     * @param token the command line token to handle
379     */
380    private void handleLongOptionWithoutEqual(final String token) throws ParseException {
381        final List<String> matchingOpts = getMatchingLongOptions(token);
382        if (matchingOpts.isEmpty()) {
383            handleUnknownToken(currentToken);
384        } else if (matchingOpts.size() > 1 && !options.hasLongOption(token)) {
385            throw new AmbiguousOptionException(token, matchingOpts);
386        } else {
387            final String key = options.hasLongOption(token) ? token : matchingOpts.get(0);
388            handleOption(options.getOption(key));
389        }
390    }
391
392    private void handleOption(Option option) throws ParseException {
393        // check the previous option before handling the next one
394        checkRequiredArgs();
395
396        option = (Option) option.clone();
397
398        updateRequiredOptions(option);
399
400        cmd.addOption(option);
401
402        if (option.hasArg()) {
403            currentOption = option;
404        } else {
405            currentOption = null;
406        }
407    }
408
409    /**
410     * Sets the values of Options using the values in {@code properties}.
411     *
412     * @param properties The value properties to be processed.
413     */
414    private void handleProperties(final Properties properties) throws ParseException {
415        if (properties == null) {
416            return;
417        }
418
419        for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
420            final String option = e.nextElement().toString();
421
422            final Option opt = options.getOption(option);
423            if (opt == null) {
424                throw new UnrecognizedOptionException("Default option wasn't defined", option);
425            }
426
427            // if the option is part of a group, check if another option of the group has been selected
428            final OptionGroup group = options.getOptionGroup(opt);
429            final boolean selected = group != null && group.getSelected() != null;
430
431            if (!cmd.hasOption(option) && !selected) {
432                // get the value from the properties
433                final String value = properties.getProperty(option);
434
435                if (opt.hasArg()) {
436                    if (opt.getValues() == null || opt.getValues().length == 0) {
437                        opt.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(value));
438                    }
439                } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) {
440                    // if the value is not yes, true or 1 then don't add the option to the CommandLine
441                    continue;
442                }
443
444                handleOption(opt);
445                currentOption = null;
446            }
447        }
448    }
449
450    /**
451     * Handles the following tokens:
452     *
453     * -S -SV -S V -S=V -S1S2 -S1S2 V -SV1=V2
454     *
455     * -L -LV -L V -L=V -l
456     *
457     * @param token the command line token to handle
458     */
459    private void handleShortAndLongOption(final String token) throws ParseException {
460        final String t = Util.stripLeadingHyphens(token);
461
462        final int pos = t.indexOf('=');
463
464        if (t.length() == 1) {
465            // -S
466            if (options.hasShortOption(t)) {
467                handleOption(options.getOption(t));
468            } else {
469                handleUnknownToken(token);
470            }
471        } else if (pos == -1) {
472            // no equal sign found (-xxx)
473            if (options.hasShortOption(t)) {
474                handleOption(options.getOption(t));
475            } else if (!getMatchingLongOptions(t).isEmpty()) {
476                // -L or -l
477                handleLongOptionWithoutEqual(token);
478            } else {
479                // look for a long prefix (-Xmx512m)
480                final String opt = getLongPrefix(t);
481
482                if (opt != null && options.getOption(opt).acceptsArg()) {
483                    handleOption(options.getOption(opt));
484                    currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(t.substring(opt.length())));
485                    currentOption = null;
486                } else if (isJavaProperty(t)) {
487                    // -SV1 (-Dflag)
488                    handleOption(options.getOption(t.substring(0, 1)));
489                    currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOff(t.substring(1)));
490                    currentOption = null;
491                } else {
492                    // -S1S2S3 or -S1S2V
493                    handleConcatenatedOptions(token);
494                }
495            }
496        } else {
497            // equal sign found (-xxx=yyy)
498            final String opt = t.substring(0, pos);
499            final String value = t.substring(pos + 1);
500
501            if (opt.length() == 1) {
502                // -S=V
503                final Option option = options.getOption(opt);
504                if (option != null && option.acceptsArg()) {
505                    handleOption(option);
506                    currentOption.addValueForProcessing(value);
507                    currentOption = null;
508                } else {
509                    handleUnknownToken(token);
510                }
511            } else if (isJavaProperty(opt)) {
512                // -SV1=V2 (-Dkey=value)
513                handleOption(options.getOption(opt.substring(0, 1)));
514                currentOption.addValueForProcessing(opt.substring(1));
515                currentOption.addValueForProcessing(value);
516                currentOption = null;
517            } else {
518                // -L=V or -l=V
519                handleLongOptionWithEqual(token);
520            }
521        }
522    }
523
524    /**
525     * Handles any command line token.
526     *
527     * @param token the command line token to handle
528     * @throws ParseException
529     */
530    private void handleToken(final String token) throws ParseException {
531        currentToken = token;
532
533        if (skipParsing) {
534            cmd.addArg(token);
535        } else if ("--".equals(token)) {
536            skipParsing = true;
537        } else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) {
538            currentOption.addValueForProcessing(stripLeadingAndTrailingQuotesDefaultOn(token));
539        } else if (token.startsWith("--")) {
540            handleLongOption(token);
541        } else if (token.startsWith("-") && !"-".equals(token)) {
542            handleShortAndLongOption(token);
543        } else {
544            handleUnknownToken(token);
545        }
546
547        if (currentOption != null && !currentOption.acceptsArg()) {
548            currentOption = null;
549        }
550    }
551
552    /**
553     * Handles an unknown token. If the token starts with a dash an UnrecognizedOptionException is thrown. Otherwise the
554     * token is added to the arguments of the command line. If the stopAtNonOption flag is set, this stops the parsing and
555     * the remaining tokens are added as-is in the arguments of the command line.
556     *
557     * @param token the command line token to handle
558     */
559    private void handleUnknownToken(final String token) throws ParseException {
560        if (token.startsWith("-") && token.length() > 1 && !stopAtNonOption) {
561            throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
562        }
563
564        cmd.addArg(token);
565        if (stopAtNonOption) {
566            skipParsing = true;
567        }
568    }
569
570    /**
571     * Tests if the token is a valid argument.
572     *
573     * @param token
574     */
575    private boolean isArgument(final String token) {
576        return !isOption(token) || isNegativeNumber(token);
577    }
578
579    /**
580     * Tests if the specified token is a Java-like property (-Dkey=value).
581     */
582    private boolean isJavaProperty(final String token) {
583        final String opt = token.substring(0, 1);
584        final Option option = options.getOption(opt);
585
586        return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES);
587    }
588
589    /**
590     * Tests if the token looks like a long option.
591     *
592     * @param token
593     */
594    private boolean isLongOption(final String token) {
595        if (token == null || !token.startsWith("-") || token.length() == 1) {
596            return false;
597        }
598
599        final int pos = token.indexOf("=");
600        final String t = pos == -1 ? token : token.substring(0, pos);
601
602        if (!getMatchingLongOptions(t).isEmpty()) {
603            // long or partial long options (--L, -L, --L=V, -L=V, --l, --l=V)
604            return true;
605        }
606        if (getLongPrefix(token) != null && !token.startsWith("--")) {
607            // -LV
608            return true;
609        }
610
611        return false;
612    }
613
614    /**
615     * Tests if the token is a negative number.
616     *
617     * @param token
618     */
619    private boolean isNegativeNumber(final String token) {
620        try {
621            Double.parseDouble(token);
622            return true;
623        } catch (final NumberFormatException e) {
624            return false;
625        }
626    }
627
628    /**
629     * Tests if the token looks like an option.
630     *
631     * @param token
632     */
633    private boolean isOption(final String token) {
634        return isLongOption(token) || isShortOption(token);
635    }
636
637    /**
638     * Tests if the token looks like a short option.
639     *
640     * @param token
641     */
642    private boolean isShortOption(final String token) {
643        // short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
644        if (token == null || !token.startsWith("-") || token.length() == 1) {
645            return false;
646        }
647
648        // remove leading "-" and "=value"
649        final int pos = token.indexOf("=");
650        final String optName = pos == -1 ? token.substring(1) : token.substring(1, pos);
651        if (options.hasShortOption(optName)) {
652            return true;
653        }
654        // check for several concatenated short options
655        return !optName.isEmpty() && options.hasShortOption(String.valueOf(optName.charAt(0)));
656    }
657
658    @Override
659    public CommandLine parse(final Options options, final String[] arguments) throws ParseException {
660        return parse(options, arguments, null);
661    }
662
663    @Override
664    public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
665        return parse(options, arguments, null, stopAtNonOption);
666    }
667
668    /**
669     * Parses the arguments according to the specified options and properties.
670     *
671     * @param options the specified Options
672     * @param arguments the command line arguments
673     * @param properties command line option name-value pairs
674     * @return the list of atomic option and value tokens
675     *
676     * @throws ParseException if there are any problems encountered while parsing the command line tokens.
677     */
678    public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException {
679        return parse(options, arguments, properties, false);
680    }
681
682    /**
683     * Parses the arguments according to the specified options and properties.
684     *
685     * @param options the specified Options
686     * @param arguments the command line arguments
687     * @param properties command line option name-value pairs
688     * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments
689     *        are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a
690     *        ParseException.
691     *
692     * @return the list of atomic option and value tokens
693     * @throws ParseException if there are any problems encountered while parsing the command line tokens.
694     */
695    public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
696        throws ParseException {
697        this.options = options;
698        this.stopAtNonOption = stopAtNonOption;
699        skipParsing = false;
700        currentOption = null;
701        expectedOpts = new ArrayList<>(options.getRequiredOptions());
702
703        // clear the data from the groups
704        for (final OptionGroup group : options.getOptionGroups()) {
705            group.setSelected(null);
706        }
707
708        cmd = new CommandLine();
709
710        if (arguments != null) {
711            for (final String argument : arguments) {
712                handleToken(argument);
713            }
714        }
715
716        // check the arguments of the last option
717        checkRequiredArgs();
718
719        // add the default options
720        handleProperties(properties);
721
722        checkRequiredOptions();
723
724        return cmd;
725    }
726
727    /**
728     * Strips balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set
729     * If stripLeadingAndTrailingQuotes is null, then do not strip
730     *
731     * @param token a string
732     * @return token with the quotes stripped (if set)
733     */
734    private String stripLeadingAndTrailingQuotesDefaultOff(final String token) {
735        if (stripLeadingAndTrailingQuotes != null && stripLeadingAndTrailingQuotes) {
736            return Util.stripLeadingAndTrailingQuotes(token);
737        }
738        return token;
739    }
740
741    /**
742     * Strips balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set
743     * If stripLeadingAndTrailingQuotes is null, then do not strip
744     *
745     * @param token a string
746     * @return token with the quotes stripped (if set)
747     */
748    private String stripLeadingAndTrailingQuotesDefaultOn(final String token) {
749        if (stripLeadingAndTrailingQuotes == null || stripLeadingAndTrailingQuotes) {
750            return Util.stripLeadingAndTrailingQuotes(token);
751        }
752        return token;
753    }
754
755    /**
756     * Removes the option or its group from the list of expected elements.
757     *
758     * @param option
759     */
760    private void updateRequiredOptions(final Option option) throws AlreadySelectedException {
761        if (option.isRequired()) {
762            expectedOpts.remove(option.getKey());
763        }
764
765        // if the option is in an OptionGroup make that option the selected option of the group
766        if (options.getOptionGroup(option) != null) {
767            final OptionGroup group = options.getOptionGroup(option);
768
769            if (group.isRequired()) {
770                expectedOpts.remove(group);
771            }
772
773            group.setSelected(option);
774        }
775    }
776}