1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package info.rolandkrueger.roklib.cli;
26
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashSet;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Set;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class CommandLineArgumentEvaluator
50 {
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public enum UserInputValidity
65 {
66
67
68
69
70
71
72
73 NOT_YET_EVALUATED,
74
75
76
77 OK,
78
79
80
81
82 UNRECOGNIZED_OPTION,
83
84
85
86 TOO_FEW_OPTIONS,
87
88
89
90
91 TOO_MANY_VALUES,
92
93
94
95
96
97
98
99 MISSING_ASSOCIATE_OPTION,
100
101
102
103
104
105
106
107
108 MISSING_ARGUMENT,
109
110
111
112
113
114
115
116
117
118
119
120
121
122 MUTUALLY_EXCLUSIVE_OPTIONS_GIVEN,
123
124
125
126
127
128
129
130
131
132
133
134
135
136 DUPLICATE_OPTION
137 };
138
139 private String mShortOptionsMarker = "-";
140 private String mLongOptionsMarker = "--";
141
142 private Set<CommandLineOption> mOptionSet;
143 private Set<CommandLineOption> mMandatoryList;
144 private String[] mArgs;
145 private UserInputValidity mStatus = UserInputValidity.NOT_YET_EVALUATED;
146 private boolean mEvaluated = false;
147 private boolean mAllowClusteredShortOptions = true;
148 private boolean mAllowStandaloneValues = false;
149 private String mDescription;
150 private List<String> mStandaloneValues;
151
152 private String mUnrecognizedOption;
153 private CommandLineOption mErroneousOption;
154 private List<CommandLineOption> mMutuallyExclusiveOptionsGiven;
155
156 public CommandLineArgumentEvaluator ()
157 {
158 mOptionSet = new HashSet<CommandLineOption> ();
159 mMandatoryList = new HashSet<CommandLineOption> ();
160 mDescription = "";
161 }
162
163
164
165
166
167
168
169
170
171 public CommandLineArgumentEvaluator (String[] arguments)
172 {
173 this ();
174 mArgs = arguments;
175 }
176
177 public void setArgumentList (String[] args)
178 {
179 if (args == null) throw new NullPointerException ("Argument list is null.");
180 mArgs = args;
181 }
182
183
184
185
186
187
188
189
190 public void addOptions (CommandLineOption... options)
191 {
192 for (CommandLineOption option : options)
193 {
194 for (CommandLineOption opt : mOptionSet)
195 {
196 if (opt.equals (option))
197 throw new IllegalArgumentException (String.format ("Option %s has already been added.",
198 option));
199 }
200 mOptionSet.add (option);
201 if (option.isMandatory ())
202 {
203 mMandatoryList.add (option);
204 }
205 }
206 }
207
208 public void allowClusteredShortOptions (boolean yesNo)
209 {
210 mAllowClusteredShortOptions = yesNo;
211 }
212
213 public void allowStandAloneValues (boolean yesNo)
214 {
215 mAllowStandaloneValues = yesNo;
216 }
217
218 public List<String> getStandaloneValues ()
219 {
220 if (! mAllowStandaloneValues)
221 throw new IllegalStateException ("Stand-alone values are not allowed for this evaluator.");
222
223 if (mStandaloneValues == null) return Collections.emptyList ();
224 return mStandaloneValues;
225 }
226
227
228
229
230
231
232
233
234 public void addOptions (Collection<CommandLineOption> options)
235 {
236 for (CommandLineOption option : options)
237 {
238 addOptions (option);
239 }
240 }
241
242
243
244
245
246
247 public Set<CommandLineOption> getOptions ()
248 {
249 return mOptionSet;
250 }
251
252
253
254
255
256
257
258
259
260
261
262 public void evaluate ()
263 {
264 if (mEvaluated) throw new IllegalStateException ("evaluate() has already been called.");
265 if (mArgs == null)
266 throw new IllegalStateException (
267 "No argument list has been set yet. Call setArgumentList() first.");
268
269 mEvaluated = true;
270 mStatus = UserInputValidity.OK;
271
272 List<String> argList = new ArrayList<String> (Arrays.asList (mArgs));
273
274
275
276 for (String value : new ArrayList<String> (argList))
277 {
278 if (value.indexOf ('=') != - 1)
279 {
280 List<String> splitList = splitAtEqualSign (value);
281 if (getOptionFor (splitList.get (0), true) != null)
282 {
283 int index = argList.indexOf (value);
284 argList.remove (value);
285 if (splitList.size () > 1) argList.add (index, splitList.get (1));
286 argList.add (index, splitList.get (0));
287 }
288 }
289 }
290
291 CommandLineOption currentOption = null;
292
293
294 for (String value : argList)
295 {
296
297 CommandLineOption option = getOptionFor (value, true);
298
299 if (option == null && currentOption == null)
300 {
301
302
303
304
305
306 if (mAllowClusteredShortOptions && value.startsWith (mShortOptionsMarker))
307 {
308 if (! disassembleFlagCluster (value))
309 {
310 if (setUnrecognizedOption (value)) return;
311 }
312 } else
313 {
314 if (setUnrecognizedOption (value)) return;
315 }
316 }
317
318 if (option == null && currentOption != null)
319 {
320
321
322
323 if (! currentOption.addUserInput (value) && ! disassembleFlagCluster (value))
324 {
325 mStatus = UserInputValidity.TOO_MANY_VALUES;
326 setErroneousOption (currentOption);
327 return;
328 }
329 continue;
330 }
331
332 if (option != null)
333 {
334 if (option.isFlag ())
335 {
336 if (option.isSet () && option.isOptionRepetitionAllowed ())
337 {
338 option.increaseRepetitions ();
339 } else if (option.isSet () && ! option.isOptionRepetitionAllowed ())
340 {
341 mStatus = UserInputValidity.DUPLICATE_OPTION;
342 setErroneousOption (option);
343 return;
344 } else
345 {
346 option.set ();
347 continue;
348 }
349 }
350 currentOption = option;
351 currentOption.set ();
352 }
353 }
354
355 for (CommandLineOption option : mMandatoryList)
356 {
357 if (! option.isSet ()) mStatus = UserInputValidity.TOO_FEW_OPTIONS;
358 }
359
360 for (CommandLineOption option : mOptionSet)
361 {
362 if (option.isSet ())
363 {
364 if ((option.getType () == CommandLineOptionType.LIST || option.getType () == CommandLineOptionType.SINGULAR)
365 && option.getInputList ().length == 0)
366 {
367 mStatus = UserInputValidity.MISSING_ARGUMENT;
368 setErroneousOption (option);
369 return;
370 }
371
372 for (CommandLineOption other : mOptionSet)
373 {
374 if (option.isMutuallyExclusiveWith (other) && other.isSet ())
375 {
376 mStatus = UserInputValidity.MUTUALLY_EXCLUSIVE_OPTIONS_GIVEN;
377 mMutuallyExclusiveOptionsGiven = new LinkedList<CommandLineOption> ();
378 mMutuallyExclusiveOptionsGiven.add (option);
379 mMutuallyExclusiveOptionsGiven.add (other);
380 return;
381 }
382
383 if (option.isAssociateTo (other) && ! other.isSet ())
384 {
385 mStatus = UserInputValidity.MISSING_ASSOCIATE_OPTION;
386 setErroneousOption (option);
387 return;
388 }
389 }
390 }
391 }
392 }
393
394 private boolean setUnrecognizedOption (String option)
395 {
396 if (mAllowStandaloneValues)
397 {
398 if (mStandaloneValues == null) mStandaloneValues = new LinkedList<String> ();
399 mStandaloneValues.add (option);
400 return false;
401 }
402 mUnrecognizedOption = option;
403 mStatus = UserInputValidity.UNRECOGNIZED_OPTION;
404 return true;
405 }
406
407 public String getUnrecognizedOption ()
408 {
409 if (mStatus != UserInputValidity.UNRECOGNIZED_OPTION)
410 throw new IllegalStateException ("No unrecognized option was given.");
411 return mUnrecognizedOption;
412 }
413
414 private void setErroneousOption (CommandLineOption erroneous)
415 {
416 mErroneousOption = erroneous;
417 }
418
419 public CommandLineOption getErroneousOption ()
420 {
421 if (mErroneousOption == null)
422 throw new IllegalStateException ("No option has been set incorrectly.");
423 return mErroneousOption;
424 }
425
426 public String getDescriptiveSummaryFor (CommandLineOption option)
427 {
428 return getDescriptiveSummaryFor (option, "\n\t\t");
429 }
430
431 public String getDescriptiveSummaryFor (CommandLineOption option, String descriptionSeparator)
432 {
433 String description = option.getDescription ();
434 String longOption = "";
435 if (! option.getLongOption ().equals (""))
436 longOption = String.format ("%s%s", getLongOptionsMarker (), option.getLongOption ());
437 String shortOption = "";
438 if (! option.getShortOption ().equals (""))
439 shortOption = String.format ("%s%s", getShortOptionsMarker (), option.getShortOption ());
440
441 String separator = description.equals ("") ? "" : descriptionSeparator;
442 if (shortOption.equals (""))
443 return String.format ("%s%s%s", longOption, separator, description);
444
445 if (longOption.equals (""))
446 return String.format ("%s%s%s", shortOption, separator, description);
447
448 return String.format ("%s, %s%s%s", shortOption, longOption, separator, description);
449 }
450
451 public List<CommandLineOption> getMutuallyExclusiveOptionsBeingUsed ()
452 {
453 if (mMutuallyExclusiveOptionsGiven == null)
454 throw new IllegalStateException ("No mutually exclusive options have been used");
455 return mMutuallyExclusiveOptionsGiven;
456 }
457
458 private boolean disassembleFlagCluster (String value)
459 {
460 List<CommandLineOption> optionsToSet = new LinkedList<CommandLineOption> ();
461 boolean result = true;
462
463 value = value.replaceAll (mShortOptionsMarker, "");
464 for (int i = 0; i < value.length (); ++i)
465 {
466 String flag = String.valueOf (value.charAt (i));
467 CommandLineOption option = getOptionFor (flag, false);
468 if (option == null)
469 {
470 result = false;
471 break;
472 } else
473 {
474 optionsToSet.add (option);
475 }
476 }
477
478 if (result)
479 {
480 for (CommandLineOption option : optionsToSet)
481 {
482 if (option.isOptionRepetitionAllowed ())
483 {
484 option.increaseRepetitions ();
485 } else if (option.isSet ())
486 {
487 mStatus = UserInputValidity.DUPLICATE_OPTION;
488 setErroneousOption (option);
489 result = false;
490 }
491 option.set ();
492 }
493 }
494
495 return result;
496 }
497
498 private List<String> splitAtEqualSign (String value)
499 {
500 List<String> result = new LinkedList<String> ();
501 assert value.indexOf ('=') != - 1;
502
503 result.add (value.substring (0, value.indexOf ('=')));
504 result.add (value.substring (value.indexOf ('=') + 1));
505 return result;
506 }
507
508 private CommandLineOption getOptionFor (String value, boolean valueContainsMarker)
509 {
510 String longOption;
511 String shortOption;
512
513 for (CommandLineOption option : mOptionSet)
514 {
515 longOption = valueContainsMarker ? mLongOptionsMarker + option.getLongOption () : option
516 .getLongOption ();
517 shortOption = valueContainsMarker ? mShortOptionsMarker + option.getShortOption () : option
518 .getShortOption ();
519 if (shortOption.equals (value) || longOption.equals (value))
520 {
521 return option;
522 }
523 }
524
525 return null;
526 }
527
528 public boolean isValid ()
529 {
530 return getInputStatus () == UserInputValidity.OK;
531 }
532
533 public void evaluateAndInvokeCallbacks ()
534 {
535 evaluate ();
536 invokeCallbacks ();
537 }
538
539 public void invokeCallbacks ()
540 {
541 if (! mEvaluated)
542 throw new IllegalStateException (
543 "Command line arguments havn't been evaluated yet. Call evaluate() first.");
544
545 for (CommandLineOption option : mOptionSet)
546 {
547 if (option.isSet ())
548 {
549 option.invokeCallback (this);
550 }
551 }
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565
566 public UserInputValidity getInputStatus ()
567 {
568 if (mStatus == UserInputValidity.NOT_YET_EVALUATED)
569 throw new IllegalStateException (
570 "Command line options haven't been evaluated yet. Call evaluate() first.");
571 return mStatus;
572 }
573
574
575
576
577
578
579
580
581
582 public String getOptionDescription ()
583 {
584 StringBuffer result = new StringBuffer ();
585 String leftBracket;
586 String rightBracket;
587 String argument;
588 String optionString;
589 for (CommandLineOption option : mOptionSet)
590 {
591 if (option.isMandatory ())
592 {
593 leftBracket = "";
594 rightBracket = "";
595 } else
596 {
597 leftBracket = "[";
598 rightBracket = "]";
599 }
600
601 if (option.getType () == CommandLineOptionType.LIST)
602 argument = " argument_list...";
603 else if (option.getType () == CommandLineOptionType.SINGULAR)
604 argument = " argument";
605 else
606 argument = "";
607
608 if (option.getShortOption ().equals (""))
609 optionString = option.getLongOption ();
610 else if (option.getLongOption ().equals (""))
611 optionString = option.getShortOption ();
612 else
613 optionString = String.format ("(%s|%s)", option.getShortOption (), option.getLongOption ());
614
615 result
616 .append (String.format ("%s%s%s%s ", leftBracket, optionString, argument, rightBracket));
617 }
618 return result.toString ();
619 }
620
621
622
623
624
625
626
627
628
629 public List<CommandLineOption> getMissingOptions ()
630 {
631 ArrayList<CommandLineOption> result = new ArrayList<CommandLineOption> ();
632 for (CommandLineOption option : mOptionSet)
633 {
634 if (! option.isSet () && option.isMandatory ()) result.add (option);
635 }
636 return result;
637 }
638
639 public String getShortOptionsMarker ()
640 {
641 return mShortOptionsMarker;
642 }
643
644 public void setShortOptionsMarker (String shortOptionsMarker)
645 {
646 if (shortOptionsMarker == null)
647 throw new NullPointerException ("Short options marker is null.");
648
649 if (mEvaluated) throw new IllegalStateException ("Arguments have already been evaluated.");
650
651 mShortOptionsMarker = shortOptionsMarker;
652 }
653
654 public String getLongOptionsMarker ()
655 {
656 return mLongOptionsMarker;
657 }
658
659 public void setDescription (String description)
660 {
661 if (description == null) mDescription = "";
662 mDescription = description;
663 }
664
665 public String getDescription ()
666 {
667 return mDescription;
668 }
669
670 public void setLongOptionsMarker (String longOptionsMarker)
671 {
672 if (longOptionsMarker == null) throw new NullPointerException ("Long options marker is null.");
673
674 if (mEvaluated) throw new IllegalStateException ("Arguments have already been evaluated.");
675
676 mLongOptionsMarker = longOptionsMarker;
677 }
678 }