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