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