/*
* 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 com.google.common.base.Objects;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.TimeVal;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Populates {@link Option} fields.
*
* Setting of numeric fields such byte, short, int, long, float, and double fields is supported.
* This includes both unboxed and boxed versions (e.g. int vs Integer). If there is a problem
* setting the argument to match the desired type, a {@link ConfigurationException} is thrown.
*
* File option fields are supported by simply wrapping the string argument in a File object without
* testing for the existence of the file.
*
* Parameterized Collection fields such as List<File> and Set<String> are supported as
* long as the parameter type is otherwise supported by the option setter. The collection field
* should be initialized with an appropriate collection instance.
*
* All fields will be processed, including public, protected, default (package) access, private and
* inherited fields.
*
*
* ported from dalvik.runner.OptionParser
* @see ArgsOptionParser
*/
@SuppressWarnings("rawtypes")
public class OptionSetter {
static final String BOOL_FALSE_PREFIX = "no-";
private static final HashMap, Handler> handlers = new HashMap, Handler>();
static final char NAMESPACE_SEPARATOR = ':';
static final Pattern USE_KEYSTORE_REGEX = Pattern.compile("USE_KEYSTORE@(.*)");
private IKeyStoreClient mKeyStoreClient = null;
static {
handlers.put(boolean.class, new BooleanHandler());
handlers.put(Boolean.class, new BooleanHandler());
handlers.put(byte.class, new ByteHandler());
handlers.put(Byte.class, new ByteHandler());
handlers.put(short.class, new ShortHandler());
handlers.put(Short.class, new ShortHandler());
handlers.put(int.class, new IntegerHandler());
handlers.put(Integer.class, new IntegerHandler());
handlers.put(long.class, new LongHandler());
handlers.put(Long.class, new LongHandler());
handlers.put(float.class, new FloatHandler());
handlers.put(Float.class, new FloatHandler());
handlers.put(double.class, new DoubleHandler());
handlers.put(Double.class, new DoubleHandler());
handlers.put(String.class, new StringHandler());
handlers.put(File.class, new FileHandler());
handlers.put(TimeVal.class, new TimeValHandler());
}
static class FieldDef {
Object object;
Field field;
Object key;
FieldDef(Object object, Field field, Object key) {
this.object = object;
this.field = field;
this.key = key;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof FieldDef) {
FieldDef other = (FieldDef)obj;
return Objects.equal(this.object, other.object) &&
Objects.equal(this.field, other.field) &&
Objects.equal(this.key, other.key);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(object, field, key);
}
}
private static Handler getHandler(Type type) throws ConfigurationException {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Class> rawClass = (Class>) parameterizedType.getRawType();
if (Collection.class.isAssignableFrom(rawClass)) {
// handle Collection
Type actualType = parameterizedType.getActualTypeArguments()[0];
if (!(actualType instanceof Class)) {
throw new ConfigurationException(
"cannot handle nested parameterized type " + type);
}
return getHandler(actualType);
} else if (Map.class.isAssignableFrom(rawClass) ||
MultiMap.class.isAssignableFrom(rawClass)) {
// handle Map
Type keyType = parameterizedType.getActualTypeArguments()[0];
Type valueType = parameterizedType.getActualTypeArguments()[1];
if (!(keyType instanceof Class)) {
throw new ConfigurationException(
"cannot handle nested parameterized type " + keyType);
} else if (!(valueType instanceof Class)) {
throw new ConfigurationException(
"cannot handle nested parameterized type " + valueType);
}
return new MapHandler(getHandler(keyType), getHandler(valueType));
} else {
throw new ConfigurationException(String.format(
"can't handle parameterized type %s; only Collection, Map, and MultiMap "
+ "are supported", type));
}
}
if (type instanceof Class) {
Class> cType = (Class>) type;
if (cType.isEnum()) {
return new EnumHandler(cType);
} else if (Collection.class.isAssignableFrom(cType)) {
// could handle by just having a default of treating
// contents as String but consciously decided this
// should be an error
throw new ConfigurationException(String.format(
"Cannot handle non-parameterized collection %s. Use a generic Collection "
+ "to specify a desired element type.", type));
} else if (Map.class.isAssignableFrom(cType)) {
// could handle by just having a default of treating
// contents as String but consciously decided this
// should be an error
throw new ConfigurationException(String.format(
"Cannot handle non-parameterized map %s. Use a generic Map to specify "
+ "desired element types.", type));
} else if (MultiMap.class.isAssignableFrom(cType)) {
// could handle by just having a default of treating
// contents as String but consciously decided this
// should be an error
throw new ConfigurationException(String.format(
"Cannot handle non-parameterized multimap %s. Use a generic MultiMap to "
+ "specify desired element types.", type));
}
return handlers.get(cType);
}
throw new ConfigurationException(String.format("cannot handle unknown field type %s",
type));
}
/**
* Does some magic to distinguish TimeVal long field from normal long fields, then calls
* {@link #getHandler(Type)} in the appropriate manner.
*/
private Handler getHandlerOrTimeVal(Field field, Object optionSource)
throws ConfigurationException {
// Do some magic to distinguish TimeVal long fields from normal long fields
final Option option = field.getAnnotation(Option.class);
if (option == null) {
// Shouldn't happen, but better to check.
throw new ConfigurationException(String.format(
"internal error: @Option annotation for field %s in class %s was " +
"unexpectedly null",
field.getName(), optionSource.getClass().getName()));
}
final Type type = field.getGenericType();
if (option.isTimeVal()) {
// We've got a field that marks itself as a time value. First off, verify that it's
// a compatible type
if (type instanceof Class) {
final Class> cType = (Class>) type;
if (long.class.equals(cType) || Long.class.equals(cType)) {
// Parse time value and return a Long
return new TimeValLongHandler();
} else if (TimeVal.class.equals(cType)) {
// Parse time value and return a TimeVal object
return new TimeValHandler();
}
}
throw new ConfigurationException(String.format("Only fields of type long, " +
"Long, or TimeVal may be declared as isTimeVal. Field %s has " +
"incompatible type %s.", field.getName(), field.getGenericType()));
} else {
// Note that fields declared as TimeVal (or Generic types with TimeVal parameters) will
// follow this branch, but will still work as expected.
return getHandler(type);
}
}
private final Collection