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