blob: f585a6b251d44ae18764b28343082f04391dd772 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed 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 com.android.tradefed.config;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
/**
* An option parser that parses options from command line arguments.
*/
class ArgsOptionParser extends OptionParser {
static final String SHORT_NAME_PREFIX = "-";
static final String OPTION_NAME_PREFIX = "--";
/**
* Creates a {@link ArgsOptionParser} for a single object.
*
* @param optionSource the config object.
* @throws ConfigurationException if config object is improperly configured.
*/
public ArgsOptionParser(Object optionSource) throws ConfigurationException {
super(optionSource);
}
/**
* Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource'
* provided to the constructor.
*
* @returns a {@link List} of the positional arguments left over after processing all options.
* @throws ConfigurationException if error occurred parsing the arguments.
*/
public List<String> parse(String[] args) throws ConfigurationException {
return parseOptions(Arrays.asList(args).listIterator());
}
private List<String> parseOptions(ListIterator<String> args) throws ConfigurationException {
final List<String> leftovers = new ArrayList<String>();
// Scan 'args'.
while (args.hasNext()) {
final String arg = args.next();
if (arg.equals(OPTION_NAME_PREFIX)) {
// "--" marks the end of options and the beginning of positional arguments.
break;
} else if (arg.startsWith(OPTION_NAME_PREFIX)) {
// A long option.
parseLongOption(arg, args);
} else if (arg.startsWith(SHORT_NAME_PREFIX)) {
// A short option.
parseGroupedShortOptions(arg, args);
} else {
// The first non-option marks the end of options.
leftovers.add(arg);
break;
}
}
// Package up the leftovers.
while (args.hasNext()) {
leftovers.add(args.next());
}
return leftovers;
}
private void parseLongOption(String arg, ListIterator<String> args)
throws ConfigurationException {
// remove prefix to just get name
String name = arg.replaceFirst("^" + OPTION_NAME_PREFIX, "");
String value = null;
// Support "--name=value" as well as "--name value".
final int equalsIndex = name.indexOf('=');
if (equalsIndex != -1) {
value = name.substring(equalsIndex + 1);
name = name.substring(0, equalsIndex);
}
final Field field = fieldForArg(name);
if (field == null) {
// not an option for this source - discard the value associated with this arg
discardNextValue(args);
return;
}
final Handler handler = getHandler(field.getGenericType());
if (value == null) {
if (handler.isBoolean()) {
value = name.startsWith(BOOL_FALSE_PREFIX) ? "false" : "true";
} else {
value = grabNextValue(args, name, field);
}
}
setValue(field, arg, handler, value);
}
// Given boolean options a and b, and non-boolean option f, we want to allow:
// -ab
// -abf out.txt
// -abfout.txt
// (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids
// it.)
private void parseGroupedShortOptions(String arg, ListIterator<String> args)
throws ConfigurationException {
for (int i = 1; i < arg.length(); ++i) {
final String name = String.valueOf(arg.charAt(i));
final Field field = fieldForArg(name);
if (field == null) {
// not an option for this source
discardNextValue(args);
continue;
}
final Handler handler = getHandler(field.getGenericType());
String value;
if (handler.isBoolean()) {
value = "true";
} else {
// We need a value. If there's anything left, we take the rest of this
// "short option".
if (i + 1 < arg.length()) {
value = arg.substring(i + 1);
i = arg.length() - 1;
} else {
value = grabNextValue(args, name, field);
}
}
setValue(field, arg, handler, value);
}
}
/**
* Returns the next element of 'args' if there is one. Uses 'name' and 'field' to
* construct a helpful error message.
*
* @param args
* @param name
* @param field
* @throws ConfigurationException
*
* @returns
*/
private String grabNextValue(ListIterator<String> args, String name, Field field)
throws ConfigurationException {
if (!args.hasNext()) {
final String type = field.getType().getSimpleName().toLowerCase();
throw new ConfigurationException(String.format("option '%s' requires a %s argument",
name, type));
}
return args.next();
}
/**
* Removes the next element of args if it is a value for previous option argument. (ie is a
* non-option).
*/
private void discardNextValue(ListIterator<String> args) {
if (args.hasNext()) {
String value = args.next();
if (value.startsWith(OPTION_NAME_PREFIX) || value.startsWith(SHORT_NAME_PREFIX)) {
// this is an option, put it back
args.previous();
}
}
}
}