/* * 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.android.tradefed.build.IBuildProvider; import com.android.tradefed.build.StubBuildProvider; import com.android.tradefed.command.CommandOptions; import com.android.tradefed.command.ICommandOptions; import com.android.tradefed.device.DeviceSelectionOptions; import com.android.tradefed.device.IDeviceRecovery; import com.android.tradefed.device.IDeviceSelection; import com.android.tradefed.device.TestDeviceOptions; import com.android.tradefed.device.WaitDeviceRecovery; import com.android.tradefed.log.ILeveledLogOutput; import com.android.tradefed.log.StdoutLogger; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.TextResultReporter; import com.android.tradefed.targetprep.ITargetPreparer; import com.android.tradefed.targetprep.StubTargetPreparer; import com.android.tradefed.testtype.IRemoteTest; import com.android.tradefed.testtype.StubTest; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * A concrete {@link IConfiguration} implementation that stores the loaded config objects in a map */ public class Configuration implements IConfiguration { // type names for built in configuration objects public static final String BUILD_PROVIDER_TYPE_NAME = "build_provider"; public static final String TARGET_PREPARER_TYPE_NAME = "target_preparer"; public static final String TEST_TYPE_NAME = "test"; public static final String DEVICE_RECOVERY_TYPE_NAME = "device_recovery"; public static final String LOGGER_TYPE_NAME = "logger"; public static final String RESULT_REPORTER_TYPE_NAME = "result_reporter"; public static final String CMD_OPTIONS_TYPE_NAME = "cmd_options"; public static final String DEVICE_REQUIREMENTS_TYPE_NAME = "device_requirements"; public static final String DEVICE_OPTIONS_TYPE_NAME = "device_options"; private static Map<String, ObjTypeInfo> sObjTypeMap = null; /** Mapping of config object type name to config objects. */ private Map<String, List<Object>> mConfigMap; private final String mName; private final String mDescription; /** * Container struct for built-in config object type */ private static class ObjTypeInfo { final Class<?> mExpectedType; /** true if a list (ie many objects in a single config) are supported for this type */ final boolean mIsListSupported; ObjTypeInfo(Class<?> expectedType, boolean isList) { mExpectedType = expectedType; mIsListSupported = isList; } } /** * Determine if given config object type name is a built in object * * @param typeName the config object type name * @return <code>true</code> if name is a built in object type */ static boolean isBuiltInObjType(String typeName) { return getObjTypeMap().containsKey(typeName); } private static synchronized Map<String, ObjTypeInfo> getObjTypeMap() { if (sObjTypeMap == null) { sObjTypeMap = new HashMap<String, ObjTypeInfo>(); sObjTypeMap.put(BUILD_PROVIDER_TYPE_NAME, new ObjTypeInfo(IBuildProvider.class, false)); sObjTypeMap.put(TARGET_PREPARER_TYPE_NAME, new ObjTypeInfo(ITargetPreparer.class, true)); sObjTypeMap.put(TEST_TYPE_NAME, new ObjTypeInfo(IRemoteTest.class, true)); sObjTypeMap.put(DEVICE_RECOVERY_TYPE_NAME, new ObjTypeInfo(IDeviceRecovery.class, false)); sObjTypeMap.put(LOGGER_TYPE_NAME, new ObjTypeInfo(ILeveledLogOutput.class, false)); sObjTypeMap.put(RESULT_REPORTER_TYPE_NAME, new ObjTypeInfo(ITestInvocationListener.class, true)); sObjTypeMap.put(CMD_OPTIONS_TYPE_NAME, new ObjTypeInfo(ICommandOptions.class, false)); sObjTypeMap.put(DEVICE_REQUIREMENTS_TYPE_NAME, new ObjTypeInfo(IDeviceSelection.class, false)); sObjTypeMap.put(DEVICE_OPTIONS_TYPE_NAME, new ObjTypeInfo(TestDeviceOptions.class, false)); } return sObjTypeMap; } /** * Creates an {@link Configuration} with default config objects. */ public Configuration(String name, String description) { mName = name; mDescription = description; mConfigMap = new LinkedHashMap<String, List<Object>>(); setCommandOptions(new CommandOptions()); setDeviceRequirements(new DeviceSelectionOptions()); setDeviceOptions(new TestDeviceOptions()); setBuildProvider(new StubBuildProvider()); setTargetPreparer(new StubTargetPreparer()); setTest(new StubTest()); setDeviceRecovery(new WaitDeviceRecovery()); setLogOutput(new StdoutLogger()); setTestInvocationListener(new TextResultReporter()); } /** * @return the name of this {@link Configuration} */ public String getName() { return mName; } /** * @return a short user readable description this {@link Configuration} */ public String getDescription() { return mDescription; } /** * {@inheritDoc} */ @Override public IBuildProvider getBuildProvider() { return (IBuildProvider)getConfigurationObject(BUILD_PROVIDER_TYPE_NAME); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public List<ITargetPreparer> getTargetPreparers() { return (List<ITargetPreparer>)getConfigurationObjectList(TARGET_PREPARER_TYPE_NAME); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public List<IRemoteTest> getTests() { return (List<IRemoteTest>)getConfigurationObjectList(TEST_TYPE_NAME); } /** * <device_recovery>标签的内容 * {@inheritDoc} */ @Override public IDeviceRecovery getDeviceRecovery() { return (IDeviceRecovery)getConfigurationObject(DEVICE_RECOVERY_TYPE_NAME); } /** * {@inheritDoc} */ @Override public ILeveledLogOutput getLogOutput() { return (ILeveledLogOutput)getConfigurationObject(LOGGER_TYPE_NAME); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public List<ITestInvocationListener> getTestInvocationListeners() { return (List<ITestInvocationListener>)getConfigurationObjectList(RESULT_REPORTER_TYPE_NAME); } /** * {@inheritDoc} */ @Override public ICommandOptions getCommandOptions() { return (ICommandOptions)getConfigurationObject(CMD_OPTIONS_TYPE_NAME); } /** * {@inheritDoc} */ @Override public IDeviceSelection getDeviceRequirements() { return (IDeviceSelection)getConfigurationObject(DEVICE_REQUIREMENTS_TYPE_NAME); } /** * {@inheritDoc} */ @Override public TestDeviceOptions getDeviceOptions() { return (TestDeviceOptions)getConfigurationObject(DEVICE_OPTIONS_TYPE_NAME); } /** * {@inheritDoc} */ @Override public List<?> getConfigurationObjectList(String typeName) { return mConfigMap.get(typeName); } /** * {@inheritDoc} */ @Override public Object getConfigurationObject(String typeName) { List<?> configObjects = getConfigurationObjectList(typeName); if (configObjects == null) { return null; } ObjTypeInfo typeInfo = getObjTypeMap().get(typeName); if (typeInfo != null && typeInfo.mIsListSupported) { throw new IllegalStateException(String.format("Wrong method call. " + "Used getConfigurationObject() for a config object that is stored as a list", typeName)); } if (configObjects.size() != 1) { throw new IllegalStateException(String.format( "Attempted to retrieve single object for %s, but %d are present", typeName, configObjects.size())); } return configObjects.get(0); } /** * Return a copy of all config objects */ private Collection<Object> getAllConfigurationObjects() { Collection<Object> objectsCopy = new ArrayList<Object>(); for (List<Object> objectList : mConfigMap.values()) { objectsCopy.addAll(objectList); } return objectsCopy; } /** * {@inheritDoc} */ @Override public void injectOptionValue(String optionName, String optionValue) throws ConfigurationException { OptionSetter optionSetter = new OptionSetter(getAllConfigurationObjects()); optionSetter.setOptionValue(optionName, optionValue); } /** * {@inheritDoc} */ @Override public void injectOptionValue(String optionName, String optionKey, String optionValue) throws ConfigurationException { OptionSetter optionSetter = new OptionSetter(getAllConfigurationObjects()); optionSetter.setOptionMapValue(optionName, optionKey, optionValue); } /** * Creates a shallow copy of this object. */ @Override public Configuration clone() { Configuration clone = new Configuration(getName(), getDescription()); for (Map.Entry<String, List<Object>> entry : mConfigMap.entrySet()) { clone.setConfigurationObjectListNoThrow(entry.getKey(), entry.getValue()); } return clone; } /** * {@inheritDoc} */ @Override public void setBuildProvider(IBuildProvider provider) { setConfigurationObjectNoThrow(BUILD_PROVIDER_TYPE_NAME, provider); } /** * {@inheritDoc} */ @Override public void setTestInvocationListeners(List<ITestInvocationListener> listeners) { setConfigurationObjectListNoThrow(RESULT_REPORTER_TYPE_NAME, listeners); } /** * {@inheritDoc} */ @Override public void setTestInvocationListener(ITestInvocationListener listener) { setConfigurationObjectNoThrow(RESULT_REPORTER_TYPE_NAME, listener); } /** * {@inheritDoc} */ @Override public void setTest(IRemoteTest test) { setConfigurationObjectNoThrow(TEST_TYPE_NAME, test); } /** * {@inheritDoc} */ @Override public void setTests(List<IRemoteTest> tests) { setConfigurationObjectListNoThrow(TEST_TYPE_NAME, tests); } /** * {@inheritDoc} */ @Override public void setLogOutput(ILeveledLogOutput logger) { setConfigurationObjectNoThrow(LOGGER_TYPE_NAME, logger); } /** * {@inheritDoc} */ @Override public void setDeviceRecovery(IDeviceRecovery recovery) { setConfigurationObjectNoThrow(DEVICE_RECOVERY_TYPE_NAME, recovery); } /** * {@inheritDoc} */ @Override public void setTargetPreparer(ITargetPreparer preparer) { setConfigurationObjectNoThrow(TARGET_PREPARER_TYPE_NAME, preparer); } /** * {@inheritDoc} */ @Override public void setCommandOptions(ICommandOptions cmdOptions) { setConfigurationObjectNoThrow(CMD_OPTIONS_TYPE_NAME, cmdOptions); } /** * {@inheritDoc} */ @Override public void setDeviceRequirements(IDeviceSelection devRequirements) { setConfigurationObjectNoThrow(DEVICE_REQUIREMENTS_TYPE_NAME, devRequirements); } /** * {@inheritDoc} */ @Override public void setDeviceOptions(TestDeviceOptions devOptions) { setConfigurationObjectNoThrow(DEVICE_OPTIONS_TYPE_NAME, devOptions); } /** * {@inheritDoc} */ @Override public void setConfigurationObject(String typeName, Object configObject) throws ConfigurationException { if (configObject == null) { throw new IllegalArgumentException("configObject cannot be null"); } mConfigMap.remove(typeName); addObject(typeName, configObject); } /** * {@inheritDoc} */ @Override public void setConfigurationObjectList(String typeName, List<?> configList) throws ConfigurationException { if (configList == null) { throw new IllegalArgumentException("configList cannot be null"); } mConfigMap.remove(typeName); for (Object configObject : configList) { addObject(typeName, configObject); } } /** * Adds a loaded object to this configuration. * * @param typeName the unique object type name of the configuration object * @param configObject the configuration object * @throws ConfigurationException if object was not the correct type */ private void addObject(String typeName, Object configObject) throws ConfigurationException { List<Object> objList = mConfigMap.get(typeName); if (objList == null) { objList = new ArrayList<Object>(1); mConfigMap.put(typeName, objList); } ObjTypeInfo typeInfo = getObjTypeMap().get(typeName); if (typeInfo != null && !typeInfo.mExpectedType.isInstance(configObject)) { throw new ConfigurationException(String.format( "The config object %s is not the correct type. Expected %s, received %s", typeName, typeInfo.mExpectedType.getCanonicalName(), configObject.getClass().getCanonicalName())); } if (typeInfo != null && !typeInfo.mIsListSupported && objList.size() > 0) { throw new ConfigurationException(String.format( "Only one config object allowed for %s, but multiple were specified.", typeName)); } objList.add(configObject); if (configObject instanceof IConfigurationReceiver) { ((IConfigurationReceiver)configObject).setConfiguration(this); } } /** * A wrapper around {@link #setConfigurationObject(String, Object)} that will not throw * {@link ConfigurationException}. * <p/> * Intended to be used in cases where its guaranteed that <var>configObject</var> is the * correct type. * * @param typeName * @param configObject */ private void setConfigurationObjectNoThrow(String typeName, Object configObject) { try { setConfigurationObject(typeName, configObject); } catch (ConfigurationException e) { // should never happen throw new IllegalArgumentException(e); } } /** * A wrapper around {@link #setConfigurationObjectList(String, List)} that will not throw * {@link ConfigurationException}. * <p/> * Intended to be used in cases where its guaranteed that <var>configObject</var> is the * correct type * * @param typeName * @param configObject */ private void setConfigurationObjectListNoThrow(String typeName, List<?> configList) { try { setConfigurationObjectList(typeName, configList); } catch (ConfigurationException e) { // should never happen throw new IllegalArgumentException(e); } } /** * {@inheritDoc} */ @Override public void setOptionsFromCommandLineArgs(List<String> listArgs) throws ConfigurationException { ArgsOptionParser parser = new ArgsOptionParser(getAllConfigurationObjects()); List<String> unprocessedArgs = parser.parse(listArgs); if (unprocessedArgs.size() > 0) { throw new ConfigurationException(String.format( "Invalid arguments provided. Unprocessed arguments: %s", unprocessedArgs)); } } /** * Outputs a command line usage help text for this configuration to given printStream. * * @param out the {@link PrintStream} to use. * @throws {@link ConfigurationException} */ @Override public void printCommandUsage(boolean importantOnly, PrintStream out) throws ConfigurationException { out.println(String.format("'%s' configuration: %s", getName(), getDescription())); out.println(); if (importantOnly) { out.println("Printing help for only the important options. " + "To see help for all options, use the --help-all flag"); out.println(); } for (Map.Entry<String, List<Object>> configObjectsEntry : mConfigMap.entrySet()) { for (Object configObject : configObjectsEntry.getValue()) { String optionHelp = printOptionsForObject(importantOnly, configObjectsEntry.getKey(), configObject); // only print help for object if optionHelp is non zero length if (optionHelp.length() > 0) { String classAlias = ""; if (configObject.getClass().isAnnotationPresent(OptionClass.class)) { final OptionClass classAnnotation = configObject.getClass().getAnnotation( OptionClass.class); classAlias = String.format("'%s' ", classAnnotation.alias()); } out.printf(" %s%s options:", classAlias, configObjectsEntry.getKey()); out.println(); out.print(optionHelp); out.println(); } } } } /** * Prints out the available config options for given configuration object. * * @param importantOnly print only the important options * @param objectTypeName the config object type name. Used to generate more descriptive error * messages * @param configObject the config object * @return a {@link String} of option help text * @throws ConfigurationException */ private String printOptionsForObject(boolean importantOnly, String objectTypeName, Object configObject) throws ConfigurationException { return ArgsOptionParser.getOptionHelp(importantOnly, configObject); } /** * {@inheritDoc} */ @Override public void validateOptions() throws ConfigurationException { new ArgsOptionParser(getAllConfigurationObjects()).validateMandatoryOptions(); } }