1 package com.trendmicro.grid.acl.client.util;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.io.PrintStream;
7 import java.lang.reflect.Method;
8 import java.net.URL;
9 import java.nio.charset.Charset;
10 import java.util.*;
11
12
13
14
15
16
17
18 public class CommandlineParser {
19
20 final List<Parameter> parameterDefinitions = new ArrayList<Parameter>();
21 final Map<Parameter, Object> values = new HashMap<Parameter, Object>();
22
23
24
25
26
27
28
29 public static String[] loadHelp(URL helpURL) {
30 try {
31 BufferedReader reader = null;
32 try {
33 String line;
34 List<String> lines = new ArrayList<String>();
35 reader = new BufferedReader(new InputStreamReader(helpURL.openStream(), Charset.forName("UTF-8")));
36 while ((line = reader.readLine()) != null)
37 lines.add(line);
38 return lines.toArray(new String[lines.size()]);
39 } finally {
40 if (reader != null) reader.close();
41 }
42 } catch (IOException e) {
43 throw new RuntimeException(e);
44 }
45 }
46
47
48
49
50 public CommandlineParser() {
51 defineSwitchParameter("Prints help.", "-h", "--help", "/?");
52 }
53
54
55
56
57
58
59
60
61 public CommandlineParser defineSwitchParameter(String description, String... names) {
62 return defineParameter(description, false, Boolean.class, false, names);
63 }
64
65
66
67
68
69
70
71
72
73
74
75 public CommandlineParser defineParameter(String description,
76 boolean required, Class type, Object defaultValue, String... names) {
77 parameterDefinitions.add(new Parameter(description, required, type, defaultValue, names));
78 return this;
79 }
80
81
82
83
84
85
86
87
88
89 public boolean parse(String[] args, PrintStream out, URL help) {
90 return parse(args, out, loadHelp(help));
91 }
92
93
94
95
96
97
98
99
100
101 public boolean parse(String[] args, PrintStream out, String[] help) {
102 values.clear();
103 boolean printHelp = args.length == 0;
104
105 for (int i = 0; i < args.length; i++) {
106 final String arg = args[i];
107 final Parameter definition = getParameterDefinition(arg);
108 if (!Boolean.class.equals(definition.type)) {
109 if (i == args.length - 1 || findParameterDefinition(args[i + 1]) != null)
110 throw new IllegalArgumentException("Expected a value for parameter '" + arg + "' but non was given.");
111 Object value = parseInputValue(definition, args[++i]);
112 Object[] existing = (Object[]) values.put(definition, new Object[]{value});
113
114 if (existing != null) {
115 Object[] values = new Object[existing.length + 1];
116 System.arraycopy(existing, 0, values, 0, existing.length);
117 values[values.length - 1] = value;
118 this.values.put(definition, values);
119 }
120 } else
121 values.put(definition, new Object[]{Boolean.TRUE});
122 }
123
124 printHelp = printHelp || isParameterTrue("-h");
125 if (printHelp) {
126 for (String s : help)
127 out.println(s);
128 printHelp(out);
129 out.println();
130 return false;
131 }
132
133 final List<String> missingParams = new ArrayList<String>();
134 for (Parameter definition : parameterDefinitions) {
135 if (definition.required && !values.containsKey(definition))
136 missingParams.add(definition.names.iterator().next());
137 }
138
139 if (!missingParams.isEmpty()) {
140 throw new IllegalArgumentException("The parameters " + missingParams + " were not set.\n" +
141 "See command line help for more info.");
142 }
143
144 return true;
145 }
146
147 private Object parseInputValue(Parameter definition, String rawValue) {
148 if (String.class.equals(definition.type))
149 return rawValue;
150 try {
151 final Method method = definition.type.getMethod("valueOf", String.class);
152 return method.invoke(null, rawValue);
153 } catch (Exception e) {
154 try {
155 return definition.type.getConstructor(String.class).newInstance(rawValue);
156 } catch (Exception e1) {
157 throw new IllegalStateException("Failed converting '" +
158 rawValue + "' to target type " + definition.type);
159 }
160 }
161 }
162
163
164
165
166
167
168
169 public boolean isParameterTrue(String name) {
170 return Boolean.TRUE.equals(getParameter(name));
171 }
172
173
174
175
176
177
178
179
180 @SuppressWarnings("unchecked")
181 public <T> T getParameter(String name, Class<T> type) {
182 return (T) getParameter(name);
183 }
184
185
186
187
188
189
190
191 public Object getParameter(String name) {
192 final Parameter p = getParameterDefinition(name);
193 Object[] value = (Object[]) values.get(p);
194 return value == null ? p.defaultValue : value[0];
195 }
196
197
198
199
200
201
202
203
204 @SuppressWarnings("unchecked")
205 public <T> List<T> getParameterValues(String name, Class<T> type) {
206 Object[] parameterValues = getParameterValues(name);
207 return parameterValues == null ? null : (List<T>) Arrays.asList(parameterValues);
208 }
209
210
211
212
213
214
215
216 public Object[] getParameterValues(String name) {
217 final Parameter p = getParameterDefinition(name);
218 Object[] value = (Object[]) values.get(p);
219 return value == null ? (p.defaultValue == null ? null : new Object[]{p.defaultValue}) : value;
220 }
221
222 private Parameter getParameterDefinition(String name) {
223 final Parameter p = findParameterDefinition(name);
224 if (p == null)
225 throw new IllegalArgumentException("Parameter '" + name + "' is unknown.");
226 return p;
227 }
228
229 private Parameter findParameterDefinition(String name) {
230 for (Parameter definition : parameterDefinitions)
231 if (definition.names.contains(name))
232 return definition;
233 return null;
234 }
235
236
237
238
239
240
241 public void printHelp(PrintStream out) {
242 out.println("Options:");
243
244 int colWidth = 2;
245 for (Parameter definition : parameterDefinitions) {
246 for (String name : definition.names)
247 colWidth = Math.max(colWidth, name.length() + 2);
248 }
249
250 for (Parameter definition : parameterDefinitions) {
251 final List<String> leftColumn = new ArrayList<String>(), rightColumn = new ArrayList<String>();
252 Iterator<String> nI = definition.names.iterator();
253 for (String name; nI.hasNext(); ) {
254 name = nI.next();
255 if (nI.hasNext())
256 name = name + ",";
257 if (!leftColumn.isEmpty())
258 name = " " + name;
259 addWord(leftColumn, colWidth, name);
260 }
261
262 final StringTokenizer tokenizer = new StringTokenizer(definition.description, ",. -\t\n", true);
263 while (tokenizer.hasMoreTokens())
264 addWord(rightColumn, 80 - colWidth, tokenizer.nextToken());
265
266 if (definition.defaultValue != null && !Boolean.class.equals(definition.type))
267 rightColumn.add(String.format("Default: '%s'", definition.defaultValue));
268
269 out.println();
270 printColumns(out, leftColumn, rightColumn, colWidth);
271 }
272 }
273
274 private void printColumns(PrintStream out, List<String> leftColumn, List<String> rightColumn, int columnWidth) {
275 final Iterator<String> lI = leftColumn.iterator(), rI = rightColumn.iterator();
276 final String formatPattern = "%" + columnWidth + "s %s%n";
277 while (lI.hasNext() || rI.hasNext())
278 out.printf(formatPattern, lI.hasNext() ? lI.next() : "", rI.hasNext() ? rI.next() : "");
279 }
280
281 private void addWord(List<String> lines, int maxWidth, String word) {
282 int idx = lines.size() - 1;
283 String currentLine = idx == -1 ? null : lines.get(idx);
284 if (currentLine != null && currentLine.length() + word.length() < maxWidth) {
285 if (word.equals("\n"))
286 lines.add("");
287 else {
288 currentLine += word;
289 lines.set(idx, currentLine);
290 }
291 } else
292 lines.add(word);
293 }
294
295 private final static class Parameter {
296
297 Set<String> names;
298 String description;
299 boolean required;
300
301 Class type;
302 Object defaultValue;
303
304 private Parameter(String description, boolean required, Class type, Object defaultValue, String... names) {
305 this.required = required;
306 this.names = new LinkedHashSet<String>(Arrays.asList(names));
307 this.description = description;
308 this.type = type;
309 this.defaultValue = defaultValue;
310 }
311
312 @Override
313 public boolean equals(Object o) {
314 if (this == o) return true;
315 if (!(o instanceof Parameter)) return false;
316 Parameter parameter = (Parameter) o;
317 return names.equals(parameter.names);
318 }
319
320 @Override
321 public int hashCode() {
322 return names.hashCode();
323 }
324 }
325 }