// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.core.cli; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.pipeline.common.PipelineConstants; import org.openstreetmap.osmosis.core.pipeline.common.TaskConfiguration; /** * Parses command line arguments into a form that can be consumed by the rest of * the application. * * @author Brett Henderson */ public class CommandLineParser { private static final String GLOBAL_ARGUMENT_PREFIX = "-"; private static final String TASK_ARGUMENT_PREFIX = "--"; private static final String OPTION_QUIET_SHORT = "q"; private static final String OPTION_QUIET_LONG = "quiet"; private static final String OPTION_VERBOSE_SHORT = "v"; private static final String OPTION_VERBOSE_LONG = "verbose"; private static final String OPTION_PLUGIN_SHORT = "p"; private static final String OPTION_PLUGIN_LONG = "plugin"; /** * The index into the LOG_LEVELS array for the default log level. */ private static final int DEFAULT_LOG_LEVEL_INDEX = 3; private List<TaskConfiguration> taskConfigList; private int quietValue; private int verboseValue; private List<String> plugins; /** * Creates a new instance. */ public CommandLineParser() { taskConfigList = new ArrayList<TaskConfiguration>(); quietValue = 0; verboseValue = 0; plugins = new ArrayList<String>(); } /** * Parses the command line arguments. * * @param programArgs * The arguments. */ public void parse(String [] programArgs) { List<GlobalOptionConfiguration> globalOptions; // Create the global options list. globalOptions = new ArrayList<GlobalOptionConfiguration>(); // Process the command line arguments to build all nodes in the pipeline. for (int i = 0; i < programArgs.length;) { String arg; arg = programArgs[i]; if (arg.indexOf(TASK_ARGUMENT_PREFIX) == 0) { i = parseTask(programArgs, i); } else if (arg.indexOf(GLOBAL_ARGUMENT_PREFIX) == 0) { i = parseGlobalOption(globalOptions, programArgs, i); } else { throw new OsmosisRuntimeException("Expected argument " + (i + 1) + " to be an option or task name."); } } // Process the global options. for (GlobalOptionConfiguration globalOption : globalOptions) { if (isArgumentForOption(OPTION_QUIET_SHORT, OPTION_QUIET_LONG, globalOption.name)) { quietValue = parseOptionIntegerWithDefault(globalOption, 0) + 1; } else if (isArgumentForOption(OPTION_VERBOSE_SHORT, OPTION_VERBOSE_LONG, globalOption.name)) { verboseValue = parseOptionIntegerWithDefault(globalOption, 0) + 1; } else if (isArgumentForOption(OPTION_PLUGIN_SHORT, OPTION_PLUGIN_LONG, globalOption.name)) { plugins.add(parseOptionString(globalOption)); } else { throw new OsmosisRuntimeException("Argument " + (globalOption.offset + 1) + " specifies an unrecognised option \"" + GLOBAL_ARGUMENT_PREFIX + globalOption.name + "\"."); } } } /** * Checks if the current command line argument is for the specified option. * * @param shortOptionName * The short name of the option to check for. * @param longOptionName * The long name of the option to check for. * @param argument * The command line argument without the option prefix. * @return True if the argument is for the specified option. */ private boolean isArgumentForOption(String shortOptionName, String longOptionName, String argument) { return shortOptionName.equals(argument) || longOptionName.equals(argument); } /** * Determines if an argument is a parameter to the current option/task or * the start of another option/task. * * @param argument * The argument. * @return True if this is an option parameter. */ private boolean isOptionParameter(String argument) { if (argument.length() >= GLOBAL_ARGUMENT_PREFIX.length()) { if (argument.substring(0, GLOBAL_ARGUMENT_PREFIX.length()).equals(GLOBAL_ARGUMENT_PREFIX)) { return false; } } if (argument.length() >= TASK_ARGUMENT_PREFIX.length()) { if (argument.substring(0, TASK_ARGUMENT_PREFIX.length()).equals(TASK_ARGUMENT_PREFIX)) { return false; } } return true; } /** * Parses a command line option into an integer. If none is specified, zero * will be returned. * * @param globalOption * The global option to be parsed. * @return The integer value. */ private int parseOptionIntegerWithDefault(GlobalOptionConfiguration globalOption, int defaultValue) { int result; // If no parameters are available, we use the default value. if (globalOption.parameters.size() <= 0) { return defaultValue; } // An integer option may only have one parameter. if (globalOption.parameters.size() > 1) { throw new OsmosisRuntimeException( "Expected argument " + (globalOption.offset + 1) + " to have no more than one parameter."); } // Parse the option. try { result = Integer.parseInt(globalOption.parameters.get(0)); } catch (NumberFormatException e) { throw new OsmosisRuntimeException( "Expected argument " + (globalOption.offset + 2) + " to contain an integer value."); } return result; } /** * Parses a command line option into an string. * * @param globalOption * The global option to be parsed. */ private String parseOptionString(GlobalOptionConfiguration globalOption) { // A string option must have one parameter. if (globalOption.parameters.size() != 1) { throw new OsmosisRuntimeException( "Expected argument " + (globalOption.offset + 1) + " to have one parameter."); } return globalOption.parameters.get(0); } /** * Parses the details of a single option. * * @param globalOptions * The list of global options being parsed, the new option will * be stored into this object. * @param programArgs * The command line arguments passed to this application. * @param offset * The current offset through the command line arguments. * @return The new offset through the command line arguments. */ private int parseGlobalOption(List<GlobalOptionConfiguration> globalOptions, String [] programArgs, int offset) { int i; String argument; GlobalOptionConfiguration globalOption; i = offset; argument = programArgs[i++].substring(1); globalOption = new GlobalOptionConfiguration(); globalOption.name = argument; globalOption.offset = offset; // Loop until the next option or task is reached and add the arguments as parameters to the option. while ((i < programArgs.length) && isOptionParameter(programArgs[i])) { globalOption.parameters.add(programArgs[i++]); } // Add the fully populated global option object to the list. globalOptions.add(globalOption); return i; } /** * Parses the details of a single task and creates a task information object. * * @param programArgs * The command line arguments passed to this application. * @param offset * The current offset through the command line arguments. * @return The new offset through the command line arguments. */ private int parseTask(String [] programArgs, int offset) { int i; String taskType; Map<String, String> taskArgs; Map<String, String> pipeArgs; String taskId; int defaultArgIndex; String defaultArg; i = offset; // Extract the task type from the current argument. taskType = programArgs[i++].substring(TASK_ARGUMENT_PREFIX.length()); // Build up a list of task and pipe arguments. taskArgs = new HashMap<String, String>(); pipeArgs = new HashMap<String, String>(); defaultArg = null; defaultArgIndex = -1; while (i < programArgs.length) { String arg; int equalsIndex; String argName; String argValue; arg = programArgs[i]; if (arg.indexOf(TASK_ARGUMENT_PREFIX) == 0) { break; } equalsIndex = arg.indexOf("="); // If an equals sign exists this is a named argument, otherwise it is a default argument. if (equalsIndex >= 0) { // Check if the name component of the argument exists. if (equalsIndex == 0) { throw new OsmosisRuntimeException( "Argument " + (i + 1) + " doesn't contain a name before the '=' (ie. name=value)."); } // Check if the value component of the argument exists. if (equalsIndex >= (arg.length() - 1)) { throw new OsmosisRuntimeException( "Argument " + (i + 1) + " doesn't contain a value after the '=' (ie. name=value)."); } // Split the argument into name and value. argName = arg.substring(0, equalsIndex); argValue = arg.substring(equalsIndex + 1); // Add pipeline arguments to pipeArgs, all other arguments to taskArgs. // A pipeline arg is inPipe, inPipe.x, outPipe or outPipe.x. if ( PipelineConstants.IN_PIPE_ARGUMENT_PREFIX.equals(argName) || argName.indexOf(PipelineConstants.IN_PIPE_ARGUMENT_PREFIX + ".") == 0 || PipelineConstants.OUT_PIPE_ARGUMENT_PREFIX.equals(argName) || argName.indexOf(PipelineConstants.OUT_PIPE_ARGUMENT_PREFIX + ".") == 0) { pipeArgs.put(argName, argValue); } else { taskArgs.put(argName, argValue); } } else { if (defaultArgIndex >= 0) { throw new OsmosisRuntimeException( "Only one default (un-named) argument can exist per task. Arguments " + (i + 1) + " and " + (defaultArgIndex + 1) + " have no name."); } defaultArg = arg; defaultArgIndex = i; } i++; } // Build a unique task id. taskId = (taskConfigList.size() + 1) + "-" + taskType; // Create a new task information object and add it to the list. taskConfigList.add( new TaskConfiguration(taskId, taskType, pipeArgs, taskArgs, defaultArg) ); return i; } /** * The list of task information objects. * * @return The taskInfoList. */ public List<TaskConfiguration> getTaskInfoList() { return taskConfigList; } /** * Gets the level of logging required. This is a number that can be used to * access a log level from the LogLevels class. * * @return The index of the log level to be used. */ public int getLogLevelIndex() { return DEFAULT_LOG_LEVEL_INDEX + verboseValue - quietValue; } /** * Returns the plugins to be loaded. * * @return The list of plugin class names. */ public List<String> getPlugins() { return plugins; } /** * A data storage class holding information relating to a global option * during parsing. */ private class GlobalOptionConfiguration { /** * The name of the option. */ public String name; /** * The parameters for the option. */ public List<String> parameters; /** * The command line argument offset of this global option. */ public int offset; /** * Creates a new instance. */ public GlobalOptionConfiguration() { parameters = new ArrayList<String>(); } } }