Home | History | Annotate | Download | only in config
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tradefed.config;
     18 
     19 import com.android.tradefed.build.IBuildProvider;
     20 import com.android.tradefed.command.CommandOptions;
     21 import com.android.tradefed.command.ICommandOptions;
     22 import com.android.tradefed.config.ConfigurationDef.OptionDef;
     23 import com.android.tradefed.config.OptionSetter.FieldDef;
     24 import com.android.tradefed.device.IDeviceRecovery;
     25 import com.android.tradefed.device.IDeviceSelection;
     26 import com.android.tradefed.device.TestDeviceOptions;
     27 import com.android.tradefed.device.metric.IMetricCollector;
     28 import com.android.tradefed.log.ILeveledLogOutput;
     29 import com.android.tradefed.log.StdoutLogger;
     30 import com.android.tradefed.profiler.ITestProfiler;
     31 import com.android.tradefed.profiler.StubTestProfiler;
     32 import com.android.tradefed.result.FileSystemLogSaver;
     33 import com.android.tradefed.result.ILogSaver;
     34 import com.android.tradefed.result.ITestInvocationListener;
     35 import com.android.tradefed.result.TextResultReporter;
     36 import com.android.tradefed.suite.checker.ISystemStatusChecker;
     37 import com.android.tradefed.targetprep.ITargetPreparer;
     38 import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
     39 import com.android.tradefed.targetprep.multi.StubMultiTargetPreparer;
     40 import com.android.tradefed.testtype.IRemoteTest;
     41 import com.android.tradefed.testtype.StubTest;
     42 import com.android.tradefed.util.MultiMap;
     43 import com.android.tradefed.util.QuotationAwareTokenizer;
     44 import com.android.tradefed.util.keystore.IKeyStoreClient;
     45 
     46 import com.google.common.base.Joiner;
     47 
     48 import org.json.JSONArray;
     49 import org.json.JSONException;
     50 import org.json.JSONObject;
     51 import org.kxml2.io.KXmlSerializer;
     52 
     53 import java.io.IOException;
     54 import java.io.PrintStream;
     55 import java.io.PrintWriter;
     56 import java.lang.reflect.Field;
     57 import java.lang.reflect.ParameterizedType;
     58 import java.lang.reflect.Type;
     59 import java.util.ArrayList;
     60 import java.util.Collection;
     61 import java.util.HashMap;
     62 import java.util.HashSet;
     63 import java.util.LinkedHashMap;
     64 import java.util.List;
     65 import java.util.Map;
     66 import java.util.Map.Entry;
     67 import java.util.Set;
     68 import java.util.regex.Pattern;
     69 
     70 /**
     71  * A concrete {@link IConfiguration} implementation that stores the loaded config objects in a map.
     72  */
     73 public class Configuration implements IConfiguration {
     74 
     75     // type names for built in configuration objects
     76     public static final String BUILD_PROVIDER_TYPE_NAME = "build_provider";
     77     public static final String TARGET_PREPARER_TYPE_NAME = "target_preparer";
     78     public static final String MULTI_PREPARER_TYPE_NAME = "multi_target_preparer";
     79     public static final String TEST_TYPE_NAME = "test";
     80     public static final String DEVICE_RECOVERY_TYPE_NAME = "device_recovery";
     81     public static final String LOGGER_TYPE_NAME = "logger";
     82     public static final String LOG_SAVER_TYPE_NAME = "log_saver";
     83     public static final String RESULT_REPORTER_TYPE_NAME = "result_reporter";
     84     public static final String CMD_OPTIONS_TYPE_NAME = "cmd_options";
     85     public static final String DEVICE_REQUIREMENTS_TYPE_NAME = "device_requirements";
     86     public static final String DEVICE_OPTIONS_TYPE_NAME = "device_options";
     87     public static final String SYSTEM_STATUS_CHECKER_TYPE_NAME = "system_checker";
     88     public static final String CONFIGURATION_DESCRIPTION_TYPE_NAME = "config_desc";
     89     public static final String DEVICE_NAME = "device";
     90     public static final String TEST_PROFILER_TYPE_NAME = "test_profiler";
     91     public static final String DEVICE_METRICS_COLLECTOR_TYPE_NAME = "metrics_collector";
     92     public static final String SANDBOX_TYPE_NAME = "sandbox";
     93 
     94     private static Map<String, ObjTypeInfo> sObjTypeMap = null;
     95     private static Set<String> sMultiDeviceSupportedTag = null;
     96 
     97     // regexp pattern used to parse map option values
     98     private static final Pattern OPTION_KEY_VALUE_PATTERN = Pattern.compile("(?<!\\\\)=");
     99 
    100     private static final String CONFIG_EXCEPTION_PATTERN = "Could not find option with name ";
    101 
    102     /** Mapping of config object type name to config objects. */
    103     private Map<String, List<Object>> mConfigMap;
    104     private final String mName;
    105     private final String mDescription;
    106     // original command line used to create this given configuration.
    107     private String[] mCommandLine;
    108 
    109     // Used to track config names that were used to set field values
    110     private MultiMap<FieldDef, String> mFieldSources = new MultiMap<>();
    111 
    112     /**
    113      * Container struct for built-in config object type
    114      */
    115     private static class ObjTypeInfo {
    116         final Class<?> mExpectedType;
    117         /**
    118          * true if a list (ie many objects in a single config) are supported for this type
    119          */
    120         final boolean mIsListSupported;
    121 
    122         ObjTypeInfo(Class<?> expectedType, boolean isList) {
    123             mExpectedType = expectedType;
    124             mIsListSupported = isList;
    125         }
    126     }
    127 
    128     /**
    129      * Determine if given config object type name is a built in object
    130      *
    131      * @param typeName the config object type name
    132      * @return <code>true</code> if name is a built in object type
    133      */
    134     static boolean isBuiltInObjType(String typeName) {
    135         return getObjTypeMap().containsKey(typeName);
    136     }
    137 
    138     private static synchronized Map<String, ObjTypeInfo> getObjTypeMap() {
    139         if (sObjTypeMap == null) {
    140             sObjTypeMap = new HashMap<String, ObjTypeInfo>();
    141             sObjTypeMap.put(BUILD_PROVIDER_TYPE_NAME, new ObjTypeInfo(IBuildProvider.class, false));
    142             sObjTypeMap.put(TARGET_PREPARER_TYPE_NAME,
    143                     new ObjTypeInfo(ITargetPreparer.class, true));
    144             sObjTypeMap.put(MULTI_PREPARER_TYPE_NAME,
    145                     new ObjTypeInfo(IMultiTargetPreparer.class, true));
    146             sObjTypeMap.put(TEST_TYPE_NAME, new ObjTypeInfo(IRemoteTest.class, true));
    147             sObjTypeMap.put(DEVICE_RECOVERY_TYPE_NAME,
    148                     new ObjTypeInfo(IDeviceRecovery.class, false));
    149             sObjTypeMap.put(LOGGER_TYPE_NAME, new ObjTypeInfo(ILeveledLogOutput.class, false));
    150             sObjTypeMap.put(LOG_SAVER_TYPE_NAME, new ObjTypeInfo(ILogSaver.class, false));
    151             sObjTypeMap.put(RESULT_REPORTER_TYPE_NAME,
    152                     new ObjTypeInfo(ITestInvocationListener.class, true));
    153             sObjTypeMap.put(CMD_OPTIONS_TYPE_NAME, new ObjTypeInfo(ICommandOptions.class,
    154                     false));
    155             sObjTypeMap.put(DEVICE_REQUIREMENTS_TYPE_NAME, new ObjTypeInfo(IDeviceSelection.class,
    156                     false));
    157             sObjTypeMap.put(DEVICE_OPTIONS_TYPE_NAME, new ObjTypeInfo(TestDeviceOptions.class,
    158                     false));
    159             sObjTypeMap.put(DEVICE_NAME, new ObjTypeInfo(IDeviceConfiguration.class, true));
    160             sObjTypeMap.put(SYSTEM_STATUS_CHECKER_TYPE_NAME,
    161                     new ObjTypeInfo(ISystemStatusChecker.class, true));
    162             sObjTypeMap.put(
    163                     CONFIGURATION_DESCRIPTION_TYPE_NAME,
    164                     new ObjTypeInfo(ConfigurationDescriptor.class, false));
    165             sObjTypeMap.put(TEST_PROFILER_TYPE_NAME, new ObjTypeInfo(ITestProfiler.class, false));
    166             sObjTypeMap.put(
    167                     DEVICE_METRICS_COLLECTOR_TYPE_NAME,
    168                     new ObjTypeInfo(IMetricCollector.class, true));
    169         }
    170         return sObjTypeMap;
    171     }
    172 
    173     /**
    174      * Determine if a given config object type is allowed to exists inside a device tag
    175      * configuration.
    176      * Authorized type are: build_provider, target_preparer, device_recovery, device_requirements,
    177      * device_options
    178      *
    179      * @param typeName the config object type name
    180      * @return True if name is allowed to exists inside the device tag
    181      */
    182     static boolean doesBuiltInObjSupportMultiDevice(String typeName) {
    183         return getMultiDeviceSupportedTag().contains(typeName);
    184     }
    185 
    186     /**
    187      * Return the {@link Set} of tags that are supported in a device tag for multi device
    188      * configuration.
    189      */
    190     private static synchronized Set<String> getMultiDeviceSupportedTag() {
    191         if (sMultiDeviceSupportedTag == null) {
    192             sMultiDeviceSupportedTag = new HashSet<String>();
    193             sMultiDeviceSupportedTag.add(BUILD_PROVIDER_TYPE_NAME);
    194             sMultiDeviceSupportedTag.add(TARGET_PREPARER_TYPE_NAME);
    195             sMultiDeviceSupportedTag.add(DEVICE_RECOVERY_TYPE_NAME);
    196             sMultiDeviceSupportedTag.add(DEVICE_REQUIREMENTS_TYPE_NAME);
    197             sMultiDeviceSupportedTag.add(DEVICE_OPTIONS_TYPE_NAME);
    198         }
    199         return sMultiDeviceSupportedTag;
    200     }
    201 
    202     /**
    203      * Creates an {@link Configuration} with default config objects.
    204      */
    205     public Configuration(String name, String description) {
    206         mName = name;
    207         mDescription = description;
    208         mConfigMap = new LinkedHashMap<String, List<Object>>();
    209         setDeviceConfig(new DeviceConfigurationHolder(ConfigurationDef.DEFAULT_DEVICE_NAME));
    210         setCommandOptions(new CommandOptions());
    211         setTest(new StubTest());
    212         setLogOutput(new StdoutLogger());
    213         setLogSaver(new FileSystemLogSaver()); // FileSystemLogSaver saves to tmp by default.
    214         setTestInvocationListener(new TextResultReporter());
    215         setMultiTargetPreparer(new StubMultiTargetPreparer());
    216         setSystemStatusCheckers(new ArrayList<ISystemStatusChecker>());
    217         setConfigurationDescriptor(new ConfigurationDescriptor());
    218         setProfiler(new StubTestProfiler());
    219         setDeviceMetricCollectors(new ArrayList<>());
    220     }
    221 
    222     /**
    223      * If we are in multi device mode, we cannot allow fetching the regular references because
    224      * they are most likely wrong.
    225      */
    226     private void notAllowedInMultiMode(String function) {
    227         if (getConfigurationObjectList(DEVICE_NAME).size() > 1) {
    228             throw new UnsupportedOperationException(String.format("Calling %s is not allowed "
    229                     + "in multi device mode", function));
    230         }
    231         if (getConfigurationObjectList(DEVICE_NAME).size() == 0) {
    232             throw new UnsupportedOperationException(
    233                     "We should always have at least 1 Device config");
    234         }
    235     }
    236 
    237     /** {@inheritDoc} */
    238     @Override
    239     public String getName() {
    240         return mName;
    241     }
    242 
    243     /**
    244      * @return a short user readable description this {@link Configuration}
    245      */
    246     public String getDescription() {
    247         return mDescription;
    248     }
    249 
    250     /**
    251      * {@inheritDoc}
    252      */
    253     @Override
    254     public void setCommandLine(String[] arrayArgs) {
    255         mCommandLine = arrayArgs;
    256     }
    257 
    258     /**
    259      * {@inheritDoc}
    260      */
    261     @Override
    262     public String getCommandLine() {
    263         // FIXME: obfuscated passwords from command line.
    264         if (mCommandLine != null && mCommandLine.length != 0) {
    265             return QuotationAwareTokenizer.combineTokens(mCommandLine);
    266         }
    267         // If no args were available return null.
    268         return null;
    269     }
    270 
    271     /**
    272      * {@inheritDoc}
    273      */
    274     @SuppressWarnings("unchecked")
    275     @Override
    276     public IBuildProvider getBuildProvider() {
    277         notAllowedInMultiMode("getBuildProvider");
    278         return ((List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME))
    279                 .get(0).getBuildProvider();
    280     }
    281 
    282     /**
    283      * {@inheritDoc}
    284      */
    285     @SuppressWarnings("unchecked")
    286     @Override
    287     public List<ITargetPreparer> getTargetPreparers() {
    288         notAllowedInMultiMode("getTargetPreparers");
    289         return ((List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME))
    290                 .get(0).getTargetPreparers();
    291     }
    292 
    293     /**
    294      * {@inheritDoc}
    295      */
    296     @SuppressWarnings("unchecked")
    297     @Override
    298     public List<IRemoteTest> getTests() {
    299         return (List<IRemoteTest>) getConfigurationObjectList(TEST_TYPE_NAME);
    300     }
    301 
    302     /**
    303      * {@inheritDoc}
    304      */
    305     @SuppressWarnings("unchecked")
    306     @Override
    307     public IDeviceRecovery getDeviceRecovery() {
    308         notAllowedInMultiMode("getDeviceRecovery");
    309         return ((List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME))
    310                 .get(0).getDeviceRecovery();
    311     }
    312 
    313     /**
    314      * {@inheritDoc}
    315      */
    316     @Override
    317     public ILeveledLogOutput getLogOutput() {
    318         return (ILeveledLogOutput) getConfigurationObject(LOGGER_TYPE_NAME);
    319     }
    320 
    321     /**
    322      * {@inheritDoc}
    323      */
    324     @Override
    325     public ILogSaver getLogSaver() {
    326         return (ILogSaver) getConfigurationObject(LOG_SAVER_TYPE_NAME);
    327     }
    328 
    329     /**
    330      * {@inheritDoc}
    331      */
    332     @SuppressWarnings("unchecked")
    333     @Override
    334     public List<IMultiTargetPreparer> getMultiTargetPreparers() {
    335         return (List<IMultiTargetPreparer>) getConfigurationObjectList(MULTI_PREPARER_TYPE_NAME);
    336     }
    337 
    338     /**
    339      * {@inheritDoc}
    340      */
    341     @SuppressWarnings("unchecked")
    342     @Override
    343     public List<ISystemStatusChecker> getSystemStatusCheckers() {
    344         return (List<ISystemStatusChecker>)
    345                 getConfigurationObjectList(SYSTEM_STATUS_CHECKER_TYPE_NAME);
    346     }
    347 
    348     /**
    349      * {@inheritDoc}
    350      */
    351     @SuppressWarnings("unchecked")
    352     @Override
    353     public List<ITestInvocationListener> getTestInvocationListeners() {
    354         return (List<ITestInvocationListener>) getConfigurationObjectList(
    355                 RESULT_REPORTER_TYPE_NAME);
    356     }
    357 
    358     @SuppressWarnings("unchecked")
    359     @Override
    360     public List<IMetricCollector> getMetricCollectors() {
    361         return (List<IMetricCollector>)
    362                 getConfigurationObjectList(DEVICE_METRICS_COLLECTOR_TYPE_NAME);
    363     }
    364 
    365     /** {@inheritDoc} */
    366     @SuppressWarnings("unchecked")
    367     @Override
    368     public ITestProfiler getProfiler() {
    369         return (ITestProfiler) getConfigurationObject(TEST_PROFILER_TYPE_NAME);
    370     }
    371 
    372     /** {@inheritDoc} */
    373     @Override
    374     public ICommandOptions getCommandOptions() {
    375         return (ICommandOptions) getConfigurationObject(CMD_OPTIONS_TYPE_NAME);
    376     }
    377 
    378     /** {@inheritDoc} */
    379     @Override
    380     public ConfigurationDescriptor getConfigurationDescription() {
    381         return (ConfigurationDescriptor)
    382                 getConfigurationObject(CONFIGURATION_DESCRIPTION_TYPE_NAME);
    383     }
    384 
    385     /** {@inheritDoc} */
    386     @SuppressWarnings("unchecked")
    387     @Override
    388     public IDeviceSelection getDeviceRequirements() {
    389         notAllowedInMultiMode("getDeviceRequirements");
    390         return ((List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME))
    391                 .get(0).getDeviceRequirements();
    392     }
    393 
    394     /**
    395      * {@inheritDoc}
    396      */
    397     @SuppressWarnings("unchecked")
    398     @Override
    399     public TestDeviceOptions getDeviceOptions() {
    400         notAllowedInMultiMode("getDeviceOptions");
    401         return ((List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME))
    402                 .get(0).getDeviceOptions();
    403     }
    404 
    405     /**
    406      * {@inheritDoc}
    407      */
    408     @Override
    409     public List<?> getConfigurationObjectList(String typeName) {
    410         return mConfigMap.get(typeName);
    411     }
    412 
    413     /**
    414      * {@inheritDoc}
    415      */
    416     @SuppressWarnings("unchecked")
    417     @Override
    418     public IDeviceConfiguration getDeviceConfigByName(String nameDevice) {
    419         for (IDeviceConfiguration deviceHolder :
    420                 (List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME)) {
    421             if (deviceHolder.getDeviceName().equals(nameDevice)) {
    422                 return deviceHolder;
    423             }
    424         }
    425         return null;
    426     }
    427 
    428     /**
    429      * {@inheritDoc}
    430      */
    431     @SuppressWarnings("unchecked")
    432     @Override
    433     public List<IDeviceConfiguration> getDeviceConfig() {
    434         return (List<IDeviceConfiguration>)getConfigurationObjectList(DEVICE_NAME);
    435     }
    436 
    437     /**
    438      * {@inheritDoc}
    439      */
    440     @Override
    441     public Object getConfigurationObject(String typeName) {
    442         List<?> configObjects = getConfigurationObjectList(typeName);
    443         if (configObjects == null) {
    444             return null;
    445         }
    446         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
    447         if (typeInfo != null && typeInfo.mIsListSupported) {
    448             throw new IllegalStateException(
    449                     String.format(
    450                             "Wrong method call for type %s. Used getConfigurationObject() for a "
    451                                     + "config object that is stored as a list",
    452                             typeName));
    453         }
    454         if (configObjects.size() != 1) {
    455             throw new IllegalStateException(String.format(
    456                     "Attempted to retrieve single object for %s, but %d are present",
    457                     typeName, configObjects.size()));
    458         }
    459         return configObjects.get(0);
    460     }
    461 
    462     /**
    463      * Return a copy of all config objects
    464      */
    465     private Collection<Object> getAllConfigurationObjects() {
    466         return getAllConfigurationObjects(null);
    467     }
    468 
    469     /**
    470      * Return a copy of all config objects, minus the object configuration of the type specified.
    471      * Returns all the config objects if param is null.
    472      */
    473     private Collection<Object> getAllConfigurationObjects(String excludedConfigName) {
    474         Collection<Object> objectsCopy = new ArrayList<Object>();
    475         for (Entry<String, List<Object>> entryList : mConfigMap.entrySet()) {
    476             if (excludedConfigName != null) {
    477                 // Only add if not a descriptor config object type.
    478                 if (!excludedConfigName.equals(entryList.getKey())) {
    479                     objectsCopy.addAll(entryList.getValue());
    480                 }
    481             } else {
    482                 objectsCopy.addAll(entryList.getValue());
    483             }
    484         }
    485         return objectsCopy;
    486     }
    487 
    488     /**
    489      * Creates an OptionSetter which is appropriate for setting options on all objects which
    490      * will be returned by {@link #getAllConfigurationObjects}.
    491      */
    492     private OptionSetter createOptionSetter() throws ConfigurationException {
    493         return new OptionSetter(getAllConfigurationObjects());
    494     }
    495 
    496     /**
    497      * Injects an option value into the set of configuration objects.
    498      *
    499      * Uses provided arguments as is and fails if arguments have invalid format or
    500      * provided ambiguously, e.g. {@code optionKey} argument is provided for non-map option,
    501      * or the value for an option of integer type cannot be parsed as an integer number.
    502      *
    503      * @param optionSetter setter to use for the injection
    504      * @param optionName name of the option
    505      * @param optionKey map key, if the option is of map type
    506      * @param optionValue value of the option or map value, if the option is of map type
    507      * @param source source of the option
    508      * @throws ConfigurationException if option value cannot be injected
    509      */
    510     private void internalInjectOptionValue(OptionSetter optionSetter, String optionName,
    511             String optionKey, String optionValue, String source) throws ConfigurationException {
    512         if (optionSetter == null) {
    513             throw new IllegalArgumentException("optionSetter cannot be null");
    514         }
    515 
    516         // Set all fields that match this option name / key
    517         List<FieldDef> affectedFields = optionSetter.setOptionValue(
    518                 optionName, optionKey, optionValue);
    519 
    520         if (source != null) {
    521             // Update the source for each affected field
    522             for (FieldDef field : affectedFields) {
    523                 // Unless the field is a Collection or MultiMap entry, it can only have one source
    524                 if (!Collection.class.isAssignableFrom(field.field.getType()) &&
    525                         !MultiMap.class.isAssignableFrom(field.field.getType())) {
    526                     mFieldSources.remove(field);
    527                 }
    528                 mFieldSources.put(field, source);
    529             }
    530         }
    531     }
    532 
    533     /**
    534      * Injects an option value into the set of configuration objects.
    535      *
    536      * If the option to be set is of map type, an attempt to parse {@code optionValue} argument
    537      * into key-value pair is made. In this case {@code optionValue} must have an equal sign
    538      * separating a key and a value (e.g. my_key=my_value).
    539      * In case a key or a value themselves contain an equal sign, this equal sign in them
    540      * must be escaped using a backslash (e.g. a\=b=y\=z).
    541      *
    542      * @param optionSetter setter to use for the injection
    543      * @param optionName name of the option
    544      * @param optionValue value of the option
    545      * @throws ConfigurationException if option value cannot be injected
    546      */
    547     private void internalInjectOptionValue(OptionSetter optionSetter, String optionName,
    548             String optionValue) throws ConfigurationException {
    549         // Cannot continue without optionSetter
    550         if (optionSetter == null) {
    551             throw new IllegalArgumentException("optionSetter cannot be null");
    552         }
    553 
    554         // If the option is not a map, then the key is null...
    555         if (!optionSetter.isMapOption(optionName)) {
    556             internalInjectOptionValue(optionSetter, optionName, null, optionValue, null);
    557             return;
    558         }
    559 
    560         // ..., otherwise try to parse the value to retrieve the key
    561         String[] parts = OPTION_KEY_VALUE_PATTERN.split(optionValue);
    562         if (parts.length != 2) {
    563             throw new ConfigurationException(String.format(
    564                     "option '%s' has an invalid format for value %s:w",
    565                     optionName, optionValue));
    566         }
    567         internalInjectOptionValue(optionSetter, optionName,
    568                 parts[0].replace("\\\\=", "="), parts[1].replace("\\\\=", "="), null);
    569     }
    570 
    571     /**
    572      * {@inheritDoc}
    573      */
    574     @Override
    575     public void injectOptionValue(String optionName, String optionValue)
    576             throws ConfigurationException {
    577         internalInjectOptionValue(createOptionSetter(), optionName, optionValue);
    578     }
    579 
    580     /**
    581      * {@inheritDoc}
    582      */
    583     @Override
    584     public void injectOptionValue(String optionName, String optionKey, String optionValue)
    585             throws ConfigurationException {
    586         internalInjectOptionValue(createOptionSetter(), optionName, optionKey, optionValue, null);
    587     }
    588 
    589     /**
    590      * {@inheritDoc}
    591      */
    592     @Override
    593     public void injectOptionValueWithSource(String optionName, String optionKey, String optionValue,
    594             String source) throws ConfigurationException {
    595         internalInjectOptionValue(createOptionSetter(), optionName, optionKey, optionValue, source);
    596     }
    597 
    598     /**
    599      * {@inheritDoc}
    600      */
    601     @Override
    602     public void injectOptionValues(List<OptionDef> optionDefs) throws ConfigurationException {
    603         OptionSetter optionSetter = createOptionSetter();
    604         for (OptionDef optionDef : optionDefs) {
    605             internalInjectOptionValue(optionSetter, optionDef.name, optionDef.key, optionDef.value,
    606                     optionDef.source);
    607         }
    608     }
    609 
    610     /**
    611      * Creates a shallow copy of this object.
    612      */
    613     @Override
    614     public Configuration clone() {
    615         Configuration clone = new Configuration(getName(), getDescription());
    616         for (Map.Entry<String, List<Object>> entry : mConfigMap.entrySet()) {
    617             if (DEVICE_NAME.equals(entry.getKey())) {
    618                 List<Object> newDeviceConfigList = new ArrayList<Object>();
    619                 for (Object deviceConfig : entry.getValue()) {
    620                     IDeviceConfiguration config = ((IDeviceConfiguration)deviceConfig);
    621                     IDeviceConfiguration newDeviceConfig = config.clone();
    622                     newDeviceConfigList.add(newDeviceConfig);
    623                 }
    624                 clone.setConfigurationObjectListNoThrow(entry.getKey(), newDeviceConfigList);
    625             } else {
    626                 clone.setConfigurationObjectListNoThrow(entry.getKey(), entry.getValue());
    627             }
    628         }
    629         return clone;
    630     }
    631 
    632     private void addToDefaultDeviceConfig(Object obj) {
    633         try {
    634             getDeviceConfigByName(ConfigurationDef.DEFAULT_DEVICE_NAME).addSpecificConfig(obj);
    635         } catch (ConfigurationException e) {
    636             // should never happen
    637             throw new IllegalArgumentException(e);
    638         }
    639     }
    640 
    641     /**
    642      * {@inheritDoc}
    643      */
    644     @Override
    645     public void setBuildProvider(IBuildProvider provider) {
    646         notAllowedInMultiMode("setBuildProvider");
    647         addToDefaultDeviceConfig(provider);
    648     }
    649 
    650     /**
    651      * {@inheritDoc}
    652      */
    653     @Override
    654     public void setTestInvocationListeners(List<ITestInvocationListener> listeners) {
    655         setConfigurationObjectListNoThrow(RESULT_REPORTER_TYPE_NAME, listeners);
    656     }
    657 
    658     @Override
    659     public void setDeviceMetricCollectors(List<IMetricCollector> collectors) {
    660         setConfigurationObjectListNoThrow(DEVICE_METRICS_COLLECTOR_TYPE_NAME, collectors);
    661     }
    662 
    663     /**
    664      * {@inheritDoc}
    665      */
    666     @Override
    667     public void setTestInvocationListener(ITestInvocationListener listener) {
    668         setConfigurationObjectNoThrow(RESULT_REPORTER_TYPE_NAME, listener);
    669     }
    670 
    671     /**
    672      * {@inheritDoc}
    673      */
    674     @Override
    675     public void setDeviceConfig(IDeviceConfiguration deviceConfig) {
    676         setConfigurationObjectNoThrow(DEVICE_NAME, deviceConfig);
    677     }
    678 
    679     /**
    680      * {@inheritDoc}
    681      */
    682     @Override
    683     public void setDeviceConfigList(List<IDeviceConfiguration> deviceConfigs) {
    684         setConfigurationObjectListNoThrow(DEVICE_NAME, deviceConfigs);
    685     }
    686 
    687     /**
    688      * {@inheritDoc}
    689      */
    690     @Override
    691     public void setTest(IRemoteTest test) {
    692         setConfigurationObjectNoThrow(TEST_TYPE_NAME, test);
    693     }
    694 
    695     /**
    696      * {@inheritDoc}
    697      */
    698     @Override
    699     public void setTests(List<IRemoteTest> tests) {
    700         setConfigurationObjectListNoThrow(TEST_TYPE_NAME, tests);
    701     }
    702 
    703     /**
    704      * {@inheritDoc}
    705      */
    706     @Override
    707     public void setMultiTargetPreparers(List<IMultiTargetPreparer> multiTargPreps) {
    708         setConfigurationObjectListNoThrow(MULTI_PREPARER_TYPE_NAME, multiTargPreps);
    709     }
    710 
    711     /**
    712      * {@inheritDoc}
    713      */
    714     @Override
    715     public void setMultiTargetPreparer(IMultiTargetPreparer multiTargPrep) {
    716         setConfigurationObjectNoThrow(MULTI_PREPARER_TYPE_NAME, multiTargPrep);
    717     }
    718 
    719     /**
    720      * {@inheritDoc}
    721      */
    722     @Override
    723     public void setSystemStatusCheckers(List<ISystemStatusChecker> systemCheckers) {
    724         setConfigurationObjectListNoThrow(SYSTEM_STATUS_CHECKER_TYPE_NAME, systemCheckers);
    725     }
    726 
    727     /**
    728      * {@inheritDoc}
    729      */
    730     @Override
    731     public void setSystemStatusChecker(ISystemStatusChecker systemChecker) {
    732         setConfigurationObjectNoThrow(SYSTEM_STATUS_CHECKER_TYPE_NAME, systemChecker);
    733     }
    734 
    735     /** {@inheritDoc} */
    736     @Override
    737     public void setProfiler(ITestProfiler profiler) {
    738         setConfigurationObjectNoThrow(TEST_PROFILER_TYPE_NAME, profiler);
    739     }
    740 
    741     /** {@inheritDoc} */
    742     @Override
    743     public void setLogOutput(ILeveledLogOutput logger) {
    744         setConfigurationObjectNoThrow(LOGGER_TYPE_NAME, logger);
    745     }
    746 
    747     /** {@inheritDoc} */
    748     @Override
    749     public void setLogSaver(ILogSaver logSaver) {
    750         setConfigurationObjectNoThrow(LOG_SAVER_TYPE_NAME, logSaver);
    751     }
    752 
    753     /** Sets the {@link ConfigurationDescriptor} to be used in the configuration. */
    754     private void setConfigurationDescriptor(ConfigurationDescriptor configDescriptor) {
    755         setConfigurationObjectNoThrow(CONFIGURATION_DESCRIPTION_TYPE_NAME, configDescriptor);
    756     }
    757 
    758     /** {@inheritDoc} */
    759     @Override
    760     public void setDeviceRecovery(IDeviceRecovery recovery) {
    761         notAllowedInMultiMode("setDeviceRecovery");
    762         addToDefaultDeviceConfig(recovery);
    763     }
    764 
    765     /**
    766      * {@inheritDoc}
    767      */
    768     @Override
    769     public void setTargetPreparer(ITargetPreparer preparer) {
    770         notAllowedInMultiMode("setTargetPreparer");
    771         addToDefaultDeviceConfig(preparer);
    772     }
    773 
    774     /**
    775      * {@inheritDoc}
    776      */
    777     @Override
    778     public void setCommandOptions(ICommandOptions cmdOptions) {
    779         setConfigurationObjectNoThrow(CMD_OPTIONS_TYPE_NAME, cmdOptions);
    780     }
    781 
    782     /**
    783      * {@inheritDoc}
    784      */
    785     @Override
    786     public void setDeviceRequirements(IDeviceSelection devRequirements) {
    787         notAllowedInMultiMode("setDeviceRequirements");
    788         addToDefaultDeviceConfig(devRequirements);
    789     }
    790 
    791     /**
    792      * {@inheritDoc}
    793      */
    794     @Override
    795     public void setDeviceOptions(TestDeviceOptions devOptions) {
    796         notAllowedInMultiMode("setDeviceOptions");
    797         addToDefaultDeviceConfig(devOptions);
    798     }
    799 
    800     /**
    801      * {@inheritDoc}
    802      */
    803     @Override
    804     public synchronized void setConfigurationObject(String typeName, Object configObject)
    805             throws ConfigurationException {
    806         if (configObject == null) {
    807             throw new IllegalArgumentException("configObject cannot be null");
    808         }
    809         mConfigMap.remove(typeName);
    810         addObject(typeName, configObject);
    811     }
    812 
    813     /**
    814      * {@inheritDoc}
    815      */
    816     @Override
    817     public synchronized void setConfigurationObjectList(String typeName, List<?> configList)
    818             throws ConfigurationException {
    819         if (configList == null) {
    820             throw new IllegalArgumentException("configList cannot be null");
    821         }
    822         mConfigMap.remove(typeName);
    823         mConfigMap.put(typeName, new ArrayList<Object>(1));
    824         for (Object configObject : configList) {
    825             addObject(typeName, configObject);
    826         }
    827     }
    828 
    829     /**
    830      * Adds a loaded object to this configuration.
    831      *
    832      * @param typeName the unique object type name of the configuration object
    833      * @param configObject the configuration object
    834      * @throws ConfigurationException if object was not the correct type
    835      */
    836     private synchronized void addObject(String typeName, Object configObject)
    837             throws ConfigurationException {
    838         List<Object> objList = mConfigMap.get(typeName);
    839         if (objList == null) {
    840             objList = new ArrayList<Object>(1);
    841             mConfigMap.put(typeName, objList);
    842         }
    843         ObjTypeInfo typeInfo = getObjTypeMap().get(typeName);
    844         if (typeInfo != null && !typeInfo.mExpectedType.isInstance(configObject)) {
    845             throw new ConfigurationException(String.format(
    846                     "The config object %s is not the correct type. Expected %s, received %s",
    847                     typeName, typeInfo.mExpectedType.getCanonicalName(),
    848                     configObject.getClass().getCanonicalName()));
    849         }
    850         if (typeInfo != null && !typeInfo.mIsListSupported && objList.size() > 0) {
    851             throw new ConfigurationException(String.format(
    852                     "Only one config object allowed for %s, but multiple were specified.",
    853                     typeName));
    854         }
    855         objList.add(configObject);
    856         if (configObject instanceof IConfigurationReceiver) {
    857             ((IConfigurationReceiver) configObject).setConfiguration(this);
    858         }
    859         // Inject to object inside device holder too.
    860         if (configObject instanceof IDeviceConfiguration) {
    861             for (Object obj : ((IDeviceConfiguration) configObject).getAllObjects()) {
    862                 if (obj instanceof IConfigurationReceiver) {
    863                     ((IConfigurationReceiver) obj).setConfiguration(this);
    864                 }
    865             }
    866         }
    867     }
    868 
    869     /**
    870      * A wrapper around {@link #setConfigurationObject(String, Object)} that
    871      * will not throw {@link ConfigurationException}.
    872      * <p/>
    873      * Intended to be used in cases where its guaranteed that
    874      * <var>configObject</var> is the correct type.
    875      *
    876      * @param typeName
    877      * @param configObject
    878      */
    879     private void setConfigurationObjectNoThrow(String typeName, Object configObject) {
    880         try {
    881             setConfigurationObject(typeName, configObject);
    882         } catch (ConfigurationException e) {
    883             // should never happen
    884             throw new IllegalArgumentException(e);
    885         }
    886     }
    887 
    888     /**
    889      * A wrapper around {@link #setConfigurationObjectList(String, List)} that
    890      * will not throw {@link ConfigurationException}.
    891      * <p/>
    892      * Intended to be used in cases where its guaranteed that
    893      * <var>configObject</var> is the correct type
    894      *
    895      * @param typeName
    896      * @param configList
    897      */
    898     private void setConfigurationObjectListNoThrow(String typeName, List<?> configList) {
    899         try {
    900             setConfigurationObjectList(typeName, configList);
    901         } catch (ConfigurationException e) {
    902             // should never happen
    903             throw new IllegalArgumentException(e);
    904         }
    905     }
    906 
    907     /**
    908      * {@inheritDoc}
    909      */
    910     @Override
    911     public List<String> setOptionsFromCommandLineArgs(List<String> listArgs)
    912             throws ConfigurationException {
    913         return setOptionsFromCommandLineArgs(listArgs, null);
    914     }
    915 
    916     /**
    917      * {@inheritDoc}
    918      */
    919     @Override
    920     public List<String> setOptionsFromCommandLineArgs(List<String> listArgs,
    921             IKeyStoreClient keyStoreClient)
    922             throws ConfigurationException {
    923         // We get all the objects except the one describing the Configuration itself which does not
    924         // allow passing its option via command line.
    925         ArgsOptionParser parser =
    926                 new ArgsOptionParser(
    927                         getAllConfigurationObjects(CONFIGURATION_DESCRIPTION_TYPE_NAME));
    928         if (keyStoreClient != null) {
    929             parser.setKeyStore(keyStoreClient);
    930         }
    931         try {
    932             return parser.parse(listArgs);
    933         } catch (ConfigurationException e) {
    934             if (!e.getMessage().contains(CONFIG_EXCEPTION_PATTERN)) {
    935                 throw e;
    936             }
    937             String optionName = e.getMessage().split(CONFIG_EXCEPTION_PATTERN)[1];
    938             try {
    939                 // In case the option exists in the config descriptor, we change the error message
    940                 // to be more specific about why the option is rejected.
    941                 OptionSetter setter = new OptionSetter(getConfigurationDescription());
    942                 setter.getTypeForOption(optionName);
    943             } catch (ConfigurationException stillThrowing) {
    944                 // Throw the original exception since it cannot be found at all.
    945                 throw e;
    946             }
    947             throw new OptionNotAllowedException(
    948                     String.format(
    949                             "Option %s cannot be specified via "
    950                                     + "command line. Only in the configuration xml.",
    951                             optionName));
    952         }
    953     }
    954 
    955     /**
    956      * Outputs a command line usage help text for this configuration to given
    957      * printStream.
    958      *
    959      * @param out the {@link PrintStream} to use.
    960      * @throws ConfigurationException
    961      */
    962     @Override
    963     public void printCommandUsage(boolean importantOnly, PrintStream out)
    964             throws ConfigurationException {
    965         out.println(String.format("'%s' configuration: %s", getName(), getDescription()));
    966         out.println();
    967         if (importantOnly) {
    968             out.println("Printing help for only the important options. " +
    969                     "To see help for all options, use the --help-all flag");
    970             out.println();
    971         }
    972         for (Map.Entry<String, List<Object>> configObjectsEntry : mConfigMap.entrySet()) {
    973             for (Object configObject : configObjectsEntry.getValue()) {
    974                 if (configObject instanceof IDeviceConfiguration) {
    975                     // We expand the Device Config Object.
    976                     for (Object subconfigObject : ((IDeviceConfiguration)configObject)
    977                             .getAllObjects()) {
    978                         printCommandUsageForObject(importantOnly, out, configObjectsEntry.getKey(),
    979                                 subconfigObject);
    980                     }
    981                 } else {
    982                     printCommandUsageForObject(importantOnly, out, configObjectsEntry.getKey(),
    983                             configObject);
    984                 }
    985             }
    986         }
    987     }
    988 
    989     private void printCommandUsageForObject(boolean importantOnly, PrintStream out, String key,
    990             Object obj) throws ConfigurationException {
    991         String optionHelp = printOptionsForObject(importantOnly, key, obj);
    992         // only print help for object if optionHelp is non zero length
    993         if (optionHelp.length() > 0) {
    994             String classAlias = "";
    995             if (obj.getClass().isAnnotationPresent(OptionClass.class)) {
    996                 final OptionClass classAnnotation = obj.getClass().getAnnotation(
    997                         OptionClass.class);
    998                 classAlias = String.format("'%s' ", classAnnotation.alias());
    999             }
   1000             out.printf("  %s%s options:", classAlias, key);
   1001             out.println();
   1002             out.print(optionHelp);
   1003             out.println();
   1004         }
   1005     }
   1006 
   1007     /**
   1008      * Get the JSON representation of a single {@link Option} field.
   1009      */
   1010     @SuppressWarnings({
   1011             "unchecked", "rawtypes"
   1012     })
   1013     private JSONObject getOptionJson(Object optionObject, Field field) throws JSONException {
   1014         // Build a JSON representation of the option
   1015         JSONObject jsonOption = new JSONObject();
   1016 
   1017         // Store values from the @Option annotation
   1018         Option option = field.getAnnotation(Option.class);
   1019         jsonOption.put("name", option.name());
   1020         if (option.shortName() != Option.NO_SHORT_NAME) {
   1021             jsonOption.put("shortName", option.shortName());
   1022         }
   1023         jsonOption.put("description", option.description());
   1024         jsonOption.put("importance", option.importance());
   1025         jsonOption.put("mandatory", option.mandatory());
   1026         jsonOption.put("isTimeVal", option.isTimeVal());
   1027         jsonOption.put("updateRule", option.updateRule().name());
   1028 
   1029         // Store the field's class
   1030         Type fieldType = field.getGenericType();
   1031         if (fieldType instanceof ParameterizedType) {
   1032             // Resolve paramaterized type arguments
   1033             Type[] paramTypes = ((ParameterizedType) fieldType).getActualTypeArguments();
   1034             String[] paramStrings = new String[paramTypes.length];
   1035             for (int i = 0; i < paramTypes.length; i++) {
   1036                 paramStrings[i] = ((Class<?>) paramTypes[i]).getName();
   1037             }
   1038 
   1039             jsonOption.put("javaClass", String.format("%s<%s>",
   1040                     field.getType().getName(), Joiner.on(", ").join(paramStrings)));
   1041         } else {
   1042             jsonOption.put("javaClass", field.getType().getName());
   1043         }
   1044 
   1045         // Store the field's value
   1046         Object value = null;
   1047         try {
   1048             field.setAccessible(true);
   1049             value = field.get(optionObject);
   1050 
   1051             // Convert nulls to JSONObject.NULL
   1052             if (value == null) {
   1053                 jsonOption.put("value", JSONObject.NULL);
   1054                 // Convert MuliMap values to a JSON representation
   1055             } else if (value instanceof MultiMap) {
   1056                 MultiMap multimap = (MultiMap) value;
   1057                 JSONObject jsonValue = new JSONObject();
   1058                 for (Object keyObj : multimap.keySet()) {
   1059                     jsonValue.put(keyObj.toString(), multimap.get(keyObj));
   1060                 }
   1061                 jsonOption.put("value", jsonValue);
   1062                 // Convert Map values to JSON
   1063             } else if (value instanceof Map) {
   1064                 jsonOption.put("value", new JSONObject((Map) value));
   1065                 // For everything else, just use the default representation
   1066             } else {
   1067                 jsonOption.put("value", value);
   1068             }
   1069         } catch (IllegalAccessException e) {
   1070             // Shouldn't happen
   1071             throw new RuntimeException(e);
   1072         }
   1073 
   1074         // Store the field's source
   1075         // Maps and MultiMaps track sources per key, so use a JSONObject to
   1076         // represent their sources
   1077         if (Map.class.isAssignableFrom(field.getType())) {
   1078             JSONObject jsonSourcesMap = new JSONObject();
   1079             if (value != null) {
   1080                 // For each entry in the map, store the source as a JSONArray
   1081                 for (Object key : ((Map) value).keySet()) {
   1082                     List<String> source = mFieldSources.get(new FieldDef(optionObject, field, key));
   1083                     jsonSourcesMap.put(key.toString(), source == null ? new JSONArray() : source);
   1084                 }
   1085             }
   1086             jsonOption.put("source", jsonSourcesMap);
   1087 
   1088         } else if (MultiMap.class.isAssignableFrom(field.getType())) {
   1089             JSONObject jsonSourcesMap = new JSONObject();
   1090             if (value != null) {
   1091                 // For each entry in the map, store the sources as a JSONArray
   1092                 for (Object key : ((MultiMap) value).keySet()) {
   1093                     List<String> source = mFieldSources.get(new FieldDef(optionObject, field, key));
   1094                     jsonSourcesMap.put(key.toString(), source == null ? new JSONArray() : source);
   1095                 }
   1096             }
   1097             jsonOption.put("source", jsonSourcesMap);
   1098 
   1099             // Collections and regular objects only have one set of sources for
   1100             // the whole field, so use
   1101             // a JSONArray
   1102         } else {
   1103             List<String> source = mFieldSources.get(new FieldDef(optionObject, field, null));
   1104             jsonOption.put("source", source == null ? new JSONArray() : source);
   1105         }
   1106 
   1107         return jsonOption;
   1108     }
   1109 
   1110     /**
   1111      * {@inheritDoc}
   1112      */
   1113     @Override
   1114     public JSONArray getJsonCommandUsage() throws JSONException {
   1115         JSONArray ret = new JSONArray();
   1116         for (Map.Entry<String, List<Object>> configObjectsEntry : mConfigMap.entrySet()) {
   1117             for (Object optionObject : configObjectsEntry.getValue()) {
   1118 
   1119                 // Build a JSON representation of the current class
   1120                 JSONObject jsonClass = new JSONObject();
   1121                 jsonClass.put("name", configObjectsEntry.getKey());
   1122                 String alias = null;
   1123                 if (optionObject.getClass().isAnnotationPresent(OptionClass.class)) {
   1124                     OptionClass optionClass = optionObject.getClass()
   1125                             .getAnnotation(OptionClass.class);
   1126                     alias = optionClass.alias();
   1127                 }
   1128                 jsonClass.put("alias", alias == null ? JSONObject.NULL : alias);
   1129                 jsonClass.put("class", optionObject.getClass().getName());
   1130 
   1131                 // For each of the @Option annotated fields
   1132                 Collection<Field> optionFields = OptionSetter
   1133                         .getOptionFieldsForClass(optionObject.getClass());
   1134                 JSONArray jsonOptions = new JSONArray();
   1135                 for (Field field : optionFields) {
   1136                     // Add the JSON field representation to the JSON class
   1137                     // representation
   1138                     jsonOptions.put(getOptionJson(optionObject, field));
   1139                 }
   1140                 jsonClass.put("options", jsonOptions);
   1141 
   1142                 // Add the JSON class representation to the list
   1143                 ret.put(jsonClass);
   1144             }
   1145         }
   1146 
   1147         return ret;
   1148     }
   1149 
   1150     /**
   1151      * Prints out the available config options for given configuration object.
   1152      *
   1153      * @param importantOnly print only the important options
   1154      * @param objectTypeName the config object type name. Used to generate more
   1155      *            descriptive error messages
   1156      * @param configObject the config object
   1157      * @return a {@link String} of option help text
   1158      * @throws ConfigurationException
   1159      */
   1160     private String printOptionsForObject(boolean importantOnly, String objectTypeName,
   1161             Object configObject) throws ConfigurationException {
   1162         return ArgsOptionParser.getOptionHelp(importantOnly, configObject);
   1163     }
   1164 
   1165     /**
   1166      * {@inheritDoc}
   1167      */
   1168     @Override
   1169     public void validateOptions() throws ConfigurationException {
   1170         new ArgsOptionParser(getAllConfigurationObjects()).validateMandatoryOptions();
   1171         ICommandOptions options = getCommandOptions();
   1172         if (options.getShardCount() != null && options.getShardCount() < 1) {
   1173             throw new ConfigurationException("a shard count must be a positive number");
   1174         }
   1175         if (options.getShardIndex() != null
   1176                 && (options.getShardCount() == null || options.getShardIndex() < 0
   1177                         || options.getShardIndex() >= options.getShardCount())) {
   1178             throw new ConfigurationException("a shard index must be in range [0, shard count)");
   1179         }
   1180     }
   1181 
   1182     /**
   1183      * {@inheritDoc}
   1184      */
   1185     @Override
   1186     public void dumpXml(PrintWriter output) throws IOException {
   1187         dumpXml(output, new ArrayList<String>());
   1188     }
   1189 
   1190     /** {@inheritDoc} */
   1191     @Override
   1192     public void dumpXml(PrintWriter output, List<String> excludeFilters) throws IOException {
   1193         KXmlSerializer serializer = new KXmlSerializer();
   1194         serializer.setOutput(output);
   1195         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
   1196         serializer.startDocument("UTF-8", null);
   1197         serializer.startTag(null, ConfigurationUtil.CONFIGURATION_NAME);
   1198 
   1199         for (IMultiTargetPreparer multipreparer : getMultiTargetPreparers()) {
   1200             ConfigurationUtil.dumpClassToXml(
   1201                     serializer, MULTI_PREPARER_TYPE_NAME, multipreparer, excludeFilters);
   1202         }
   1203         for (ISystemStatusChecker checker : getSystemStatusCheckers()) {
   1204             ConfigurationUtil.dumpClassToXml(
   1205                     serializer, SYSTEM_STATUS_CHECKER_TYPE_NAME, checker, excludeFilters);
   1206         }
   1207 
   1208         if (getDeviceConfig().size() > 1) {
   1209             // Handle multi device.
   1210             for (IDeviceConfiguration deviceConfig : getDeviceConfig()) {
   1211                 serializer.startTag(null, Configuration.DEVICE_NAME);
   1212                 serializer.attribute(null, "name", deviceConfig.getDeviceName());
   1213                 ConfigurationUtil.dumpClassToXml(
   1214                         serializer,
   1215                         BUILD_PROVIDER_TYPE_NAME,
   1216                         deviceConfig.getBuildProvider(),
   1217                         excludeFilters);
   1218                 for (ITargetPreparer preparer : deviceConfig.getTargetPreparers()) {
   1219                     ConfigurationUtil.dumpClassToXml(
   1220                             serializer, TARGET_PREPARER_TYPE_NAME, preparer, excludeFilters);
   1221                 }
   1222                 ConfigurationUtil.dumpClassToXml(
   1223                         serializer,
   1224                         DEVICE_RECOVERY_TYPE_NAME,
   1225                         deviceConfig.getDeviceRecovery(),
   1226                         excludeFilters);
   1227                 ConfigurationUtil.dumpClassToXml(
   1228                         serializer,
   1229                         DEVICE_REQUIREMENTS_TYPE_NAME,
   1230                         deviceConfig.getDeviceRequirements(),
   1231                         excludeFilters);
   1232                 ConfigurationUtil.dumpClassToXml(
   1233                         serializer,
   1234                         DEVICE_OPTIONS_TYPE_NAME,
   1235                         deviceConfig.getDeviceOptions(),
   1236                         excludeFilters);
   1237                 serializer.endTag(null, Configuration.DEVICE_NAME);
   1238             }
   1239         } else {
   1240             // Put single device tags
   1241             ConfigurationUtil.dumpClassToXml(
   1242                     serializer, BUILD_PROVIDER_TYPE_NAME, getBuildProvider(), excludeFilters);
   1243             for (ITargetPreparer preparer : getTargetPreparers()) {
   1244                 ConfigurationUtil.dumpClassToXml(
   1245                         serializer, TARGET_PREPARER_TYPE_NAME, preparer, excludeFilters);
   1246             }
   1247             ConfigurationUtil.dumpClassToXml(
   1248                     serializer, DEVICE_RECOVERY_TYPE_NAME, getDeviceRecovery(), excludeFilters);
   1249             ConfigurationUtil.dumpClassToXml(
   1250                     serializer,
   1251                     DEVICE_REQUIREMENTS_TYPE_NAME,
   1252                     getDeviceRequirements(),
   1253                     excludeFilters);
   1254             ConfigurationUtil.dumpClassToXml(
   1255                     serializer, DEVICE_OPTIONS_TYPE_NAME, getDeviceOptions(), excludeFilters);
   1256         }
   1257         for (IRemoteTest test : getTests()) {
   1258             ConfigurationUtil.dumpClassToXml(serializer, TEST_TYPE_NAME, test, excludeFilters);
   1259         }
   1260         ConfigurationUtil.dumpClassToXml(
   1261                 serializer,
   1262                 CONFIGURATION_DESCRIPTION_TYPE_NAME,
   1263                 getConfigurationDescription(),
   1264                 excludeFilters);
   1265         ConfigurationUtil.dumpClassToXml(
   1266                 serializer, LOGGER_TYPE_NAME, getLogOutput(), excludeFilters);
   1267         ConfigurationUtil.dumpClassToXml(
   1268                 serializer, LOG_SAVER_TYPE_NAME, getLogSaver(), excludeFilters);
   1269         for (ITestInvocationListener listener : getTestInvocationListeners()) {
   1270             ConfigurationUtil.dumpClassToXml(
   1271                     serializer, RESULT_REPORTER_TYPE_NAME, listener, excludeFilters);
   1272         }
   1273         ConfigurationUtil.dumpClassToXml(
   1274                 serializer, CMD_OPTIONS_TYPE_NAME, getCommandOptions(), excludeFilters);
   1275 
   1276         ConfigurationUtil.dumpClassToXml(
   1277                 serializer, TEST_PROFILER_TYPE_NAME, getProfiler(), excludeFilters);
   1278 
   1279         serializer.endTag(null, ConfigurationUtil.CONFIGURATION_NAME);
   1280         serializer.endDocument();
   1281     }
   1282 }
   1283