1 /*
2  * Copyright (C) 2010 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.apicoverage;
18 
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
27 
28 /** Representation of a class in the API with constructors and methods. */
29 class ApiClass implements Comparable<ApiClass>, HasCoverage {
30 
31     private static final String VOID = "void";
32 
33     private final String mName;
34 
35     private final boolean mDeprecated;
36 
37     private final boolean mAbstract;
38 
39     private final List<ApiConstructor> mApiConstructors = Collections.synchronizedList(new ArrayList<>());
40 
41     private final List<ApiMethod> mApiMethods = Collections.synchronizedList(new ArrayList<>());
42 
43     private final String mSuperClassName;
44 
45     private ApiClass mSuperClass;
46 
47     private Map<String, ApiClass> mInterfaceMap = new HashMap<String, ApiClass>();
48 
49     /**
50      * @param name The name of the class
51      * @param deprecated true iff the class is marked as deprecated
52      * @param classAbstract true iff the class is abstract
53      * @param superClassName The fully qualified name of the super class
54      */
ApiClass( String name, boolean deprecated, boolean classAbstract, String superClassName)55     ApiClass(
56             String name,
57             boolean deprecated,
58             boolean classAbstract,
59             String superClassName) {
60         mName = name;
61         mDeprecated = deprecated;
62         mAbstract = classAbstract;
63         mSuperClassName = superClassName;
64     }
65 
66     @Override
compareTo(ApiClass another)67     public int compareTo(ApiClass another) {
68         return mName.compareTo(another.mName);
69     }
70 
71     @Override
getName()72     public String getName() {
73         return mName;
74     }
75 
isDeprecated()76     public boolean isDeprecated() {
77         return mDeprecated;
78     }
79 
getSuperClassName()80     public String getSuperClassName() {
81         return mSuperClassName;
82     }
83 
isAbstract()84     public boolean isAbstract() {
85         return mAbstract;
86     }
87 
setSuperClass(ApiClass superClass)88     public void setSuperClass(ApiClass superClass) { mSuperClass = superClass; }
89 
addInterface(String interfaceName)90     public void addInterface(String interfaceName) {
91         mInterfaceMap.put(interfaceName, null);
92     }
93 
resolveInterface(String interfaceName, ApiClass apiInterface)94     public void resolveInterface(String interfaceName, ApiClass apiInterface) {
95         mInterfaceMap.replace(interfaceName, apiInterface);
96     }
97 
getInterfaceNames()98     public Set<String> getInterfaceNames() {
99         return mInterfaceMap.keySet();
100     }
101 
addConstructor(ApiConstructor constructor)102     public void addConstructor(ApiConstructor constructor) {
103         mApiConstructors.add(constructor);
104     }
105 
getConstructors()106     public Collection<ApiConstructor> getConstructors() {
107         return Collections.unmodifiableList(mApiConstructors);
108     }
109 
addMethod(ApiMethod method)110     public void addMethod(ApiMethod method) {
111         mApiMethods.add(method);
112     }
113 
114     /** Look for a matching constructor and mark it as covered */
markConstructorCovered(List<String> parameterTypes, String coveredbyApk)115     public void markConstructorCovered(List<String> parameterTypes, String coveredbyApk) {
116         if (mSuperClass != null) {
117             // Mark matching constructors in the superclass
118             mSuperClass.markConstructorCovered(parameterTypes, coveredbyApk);
119         }
120         Optional<ApiConstructor> apiConstructor = getConstructor(parameterTypes);
121         apiConstructor.ifPresent(constructor -> constructor.setCovered(coveredbyApk));
122     }
123 
124     /** Look for a matching method and if found and mark it as covered */
markMethodCovered(String name, List<String> parameterTypes, String coveredbyApk)125     public void markMethodCovered(String name, List<String> parameterTypes, String coveredbyApk) {
126         if (mSuperClass != null) {
127             // Mark matching methods in the super class
128             mSuperClass.markMethodCovered(name, parameterTypes, coveredbyApk);
129         }
130         if (!mInterfaceMap.isEmpty()) {
131             // Mark matching methods in the interfaces
132             for (ApiClass mInterface : mInterfaceMap.values()) {
133                 if (mInterface != null) {
134                     mInterface.markMethodCovered(name, parameterTypes, coveredbyApk);
135                 }
136             }
137         }
138         Optional<ApiMethod> apiMethod = getMethod(name, parameterTypes);
139         apiMethod.ifPresent(method -> method.setCovered(coveredbyApk));
140     }
141 
getMethods()142     public Collection<ApiMethod> getMethods() {
143         return Collections.unmodifiableList(mApiMethods);
144     }
145 
getNumCoveredMethods()146     public int getNumCoveredMethods() {
147         int numCovered = 0;
148         for (ApiConstructor constructor : mApiConstructors) {
149             if (constructor.isCovered()) {
150                 numCovered++;
151             }
152         }
153         for (ApiMethod method : mApiMethods) {
154             if (method.isCovered()) {
155                 numCovered++;
156             }
157         }
158         return numCovered;
159     }
160 
getTotalMethods()161     public int getTotalMethods() {
162         return mApiConstructors.size() + mApiMethods.size();
163     }
164 
165     @Override
getCoveragePercentage()166     public float getCoveragePercentage() {
167         if (getTotalMethods() == 0) {
168             return 100;
169         } else {
170             return (float) getNumCoveredMethods() / getTotalMethods() * 100;
171         }
172     }
173 
174     @Override
getMemberSize()175     public int getMemberSize() {
176         return getTotalMethods();
177     }
178 
getMethod(String name, List<String> parameterTypes)179     private Optional<ApiMethod> getMethod(String name, List<String> parameterTypes) {
180         for (ApiMethod method : mApiMethods) {
181             boolean methodNameMatch = name.equals(method.getName());
182             boolean parameterTypeMatch =
183                     compareParameterTypes(method.getParameterTypes(), parameterTypes);
184             if (methodNameMatch && parameterTypeMatch) {
185                 return Optional.of(method);
186             }
187         }
188         return Optional.empty();
189     }
190 
191     /**
192      * The method compares two lists of parameters. If the {@code apiParameterTypeList} contains
193      * generic types, test parameter types are ignored.
194      *
195      * @param apiParameterTypeList The list of parameter types from the API
196      * @param testParameterTypeList The list of parameter types used in a test
197      * @return true iff the list of types are the same.
198      */
compareParameterTypes( List<String> apiParameterTypeList, List<String> testParameterTypeList)199     private static boolean compareParameterTypes(
200             List<String> apiParameterTypeList, List<String> testParameterTypeList) {
201         if (apiParameterTypeList.equals(testParameterTypeList)) {
202             return true;
203         }
204         if (apiParameterTypeList.size() != testParameterTypeList.size()) {
205             return false;
206         }
207 
208         for (int i = 0; i < apiParameterTypeList.size(); i++) {
209             String apiParameterType = apiParameterTypeList.get(i);
210             String testParameterType = testParameterTypeList.get(i);
211             if (!compareType(apiParameterType, testParameterType)) {
212                 return false;
213             }
214         }
215         return true;
216     }
217 
218     /**
219      * @return true iff the parameter is a var arg parameter.
220      */
isVarArg(String parameter)221     private static boolean isVarArg(String parameter) {
222         return parameter.endsWith("...");
223     }
224 
225     /**
226      * Compare class types.
227      * @param apiType The type as reported by the api
228      * @param testType The type as found used in a test
229      * @return true iff the strings are equal,
230      * or the apiType is generic and the test type is not void
231      */
compareType(String apiType, String testType)232     private static boolean compareType(String apiType, String testType) {
233         return apiType.equals(testType) ||
234                 isGenericType(apiType) && !testType.equals(VOID) ||
235                 isGenericArrayType(apiType) && isArrayType(testType) ||
236                 isVarArg(apiType) && isArrayType(testType) &&
237                         apiType.startsWith(testType.substring(0, testType.indexOf("[")));
238     }
239 
240     /**
241      * @return true iff the given parameterType is a generic type.
242      */
isGenericType(String type)243     private static boolean isGenericType(String type) {
244         return type.length() == 1 &&
245                 type.charAt(0) >= 'A' &&
246                 type.charAt(0) <= 'Z';
247     }
248 
249     /**
250      * @return true iff {@code type} ends with an [].
251      */
isArrayType(String type)252     private static boolean isArrayType(String type) {
253         return type.endsWith("[]");
254     }
255 
256     /**
257      * @return true iff the given parameterType is an array of generic type.
258      */
isGenericArrayType(String type)259     private static boolean isGenericArrayType(String type) {
260         return type.length() == 3 && isGenericType(type.substring(0, 1)) && isArrayType(type);
261     }
262 
getConstructor(List<String> parameterTypes)263     private Optional<ApiConstructor> getConstructor(List<String> parameterTypes) {
264         for (ApiConstructor constructor : mApiConstructors) {
265             if (compareParameterTypes(constructor.getParameterTypes(), parameterTypes)) {
266                 return Optional.of(constructor);
267             }
268         }
269         return Optional.empty();
270     }
271 }
272