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