1 /* 2 * Copyright (C) 2016 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.cts.managedprofile; 18 19 import com.google.common.base.Strings; 20 import com.google.common.collect.ImmutableMap; 21 import com.google.common.collect.ImmutableList; 22 import com.google.common.primitives.Primitives; 23 24 import org.w3c.dom.Document; 25 import org.w3c.dom.Element; 26 import org.w3c.dom.NodeList; 27 28 import java.io.File; 29 import java.lang.reflect.Method; 30 31 import javax.xml.parsers.DocumentBuilder; 32 import javax.xml.parsers.DocumentBuilderFactory; 33 34 /** 35 * Helper class for retrieving the current list of API methods. 36 */ 37 public class CurrentApiHelper { 38 39 /** 40 * Location of the XML file that lists the current public APIs. 41 * 42 * <p><b>Note:</b> must be consistent with 43 * {@code cts/hostsidetests/devicepolicy/AndroidTest.xml} 44 */ 45 private static final String CURRENT_API_FILE = "/data/local/tmp/device-policy-test/current.api"; 46 47 private static final String LOG_TAG = "CurrentApiHelper"; 48 49 private static final ImmutableMap<String, Class> PRIMITIVE_TYPES = getPrimitiveTypes(); 50 private static final ImmutableMap<String, String> PRIMITIVE_ENCODINGS = 51 new ImmutableMap.Builder<String, String>() 52 .put("boolean", "Z") 53 .put("byte", "B") 54 .put("char", "C") 55 .put("double", "D") 56 .put("float", "F") 57 .put("int", "I") 58 .put("long", "J") 59 .put("short", "S") 60 .build(); 61 62 private static final String TAG_PACKAGE = "package"; 63 private static final String TAG_CLASS = "class"; 64 private static final String TAG_METHOD = "method"; 65 private static final String TAG_PARAMETER = "parameter"; 66 67 private static final String ATTRIBUTE_NAME = "name"; 68 private static final String ATTRIBUTE_TYPE = "type"; 69 70 /** 71 * Get public API methods of a specific class as defined in the API document. 72 * 73 * @param packageName The name of the package containing the class, e.g. {@code android.app}. 74 * @param className The name of the class, e.g. {@code Application}. 75 * @return an immutable list of {@link Method} instances. 76 */ getPublicApis(String packageName, String className)77 public static ImmutableList<Method> getPublicApis(String packageName, String className) 78 throws Exception { 79 Document apiDocument = parseXmlFile(CURRENT_API_FILE); 80 Element rootElement = apiDocument.getDocumentElement(); 81 Element packageElement = getChildElementByName(rootElement, TAG_PACKAGE, packageName); 82 Element classElement = getChildElementByName(packageElement, TAG_CLASS, className); 83 84 ImmutableList.Builder<Method> builder = new ImmutableList.Builder<>(); 85 86 NodeList nodes = classElement.getElementsByTagName(TAG_METHOD); 87 if (nodes != null && nodes.getLength() > 0) { 88 Class clazz = Class.forName(packageName + "." + className); 89 90 for (int i = 0; i < nodes.getLength(); ++i) { 91 Element element = (Element) nodes.item(i); 92 String name = element.getAttribute(ATTRIBUTE_NAME); 93 Class[] paramTypes = getParamTypes(element); 94 builder.add(clazz.getMethod(name, paramTypes)); 95 } 96 } 97 98 return builder.build(); 99 } 100 101 /** 102 * Given a {@link Class} object, get the default value if the {@link Class} refers to a 103 * primitive type, or null if it refers to an object. 104 * 105 * <p><ul> 106 * <li>For boolean type, return {@code false} 107 * <li>For other primitive types, return {@code 0} 108 * <li>For all other types, return {@code null} 109 * </ul> 110 * @param clazz The desired class to instantiate. 111 * @return Default instance as described above. 112 */ instantiate(Class clazz)113 public static Object instantiate(Class clazz) { 114 if (clazz.isPrimitive()) { 115 if (boolean.class.equals(clazz)) { 116 return false; 117 } else { 118 return 0; 119 } 120 } else { 121 return null; 122 } 123 } 124 parseXmlFile(String filePath)125 private static Document parseXmlFile(String filePath) throws Exception { 126 File apiFile = new File(filePath); 127 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 128 DocumentBuilder db = dbf.newDocumentBuilder(); 129 Document dom = db.parse(apiFile.toURI().toString()); 130 131 return dom; 132 } 133 getChildElementByName(Element parent, String childTag, String childName)134 private static Element getChildElementByName(Element parent, 135 String childTag, String childName) { 136 NodeList nodeList = parent.getElementsByTagName(childTag); 137 if (nodeList != null && nodeList.getLength() > 0) { 138 for (int i = 0; i < nodeList.getLength(); ++i) { 139 Element el = (Element) nodeList.item(i); 140 if (childName.equals(el.getAttribute(ATTRIBUTE_NAME))) { 141 return el; 142 } 143 } 144 } 145 return null; 146 } 147 getParamTypes(Element methodElement)148 private static Class[] getParamTypes(Element methodElement) throws Exception { 149 NodeList nodes = methodElement.getElementsByTagName(TAG_PARAMETER); 150 if (nodes != null && nodes.getLength() > 0) { 151 int paramCount = nodes.getLength(); 152 Class[] paramTypes = new Class[paramCount]; 153 for (int i = 0; i < paramCount; ++i) { 154 String typeName = ((Element) nodes.item(i)).getAttribute(ATTRIBUTE_TYPE); 155 paramTypes[i] = getClassByName(typeName); 156 } 157 return paramTypes; 158 } else { 159 return new Class[0]; 160 } 161 } 162 getClassByName(String typeName)163 private static Class getClassByName(String typeName) throws ClassNotFoundException { 164 // Check if typeName represents an array 165 int arrayDim = 0; 166 while (typeName.endsWith("[]")) { 167 arrayDim++; 168 typeName = typeName.substring(0, typeName.length() - 2); 169 } 170 171 // Resolve inner classes 172 typeName = typeName.replaceAll("([A-Z].*)\\.", "$1\\$"); 173 174 // Remove type parameters, if any 175 typeName = typeName.replaceAll("<.*>$", ""); 176 177 if (arrayDim == 0) { 178 if (isPrimitiveTypeName(typeName)) { 179 return PRIMITIVE_TYPES.get(typeName); 180 } else { 181 return Class.forName(typeName); 182 } 183 184 } else { 185 String prefix = Strings.repeat("[", arrayDim); 186 if (isPrimitiveTypeName(typeName)) { 187 return Class.forName(prefix + PRIMITIVE_ENCODINGS.get(typeName)); 188 } else { 189 return Class.forName(prefix + "L" + typeName + ";"); 190 } 191 } 192 } 193 getPrimitiveTypes()194 private static ImmutableMap<String, Class> getPrimitiveTypes() { 195 ImmutableMap.Builder<String, Class> builder = new ImmutableMap.Builder<>(); 196 for (Class type : Primitives.allPrimitiveTypes()) { 197 builder.put(type.getName(), type); 198 } 199 return builder.build(); 200 } 201 isPrimitiveTypeName(String typeName)202 private static boolean isPrimitiveTypeName(String typeName) { 203 return PRIMITIVE_TYPES.containsKey(typeName); 204 } 205 } 206