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