View Javadoc
1   /*
2     Licensed to the Apache Software Foundation (ASF) under one or more
3     contributor license agreements.  See the NOTICE file distributed with
4     this work for additional information regarding copyright ownership.
5     The ASF licenses this file to You under the Apache License, Version 2.0
6     (the "License"); you may not use this file except in compliance with
7     the License.  You may obtain a copy of the License at
8   
9         http://www.apache.org/licenses/LICENSE-2.0
10  
11    Unless required by applicable law or agreed to in writing, software
12    distributed under the License is distributed on an "AS IS" BASIS,
13    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14    See the License for the specific language governing permissions and
15    limitations under the License.
16   */
17  
18  package org.apache.commons.cli;
19  
20  import java.util.ArrayList;
21  import java.util.Enumeration;
22  import java.util.List;
23  import java.util.Properties;
24  
25  /**
26   * Default parser.
27   *
28   * @since 1.3
29   */
30  public class DefaultParser implements CommandLineParser {
31  
32      /**
33       * A nested builder class to create {@code DefaultParser} instances
34       * using descriptive methods.
35       *
36       * Example usage:
37       * <pre>
38       * DefaultParser parser = Option.builder()
39       *     .setAllowPartialMatching(false)
40       *     .setStripLeadingAndTrailingQuotes(false)
41       *     .build();
42       * </pre>
43       *
44       * @since 1.5.0
45       */
46      public static final class Builder {
47  
48          /** Flag indicating if partial matching of long options is supported. */
49          private boolean allowPartialMatching = true;
50  
51          /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments. */
52          private Boolean stripLeadingAndTrailingQuotes;
53  
54          /**
55           * Constructs a new {@code Builder} for a {@code DefaultParser} instance.
56           *
57           * Both allowPartialMatching and stripLeadingAndTrailingQuotes are true by default,
58           * mimicking the argument-less constructor.
59           */
60          private Builder() {
61          }
62  
63          /**
64           * Builds an DefaultParser with the values declared by this {@link Builder}.
65           *
66           * @return the new {@link DefaultParser}
67           * @since 1.5.0
68           */
69          public DefaultParser build() {
70              return new DefaultParser(allowPartialMatching, stripLeadingAndTrailingQuotes);
71          }
72  
73          /**
74           * Sets if partial matching of long options is supported.
75           *
76           * By "partial matching" we mean that given the following code:
77           *
78           * <pre>
79           * {
80           *     &#64;code
81           *     final Options options = new Options();
82           *     options.addOption(new Option("d", "debug", false, "Turn on debug."));
83           *     options.addOption(new Option("e", "extract", false, "Turn on extract."));
84           *     options.addOption(new Option("o", "option", true, "Turn on option with argument."));
85           * }
86           * </pre>
87           *
88           * If "partial matching" is turned on, {@code -de} only matches the {@code "debug"} option. However, with
89           * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract}
90           *
91           * @param allowPartialMatching whether to allow partial matching of long options
92           * @return this builder, to allow method chaining
93           * @since 1.5.0
94           */
95          public Builder setAllowPartialMatching(final boolean allowPartialMatching) {
96              this.allowPartialMatching = allowPartialMatching;
97              return this;
98          }
99  
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 }