1 /*
2  * Copyright (C) 2017 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.log.LogUtil.CLog;
20 import com.android.tradefed.util.FileUtil;
21 import com.android.tradefed.util.MultiMap;
22 
23 import org.kxml2.io.KXmlSerializer;
24 
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.PrintWriter;
28 import java.lang.reflect.Field;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 import java.util.Set;
36 
37 /** Utility functions to handle configuration files. */
38 public class ConfigurationUtil {
39 
40     // Element names used for emitting the configuration XML.
41     public static final String CONFIGURATION_NAME = "configuration";
42     public static final String OPTION_NAME = "option";
43     public static final String CLASS_NAME = "class";
44     public static final String NAME_NAME = "name";
45     public static final String KEY_NAME = "key";
46     public static final String VALUE_NAME = "value";
47 
48     /**
49      * Create a serializer to be used to create a new configuration file.
50      *
51      * @param outputXml the XML file to write to
52      * @return a {@link KXmlSerializer}
53      */
createSerializer(File outputXml)54     static KXmlSerializer createSerializer(File outputXml) throws IOException {
55         PrintWriter output = new PrintWriter(outputXml);
56         KXmlSerializer serializer = new KXmlSerializer();
57         serializer.setOutput(output);
58         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
59         serializer.startDocument("UTF-8", null);
60         return serializer;
61     }
62 
63     /**
64      * Add a class to the configuration XML dump.
65      *
66      * @param serializer a {@link KXmlSerializer} to create the XML dump
67      * @param classTypeName a {@link String} of the class type's name
68      * @param obj {@link Object} to be added to the XML dump
69      * @param excludeClassFilter list of object configuration type or fully qualified class names to
70      *     be excluded from the dump. for example: {@link Configuration#TARGET_PREPARER_TYPE_NAME}.
71      *     com.android.tradefed.testtype.StubTest
72      */
dumpClassToXml( KXmlSerializer serializer, String classTypeName, Object obj, List<String> excludeClassFilter)73     static void dumpClassToXml(
74             KXmlSerializer serializer,
75             String classTypeName,
76             Object obj,
77             List<String> excludeClassFilter)
78             throws IOException {
79         if (excludeClassFilter.contains(classTypeName)) {
80             return;
81         }
82         if (excludeClassFilter.contains(obj.getClass().getName())) {
83             return;
84         }
85         serializer.startTag(null, classTypeName);
86         serializer.attribute(null, CLASS_NAME, obj.getClass().getName());
87         dumpOptionsToXml(serializer, obj);
88         serializer.endTag(null, classTypeName);
89     }
90 
91     /**
92      * Add all the options of class to the command XML dump.
93      *
94      * @param serializer a {@link KXmlSerializer} to create the XML dump
95      * @param obj {@link Object} to be added to the XML dump
96      */
97     @SuppressWarnings({"rawtypes", "unchecked"})
dumpOptionsToXml(KXmlSerializer serializer, Object obj)98     private static void dumpOptionsToXml(KXmlSerializer serializer, Object obj) throws IOException {
99         for (Field field : OptionSetter.getOptionFieldsForClass(obj.getClass())) {
100             Option option = field.getAnnotation(Option.class);
101             Object fieldVal = OptionSetter.getFieldValue(field, obj);
102             if (fieldVal == null) {
103                 continue;
104             } else if (fieldVal instanceof Collection) {
105                 for (Object entry : (Collection) fieldVal) {
106                     dumpOptionToXml(serializer, option.name(), null, entry.toString());
107                 }
108             } else if (fieldVal instanceof Map) {
109                 Map map = (Map) fieldVal;
110                 for (Object entryObj : map.entrySet()) {
111                     Map.Entry entry = (Entry) entryObj;
112                     dumpOptionToXml(
113                             serializer,
114                             option.name(),
115                             entry.getKey().toString(),
116                             entry.getValue().toString());
117                 }
118             } else if (fieldVal instanceof MultiMap) {
119                 MultiMap multimap = (MultiMap) fieldVal;
120                 for (Object keyObj : multimap.keySet()) {
121                     for (Object valueObj : multimap.get(keyObj)) {
122                         dumpOptionToXml(
123                                 serializer, option.name(), keyObj.toString(), valueObj.toString());
124                     }
125                 }
126             } else {
127                 dumpOptionToXml(serializer, option.name(), null, fieldVal.toString());
128             }
129         }
130     }
131 
132     /**
133      * Add a single option to the command XML dump.
134      *
135      * @param serializer a {@link KXmlSerializer} to create the XML dump
136      * @param name a {@link String} of the option's name
137      * @param key a {@link String} of the option's key, used as name if param name is null
138      * @param value a {@link String} of the option's value
139      */
dumpOptionToXml( KXmlSerializer serializer, String name, String key, String value)140     private static void dumpOptionToXml(
141             KXmlSerializer serializer, String name, String key, String value) throws IOException {
142         serializer.startTag(null, OPTION_NAME);
143         serializer.attribute(null, NAME_NAME, name);
144         if (key != null) {
145             serializer.attribute(null, KEY_NAME, key);
146         }
147         serializer.attribute(null, VALUE_NAME, value);
148         serializer.endTag(null, OPTION_NAME);
149     }
150 
151     /**
152      * Helper to get the test config files from given directories.
153      *
154      * @param subPath where to look for configuration. Can be null.
155      * @param dirs a list of {@link File} of extra directories to search for test configs
156      */
getConfigNamesFromDirs(String subPath, List<File> dirs)157     public static Set<String> getConfigNamesFromDirs(String subPath, List<File> dirs) {
158         Set<File> res = getConfigNamesFileFromDirs(subPath, dirs);
159         if (res.isEmpty()) {
160             return new HashSet<>();
161         }
162         Set<String> files = new HashSet<>();
163         res.forEach(file -> files.add(file.getAbsolutePath()));
164         return files;
165     }
166 
167     /**
168      * Helper to get the test config files from given directories.
169      *
170      * @param subPath The location where to look for configuration. Can be null.
171      * @param dirs A list of {@link File} of extra directories to search for test configs
172      * @return the set of {@link File} that were found.
173      */
getConfigNamesFileFromDirs(String subPath, List<File> dirs)174     public static Set<File> getConfigNamesFileFromDirs(String subPath, List<File> dirs) {
175         List<String> patterns = new ArrayList<>();
176         patterns.add(".*.config");
177         patterns.add(".*.xml");
178         return getConfigNamesFileFromDirs(subPath, dirs, patterns);
179     }
180 
181     /**
182      * Search a particular pattern of in the given directories.
183      *
184      * @param subPath The location where to look for configuration. Can be null.
185      * @param dirs A list of {@link File} of extra directories to search for test configs
186      * @param configNamePatterns the list of patterns for files to be found.
187      * @return the set of {@link File} that were found.
188      */
getConfigNamesFileFromDirs( String subPath, List<File> dirs, List<String> configNamePatterns)189     public static Set<File> getConfigNamesFileFromDirs(
190             String subPath, List<File> dirs, List<String> configNamePatterns) {
191         Set<File> configNames = new HashSet<>();
192         for (File dir : dirs) {
193             if (subPath != null) {
194                 dir = new File(dir, subPath);
195             }
196             if (!dir.isDirectory()) {
197                 CLog.d("%s doesn't exist or is not a directory.", dir.getAbsolutePath());
198                 continue;
199             }
200             try {
201                 for (String configNamePattern : configNamePatterns) {
202                     configNames.addAll(FileUtil.findFilesObject(dir, configNamePattern));
203                 }
204             } catch (IOException e) {
205                 CLog.w("Failed to get test config files from directory %s", dir.getAbsolutePath());
206             }
207         }
208         return dedupFiles(configNames);
209     }
210 
211     /**
212      * From a same tests dir we only expect a single instance of each names, so we dedup the files
213      * if that happens.
214      */
dedupFiles(Set<File> origSet)215     private static Set<File> dedupFiles(Set<File> origSet) {
216         Set<String> tracker = new HashSet<>();
217         Set<File> newSet = new HashSet<>();
218         for (File f : origSet) {
219             if (!tracker.contains(f.getName())) {
220                 tracker.add(f.getName());
221                 newSet.add(f);
222             }
223         }
224         return newSet;
225     }
226 }
227