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