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 {@code this} instance.
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.isSelected();
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 (Util.isEmpty(opt.getValues())) {
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 hyphenToken the command line token to handle
473      */
474     private void handleShortAndLongOption(final String hyphenToken) throws ParseException {
475         final String token = Util.stripLeadingHyphens(hyphenToken);
476         final int pos = indexOfEqual(token);
477         if (token.length() == 1) {
478             // -S
479             if (options.hasShortOption(token)) {
480                 handleOption(options.getOption(token));
481             } else {
482                 handleUnknownToken(hyphenToken);
483             }
484         } else if (pos == -1) {
485             // no equal sign found (-xxx)
486             if (options.hasShortOption(token)) {
487                 handleOption(options.getOption(token));
488             } else if (!getMatchingLongOptions(token).isEmpty()) {
489                 // -L or -l
490                 handleLongOptionWithoutEqual(hyphenToken);
491             } else {
492                 // look for a long prefix (-Xmx512m)
493                 final String opt = getLongPrefix(token);
494 
495                 if (opt != null && options.getOption(opt).acceptsArg()) {
496                     handleOption(options.getOption(opt));
497                     currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(opt.length())));
498                     currentOption = null;
499                 } else if (isJavaProperty(token)) {
500                     // -SV1 (-Dflag)
501                     handleOption(options.getOption(token.substring(0, 1)));
502                     currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(1)));
503                     currentOption = null;
504                 } else {
505                     // -S1S2S3 or -S1S2V
506                     handleConcatenatedOptions(hyphenToken);
507                 }
508             }
509         } else {
510             // equal sign found (-xxx=yyy)
511             final String opt = token.substring(0, pos);
512             final String value = token.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(hyphenToken);
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(hyphenToken);
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         if (token != null) {
545             currentToken = token;
546             if (skipParsing) {
547                 cmd.addArg(token);
548             } else if ("--".equals(token)) {
549                 skipParsing = true;
550             } else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) {
551                 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOn(token));
552             } else if (token.startsWith("--")) {
553                 handleLongOption(token);
554             } else if (token.startsWith("-") && !"-".equals(token)) {
555                 handleShortAndLongOption(token);
556             } else {
557                 handleUnknownToken(token);
558             }
559             if (currentOption != null && !currentOption.acceptsArg()) {
560                 currentOption = null;
561             }
562         }
563     }
564 
565     /**
566      * Handles an unknown token. If the token starts with a dash an UnrecognizedOptionException is thrown. Otherwise the
567      * token is added to the arguments of the command line. If the stopAtNonOption flag is set, this stops the parsing and
568      * the remaining tokens are added as-is in the arguments of the command line.
569      *
570      * @param token the command line token to handle
571      */
572     private void handleUnknownToken(final String token) throws ParseException {
573         if (token.startsWith("-") && token.length() > 1 && !stopAtNonOption) {
574             throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
575         }
576         cmd.addArg(token);
577         if (stopAtNonOption) {
578             skipParsing = true;
579         }
580     }
581 
582     /**
583      * Tests if the token is a valid argument.
584      *
585      * @param token
586      */
587     private boolean isArgument(final String token) {
588         return !isOption(token) || isNegativeNumber(token);
589     }
590 
591     /**
592      * Tests if the specified token is a Java-like property (-Dkey=value).
593      */
594     private boolean isJavaProperty(final String token) {
595         final String opt = token.isEmpty() ? null : token.substring(0, 1);
596         final Option option = options.getOption(opt);
597         return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES);
598     }
599 
600     /**
601      * Tests if the token looks like a long option.
602      *
603      * @param token
604      */
605     private boolean isLongOption(final String token) {
606         if (token == null || !token.startsWith("-") || token.length() == 1) {
607             return false;
608         }
609         final int pos = indexOfEqual(token);
610         final String t = pos == -1 ? token : token.substring(0, pos);
611         if (!getMatchingLongOptions(t).isEmpty()) {
612             // long or partial long options (--L, -L, --L=V, -L=V, --l, --l=V)
613             return true;
614         }
615         if (getLongPrefix(token) != null && !token.startsWith("--")) {
616             // -LV
617             return true;
618         }
619         return false;
620     }
621 
622     /**
623      * Tests if the token is a negative number.
624      *
625      * @param token
626      */
627     private boolean isNegativeNumber(final String token) {
628         try {
629             Double.parseDouble(token);
630             return true;
631         } catch (final NumberFormatException e) {
632             return false;
633         }
634     }
635 
636     /**
637      * Tests if the token looks like an option.
638      *
639      * @param token
640      */
641     private boolean isOption(final String token) {
642         return isLongOption(token) || isShortOption(token);
643     }
644 
645     /**
646      * Tests if the token looks like a short option.
647      *
648      * @param token
649      */
650     private boolean isShortOption(final String token) {
651         // short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
652         if (token == null || !token.startsWith("-") || token.length() == 1) {
653             return false;
654         }
655         // remove leading "-" and "=value"
656         final int pos = indexOfEqual(token);
657         final String optName = pos == -1 ? token.substring(1) : token.substring(1, pos);
658         if (options.hasShortOption(optName)) {
659             return true;
660         }
661         // check for several concatenated short options
662         return !optName.isEmpty() && options.hasShortOption(String.valueOf(optName.charAt(0)));
663     }
664 
665     @Override
666     public CommandLine parse(final Options options, final String[] arguments) throws ParseException {
667         return parse(options, arguments, null);
668     }
669 
670     @Override
671     public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
672         return parse(options, arguments, null, stopAtNonOption);
673     }
674 
675     /**
676      * Parses the arguments according to the specified options and properties.
677      *
678      * @param options the specified Options
679      * @param arguments the command line arguments
680      * @param properties command line option name-value pairs
681      * @return the list of atomic option and value tokens
682      *
683      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
684      */
685     public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException {
686         return parse(options, arguments, properties, false);
687     }
688 
689     /**
690      * Parses the arguments according to the specified options and properties.
691      *
692      * @param options the specified Options
693      * @param arguments the command line arguments
694      * @param properties command line option name-value pairs
695      * @param stopAtNonOption if {@code true} an unrecognized argument stops the parsing and the remaining arguments
696      *        are added to the {@link CommandLine}s args list. If {@code false} an unrecognized argument triggers a
697      *        ParseException.
698      *
699      * @return the list of atomic option and value tokens
700      * @throws ParseException if there are any problems encountered while parsing the command line tokens.
701      */
702     public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
703         throws ParseException {
704         this.options = options;
705         this.stopAtNonOption = stopAtNonOption;
706         skipParsing = false;
707         currentOption = null;
708         expectedOpts = new ArrayList<>(options.getRequiredOptions());
709         // clear the data from the groups
710         for (final OptionGroup group : options.getOptionGroups()) {
711             group.setSelected(null);
712         }
713         cmd = CommandLine.builder().setDeprecatedHandler(deprecatedHandler).build();
714         if (arguments != null) {
715             for (final String argument : arguments) {
716                 handleToken(argument);
717             }
718         }
719         // check the arguments of the last option
720         checkRequiredArgs();
721         // add the default options
722         handleProperties(properties);
723         checkRequiredOptions();
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 }