@@ -55,6 +55,10 @@ public class DefaultParser implements CommandLineParser {
5555 /** Flag indicating if partial matching of long options is supported. */
5656 private final boolean allowPartialMatching ;
5757
58+ /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments.
59+ * null represents the historic arbitrary behaviour */
60+ private final Boolean stripLeadingAndTrailingQuotes ;
61+
5862 /**
5963 * Creates a new DefaultParser instance with partial matching enabled.
6064 *
@@ -76,6 +80,7 @@ public class DefaultParser implements CommandLineParser {
7680 */
7781 public DefaultParser () {
7882 this .allowPartialMatching = true ;
83+ this .stripLeadingAndTrailingQuotes = null ;
7984 }
8085
8186 /**
@@ -101,6 +106,20 @@ public DefaultParser() {
101106 */
102107 public DefaultParser (final boolean allowPartialMatching ) {
103108 this .allowPartialMatching = allowPartialMatching ;
109+ this .stripLeadingAndTrailingQuotes = null ;
110+ }
111+
112+ /**
113+ * Creates a new DefaultParser instance with the specified partial matching and quote
114+ * stripping policy.
115+ *
116+ * @param allowPartialMatching if partial matching of long options shall be enabled
117+ * @param stripLeadingAndTrailingQuotes if balanced outer double quoutes should be stripped
118+ */
119+ private DefaultParser (final boolean allowPartialMatching ,
120+ final Boolean stripLeadingAndTrailingQuotes ) {
121+ this .allowPartialMatching = allowPartialMatching ;
122+ this .stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes ;
104123 }
105124
106125 /**
@@ -196,7 +215,7 @@ protected void handleConcatenatedOptions(final String token) throws ParseExcepti
196215
197216 if (currentOption != null && token .length () != i + 1 ) {
198217 // add the trail as an argument of the option
199- currentOption .addValueForProcessing (token .substring (i + 1 ));
218+ currentOption .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOff ( token .substring (i + 1 ) ));
200219 break ;
201220 }
202221 }
@@ -242,7 +261,7 @@ private void handleLongOptionWithEqual(final String token) throws ParseException
242261
243262 if (option .acceptsArg ()) {
244263 handleOption (option );
245- currentOption .addValueForProcessing (value );
264+ currentOption .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOff ( value ) );
246265 currentOption = null ;
247266 } else {
248267 handleUnknownToken (currentToken );
@@ -314,7 +333,7 @@ private void handleProperties(final Properties properties) throws ParseException
314333
315334 if (opt .hasArg ()) {
316335 if (opt .getValues () == null || opt .getValues ().length == 0 ) {
317- opt .addValueForProcessing (value );
336+ opt .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOff ( value ) );
318337 }
319338 } else if (!("yes" .equalsIgnoreCase (value ) || "true" .equalsIgnoreCase (value ) || "1" .equalsIgnoreCase (value ))) {
320339 // if the value is not yes, true or 1 then don't add the option to the CommandLine
@@ -361,12 +380,12 @@ private void handleShortAndLongOption(final String token) throws ParseException
361380
362381 if (opt != null && options .getOption (opt ).acceptsArg ()) {
363382 handleOption (options .getOption (opt ));
364- currentOption .addValueForProcessing (t .substring (opt .length ()));
383+ currentOption .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOff ( t .substring (opt .length () )));
365384 currentOption = null ;
366385 } else if (isJavaProperty (t )) {
367386 // -SV1 (-Dflag)
368387 handleOption (options .getOption (t .substring (0 , 1 )));
369- currentOption .addValueForProcessing (t .substring (1 ));
388+ currentOption .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOff ( t .substring (1 ) ));
370389 currentOption = null ;
371390 } else {
372391 // -S1S2S3 or -S1S2V
@@ -415,7 +434,7 @@ private void handleToken(final String token) throws ParseException {
415434 } else if ("--" .equals (token )) {
416435 skipParsing = true ;
417436 } else if (currentOption != null && currentOption .acceptsArg () && isArgument (token )) {
418- currentOption .addValueForProcessing (Util . stripLeadingAndTrailingQuotes (token ));
437+ currentOption .addValueForProcessing (stripLeadingAndTrailingQuotesDefaultOn (token ));
419438 } else if (token .startsWith ("--" )) {
420439 handleLongOption (token );
421440 } else if (token .startsWith ("-" ) && !"-" .equals (token )) {
@@ -625,4 +644,134 @@ private void updateRequiredOptions(final Option option) throws AlreadySelectedEx
625644 group .setSelected (option );
626645 }
627646 }
647+
648+ /**
649+ * Strip balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set
650+ * If stripLeadingAndTrailingQuotes is null, then do not strip
651+ *
652+ * @param token a string
653+ * @return token with the quotes stripped (if set)
654+ */
655+ private String stripLeadingAndTrailingQuotesDefaultOff (final String token ) {
656+ if (stripLeadingAndTrailingQuotes != null && stripLeadingAndTrailingQuotes ) {
657+ return Util .stripLeadingAndTrailingQuotes (token );
658+ } else {
659+ return token ;
660+ }
661+ }
662+
663+ /**
664+ * Strip balanced leading and trailing quotes if the stripLeadingAndTrailingQuotes is set
665+ * If stripLeadingAndTrailingQuotes is null, then do not strip
666+ *
667+ * @param token a string
668+ * @return token with the quotes stripped (if set)
669+ */
670+ private String stripLeadingAndTrailingQuotesDefaultOn (final String token ) {
671+ if (stripLeadingAndTrailingQuotes == null || stripLeadingAndTrailingQuotes ) {
672+ return Util .stripLeadingAndTrailingQuotes (token );
673+ } else {
674+ return token ;
675+ }
676+ }
677+
678+ /**
679+ * Returns a {@link Builder} to create an {@link DefaultParser} using descriptive
680+ * methods.
681+ *
682+ * @return a new {@link Builder} instance
683+ * @since 1.5
684+ */
685+ public static Builder builder () {
686+ return new Builder ();
687+ }
688+
689+ /**
690+ * A nested builder class to create <code>DefaultParser</code> instances
691+ * using descriptive methods.
692+ *
693+ * Example usage:
694+ * <pre>
695+ * DefaultParser parser = Option.builder()
696+ * .setAllowPartialMatching(false)
697+ * .setStripLeadingAndTrailingQuotes(false)
698+ * .build();
699+ * </pre>
700+ *
701+ * @since 1.5
702+ */
703+ public static final class Builder {
704+
705+ /** Flag indicating if partial matching of long options is supported. */
706+ private boolean allowPartialMatching = true ;
707+
708+ /** Flag indicating if balanced leading and trailing double quotes should be stripped from option arguments. */
709+ private Boolean stripLeadingAndTrailingQuotes ;
710+
711+ /**
712+ * Constructs a new <code>Builder</code> for a <code>DefaultParser</code> instance.
713+ *
714+ * Both allowPartialMatching and stripLeadingAndTrailingQuotes are true by default,
715+ * mimicking the argument-less constructor.
716+ */
717+ private Builder () {
718+ }
719+
720+ /**
721+ * Sets if partial matching of long options is supported.
722+ *
723+ * By "partial matching" we mean that given the following code:
724+ *
725+ * <pre>
726+ * {
727+ * @code
728+ * final Options options = new Options();
729+ * options.addOption(new Option("d", "debug", false, "Turn on debug."));
730+ * options.addOption(new Option("e", "extract", false, "Turn on extract."));
731+ * options.addOption(new Option("o", "option", true, "Turn on option with argument."));
732+ * }
733+ * </pre>
734+ *
735+ * If "partial matching" is turned on, {@code -de} only matches the {@code "debug"} option. However, with
736+ * "partial matching" disabled, {@code -de} would enable both {@code debug} as well as {@code extract}
737+ *
738+ * @param allowPartialMatching whether to allow partial matching of long options
739+ * @return this builder, to allow method chaining
740+ * @since 1.5
741+ */
742+ public Builder setAllowPartialMatching (final boolean allowPartialMatching ) {
743+ this .allowPartialMatching = allowPartialMatching ;
744+ return this ;
745+ }
746+
747+ /**
748+ * Sets if balanced leading and trailing double quotes should be stripped from option arguments.
749+ *
750+ * If "stripping of balanced leading and trailing double quotes from option arguments" is true,
751+ * the outermost balanced double quotes of option arguments values will be removed.
752+ * For example, <code>-o '"x"'</code> getValue() will return <code>x</code>, instead of <code>"x"</code>
753+ *
754+ * If "stripping of balanced leading and trailing double quotes from option arguments" is null,
755+ * then quotes will be stripped from option values separated by space from the option, but
756+ * kept in other cases, which is the historic behaviour.
757+ *
758+ * @param stripLeadingAndTrailingQuotes whether balanced leading and trailing double quotes should be stripped from option arguments.
759+ * @return this builder, to allow method chaining
760+ * @since 1.5
761+ */
762+ public Builder setStripLeadingAndTrailingQuotes (final Boolean stripLeadingAndTrailingQuotes ) {
763+ this .stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes ;
764+ return this ;
765+ }
766+
767+ /**
768+ * Builds an DefaultParser with the values declared by this {@link Builder}.
769+ *
770+ * @return the new {@link DefaultParser}
771+ * @since 1.5
772+ */
773+ public DefaultParser build () {
774+ return new DefaultParser (allowPartialMatching , stripLeadingAndTrailingQuotes );
775+ }
776+ }
628777}
0 commit comments