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