1 package eu.fbk.knowledgestore.internal;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.PrintWriter;
7 import java.net.URL;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Properties;
13 import java.util.Set;
14
15 import javax.annotation.Nullable;
16
17 import com.google.common.base.Joiner;
18 import com.google.common.base.MoreObjects;
19 import com.google.common.base.Preconditions;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Maps;
23 import com.google.common.collect.Ordering;
24
25 import org.apache.commons.cli.DefaultParser;
26 import org.apache.commons.cli.HelpFormatter;
27 import org.apache.commons.cli.Option;
28 import org.apache.commons.cli.Options;
29 import org.slf4j.Logger;
30
31 import ch.qos.logback.classic.Level;
32
33 import eu.fbk.knowledgestore.data.Data;
34
35 public final class CommandLine {
36
37 private final List<String> args;
38
39 private final List<String> options;
40
41 private final Map<String, List<String>> optionValues;
42
43 private CommandLine(final List<String> args, final Map<String, List<String>> optionValues) {
44
45 final List<String> options = Lists.newArrayList();
46 for (final String letterOrName : optionValues.keySet()) {
47 if (letterOrName.length() > 1) {
48 options.add(letterOrName);
49 }
50 }
51
52 this.args = args;
53 this.options = Ordering.natural().immutableSortedCopy(options);
54 this.optionValues = optionValues;
55 }
56
57 public <T> List<T> getArgs(final Class<T> type) {
58 return convert(this.args, type);
59 }
60
61 public <T> T getArg(final int index, final Class<T> type) {
62 return convert(this.args.get(index), type);
63 }
64
65 public <T> T getArg(final int index, final Class<T> type, final T defaultValue) {
66 try {
67 return convert(this.args.get(index), type);
68 } catch (final Throwable ex) {
69 return defaultValue;
70 }
71 }
72
73 public int getArgCount() {
74 return this.args.size();
75 }
76
77 public List<String> getOptions() {
78 return this.options;
79 }
80
81 public boolean hasOption(final String letterOrName) {
82 return this.optionValues.containsKey(letterOrName);
83 }
84
85 public <T> List<T> getOptionValues(final String letterOrName, final Class<T> type) {
86 final List<String> strings = MoreObjects.firstNonNull(this.optionValues.get(letterOrName),
87 ImmutableList.<String>of());
88 return convert(strings, type);
89 }
90
91 @Nullable
92 public <T> T getOptionValue(final String letterOrName, final Class<T> type) {
93 final List<String> strings = this.optionValues.get(letterOrName);
94 if (strings == null || strings.isEmpty()) {
95 return null;
96 }
97 if (strings.size() > 1) {
98 throw new Exception("Multiple values for option '" + letterOrName + "': "
99 + Joiner.on(", ").join(strings), null);
100 }
101 try {
102 return convert(strings.get(0), type);
103 } catch (final Throwable ex) {
104 throw new Exception("'" + strings.get(0) + "' is not a valid " + type.getSimpleName(),
105 ex);
106 }
107 }
108
109 @Nullable
110 public <T> T getOptionValue(final String letterOrName, final Class<T> type,
111 @Nullable final T defaultValue) {
112 final List<String> strings = this.optionValues.get(letterOrName);
113 if (strings == null || strings.isEmpty() || strings.size() > 1) {
114 return defaultValue;
115 }
116 try {
117 return convert(strings.get(0), type);
118 } catch (final Throwable ex) {
119 return defaultValue;
120 }
121 }
122
123 public int getOptionCount() {
124 return this.options.size();
125 }
126
127 private static <T> T convert(final String string, final Class<T> type) {
128 try {
129 return Data.convert(string, type);
130 } catch (final Throwable ex) {
131 throw new Exception("'" + string + "' is not a valid " + type.getSimpleName(), ex);
132 }
133 }
134
135 @SuppressWarnings("unchecked")
136 private static <T> List<T> convert(final List<String> strings, final Class<T> type) {
137 if (type == String.class) {
138 return (List<T>) strings;
139 }
140 final List<T> list = Lists.newArrayList();
141 for (final String string : strings) {
142 list.add(convert(string, type));
143 }
144 return ImmutableList.copyOf(list);
145 }
146
147 public static void fail(final Throwable throwable) {
148 if (throwable instanceof Exception) {
149 if (throwable.getMessage() == null) {
150 System.exit(0);
151 } else {
152 System.err.println("SYNTAX ERROR: " + throwable.getMessage());
153 }
154 System.exit(-2);
155 } else {
156 System.err.println("EXECUTION FAILED: " + throwable.getMessage());
157 throwable.printStackTrace();
158 System.exit(-1);
159 }
160 }
161
162 public static Parser parser() {
163 return new Parser();
164 }
165
166 public static final class Parser {
167
168 @Nullable
169 private String name;
170
171 @Nullable
172 private String header;
173
174 @Nullable
175 private String footer;
176
177 @Nullable
178 private Logger logger;
179
180 private final Options options;
181
182 private final Map<Option, Type> optionTypes;
183
184 private final Set<String> mandatoryOptions;
185
186 public Parser() {
187 this.name = null;
188 this.header = null;
189 this.footer = null;
190 this.options = new Options();
191 this.optionTypes = new HashMap<>();
192 this.mandatoryOptions = new HashSet<>();
193 }
194
195 public Parser withName(@Nullable final String name) {
196 this.name = name;
197 return this;
198 }
199
200 public Parser withHeader(@Nullable final String header) {
201 this.header = header;
202 return this;
203 }
204
205 public Parser withFooter(@Nullable final String footer) {
206 this.footer = footer;
207 return this;
208 }
209
210 public Parser withLogger(@Nullable final Logger logger) {
211 this.logger = logger;
212 return this;
213 }
214
215 public Parser withOption(@Nullable final String letter, final String name,
216 final String description) {
217
218 Preconditions.checkNotNull(name);
219 Preconditions.checkArgument(name.length() > 1);
220 Preconditions.checkNotNull(description);
221
222 final Option option = new Option(letter, name, false, description);
223 this.options.addOption(option);
224
225 return this;
226 }
227
228 public Parser withOption(@Nullable final String letter, final String name,
229 final String description, final String argName, @Nullable final Type argType,
230 final boolean argRequired, final boolean multiValue, final boolean mandatory) {
231
232 Preconditions.checkNotNull(name);
233 Preconditions.checkArgument(name.length() > 1);
234 Preconditions.checkNotNull(description);
235 Preconditions.checkNotNull(argName);
236
237 final Option option = new Option(letter, name, true, description);
238 option.setArgName(argName);
239 option.setOptionalArg(!argRequired);
240 option.setArgs(multiValue ? Short.MAX_VALUE : 1);
241 this.options.addOption(option);
242
243 if (argType != null) {
244 this.optionTypes.put(option, argType);
245 }
246
247 if (mandatory) {
248 this.mandatoryOptions.add(name);
249 }
250
251 return this;
252 }
253
254 public CommandLine parse(final String... args) {
255
256 try {
257
258 if (this.logger != null) {
259 this.options.addOption("V", "verbose", false, "enable verbose output");
260 }
261 this.options.addOption("v", "version", false,
262 "display version information and terminate");
263 this.options.addOption("h", "help", false,
264 "display this help message and terminate");
265
266
267 org.apache.commons.cli.CommandLine cmd = null;
268 try {
269 cmd = new DefaultParser().parse(this.options, args);
270 } catch (final Throwable ex) {
271 System.err.println("SYNTAX ERROR: " + ex.getMessage());
272 printHelp();
273 throw new Exception(null);
274 }
275
276
277 if (cmd.hasOption('V')) {
278 try {
279 ((ch.qos.logback.classic.Logger) this.logger).setLevel(Level.DEBUG);
280 } catch (final Throwable ex) {
281
282 }
283 }
284
285
286 if (cmd.hasOption('v')) {
287 printVersion();
288 throw new Exception(null);
289
290 } else if (cmd.hasOption('h')) {
291 printHelp();
292 throw new Exception(null);
293 }
294
295
296 for (final String name : this.mandatoryOptions) {
297 if (!cmd.hasOption(name)) {
298 System.err.println("SYNTAX ERROR: missing mandatory option " + name);
299 printHelp();
300 throw new Exception(null);
301 }
302 }
303
304
305 final Map<String, List<String>> optionValues = Maps.newHashMap();
306 for (final Option option : cmd.getOptions()) {
307 final List<String> valueList = Lists.newArrayList();
308 final String[] values = cmd.getOptionValues(option.getLongOpt());
309 if (values != null) {
310 final Type type = this.optionTypes.get(option);
311 for (final String value : values) {
312 if (type != null) {
313 Type.validate(value, type);
314 }
315 valueList.add(value);
316 }
317 }
318 final List<String> valueSet = ImmutableList.copyOf(valueList);
319 optionValues.put(option.getLongOpt(), valueSet);
320 if (option.getOpt() != null) {
321 optionValues.put(option.getOpt(), valueSet);
322 }
323 }
324
325
326 return new CommandLine(ImmutableList.copyOf(cmd.getArgList()), optionValues);
327
328 } catch (final Throwable ex) {
329 throw new Exception(ex.getMessage(), ex);
330 }
331 }
332
333 private void printVersion() {
334 String version = "(development)";
335 final URL url = CommandLine.class.getClassLoader().getResource(
336 "META-INF/maven/eu.fbk.nafview/nafview/pom.properties");
337 if (url != null) {
338 try {
339 final InputStream stream = url.openStream();
340 try {
341 final Properties properties = new Properties();
342 properties.load(stream);
343 version = properties.getProperty("version").trim();
344 } finally {
345 stream.close();
346 }
347
348 } catch (final IOException ex) {
349 version = "(unknown)";
350 }
351 }
352 final String name = MoreObjects.firstNonNull(this.name, "Version");
353 System.out.println(String.format("%s %s\nJava %s bit (%s) %s\n", name, version,
354 System.getProperty("sun.arch.data.model"), System.getProperty("java.vendor"),
355 System.getProperty("java.version")));
356 }
357
358 private void printHelp() {
359 final HelpFormatter formatter = new HelpFormatter();
360 final PrintWriter out = new PrintWriter(System.out);
361 final String name = MoreObjects.firstNonNull(this.name, "java");
362 formatter.printUsage(out, 80, name, this.options);
363 if (this.header != null) {
364 out.println();
365 formatter.printWrapped(out, 80, this.header);
366 }
367 out.println();
368 formatter.printOptions(out, 80, this.options, 2, 2);
369 if (this.footer != null) {
370 out.println();
371 out.println(this.footer);
372
373 }
374 out.flush();
375 }
376
377 }
378
379 public static final class Exception extends RuntimeException {
380
381 private static final long serialVersionUID = 1L;
382
383 public Exception(final String message) {
384 super(message);
385 }
386
387 public Exception(final String message, final Throwable cause) {
388 super(message, cause);
389 }
390
391 }
392
393 public enum Type {
394
395 STRING,
396
397 INTEGER,
398
399 POSITIVE_INTEGER,
400
401 NON_NEGATIVE_INTEGER,
402
403 FLOAT,
404
405 POSITIVE_FLOAT,
406
407 NON_NEGATIVE_FLOAT,
408
409 FILE,
410
411 FILE_EXISTING,
412
413 DIRECTORY,
414
415 DIRECTORY_EXISTING;
416
417 public boolean validate(final String string) {
418
419 return validate(string, this);
420 }
421
422 private static boolean validate(final String string, final Type type) {
423
424 if (type == Type.INTEGER || type == Type.POSITIVE_INTEGER
425 || type == Type.NON_NEGATIVE_INTEGER) {
426 try {
427 final long n = Long.parseLong(string);
428 if (type == Type.POSITIVE_INTEGER) {
429 return n > 0L;
430 } else if (type == Type.NON_NEGATIVE_INTEGER) {
431 return n >= 0L;
432 }
433 } catch (final Throwable ex) {
434 return false;
435 }
436
437 } else if (type == Type.FLOAT || type == Type.POSITIVE_FLOAT
438 || type == Type.NON_NEGATIVE_FLOAT) {
439 try {
440 final double n = Double.parseDouble(string);
441 if (type == Type.POSITIVE_FLOAT) {
442 return n > 0.0;
443 } else if (type == Type.NON_NEGATIVE_FLOAT) {
444 return n >= 0.0;
445 }
446 } catch (final Throwable ex) {
447 return false;
448 }
449
450 } else if (type == FILE) {
451 final File file = new File(string);
452 return !file.exists() || file.isFile();
453
454 } else if (type == FILE_EXISTING) {
455 final File file = new File(string);
456 return file.exists() && file.isFile();
457
458 } else if (type == DIRECTORY) {
459 final File dir = new File(string);
460 return !dir.exists() || dir.isDirectory();
461
462 } else if (type == DIRECTORY_EXISTING) {
463 final File dir = new File(string);
464 return dir.exists() && dir.isDirectory();
465 }
466
467 return true;
468 }
469
470 }
471
472 }