blob: efd8b4a07ed4f4a00e86d5dac71a405910faa9fb [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.cli;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
/**
* Default parser.
*
* @version $Id$
* @since 1.3
*/
public class DefaultParser implements CommandLineParser
{
/** The command-line instance. */
protected CommandLine cmd;
/** The current options. */
protected Options options;
/**
* Flag indicating how unrecognized tokens are handled. <tt>true</tt> to stop
* the parsing and add the remaining tokens to the args list.
* <tt>false</tt> to throw an exception.
*/
protected boolean stopAtNonOption;
/** The token currently processed. */
protected String currentToken;
/** The last option parsed. */
protected Option currentOption;
/** Flag indicating if tokens should no longer be analyzed and simply added as arguments of the command line. */
protected boolean skipParsing;
/** The required options and groups expected to be found when parsing the command line. */
protected List expectedOpts;
public CommandLine parse(Options options, String[] arguments) throws ParseException
{
return parse(options, arguments, null);
}
/**
* Parse the arguments according to the specified options and properties.
*
* @param options the specified Options
* @param arguments the command line arguments
* @param properties command line option name-value pairs
* @return the list of atomic option and value tokens
*
* @throws ParseException if there are any problems encountered
* while parsing the command line tokens.
*/
public CommandLine parse(Options options, String[] arguments, Properties properties) throws ParseException
{
return parse(options, arguments, properties, false);
}
public CommandLine parse(Options options, String[] arguments, boolean stopAtNonOption) throws ParseException
{
return parse(options, arguments, null, stopAtNonOption);
}
/**
* Parse the arguments according to the specified options and properties.
*
* @param options the specified Options
* @param arguments the command line arguments
* @param properties command line option name-value pairs
* @param stopAtNonOption if <tt>true</tt> an unrecognized argument stops
* the parsing and the remaining arguments are added to the
* {@link CommandLine}s args list. If <tt>false</tt> an unrecognized
* argument triggers a ParseException.
*
* @return the list of atomic option and value tokens
* @throws ParseException if there are any problems encountered
* while parsing the command line tokens.
*/
public CommandLine parse(Options options, String[] arguments, Properties properties, boolean stopAtNonOption)
throws ParseException
{
this.options = options;
this.stopAtNonOption = stopAtNonOption;
skipParsing = false;
currentOption = null;
expectedOpts = new ArrayList(options.getRequiredOptions());
// clear the data from the groups
for (OptionGroup group : options.getOptionGroups())
{
group.setSelected(null);
}
cmd = new CommandLine();
if (arguments != null)
{
for (String argument : arguments)
{
handleToken(argument);
}
}
// check the arguments of the last option
checkRequiredArgs();
// add the default options
handleProperties(properties);
checkRequiredOptions();
return cmd;
}
/**
* Sets the values of Options using the values in <code>properties</code>.
*
* @param properties The value properties to be processed.
*/
private void handleProperties(Properties properties) throws ParseException
{
if (properties == null)
{
return;
}
for (Enumeration<?> e = properties.propertyNames(); e.hasMoreElements();)
{
String option = e.nextElement().toString();
Option opt = options.getOption(option);
if (opt == null)
{
throw new UnrecognizedOptionException("Default option wasn't defined", option);
}
// if the option is part of a group, check if another option of the group has been selected
OptionGroup group = options.getOptionGroup(opt);
boolean selected = group != null && group.getSelected() != null;
if (!cmd.hasOption(option) && !selected)
{
// get the value from the properties
String value = properties.getProperty(option);
if (opt.hasArg())
{
if (opt.getValues() == null || opt.getValues().length == 0)
{
opt.addValueForProcessing(value);
}
}
else if (!("yes".equalsIgnoreCase(value)
|| "true".equalsIgnoreCase(value)
|| "1".equalsIgnoreCase(value)))
{
// if the value is not yes, true or 1 then don't add the option to the CommandLine
continue;
}
handleOption(opt);
currentOption = null;
}
}
}
/**
* Throws a {@link MissingOptionException} if all of the required options
* are not present.
*
* @throws MissingOptionException if any of the required Options
* are not present.
*/
private void checkRequiredOptions() throws MissingOptionException
{
// if there are required options that have not been processed
if (!expectedOpts.isEmpty())
{
throw new MissingOptionException(expectedOpts);
}
}
/**
* Throw a {@link MissingArgumentException} if the current option
* didn't receive the number of arguments expected.
*/
private void checkRequiredArgs() throws ParseException
{
if (currentOption != null && currentOption.requiresArg())
{
throw new MissingArgumentException(currentOption);
}
}
/**
* Handle any command line token.
*
* @param token the command line token to handle
* @throws ParseException
*/
private void handleToken(String token) throws ParseException
{
currentToken = token;
if (skipParsing)
{
cmd.addArg(token);
}
else if ("--".equals(token))
{
skipParsing = true;
}
else if (currentOption != null && currentOption.acceptsArg() && isArgument(token))
{
currentOption.addValueForProcessing(Util.stripLeadingAndTrailingQuotes(token));
}
else if (token.startsWith("--"))
{
handleLongOption(token);
}
else if (token.startsWith("-") && !"-".equals(token))
{
handleShortAndLongOption(token);
}
else
{
handleUnknownToken(token);
}
if (currentOption != null && !currentOption.acceptsArg())
{
currentOption = null;
}
}
/**
* Returns true is the token is a valid argument.
*
* @param token
*/
private boolean isArgument(String token)
{
return !isOption(token) || isNegativeNumber(token);
}
/**
* Check if the token is a negative number.
*
* @param token
*/
private boolean isNegativeNumber(String token)
{
try
{
Double.parseDouble(token);
return true;
}
catch (NumberFormatException e)
{
return false;
}
}
/**
* Tells if the token looks like an option.
*
* @param token
*/
private boolean isOption(String token)
{
return isLongOption(token) || isShortOption(token);
}
/**
* Tells if the token looks like a short option.
*
* @param token
*/
private boolean isShortOption(String token)
{
// short options (-S, -SV, -S=V, -SV1=V2, -S1S2)
return token.startsWith("-") && token.length() >= 2 && options.hasShortOption(token.substring(1, 2));
}
/**
* Tells if the token looks like a long option.
*
* @param token
*/
private boolean isLongOption(String token)
{
if (!token.startsWith("-") || token.length() == 1)
{
return false;
}
int pos = token.indexOf("=");
String t = pos == -1 ? token : token.substring(0, pos);
if (!options.getMatchingOptions(t).isEmpty())
{
// long or partial long options (--L, -L, --L=V, -L=V, --l, --l=V)
return true;
}
else if (getLongPrefix(token) != null && !token.startsWith("--"))
{
// -LV
return true;
}
return false;
}
/**
* Handles an unknown token. If the token starts with a dash an
* UnrecognizedOptionException is thrown. Otherwise the token is added
* to the arguments of the command line. If the stopAtNonOption flag
* is set, this stops the parsing and the remaining tokens are added
* as-is in the arguments of the command line.
*
* @param token the command line token to handle
*/
private void handleUnknownToken(String token) throws ParseException
{
if (token.startsWith("-") && token.length() > 1 && !stopAtNonOption)
{
throw new UnrecognizedOptionException("Unrecognized option: " + token, token);
}
cmd.addArg(token);
if (stopAtNonOption)
{
skipParsing = true;
}
}
/**
* Handles the following tokens:
*
* --L
* --L=V
* --L V
* --l
*
* @param token the command line token to handle
*/
private void handleLongOption(String token) throws ParseException
{
if (token.indexOf('=') == -1)
{
handleLongOptionWithoutEqual(token);
}
else
{
handleLongOptionWithEqual(token);
}
}
/**
* Handles the following tokens:
*
* --L
* -L
* --l
* -l
*
* @param token the command line token to handle
*/
private void handleLongOptionWithoutEqual(String token) throws ParseException
{
List<String> matchingOpts = options.getMatchingOptions(token);
if (matchingOpts.isEmpty())
{
handleUnknownToken(currentToken);
}
else if (matchingOpts.size() > 1)
{
throw new AmbiguousOptionException(token, matchingOpts);
}
else
{
handleOption(options.getOption(matchingOpts.get(0)));
}
}
/**
* Handles the following tokens:
*
* --L=V
* -L=V
* --l=V
* -l=V
*
* @param token the command line token to handle
*/
private void handleLongOptionWithEqual(String token) throws ParseException
{
int pos = token.indexOf('=');
String value = token.substring(pos + 1);
String opt = token.substring(0, pos);
List<String> matchingOpts = options.getMatchingOptions(opt);
if (matchingOpts.isEmpty())
{
handleUnknownToken(currentToken);
}
else if (matchingOpts.size() > 1)
{
throw new AmbiguousOptionException(opt, matchingOpts);
}
else
{
Option option = options.getOption(matchingOpts.get(0));
if (option.acceptsArg())
{
handleOption(option);
currentOption.addValueForProcessing(value);
currentOption = null;
}
else
{
handleUnknownToken(currentToken);
}
}
}
/**
* Handles the following tokens:
*
* -S
* -SV
* -S V
* -S=V
* -S1S2
* -S1S2 V
* -SV1=V2
*
* -L
* -LV
* -L V
* -L=V
* -l
*
* @param token the command line token to handle
*/
private void handleShortAndLongOption(String token) throws ParseException
{
String t = Util.stripLeadingHyphens(token);
int pos = t.indexOf('=');
if (t.length() == 1)
{
// -S
if (options.hasShortOption(t))
{
handleOption(options.getOption(t));
}
else
{
handleUnknownToken(token);
}
}
else if (pos == -1)
{
// no equal sign found (-xxx)
if (options.hasShortOption(t))
{
handleOption(options.getOption(t));
}
else if (!options.getMatchingOptions(t).isEmpty())
{
// -L or -l
handleLongOptionWithoutEqual(token);
}
else
{
// look for a long prefix (-Xmx512m)
String opt = getLongPrefix(t);
if (opt != null && options.getOption(opt).acceptsArg())
{
handleOption(options.getOption(opt));
currentOption.addValueForProcessing(t.substring(opt.length()));
currentOption = null;
}
else if (isJavaProperty(t))
{
// -SV1 (-Dflag)
handleOption(options.getOption(t.substring(0, 1)));
currentOption.addValueForProcessing(t.substring(1));
currentOption = null;
}
else
{
// -S1S2S3 or -S1S2V
handleConcatenatedOptions(token);
}
}
}
else
{
// equal sign found (-xxx=yyy)
String opt = t.substring(0, pos);
String value = t.substring(pos + 1);
if (opt.length() == 1)
{
// -S=V
Option option = options.getOption(opt);
if (option != null && option.acceptsArg())
{
handleOption(option);
currentOption.addValueForProcessing(value);
currentOption = null;
}
else
{
handleUnknownToken(token);
}
}
else if (isJavaProperty(opt))
{
// -SV1=V2 (-Dkey=value)
handleOption(options.getOption(opt.substring(0, 1)));
currentOption.addValueForProcessing(opt.substring(1));
currentOption.addValueForProcessing(value);
currentOption = null;
}
else
{
// -L=V or -l=V
handleLongOptionWithEqual(token);
}
}
}
/**
* Search for a prefix that is the long name of an option (-Xmx512m)
*
* @param token
*/
private String getLongPrefix(String token)
{
String t = Util.stripLeadingHyphens(token);
int i;
String opt = null;
for (i = t.length() - 2; i > 1; i--)
{
String prefix = t.substring(0, i);
if (options.hasLongOption(prefix))
{
opt = prefix;
break;
}
}
return opt;
}
/**
* Check if the specified token is a Java-like property (-Dkey=value).
*/
private boolean isJavaProperty(String token)
{
String opt = token.substring(0, 1);
Option option = options.getOption(opt);
return option != null && (option.getArgs() >= 2 || option.getArgs() == Option.UNLIMITED_VALUES);
}
private void handleOption(Option option) throws ParseException
{
// check the previous option before handling the next one
checkRequiredArgs();
option = (Option) option.clone();
updateRequiredOptions(option);
cmd.addOption(option);
if (option.hasArg())
{
currentOption = option;
}
else
{
currentOption = null;
}
}
/**
* Removes the option or its group from the list of expected elements.
*
* @param option
*/
private void updateRequiredOptions(Option option) throws AlreadySelectedException
{
if (option.isRequired())
{
expectedOpts.remove(option.getKey());
}
// if the option is in an OptionGroup make that option the selected option of the group
if (options.getOptionGroup(option) != null)
{
OptionGroup group = options.getOptionGroup(option);
if (group.isRequired())
{
expectedOpts.remove(group);
}
group.setSelected(option);
}
}
/**
* Breaks <code>token</code> into its constituent parts
* using the following algorithm.
*
* <ul>
* <li>ignore the first character ("<b>-</b>")</li>
* <li>foreach remaining character check if an {@link Option}
* exists with that id.</li>
* <li>if an {@link Option} does exist then add that character
* prepended with "<b>-</b>" to the list of processed tokens.</li>
* <li>if the {@link Option} can have an argument value and there
* are remaining characters in the token then add the remaining
* characters as a token to the list of processed tokens.</li>
* <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b>
* <code>stopAtNonOption</code> <b>IS</b> set then add the special token
* "<b>--</b>" followed by the remaining characters and also
* the remaining tokens directly to the processed tokens list.</li>
* <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b>
* <code>stopAtNonOption</code> <b>IS NOT</b> set then add that
* character prepended with "<b>-</b>".</li>
* </ul>
*
* @param token The current token to be <b>burst</b>
* at the first non-Option encountered.
* @throws ParseException if there are any problems encountered
* while parsing the command line token.
*/
protected void handleConcatenatedOptions(String token) throws ParseException
{
for (int i = 1; i < token.length(); i++)
{
String ch = String.valueOf(token.charAt(i));
if (options.hasOption(ch))
{
handleOption(options.getOption(ch));
if (currentOption != null && token.length() != i + 1)
{
// add the trail as an argument of the option
currentOption.addValueForProcessing(token.substring(i + 1));
break;
}
}
else
{
handleUnknownToken(stopAtNonOption && i > 1 ? token.substring(i) : token);
break;
}
}
}
}