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.Properties;
24 import java.util.function.Consumer;
25
26
27
28
29
30
31 public class DefaultParser implements CommandLineParser {
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public static final class Builder {
48
49
50 private boolean allowPartialMatching = true;
51
52
53
54
55
56
57
58 private Consumer<Option> deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
59
60
61 private Boolean stripLeadingAndTrailingQuotes;
62
63
64
65
66
67
68
69 private Builder() {
70 }
71
72
73
74
75
76
77
78 public DefaultParser build() {
79 return new DefaultParser(allowPartialMatching, stripLeadingAndTrailingQuotes, deprecatedHandler);
80 }
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 public Builder setAllowPartialMatching(final boolean allowPartialMatching) {
105 this.allowPartialMatching = allowPartialMatching;
106 return this;
107 }
108
109
110
111
112
113
114
115
116 public Builder setDeprecatedHandler(final Consumer<Option> deprecatedHandler) {
117 this.deprecatedHandler = deprecatedHandler;
118 return this;
119 }
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public Builder setStripLeadingAndTrailingQuotes(final Boolean stripLeadingAndTrailingQuotes) {
137 this.stripLeadingAndTrailingQuotes = stripLeadingAndTrailingQuotes;
138 return this;
139 }
140 }
141
142
143
144
145
146
147
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
158 protected CommandLine cmd;
159
160
161 protected Options options;
162
163
164
165
166
167 protected boolean stopAtNonOption;
168
169
170 protected String currentToken;
171
172
173 protected Option currentOption;
174
175
176 protected boolean skipParsing;
177
178
179 protected List expectedOpts;
180
181
182 private final boolean allowPartialMatching;
183
184
185
186 private final Boolean stripLeadingAndTrailingQuotes;
187
188
189
190
191
192
193
194 private final Consumer<Option> deprecatedHandler;
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 public DefaultParser() {
216 this.allowPartialMatching = true;
217 this.stripLeadingAndTrailingQuotes = null;
218 this.deprecatedHandler = CommandLine.Builder.DEPRECATED_HANDLER;
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
250
251
252
253
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
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
275
276
277
278 protected void checkRequiredOptions() throws MissingOptionException {
279
280 if (!expectedOpts.isEmpty()) {
281 throw new MissingOptionException(expectedOpts);
282 }
283 }
284
285
286
287
288
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
306
307
308
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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
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
351 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(i + 1)));
352 break;
353 }
354 }
355 }
356
357
358
359
360
361
362
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
374
375
376
377
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
403
404
405
406
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
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
431
432
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
445 final OptionGroup group = options.getOptionGroup(opt);
446 final boolean selected = group != null && group.isSelected();
447 if (!cmd.hasOption(option) && !selected) {
448
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
457 continue;
458 }
459 handleOption(opt);
460 currentOption = null;
461 }
462 }
463 }
464
465
466
467
468
469
470
471
472
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
479 if (options.hasShortOption(token)) {
480 handleOption(options.getOption(token));
481 } else {
482 handleUnknownToken(hyphenToken);
483 }
484 } else if (pos == -1) {
485
486 if (options.hasShortOption(token)) {
487 handleOption(options.getOption(token));
488 } else if (!getMatchingLongOptions(token).isEmpty()) {
489
490 handleLongOptionWithoutEqual(hyphenToken);
491 } else {
492
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
501 handleOption(options.getOption(token.substring(0, 1)));
502 currentOption.processValue(stripLeadingAndTrailingQuotesDefaultOff(token.substring(1)));
503 currentOption = null;
504 } else {
505
506 handleConcatenatedOptions(hyphenToken);
507 }
508 }
509 } else {
510
511 final String opt = token.substring(0, pos);
512 final String value = token.substring(pos + 1);
513
514 if (opt.length() == 1) {
515
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
526 handleOption(options.getOption(opt.substring(0, 1)));
527 currentOption.processValue(opt.substring(1));
528 currentOption.processValue(value);
529 currentOption = null;
530 } else {
531
532 handleLongOptionWithEqual(hyphenToken);
533 }
534 }
535 }
536
537
538
539
540
541
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
567
568
569
570
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
584
585
586
587 private boolean isArgument(final String token) {
588 return !isOption(token) || isNegativeNumber(token);
589 }
590
591
592
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
602
603
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
613 return true;
614 }
615 if (getLongPrefix(token) != null && !token.startsWith("--")) {
616
617 return true;
618 }
619 return false;
620 }
621
622
623
624
625
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
638
639
640
641 private boolean isOption(final String token) {
642 return isLongOption(token) || isShortOption(token);
643 }
644
645
646
647
648
649
650 private boolean isShortOption(final String token) {
651
652 if (token == null || !token.startsWith("-") || token.length() == 1) {
653 return false;
654 }
655
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
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
677
678
679
680
681
682
683
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
691
692
693
694
695
696
697
698
699
700
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
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
720 checkRequiredArgs();
721
722 handleProperties(properties);
723 checkRequiredOptions();
724 return cmd;
725 }
726
727
728
729
730
731
732
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
743
744
745
746
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
757
758
759
760 private void updateRequiredOptions(final Option option) throws AlreadySelectedException {
761 if (option.isRequired()) {
762 expectedOpts.remove(option.getKey());
763 }
764
765
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 }