001 /*
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 *
020 */
021 package org.apache.qpid.util;
022
023 import java.util.ArrayList;
024 import java.util.HashMap;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.Properties;
029 import java.util.regex.*;
030
031 /**
032 * CommandLineParser provides a utility for specifying the format of a command line and parsing command lines to ensure
033 * that they fit their specified format. A command line is made up of flags and options, both may be refered to as
034 * options. A flag is an option that does not take an argument (specifying it means it has the value 'true' and not
035 * specifying it means it has the value 'false'). Options must take arguments but they can be set up with defaults so
036 * that they take a default value when not set. Options may be mandatory in wich case it is an error not to specify
037 * them on the command line. Flags are never mandatory because they are implicitly set to false when not specified.
038 *
039 * <p/>Some example command lines are:
040 *
041 * <ul>
042 * <li>This one has two options that expect arguments:
043 * <pre>
044 * cruisecontrol -configfile cruisecontrol.xml -port 9000
045 * </pre>
046 * <li>This has one no-arg flag and two 'free' arguments:
047 * <pre>
048 * zip -r project.zip project/*
049 * </pre>
050 * <li>This one concatenates multiple flags into a single block with only one '-':
051 * <pre>
052 * jar -tvf mytar.tar
053 * </pre>
054 *
055 * <p/>The parsing rules are:
056 *
057 * <ol>
058 * <li>Flags may be combined after a single '-' because they never take arguments. Normally such flags are single letter
059 * flags but this is only a convention and not enforced. Flags of more than one letter are usually specified on their own.
060 * <li>Options expecting arguments must always be on their own.
061 * <li>The argument to an option may be seperated from it by whitespace or appended directly onto the option.
062 * <li>The argument to an option may never begin with a '-' character.
063 * <li>All other arguments not beginning with a '-' character are free arguments that do not belong to any option.
064 * <li>The second or later of a set of duplicate or repeated flags are ignored.
065 * <li>Options are matched up to the shortest matching option. This is because of the possibility of having no space
066 * between an option and its argument. This rules out the possibility of using two options where one is an opening
067 * substring of the other. For example, the options "foo" and "foobar" cannot be used on the same command line because
068 * it is not possible to distinguish the argument "-foobar" from being the "foobar" option or the "foo" option with
069 * the "bar" argument.
070 * </ol>
071 *
072 * <p/>By default, unknown options are simply ignored if specified on the command line. This behaviour may be changed
073 * so that the parser reports all unknowns as errors by using the {@link #setErrorsOnUnknowns} method.
074 *
075 * <p><table id="crc"><caption>CRC Card</caption>
076 * <tr><th> Responsibilities <th> Collaborations
077 * <tr><td> Accept a command line specification.
078 * <tr><td> Parse a command line into properties, validating it against its specification.
079 * <tr><td> Report all errors between a command line and its specification.
080 * <tr><td> Provide a formatted usage string for a command line.
081 * <tr><td> Provide a formatted options in force string for a command line.
082 * <tr><td> Allow errors on unknowns behaviour to be turned on or off.
083 * </table>
084 */
085 public class CommandLineParser
086 {
087 /** Holds a mapping from command line option names to detailed information about those options. */
088 private Map<String, CommandLineOption> optionMap = new HashMap<String, CommandLineOption>();
089
090 /** Holds a list of parsing errors. */
091 private List<String> parsingErrors = new ArrayList<String>();
092
093 /** Holds the regular expression matcher to match command line options with. */
094 private Matcher optionMatcher = null;
095
096 /** Holds the parsed command line properties after parsing. */
097 private Properties parsedProperties = null;
098
099 /** Flag used to indicate that errors should be created for unknown options. False by default. */
100 private boolean errorsOnUnknowns = false;
101
102 /**
103 * Creates a command line options parser from a command line specification. This is passed to this constructor
104 * as an array of arrays of strings. Each array of strings specifies the command line for a single option. A static
105 * array may therefore easily be used to configure the command line parser in a single method call with an easily
106 * readable format.
107 *
108 * <p/>Each array of strings must be 2, 3, 4 or 5 elements long. If any of the last three elements are missing they
109 * are assumed to be null. The elements specify the following parameters:
110 * <ol>
111 * <li>The name of the option without the leading '-'. For example, "file". To specify the format of the 'free'
112 * arguments use the option names "1", "2", ... and so on.
113 * <li>The option comment. A line of text describing the usage of the option. For example, "The file to be processed."
114 * <li>The options argument. This is a very short description of the argument to the option, often a single word
115 * or a reminder as to the arguments format. When this element is null the option is a flag and does not
116 * accept any arguments. For example, "filename" or "(unix | windows)" or null. The actual text specified
117 * is only used to print in the usage message to remind the user of the usage of the option.
118 * <li>The mandatory flag. When set to "true" an option must always be specified. Any other value, including null,
119 * means that the option is mandatory. Flags are always mandatory (see class javadoc for explanation of why) so
120 * this is ignored for flags.
121 * <li>A regular expression describing the format that the argument must take. Ignored if null.
122 * </ol>
123 * <p/>An example call to this constructor is:
124 *
125 * <pre>
126 * CommandLineParser commandLine = new CommandLineParser(
127 * new String[][] {{"file", "The file to be processed. ", "filename", "true"},
128 * {"dir", "Directory to store results in. Current dir used if not set.", "out dir"},
129 * {"os", "Operating system EOL format to use.", "(windows | unix)", null, "windows\|unix"},
130 * {"v", "Verbose mode. Prints information about the processing as it goes."},
131 * {"1", "The processing command to run.", "command", "true", "add\|remove\|list"}});
132 * </pre>
133 *
134 * @param config The configuration as an array of arrays of strings.
135 */
136 public CommandLineParser(String[][] config)
137 {
138 // Loop through all the command line option specifications creating details for each in the options map.
139 for (int i = 0; i < config.length; i++)
140 {
141 String[] nextOptionSpec = config[i];
142
143 addOption(nextOptionSpec[0], nextOptionSpec[1], (nextOptionSpec.length > 2) ? nextOptionSpec[2] : null,
144 (nextOptionSpec.length > 3) ? ("true".equals(nextOptionSpec[3]) ? true : false) : false,
145 (nextOptionSpec.length > 4) ? nextOptionSpec[4] : null);
146 }
147 }
148
149 /**
150 * Lists all the parsing errors from the most recent parsing in a string.
151 *
152 * @return All the parsing errors from the most recent parsing.
153 */
154 public String getErrors()
155 {
156 // Return the empty string if there are no errors.
157 if (parsingErrors.isEmpty())
158 {
159 return "";
160 }
161
162 // Concatenate all the parsing errors together.
163 String result = "";
164
165 for (String s : parsingErrors)
166 {
167 result += s;
168 }
169
170 return result;
171 }
172
173 /**
174 * Lists the properties set from the most recent parsing or an empty string if no parsing has been done yet.
175 *
176 * @return The properties set from the most recent parsing or an empty string if no parsing has been done yet.
177 */
178 public String getOptionsInForce()
179 {
180 // Check if there are no properties to report and return and empty string if so.
181 if (parsedProperties == null)
182 {
183 return "";
184 }
185
186 // List all the properties.
187 String result = "Options in force:\n";
188
189 for (Map.Entry<Object, Object> property : parsedProperties.entrySet())
190 {
191 result += property.getKey() + " = " + property.getValue() + "\n";
192 }
193
194 return result;
195 }
196
197 /**
198 * Generates a usage string consisting of the name of each option and each options argument description and
199 * comment.
200 *
201 * @return A usage string for all the options.
202 */
203 public String getUsage()
204 {
205 String result = "Options:\n";
206
207 // Print usage on each of the command line options.
208 for (CommandLineOption optionInfo : optionMap.values())
209 {
210 result +=
211 optionInfo.option + " " + ((optionInfo.argument != null) ? (optionInfo.argument + " ") : "")
212 + optionInfo.comment + "\n";
213 }
214
215 return result;
216 }
217
218 /**
219 * Control the behaviour of the errors on unkowns reporting. When turned on this reports all unkowns options
220 * as errors. When turned off, all unknowns are simply ignored.
221 *
222 * @param errors The setting of the errors on unkown flag. True to turn it on.
223 */
224 public void setErrorsOnUnknowns(boolean errors)
225 {
226 errorsOnUnknowns = errors;
227 }
228
229 /**
230 * Parses a set of command line arguments into a set of properties, keyed by the argument flag. The free arguments
231 * are keyed by integers as strings starting at "1" and then "2", ... and so on.
232 *
233 * <p/>See the class level comment for a description of the parsing rules.
234 *
235 * @param args The command line arguments.
236 *
237 * @return The arguments as a set of properties.
238 *
239 * @throws IllegalArgumentException If the command line cannot be parsed against its specification. If this exception
240 * is thrown a call to {@link #getErrors} will provide a diagnostic of the command
241 * line errors.
242 */
243 public Properties parseCommandLine(String[] args) throws IllegalArgumentException
244 {
245 Properties options = new Properties();
246
247 // Used to keep count of the current 'free' argument.
248 int free = 1;
249
250 // Used to indicate that the most recently parsed option is expecting arguments.
251 boolean expectingArgs = false;
252
253 // The option that is expecting arguments from the next element of the command line.
254 String optionExpectingArgs = null;
255
256 // Used to indicate that the most recently parsed option is a duplicate and should be ignored.
257 boolean ignore = false;
258
259 // Create the regular expression matcher for the command line options.
260 String regexp = "^(";
261 int optionsAdded = 0;
262
263 for (Iterator<String> i = optionMap.keySet().iterator(); i.hasNext();)
264 {
265 String nextOption = i.next();
266
267 // Check that the option is not a free argument definition.
268 boolean notFree = false;
269
270 try
271 {
272 Integer.parseInt(nextOption);
273 }
274 catch (NumberFormatException e)
275 {
276 notFree = true;
277 }
278
279 // Add the option to the regular expression matcher if it is not a free argument definition.
280 if (notFree)
281 {
282 regexp += nextOption + (i.hasNext() ? "|" : "");
283 optionsAdded++;
284 }
285 }
286
287 // There has to be more that one option in the regular expression or else the compiler complains that the close
288 // cannot be nullable if the '?' token is used to make the matched option string optional.
289 regexp += ")" + ((optionsAdded > 0) ? "?" : "") + "(.*)";
290 Pattern pattern = Pattern.compile(regexp);
291
292 // Loop through all the command line arguments.
293 for (int i = 0; i < args.length; i++)
294 {
295 // Check if the next command line argument begins with a '-' character and is therefore the start of
296 // an option.
297 if (args[i].startsWith("-"))
298 {
299 // Extract the value of the option without the leading '-'.
300 String arg = args[i].substring(1);
301
302 // Match up to the longest matching option.
303 optionMatcher = pattern.matcher(arg);
304 optionMatcher.matches();
305
306 String matchedOption = optionMatcher.group(1);
307
308 // Match any argument directly appended onto the longest matching option.
309 String matchedArg = optionMatcher.group(2);
310
311 // Check that a known option was matched.
312 if ((matchedOption != null) && !"".equals(matchedOption))
313 {
314 // Get the command line option information for the matched option.
315 CommandLineOption optionInfo = optionMap.get(matchedOption);
316
317 // Check if this option is expecting arguments.
318 if (optionInfo.expectsArgs)
319 {
320 // The option is expecting arguments so swallow the next command line argument as an
321 // argument to this option.
322 expectingArgs = true;
323 optionExpectingArgs = matchedOption;
324
325 // In the mean time set this options argument to the empty string in case no argument is ever
326 // supplied.
327 // options.put(matchedOption, "");
328 }
329
330 // Check if the option was matched on its own and is a flag in which case set that flag.
331 if ("".equals(matchedArg) && !optionInfo.expectsArgs)
332 {
333 options.put(matchedOption, "true");
334 }
335 // The option was matched as a substring with its argument appended to it or is a flag that is
336 // condensed together with other flags.
337 else if (!"".equals(matchedArg))
338 {
339 // Check if the option is a flag and therefore is allowed to be condensed together
340 // with other flags.
341 if (!optionInfo.expectsArgs)
342 {
343 // Set the first matched flag.
344 options.put(matchedOption, "true");
345
346 // Repeat the longest matching process on the remainder but ensure that the remainder
347 // consists only of flags as only flags may be condensed together in this fashion.
348 do
349 {
350 // Match the remainder against the options.
351 optionMatcher = pattern.matcher(matchedArg);
352 optionMatcher.matches();
353
354 matchedOption = optionMatcher.group(1);
355 matchedArg = optionMatcher.group(2);
356
357 // Check that an option was matched.
358 if (matchedOption != null)
359 {
360 // Get the command line option information for the next matched option.
361 optionInfo = optionMap.get(matchedOption);
362
363 // Ensure that the next option is a flag or raise an error if not.
364 if (optionInfo.expectsArgs == true)
365 {
366 parsingErrors.add("Option " + matchedOption + " cannot be combined with flags.\n");
367 }
368
369 options.put(matchedOption, "true");
370 }
371 // The remainder could not be matched against a flag it is either an unknown flag
372 // or an illegal argument to a flag.
373 else
374 {
375 parsingErrors.add("Illegal argument to a flag in the option " + arg + "\n");
376
377 break;
378 }
379 }
380 // Continue until the remainder of the argument has all been matched with flags.
381 while (!"".equals(matchedArg));
382 }
383 // The option is expecting an argument, so store the unmatched portion against it
384 // as its argument.
385 else
386 {
387 // Check the arguments format is correct against any specified format.
388 checkArgumentFormat(optionInfo, matchedArg);
389
390 // Store the argument against its option (regardless of its format).
391 options.put(matchedOption, matchedArg);
392
393 // The argument to this flag has already been supplied to it. Do not swallow the
394 // next command line argument as an argument to this flag.
395 expectingArgs = false;
396 }
397 }
398 }
399 else // No matching option was found.
400 {
401 // Add this to the list of parsing errors if errors on unkowns is being used.
402 if (errorsOnUnknowns)
403 {
404 parsingErrors.add("Option " + matchedOption + " is not a recognized option.\n");
405 }
406 }
407 }
408 // The command line argument did not being with a '-' so it is an argument to the previous flag or it
409 // is a free argument.
410 else
411 {
412 // Check if a previous flag is expecting to swallow this next argument as its argument.
413 if (expectingArgs)
414 {
415 // Get the option info for the option waiting for arguments.
416 CommandLineOption optionInfo = optionMap.get(optionExpectingArgs);
417
418 // Check the arguments format is correct against any specified format.
419 checkArgumentFormat(optionInfo, args[i]);
420
421 // Store the argument against its option (regardless of its format).
422 options.put(optionExpectingArgs, args[i]);
423
424 // Clear the expecting args flag now that the argument has been swallowed.
425 expectingArgs = false;
426 optionExpectingArgs = null;
427 }
428 // This command line option is not an argument to any option. Add it to the set of 'free' options.
429 else
430 {
431 // Get the option info for the free option, if there is any.
432 CommandLineOption optionInfo = optionMap.get(Integer.toString(free));
433
434 if (optionInfo != null)
435 {
436 // Check the arguments format is correct against any specified format.
437 checkArgumentFormat(optionInfo, args[i]);
438 }
439
440 // Add to the list of free options.
441 options.put(Integer.toString(free), args[i]);
442
443 // Move on to the next free argument.
444 free++;
445 }
446 }
447 }
448
449 // Scan through all the specified options to check that all mandatory options have been set and that all flags
450 // that were not set are set to false in the set of properties.
451 for (CommandLineOption optionInfo : optionMap.values())
452 {
453 // Check if this is a flag.
454 if (!optionInfo.expectsArgs)
455 {
456 // Check if the flag is not set in the properties and set it to false if so.
457 if (!options.containsKey(optionInfo.option))
458 {
459 options.put(optionInfo.option, "false");
460 }
461 }
462 // Check if this is a mandatory option and was not set.
463 else if (optionInfo.mandatory && !options.containsKey(optionInfo.option))
464 {
465 // Create an error for the missing option.
466 parsingErrors.add("Option " + optionInfo.option + " is mandatory but not was not specified.\n");
467 }
468 }
469
470 // Check if there were any errors.
471 if (!parsingErrors.isEmpty())
472 {
473 // Throw an illegal argument exception to signify that there were parsing errors.
474 throw new IllegalArgumentException();
475 }
476
477 // Convert any name/value pairs in the free arguments into properties in the parsed options.
478 options = takeFreeArgsAsProperties(options, 1);
479
480 parsedProperties = options;
481
482 return options;
483 }
484
485 /**
486 * If a command line has been parsed, calling this method sets all of its parsed options into the specified properties.
487 */
488 public void addCommandLineToProperties(Properties properties)
489 {
490 if (parsedProperties != null)
491 {
492 for (Object propKey : parsedProperties.keySet())
493 {
494 String name = (String) propKey;
495 String value = parsedProperties.getProperty(name);
496
497 properties.setProperty(name, value);
498 }
499 }
500 }
501
502 /**
503 * Resets this command line parser after it has been used to parse a command line. This method will only need
504 * to be called to use this parser a second time which is not likely seeing as a command line is usually only
505 * specified once. However, it is exposed as a public method for the rare case where this may be done.
506 *
507 * <p/>Cleans the internal state of this parser, removing all stored errors and information about the options in
508 * force.
509 */
510 public void reset()
511 {
512 parsingErrors = new ArrayList<String>();
513 parsedProperties = null;
514 }
515
516 /**
517 * Adds the option to list of available command line options.
518 *
519 * @param option The option to add as an available command line option.
520 * @param comment A comment for the option.
521 * @param argument The text that appears after the option in the usage string.
522 * @param mandatory When true, indicates that this option is mandatory.
523 * @param formatRegexp The format that the argument must take, defined as a regular expression.
524 */
525 protected void addOption(String option, String comment, String argument, boolean mandatory, String formatRegexp)
526 {
527 // Check if usage text has been set in which case this option is expecting arguments.
528 boolean expectsArgs = ((argument == null) || argument.equals("")) ? false : true;
529
530 // Add the option to the map of command line options.
531 CommandLineOption opt = new CommandLineOption(option, expectsArgs, comment, argument, mandatory, formatRegexp);
532 optionMap.put(option, opt);
533 }
534
535 /**
536 * Converts the free arguments into property declarations. After parsing the command line the free arguments
537 * are numbered from 1, such that the parsed properties contain values for the keys "1", "2", ... This method
538 * converts any free arguments declared using the 'name=value' syntax into properties with key 'name', value
539 * 'value'.
540 *
541 * <p/>For example the comand line:
542 * <pre>
543 * ... debug=true
544 * </pre>
545 *
546 * <p/>After parsing has properties:
547 * <pre>[[1, debug=true]]</pre>
548 *
549 * <p/>After applying this method the properties are:
550 * <pre>[[1, debug=true], [debug, true]]</pre>
551 *
552 * @param properties The parsed command line properties.
553 * @param from The free argument index to convert to properties from.
554 *
555 * @return The parsed command line properties, with free argument name value pairs too.
556 */
557 private Properties takeFreeArgsAsProperties(Properties properties, int from)
558 {
559 for (int i = from; true; i++)
560 {
561 String nextFreeArg = properties.getProperty(Integer.toString(i));
562
563 // Terminate the loop once all free arguments have been consumed.
564 if (nextFreeArg == null)
565 {
566 break;
567 }
568
569 // Split it on the =, strip any whitespace and set it as a system property.
570 String[] nameValuePair = nextFreeArg.split("=");
571
572 if (nameValuePair.length == 2)
573 {
574 properties.setProperty(nameValuePair[0], nameValuePair[1]);
575 }
576 }
577
578 return properties;
579 }
580
581 /**
582 * Checks the format of an argument to an option against its specified regular expression format if one has
583 * been set. Any errors are added to the list of parsing errors.
584 *
585 * @param optionInfo The command line option information for the option which is havings its argument checked.
586 * @param matchedArg The string argument to the option.
587 */
588 private void checkArgumentFormat(CommandLineOption optionInfo, String matchedArg)
589 {
590 // Check if this option enforces a format for its argument.
591 if (optionInfo.argumentFormatRegexp != null)
592 {
593 Pattern pattern = Pattern.compile(optionInfo.argumentFormatRegexp);
594 Matcher argumentMatcher = pattern.matcher(matchedArg);
595
596 // Check if the argument does not meet its required format.
597 if (!argumentMatcher.matches())
598 {
599 // Create an error for this badly formed argument.
600 parsingErrors.add("The argument to option " + optionInfo.option + " does not meet its required format.\n");
601 }
602 }
603 }
604
605 /**
606 * Extracts all name=value pairs from the command line, sets them all as system properties and also returns
607 * a map of properties containing them.
608 *
609 * @param args The command line.
610 * @param commandLine The command line parser.
611 * @param properties The properties object to inject all parsed properties into (optional may be <tt>null</tt>).
612 *
613 * @return A set of properties containing all name=value pairs from the command line.
614 */
615 public static Properties processCommandLine(String[] args, CommandLineParser commandLine, Properties properties)
616 {
617 // Capture the command line arguments or display errors and correct usage and then exit.
618 Properties options = null;
619
620 try
621 {
622 options = commandLine.parseCommandLine(args);
623
624 // Add all the trailing command line options (name=value pairs) to system properties. They may be picked up
625 // from there.
626 commandLine.addCommandLineToProperties(properties);
627 }
628 catch (IllegalArgumentException e)
629 {
630 System.out.println(commandLine.getErrors());
631 System.out.println(commandLine.getUsage());
632 System.exit(1);
633 }
634
635 return options;
636 }
637
638 /**
639 * Holds information about a command line options. This includes what its name is, whether or not it is a flag,
640 * whether or not it is mandatory, what its user comment is, what its argument reminder text is and what its
641 * regular expression format is.
642 *
643 * <p><table id="crc"><caption>CRC Card</caption>
644 * <tr><th> Responsibilities <th> Collaborations
645 * <tr><td> Hold details of a command line option.
646 * </table>
647 */
648 protected class CommandLineOption
649 {
650 /** Holds the text for the flag to match this argument with. */
651 public String option = null;
652
653 /** Holds a string describing how to use this command line argument. */
654 public String argument = null;
655
656 /** Flag that determines whether or not this command line argument can take arguments. */
657 public boolean expectsArgs = false;
658
659 /** Holds a short comment describing what this command line argument is for. */
660 public String comment = null;
661
662 /** Flag that determines whether or not this is an mandatory command line argument. */
663 public boolean mandatory = false;
664
665 /** A regular expression describing what format the argument to this option muist have. */
666 public String argumentFormatRegexp = null;
667
668 /**
669 * Create a command line option object that holds specific information about a command line option.
670 *
671 * @param option The text that matches the option.
672 * @param expectsArgs Whether or not the option expects arguments. It is a flag if this is false.
673 * @param comment A comment explaining how to use this option.
674 * @param argument A short reminder of the format of the argument to this option/
675 * @param mandatory Set to true if this option is mandatory.
676 * @param formatRegexp The regular expression that the argument to this option must meet to be valid.
677 */
678 public CommandLineOption(String option, boolean expectsArgs, String comment, String argument, boolean mandatory,
679 String formatRegexp)
680 {
681 this.option = option;
682 this.expectsArgs = expectsArgs;
683 this.comment = comment;
684 this.argument = argument;
685 this.mandatory = mandatory;
686 this.argumentFormatRegexp = formatRegexp;
687 }
688 }
689 }
|