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