/* * 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.ddmlib.Log; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.ClassPathScanner; import com.android.tradefed.util.ClassPathScanner.IClassPathFilter; import com.android.tradefed.util.StreamUtil; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; /** * Factory for creating {@link IConfiguration}. */ public class ConfigurationFactory implements IConfigurationFactory { private static final String LOG_TAG = "ConfigurationFactory"; private static IConfigurationFactory sInstance = null; private static final String CONFIG_SUFFIX = ".xml"; private static final String CONFIG_PREFIX = "config/"; private Map<String, ConfigurationDef> mConfigDefMap; /** * A {@link IClassPathFilter} for configuration XML files. */ private class ConfigClasspathFilter implements IClassPathFilter { /** * {@inheritDoc} */ @Override public boolean accept(String pathName) { // only accept entries that match the pattern, and that we don't // already know about return pathName.startsWith(CONFIG_PREFIX) && pathName.endsWith(CONFIG_SUFFIX) && !mConfigDefMap.containsKey(pathName); } /** * {@inheritDoc} */ @Override public String transform(String pathName) { // strip off CONFIG_PREFIX and CONFIG_SUFFIX int pathStartIndex = CONFIG_PREFIX.length(); int pathEndIndex = pathName.length() - CONFIG_SUFFIX.length(); return pathName.substring(pathStartIndex, pathEndIndex); } } /** * A {@link Comparator} for {@link ConfigurationDef} that sorts by * {@link ConfigurationDef#getName()}. */ private static class ConfigDefComparator implements Comparator<ConfigurationDef> { /** * {@inheritDoc} */ @Override public int compare(ConfigurationDef d1, ConfigurationDef d2) { return d1.getName().compareTo(d2.getName()); } } /** * Implementation of {@link IConfigDefLoader} that tracks the included * configurations from one root config, and throws an exception on circular * includes. */ class ConfigLoader implements IConfigDefLoader { private final boolean mIsGlobalConfig; private Set<String> mIncludedConfigs = new HashSet<String>(); public ConfigLoader(boolean isGlobalConfig) { mIsGlobalConfig = isGlobalConfig; } /** * {@inheritDoc} */ @Override public ConfigurationDef getConfigurationDef(String name) throws ConfigurationException { if (mIncludedConfigs.contains(name)) { throw new ConfigurationException( String.format( "Circular configuration include: config '%s' is already included", name)); } mIncludedConfigs.add(name); // first attempt to load cached config def ConfigurationDef def = mConfigDefMap.get(name); if (def == null) { // not found - load from file,解析cts.xml配置文件里的信息 def = loadConfiguration(name); mConfigDefMap.put(name, def); } return def; } /** * Loads a configuration. * * @param name * the name of a built-in configuration to load or a file * path to configuration xml to load * @return the loaded {@link ConfigurationDef} * @throws ConfigurationException * if a configuration with given name/file path cannot be * loaded or parsed */ ConfigurationDef loadConfiguration(String name) throws ConfigurationException { Log.i(LOG_TAG, String.format("Loading configuration '%s'", name)); BufferedInputStream bufStream = getConfigStream(name); ConfigurationXmlParser parser = new ConfigurationXmlParser(this); return parser.parse(name, bufStream); } /** * {@inheritDoc} */ @Override public boolean isGlobalConfig() { return mIsGlobalConfig; } } ConfigurationFactory() { mConfigDefMap = new Hashtable<String, ConfigurationDef>(); } /** * Get the singleton {@link IConfigurationFactory} instance. */ public static IConfigurationFactory getInstance() { if (sInstance == null) { sInstance = new ConfigurationFactory(); } return sInstance; } /** * Retrieve the {@link ConfigurationDef} for the given name * * @param name * the name of a built-in configuration to load or a file path to * configuration xml to load * @return {@link ConfigurationDef} * @throws ConfigurationException * if an error occurred loading the config */ private ConfigurationDef getConfigurationDef(String name, boolean isGlobal) throws ConfigurationException { return new ConfigLoader(isGlobal).getConfigurationDef(name); } /** * {@inheritDoc} */ @Override public IConfiguration createConfigurationFromArgs(String[] arrayArgs) throws ConfigurationException { List<String> listArgs = new ArrayList<String>(arrayArgs.length); IConfiguration config = internalCreateConfigurationFromArgs(arrayArgs, listArgs); config.setOptionsFromCommandLineArgs(listArgs); return config; } /** * Creates a {@link Configuration} from the name given in arguments. * <p/> * Note will not populate configuration with values from options * * @param arrayArgs * the full list of command line arguments, including the config * name * @param listArgs * an empty list, that will be populated with the remaining * option arguments * @return * @throws ConfigurationException */ private IConfiguration internalCreateConfigurationFromArgs( String[] arrayArgs, List<String> optionArgsRef) throws ConfigurationException { if (arrayArgs.length == 0) { throw new ConfigurationException( "Configuration to run was not specified"); } optionArgsRef.addAll(Arrays.asList(arrayArgs)); // first arg is config name final String configName = optionArgsRef.remove(0); ConfigurationDef configDef = getConfigurationDef(configName, false); return configDef.createConfiguration(); } /** * {@inheritDoc} */ @Override public IGlobalConfiguration createGlobalConfigurationFromArgs( String[] arrayArgs, List<String> remainingArgs) throws ConfigurationException { List<String> listArgs = new ArrayList<String>(arrayArgs.length); // 读取tf_global_config.xml配置文件里的信息 IGlobalConfiguration config = internalCreateGlobalConfigurationFromArgs( arrayArgs, listArgs); remainingArgs.addAll(config.setOptionsFromCommandLineArgs(listArgs)); return config; } /** * Creates a {@link Configuration} from the name given in arguments. * <p/> * Note will not populate configuration with values from options * * @param arrayArgs * the full list of command line arguments, including the config * name * @param listArgs * an empty list, that will be populated with the remaining * option arguments * @return * @throws ConfigurationException */ private IGlobalConfiguration internalCreateGlobalConfigurationFromArgs( String[] arrayArgs, List<String> optionArgsRef) throws ConfigurationException { if (arrayArgs.length == 0) { throw new ConfigurationException( "Configuration to run was not specified"); } optionArgsRef.addAll(Arrays.asList(arrayArgs)); // first arg is config name final String configName = optionArgsRef.remove(0); // 读取tf_global_config.xml中内容,option属性存到mOptionList中,类存到mObjectClassMap中 ConfigurationDef configDef = getConfigurationDef(configName, false); return configDef.createGlobalConfiguration(); } /** * {@inheritDoc} */ @Override public void printHelp(PrintStream out) { // print general help // TODO: move this statement to Console out.println("Use 'run command <configuration_name> --help' to get list of options for a " + "configuration"); out.println("Use 'dump config <configuration_name>' to display the configuration's XML " + "content."); out.println(); out.println("Available configurations include:"); try { loadAllConfigs(true); } catch (ConfigurationException e) { // ignore, should never happen } // sort the configs by name before displaying SortedSet<ConfigurationDef> configDefs = new TreeSet<ConfigurationDef>( new ConfigDefComparator()); configDefs.addAll(mConfigDefMap.values()); for (ConfigurationDef def : configDefs) { out.printf(" %s: %s", def.getName(), def.getDescription()); out.println(); } } /** * Loads all configurations found in classpath. * * @param discardExceptions * true if any ConfigurationException should be ignored. Exposed * for unit testing * @throws ConfigurationException */ void loadAllConfigs(boolean discardExceptions) throws ConfigurationException { ClassPathScanner cpScanner = new ClassPathScanner(); Set<String> configNames = cpScanner .getClassPathEntries(new ConfigClasspathFilter()); for (String configName : configNames) { try { ConfigurationDef configDef = getConfigurationDef(configName, false); mConfigDefMap.put(configName, configDef); } catch (ConfigurationException e) { Log.e(LOG_TAG, String.format( "Failed to load configuration '%s'. Reason: %s", configName, e.toString())); if (!discardExceptions) { throw e; } } } } /** * {@inheritDoc} */ @Override public void printHelpForConfig(String[] args, boolean importantOnly, PrintStream out) { try { IConfiguration config = internalCreateConfigurationFromArgs(args, new ArrayList<String>(args.length)); config.printCommandUsage(importantOnly, out); } catch (ConfigurationException e) { // config must not be specified. Print generic help printHelp(out); } } /** * {@inheritDoc} */ @Override public void dumpConfig(String configName, PrintStream out) { try { InputStream configStream = getConfigStream(configName); StreamUtil.copyStreams(configStream, out); } catch (ConfigurationException e) { Log.e(LOG_TAG, e); } catch (IOException e) { Log.e(LOG_TAG, e); } } /** * Return the path prefix of config xml files on classpath * <p/> * Exposed so unit tests can mock. * * @return {@link String} path with trailing / */ String getConfigPrefix() { return CONFIG_PREFIX; } /** * Loads an InputStream for given config name * * @param name * the configuration name to load * @return a {@link BufferedInputStream} for reading config contents * @throws ConfigurationException * if config could not be found */ private BufferedInputStream getConfigStream(String name) throws ConfigurationException { InputStream configStream = getClass() .getResourceAsStream( String.format("/%s%s%s", getConfigPrefix(), name, CONFIG_SUFFIX)); String configFile = System.getProperty("CTS_ROOT") + File.separator + "android-cts/tools/" + File.separator + CONFIG_PREFIX + name + CONFIG_SUFFIX; if (configStream == null) { // now try to load from file try { configStream = new FileInputStream(configFile); } catch (FileNotFoundException e) { throw new ConfigurationException(String.format( "Could not find configuration '%s'", name)); } } // buffer input for performance - just in case config file is large return new BufferedInputStream(configStream); } }