1
+ package com .google .udmi .util ;
2
+
3
+ import static com .google .common .base .Preconditions .checkState ;
4
+ import static com .google .udmi .util .GeneralUtils .ifNotNullThen ;
5
+ import static com .google .udmi .util .GeneralUtils .ifNotNullThrow ;
6
+ import static java .lang .String .CASE_INSENSITIVE_ORDER ;
7
+
8
+ import java .lang .reflect .Method ;
9
+ import java .lang .reflect .Type ;
10
+ import java .util .ArrayList ;
11
+ import java .util .List ;
12
+ import java .util .Map ;
13
+ import java .util .Map .Entry ;
14
+ import java .util .Optional ;
15
+ import java .util .TreeMap ;
16
+
17
+ /**
18
+ * Simple command line parser class.
19
+ */
20
+ public class CommandLineProcessor {
21
+
22
+ private static final String TERMINATING_ARG = "--" ;
23
+ private static final String EQUALS_SIGN = "=" ;
24
+ private static final int LINUX_SUCCESS_CODE = 0 ;
25
+ private static final int LINUX_ERROR_CODE = -1 ;
26
+ private final Object target ;
27
+ private final Method showHelpMethod ;
28
+
29
+ Map <CommandLineOption , Method > optionMap = new TreeMap <>(
30
+ (a , b ) -> CASE_INSENSITIVE_ORDER .compare (getSortArg (a ), getSortArg (b )));
31
+
32
+ private static String getSortArg (CommandLineOption k ) {
33
+ String shortForm = k .short_form ();
34
+ return shortForm .isBlank () ? k .long_form ().substring (1 , 2 ) : shortForm .substring (1 , 2 );
35
+ }
36
+
37
+ /**
38
+ * Create a new command line processor for the given target object.
39
+ */
40
+ public CommandLineProcessor (Object target ) {
41
+ this .target = target ;
42
+
43
+ showHelpMethod = getShowHelpMethod ();
44
+ optionMap .put (getShowHelpOption (), showHelpMethod );
45
+
46
+ List <Method > methods = new ArrayList <>(List .of (target .getClass ().getDeclaredMethods ()));
47
+ methods .forEach (method -> ifNotNullThen (method .getAnnotation (CommandLineOption .class ),
48
+ a -> optionMap .put (a , method )));
49
+ optionMap .values ().forEach (method -> method .setAccessible (true ));
50
+ }
51
+
52
+ private Method getShowHelpMethod () {
53
+ try {
54
+ return CommandLineProcessor .class .getDeclaredMethod ("showUsage" );
55
+ } catch (Exception e ) {
56
+ throw new RuntimeException ("While getting showHelp method" , e );
57
+ }
58
+ }
59
+
60
+ private CommandLineOption getShowHelpOption () {
61
+ return showHelpMethod .getAnnotation (CommandLineOption .class );
62
+ }
63
+
64
+ @ CommandLineOption (short_form = "-h" , description = "Show help and exit" )
65
+ private void showUsage () {
66
+ showUsage (null );
67
+ }
68
+
69
+ /**
70
+ * Show program usage.
71
+ */
72
+ public void showUsage (String message ) {
73
+ ifNotNullThen (message , m -> System .err .println (m ));
74
+ System .err .println ("Options supported:" );
75
+ optionMap .forEach ((option , method ) -> System .err .printf (" %s %1s %s%n" ,
76
+ option .short_form (), option .arg_type (), option .description ()));
77
+ System .exit (message == null ? LINUX_SUCCESS_CODE : LINUX_ERROR_CODE );
78
+ }
79
+
80
+ /**
81
+ * Process the given arg list. Return a list of remaining arguments (if any).
82
+ */
83
+ public List <String > processArgs (List <String > argList ) {
84
+ try {
85
+ while (!argList .isEmpty ()) {
86
+ String arg = argList .remove (0 );
87
+ if (arg .equals (TERMINATING_ARG )) {
88
+ return argList ;
89
+ }
90
+ Optional <Entry <CommandLineOption , Method >> first = optionMap .entrySet ().stream ()
91
+ .filter (option -> processArgEntry (arg , option .getKey (), option .getValue (), argList ))
92
+ .findFirst ();
93
+ if (first .isEmpty ()) {
94
+ throw new IllegalArgumentException ("Unrecognized command line option '" + arg + "'" );
95
+ }
96
+ if (!arg .startsWith ("-" )) {
97
+ argList .add (0 , arg );
98
+ return argList ;
99
+ }
100
+ }
101
+ return null ;
102
+ } catch (Exception e ) {
103
+ showUsage (e .getMessage ());
104
+ return null ;
105
+ }
106
+ }
107
+
108
+ private boolean processArgEntry (String arg , CommandLineOption option , Method method ,
109
+ List <String > argList ) {
110
+ try {
111
+ if (arg .equals (option .short_form ())) {
112
+ if (method .equals (showHelpMethod )) {
113
+ showUsage ();
114
+ } else if (requiresArg (method )) {
115
+ checkState (!option .arg_type ().isBlank (), "Option with argument missing type parameter" );
116
+ String parameter = argList .remove (0 );
117
+ method .invoke (target , parameter );
118
+ } else {
119
+ method .invoke (target );
120
+ }
121
+ return true ;
122
+ } else if (!option .long_form ().isBlank () && arg .startsWith (option .long_form ())) {
123
+ throw new IllegalArgumentException ("Long form command line not yet supported" );
124
+ } else if (option .short_form ().isBlank () && option .long_form ().isBlank ()) {
125
+ throw new IllegalArgumentException (
126
+ "Neither long nor short form not defined for " + method .getName ());
127
+ } else if (!arg .startsWith ("-" )) {
128
+ return true ;
129
+ }
130
+ return false ;
131
+ } catch (Exception e ) {
132
+ throw new IllegalArgumentException ("Processing command line method " + method .getName (), e );
133
+ }
134
+ }
135
+
136
+ private void cleanExit (boolean success ) {
137
+ System .exit (success ? LINUX_SUCCESS_CODE : LINUX_ERROR_CODE );
138
+ }
139
+
140
+ private boolean requiresArg (Method method ) {
141
+ Type [] genericParameterTypes = method .getGenericParameterTypes ();
142
+ checkState (genericParameterTypes .length <= 1 ,
143
+ "expected <= 1 parameter for command line method %s" , method .getName ());
144
+ return genericParameterTypes .length == 1 ;
145
+ }
146
+ }
0 commit comments