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