1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Objects;
24 import java.util.Properties;
25 import java.util.function.Consumer;
26 import java.util.function.Supplier;
27
28
29
30
31
32
33 public class DefaultParser implements CommandLineParser {
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public static final class Builder implements Supplier<DefaultParser> {
50
51
52 private boolean allowPartialMatching = true;
53
54
55
56
57
58
59
60 private Consumer<Option> deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
61
62
63 private Boolean stripLeadingAndTrailingQuotes;
64
65
66
67
68
69
70
71 private Builder() {
72 }
73
74
75
76
77
78
79
80
81 @Deprecated
82 public DefaultParser build() {
83 return get();
84 }
85
86
87
88
89
90
91
92 @Override
93 public DefaultParser get() {
94 return new DefaultParser(allowPartialMatching, stripLeadingAndTrailingQuotes, deprecatedHandler);
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 public Builder setAllowPartialMatching(final boolean allowPartialMatching) {
120 this.allowPartialMatching = allowPartialMatching;
121 return this;
122 }
123
124
125
126
127
128
129
130
131 public Builder setDeprecatedHandler(final Consumer<Option> deprecatedHandler) {
132 this.deprecatedHandler = deprecatedHandler;
133 return this;
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151 public Builder setStripLeadingAndTrailingQuotes(final Boolean stripLeadingAndTrailingQuotes) {
152 this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
153 return this;
154 }
155 }
156
157
158
159
160
161
162 public enum NonOptionAction {
163
164
165
166 IGNORE,
167
168
169
170 SKIP,
171
172
173
174
175 STOP,
176
177
178
179
180 THROW;
181 }
182
183
184
185
186
187
188
189
190 public static Builder builder() {
191 return new Builder();
192 }
193
194 static int indexOfEqual(final String token) {
195 return token.indexOf(Char.EQUAL);
196 }
197
198
199 protected CommandLine cmd;
200
201
202 protected Options options;
203
204
205
206
207
208
209
210 @Deprecated
211 protected boolean stopAtNonOption;
212
213
214
215
216
217
218 protected NonOptionAction nonOptionAction;
219
220
221 protected String currentToken;
222
223
224 protected Option currentOption;
225
226
227 protected boolean skipParsing;
228
229
230
231
232 protected List expectedOpts;
233
234
235 private final boolean allowPartialMatching;
236
237
238
239 private final Boolean stripLeadingAndTrailingQuotes;
240
241
242
243
244
245
246
247 private final Consumer<Option> deprecatedHandler;
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268 public DefaultParser() {
269 this.allowPartialMatching = true;
270 this.stripLeadingAndTrailingQuotes = null;
271 this.deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 public DefaultParser(final boolean allowPartialMatching) {
296 this.allowPartialMatching = allowPartialMatching;
297 this.stripLeadingAndTrailingQuotes = null;
298 this.deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
299 }
300
301
302
303
304
305
306
307
308 private DefaultParser(final boolean allowPartialMatching, final Boolean stripLeadingAndTrailingQuotes, final Consumer<Option> deprecatedHandler) {
309 this.allowPartialMatching = allowPartialMatching;
310 this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
311 this.deprecatedHandler = deprecatedHandler;
312 }
313
314
315
316
317
318
319
320 protected void addArg(final String token) {
321 cmd.addArg(token);
322 }
323
324
325
326
327 private void checkRequiredArgs() throws ParseException {
328 if (currentOption != null && currentOption.requiresArg()) {
329 if (isJavaProperty(currentOption.getKey()) && currentOption.getValuesList().size() == 1) {
330 return;
331 }
332 throw new MissingArgumentException(currentOption);
333 }
334 }
335
336
337
338
339
340
341 protected void checkRequiredOptions() throws MissingOptionException {
342
343 if (!expectedOpts.isEmpty()) {
344 throw new MissingOptionException(expectedOpts);
345 }
346 }
347
348
349
350
351
352
353 private String getLongPrefix(final String token) {
354 final String t = Util.stripLeadingHyphens(token);
355 int i;
356 String opt = null;
357 for (i = t.length() - 2; i > 1; i--) {
358 final String prefix = t.substring(0, i);
359 if (options.hasLongOption(prefix)) {
360 opt = prefix;
361 break;
362 }
363 }
364 return opt;
365 }
366
367
368
369
370
371
372
373 private List<String> getMatchingLongOptions(final String token) {
374 if (allowPartialMatching) {
375 return options.getMatchingOptions(token);
376 }
377 final List<String> matches = new ArrayList<>(1);
378 if (options.hasLongOption(token)) {
379 matches.add(options.getOption(token).getLongOpt());
380 }
381 return matches;
382 }
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 protected void handleConcatenatedOptions(final String token) throws ParseException {
405 for (int i = 1; i < token.length(); i++) {
406 final String ch = String.valueOf(token.charAt(i));
407 if (!options.hasOption(ch)) {
408 handleUnknownToken(nonOptionAction == NonOptionAction.STOP && i > 1 ? token.substring(i) : token);
409 break;
410 }
411 handleOption(options.getOption(ch));
412 if (currentOption != null && token.length() != i + 1) {
413
414 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(i + 1)));
415 break;
416 }
417 }
418 }
419
420
421
422
423
424
425
426
427
428 private void handleLongOption(final String token) throws ParseException {
429 if (indexOfEqual(token) == -1) {
430 handleLongOptionWithoutEqual(token);
431 } else {
432 handleLongOptionWithEqual(token);
433 }
434 }
435
436
437
438
439
440
441
442
443
444 private void handleLongOptionWithEqual(final String token) throws ParseException {
445 final int pos = indexOfEqual(token);
446 final String value = token.substring(pos + 1);
447 final String opt = token.substring(0, pos);
448 final List<String> matchingOpts = getMatchingLongOptions(opt);
449 if (matchingOpts.isEmpty()) {
450 handleUnknownToken(currentToken);
451 } else if (matchingOpts.size() > 1 && !options.hasLongOption(opt)) {
452 throw new AmbiguousOptionException(opt, matchingOpts);
453 } else {
454 final String key = options.hasLongOption(opt) ? opt : matchingOpts.get(0);
455 final Option option = options.getOption(key);
456 if (option.acceptsArg()) {
457 handleOption(option);
458 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(value));
459 currentOption = null;
460 } else {
461 handleUnknownToken(currentToken);
462 }
463 }
464 }
465
466
467
468
469
470
471
472
473
474
475 private void handleLongOptionWithoutEqual(final String token) throws ParseException {
476 final List<String> matchingOpts = getMatchingLongOptions(token);
477 if (matchingOpts.isEmpty()) {
478 handleUnknownToken(currentToken);
479 } else if (matchingOpts.size() > 1 && !options.hasLongOption(token)) {
480 throw new AmbiguousOptionException(token, matchingOpts);
481 } else {
482 final String key = options.hasLongOption(token) ? token : matchingOpts.get(0);
483 handleOption(options.getOption(key));
484 }
485 }
486
487 private void handleOption(final Option option) throws ParseException {
488
489 checkRequiredArgs();
490 final Option copy = (Option) option.clone();
491 updateRequiredOptions(copy);
492 cmd.addOption(copy);
493 currentOption = copy.hasArg() ? copy : null;
494 }
495
496
497
498
499
500
501 private void handleProperties(final Properties properties) throws ParseException {
502 if (properties == null) {
503 return;
504 }
505 for (final Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();) {
506 final String option = e.nextElement().toString();
507 final Option opt = options.getOption(option);
508 if (opt == null) {
509 throw new UnrecognizedOptionException("Default option wasn't defined", option);
510 }
511
512 final OptionGroup optionGroup = options.getOptionGroup(opt);
513 final boolean selected = optionGroup != null && optionGroup.isSelected();
514 if (!cmd.hasOption(option) && !selected) {
515
516 final String value = properties.getProperty(option);
517
518 if (opt.hasArg()) {
519 if (Util.isEmpty(opt.getValues())) {
520 opt.processValue(stripLeadingAndTrailingQuotesDefaultOff(value));
521 }
522 } else if (!("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value))) {
523
524 continue;
525 }
526 handleOption(opt);
527 currentOption = null;
528 }
529 }
530 }
531
532
533
534
535
536
537
538
539
540
541
542 private void handleShortAndLongOption(final String hyphenToken) throws ParseException {
543 final String token = Util.stripLeadingHyphens(hyphenToken);
544 final int pos = indexOfEqual(token);
545 if (token.length() == 1) {
546
547 if (options.hasShortOption(token)) {
548 handleOption(options.getOption(token));
549 } else {
550 handleUnknownToken(hyphenToken);
551 }
552 } else if (pos == -1) {
553
554 if (options.hasShortOption(token)) {
555 handleOption(options.getOption(token));
556 } else if (!getMatchingLongOptions(token).isEmpty()) {
557
558 handleLongOptionWithoutEqual(hyphenToken);
559 } else {
560
561 final String opt = getLongPrefix(token);
562
563 if (opt != null && options.getOption(opt).acceptsArg()) {
564 handleOption(options.getOption(opt));
565 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(opt.length())));
566 currentOption = null;
567 } else if (isJavaProperty(token)) {
568
569 handleOption(options.getOption(token.substring(0, 1)));
570 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(1)));
571 currentOption = null;
572 } else {
573
574 handleConcatenatedOptions(hyphenToken);
575 }
576 }
577 } else {
578
579 final String opt = token.substring(0, pos);
580 final String value = token.substring(pos + 1);
581
582 if (opt.length() == 1) {
583
584 final Option option = options.getOption(opt);
585 if (option != null && option.acceptsArg()) {
586 handleOption(option);
587 currentOption.processValue(value);
588 currentOption = null;
589 } else {
590 handleUnknownToken(hyphenToken);
591 }
592 } else if (isJavaProperty(opt)) {
593
594 handleOption(options.getOption(opt.substring(0, 1)));
595 currentOption.processValue(opt.substring(1));
596 currentOption.processValue(value);
597 currentOption = null;
598 } else {
599
600 handleLongOptionWithEqual(hyphenToken);
601 }
602 }
603 }
604
605
606
607
608
609
610
611 private void handleToken(final String token) throws ParseException {
612 if (token != null) {
613 currentToken = token;
614 if (skipParsing) {
615 addArg(token);
616 } else if ("--".equals(token)) {
617 skipParsing = true;
618 } else if (currentOption != null && currentOption.acceptsArg() && isArgument(token)) {
619 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOn(token));
620 } else if (token.startsWith("--")) {
621 handleLongOption(token);
622 } else if (token.startsWith("-") && !"-".equals(token)) {
623 handleShortAndLongOption(token);
624 } else {
625 handleUnknownToken(token);
626 }
627 if (currentOption != null && !currentOption.acceptsArg()) {
628 currentOption = null;
629 }
630 }
631 }
632
633
634
635
636
637
638
639
640
641
642 protected void handleUnknownToken(final String token) throws ParseException {
643 if (token.startsWith("-") && token.length() > 1 && nonOptionAction == NonOptionAction.THROW) {
644 throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
645 }
646 if (!token.startsWith("-") || token.equals("-") || token.length() > 1 && nonOptionAction != NonOptionAction.IGNORE) {
647 addArg(token);
648 }
649 if (nonOptionAction == NonOptionAction.STOP) {
650 skipParsing = true;
651 }
652 }
653
654
655
656
657
658
659 private boolean isArgument(final String token) {
660 return !isOption(token) || isNegativeNumber(token);
661 }
662
663
664
665
666 private boolean isJavaProperty(final String token) {
667 final String opt = token.isEmpty() ? null : token.substring(0, 1);
668 final Option option = options.getOption(opt);
669 return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES);
670 }
671
672
673
674
675
676
677 private boolean isLongOption(final String token) {
678 if (token == null || !token.startsWith("-") || token.length() == 1) {
679 return false;
680 }
681 final int pos = indexOfEqual(token);
682 final String t = pos == -1 ? token : token.substring(0, pos);
683 if (!getMatchingLongOptions(t).isEmpty()) {
684
685 return true;
686 }
687 if (getLongPrefix(token) != null && !token.startsWith("--")) {
688
689 return true;
690 }
691 return false;
692 }
693
694
695
696
697
698
699 private boolean isNegativeNumber(final String token) {
700 try {
701 Double.parseDouble(token);
702 return true;
703 } catch (final NumberFormatException e) {
704 return false;
705 }
706 }
707
708
709
710
711
712
713 private boolean isOption(final String token) {
714 return isLongOption(token) || isShortOption(token);
715 }
716
717
718
719
720
721
722 private boolean isShortOption(final String token) {
723
724 if (token == null || !token.startsWith("-") || token.length() == 1) {
725 return false;
726 }
727
728 final int pos = indexOfEqual(token);
729 final String optName = pos == -1 ? token.substring(1) : token.substring(1, pos);
730 if (options.hasShortOption(optName)) {
731 return true;
732 }
733
734 return !optName.isEmpty() && options.hasShortOption(String.valueOf(optName.charAt(0)));
735 }
736
737
738
739
740
741
742
743
744
745
746
747
748
749 public CommandLine parse(final Options options, final Properties properties, final NonOptionAction nonOptionAction, final String... arguments)
750 throws ParseException {
751 this.options = Objects.requireNonNull(options, "options");
752 this.nonOptionAction = nonOptionAction;
753 skipParsing = false;
754 currentOption = null;
755 expectedOpts = new ArrayList<>(options.getRequiredOptions());
756
757 for (final OptionGroup optionGroup : options.getOptionGroups()) {
758 optionGroup.setSelected(null);
759 }
760 cmd = CommandLine.builder().setDeprecatedHandler(deprecatedHandler).get();
761 if (arguments != null) {
762 for (final String argument : arguments) {
763 handleToken(argument);
764 }
765 }
766
767 checkRequiredArgs();
768
769 handleProperties(properties);
770 checkRequiredOptions();
771 return cmd;
772 }
773
774 @Override
775 public CommandLine parse(final Options options, final String[] arguments) throws ParseException {
776 return parse(options, arguments, null);
777 }
778
779
780
781
782 @Override
783 public CommandLine parse(final Options options, final String[] arguments, final boolean stopAtNonOption) throws ParseException {
784 return parse(options, arguments, null, stopAtNonOption);
785 }
786
787
788
789
790
791
792
793
794
795
796 public CommandLine parse(final Options options, final String[] arguments, final Properties properties) throws ParseException {
797 return parse(options, arguments, properties, false);
798 }
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813 public CommandLine parse(final Options options, final String[] arguments, final Properties properties, final boolean stopAtNonOption)
814 throws ParseException {
815 return parse(options, properties, stopAtNonOption ? NonOptionAction.STOP : NonOptionAction.THROW, arguments);
816 }
817
818
819
820
821
822
823
824
825 private String stripLeadingAndTrailingQuotesDefaultOff(final String token) {
826 if (stripLeadingAndTrailingQuotes != null && stripLeadingAndTrailingQuotes) {
827 return Util.stripLeadingAndTrailingQuotes(token);
828 }
829 return token;
830 }
831
832
833
834
835
836
837
838
839 private String stripLeadingAndTrailingQuotesDefaultOn(final String token) {
840 if (stripLeadingAndTrailingQuotes == null || stripLeadingAndTrailingQuotes) {
841 return Util.stripLeadingAndTrailingQuotes(token);
842 }
843 return token;
844 }
845
846
847
848
849
850
851 private void updateRequiredOptions(final Option option) throws AlreadySelectedException {
852 if (option.isRequired()) {
853 expectedOpts.remove(option.getKey());
854 }
855
856 if (options.getOptionGroup(option) != null) {
857 final OptionGroup optionGroup = options.getOptionGroup(option);
858 if (optionGroup.isRequired()) {
859 expectedOpts.remove(optionGroup);
860 }
861 optionGroup.setSelected(option);
862 }
863 }
864 }