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.Collection; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Map.Entry; 34 import java.util.Set; 35 36 /** Utility functions to handle configuration files. */ 37 public class ConfigurationUtil { 38 39 // Element names used for emitting the configuration XML. 40 public static final String CONFIGURATION_NAME = "configuration"; 41 public static final String OPTION_NAME = "option"; 42 public static final String CLASS_NAME = "class"; 43 public static final String NAME_NAME = "name"; 44 public static final String KEY_NAME = "key"; 45 public static final String VALUE_NAME = "value"; 46 47 /** 48 * Create a serializer to be used to create a new configuration file. 49 * 50 * @param outputXml the XML file to write to 51 * @return a {@link KXmlSerializer} 52 */ 53 static KXmlSerializer createSerializer(File outputXml) throws IOException { 54 PrintWriter output = new PrintWriter(outputXml); 55 KXmlSerializer serializer = new KXmlSerializer(); 56 serializer.setOutput(output); 57 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 58 serializer.startDocument("UTF-8", null); 59 return serializer; 60 } 61 62 /** 63 * Add a class to the configuration XML dump. 64 * 65 * @param serializer a {@link KXmlSerializer} to create the XML dump 66 * @param classTypeName a {@link String} of the class type's name 67 * @param obj {@link Object} to be added to the XML dump 68 * @param excludeClassTypes list of object configuration type to be excluded from the dump. for 69 * example: {@link Configuration#TARGET_PREPARER_TYPE_NAME}. 70 */ 71 static void dumpClassToXml( 72 KXmlSerializer serializer, 73 String classTypeName, 74 Object obj, 75 List<String> excludeClassTypes) 76 throws IOException { 77 if (excludeClassTypes.contains(classTypeName)) { 78 return; 79 } 80 serializer.startTag(null, classTypeName); 81 serializer.attribute(null, CLASS_NAME, obj.getClass().getName()); 82 dumpOptionsToXml(serializer, obj); 83 serializer.endTag(null, classTypeName); 84 } 85 86 /** 87 * Add all the options of class to the command XML dump. 88 * 89 * @param serializer a {@link KXmlSerializer} to create the XML dump 90 * @param obj {@link Object} to be added to the XML dump 91 */ 92 @SuppressWarnings({"rawtypes", "unchecked"}) 93 private static void dumpOptionsToXml(KXmlSerializer serializer, Object obj) throws IOException { 94 for (Field field : OptionSetter.getOptionFieldsForClass(obj.getClass())) { 95 Option option = field.getAnnotation(Option.class); 96 Object fieldVal = OptionSetter.getFieldValue(field, obj); 97 if (fieldVal == null) { 98 continue; 99 } else if (fieldVal instanceof Collection) { 100 for (Object entry : (Collection) fieldVal) { 101 dumpOptionToXml(serializer, option.name(), null, entry.toString()); 102 } 103 } else if (fieldVal instanceof Map) { 104 Map map = (Map) fieldVal; 105 for (Object entryObj : map.entrySet()) { 106 Map.Entry entry = (Entry) entryObj; 107 dumpOptionToXml( 108 serializer, 109 option.name(), 110 entry.getKey().toString(), 111 entry.getValue().toString()); 112 } 113 } else if (fieldVal instanceof MultiMap) { 114 MultiMap multimap = (MultiMap) fieldVal; 115 for (Object keyObj : multimap.keySet()) { 116 for (Object valueObj : multimap.get(keyObj)) { 117 dumpOptionToXml( 118 serializer, option.name(), keyObj.toString(), valueObj.toString()); 119 } 120 } 121 } else { 122 dumpOptionToXml(serializer, option.name(), null, fieldVal.toString()); 123 } 124 } 125 } 126 127 /** 128 * Add a single option to the command XML dump. 129 * 130 * @param serializer a {@link KXmlSerializer} to create the XML dump 131 * @param name a {@link String} of the option's name 132 * @param key a {@link String} of the option's key, used as name if param name is null 133 * @param value a {@link String} of the option's value 134 */ 135 private static void dumpOptionToXml( 136 KXmlSerializer serializer, String name, String key, String value) throws IOException { 137 serializer.startTag(null, OPTION_NAME); 138 serializer.attribute(null, NAME_NAME, name); 139 if (key != null) { 140 serializer.attribute(null, KEY_NAME, key); 141 } 142 serializer.attribute(null, VALUE_NAME, value); 143 serializer.endTag(null, OPTION_NAME); 144 } 145 146 /** 147 * Helper to get the test config files from given directories. 148 * 149 * @param subPath where to look for configuration. Can be null. 150 * @param dirs a list of {@link File} of extra directories to search for test configs 151 */ 152 public static Set<String> getConfigNamesFromDirs(String subPath, List<File> dirs) { 153 Set<String> configNames = new HashSet<String>(); 154 for (File dir : dirs) { 155 if (subPath != null) { 156 dir = new File(dir, subPath); 157 } 158 if (!dir.isDirectory()) { 159 CLog.d("%s doesn't exist or is not a directory.", dir.getAbsolutePath()); 160 continue; 161 } 162 try { 163 configNames.addAll(FileUtil.findFiles(dir, ".*.config")); 164 configNames.addAll(FileUtil.findFiles(dir, ".*.xml")); 165 } catch (IOException e) { 166 CLog.w("Failed to get test config files from directory %s", dir.getAbsolutePath()); 167 } 168 } 169 return configNames; 170 } 171 } 172