1 /*
2  * Copyright (C) 2024 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.ctsprofiles;
18 
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 
26 /** Representation of a class included in the CTS package. */
27 public class ClassProfile {
28 
29     public final AnnotationManagement annotationManagement = new AnnotationManagement();
30 
31     private final String mModule;
32 
33     private final String mPackage;
34 
35     private final String mClass;
36 
37     private int mClassType = 0;
38 
39     private ClassProfile mSuperClass = null;
40 
41     // A list of interfaces implemented by this class.
42     private final List<ClassProfile> mInterfaces = new ArrayList<>();
43 
44     // A map of methods defined in this class with the method signature as the key.
45     private final Map<String, MethodProfile> mMethods = new HashMap<>();
46 
47     // A map of test methods defined in this class with the method signature as the key.
48     private Map<String, MethodProfile> mTestMethods = null;
49 
50     // TODO(slotus): Add known patterns.
51     private static final Set<String> JUNIT4_ANNOTATION_PATTERNS = new HashSet<>(List.of());
52 
53     // TODO(slotus): Add known patterns.
54     private static final Set<String> JUNIT3_CLASS_PATTERNS = new HashSet<>(List.of());
55 
ClassProfile(String moduleName, String packageName, String className, boolean apiClass)56     public ClassProfile(String moduleName, String packageName, String className, boolean apiClass) {
57         mModule = moduleName;
58         mClass = className;
59         mPackage = packageName;
60         if (apiClass) {
61             mClassType |= ClassType.API.getValue();
62         }
63     }
64 
65     /** Representation of the class type. */
66     public enum ClassType {
67         INTERFACE(1),
68         ABSTRACT(2),
69         JUNIT3(4),
70         JUNIT4(8),
71         ANNOTATION(16),
72         /** A non-test and non-annotation class.*/
73         COMMON(32),
74         API(64);
75 
76         private final int mValue;
77 
ClassType(int value)78         ClassType(int value) {
79             mValue = value;
80         }
81 
getValue()82         public int getValue() {
83             return mValue;
84         }
85     }
86 
getClassSignature()87     public String getClassSignature() {
88         return Utils.getClassSignature(mPackage, mClass);
89     }
90 
getClassName()91     public String getClassName() {
92         return mClass;
93     }
94 
getPackageName()95     public String getPackageName() {
96         return mPackage;
97     }
98 
getModuleName()99     public String getModuleName() {
100         return mModule;
101     }
102 
getMethods()103     public Map<String, MethodProfile> getMethods() {
104         return mMethods;
105     }
106 
107     /** Creates a class method. */
getOrCreateMethod( String methodName, List<String> params)108     public MethodProfile getOrCreateMethod(
109             String methodName, List<String> params) {
110         String methodSignature = Utils.getMethodSignature(methodName, params);
111         if (!mMethods.containsKey(methodSignature)) {
112             mMethods.put(methodSignature, new MethodProfile(this, methodName, params));
113         }
114         return mMethods.get(methodSignature);
115     }
116 
117     /** Adds an interface implemented by the class. */
addInterface(ClassProfile interfaceProfile)118     public void addInterface(ClassProfile interfaceProfile) {
119         mInterfaces.add(interfaceProfile);
120     }
121 
122     /** Adds a class type for the class. */
addClassType(ClassType classType)123     public void addClassType(ClassType classType) {
124         mClassType |= classType.getValue();
125     }
126 
setSuperClass(ClassProfile superClass)127     public void setSuperClass(ClassProfile superClass) {
128         mSuperClass = superClass;
129     }
130 
131     /** Collects all test methods contained in the class. */
getTestMethods()132     public Map<String, MethodProfile> getTestMethods() {
133         if (mTestMethods != null) {
134             return mTestMethods;
135         }
136         mTestMethods = new HashMap<>();
137         mMethods.forEach((methodKey, method) -> {
138             if (method.isTestMethod()) {
139                 mTestMethods.put(methodKey, method);
140             }
141         });
142         // Test methods defined in the super class will also be collected.
143         if (mSuperClass != null) {
144             mSuperClass.getTestMethods().forEach(mTestMethods::putIfAbsent);
145         }
146         return mTestMethods;
147     }
148 
149     /** Returns true if the class is a test class. */
isTestClass()150     public boolean isTestClass() {
151         if (matchAnyTypes(ClassType.ANNOTATION.getValue() | ClassType.API.getValue())) {
152             return false;
153         }
154         if (!isJunit4Class() && !isJunit3Class()) {
155             addClassType(ClassType.COMMON);
156             return false;
157         }
158         return true;
159     }
160 
161     /** Returns true if the class is an API class. */
isApiClass()162     public boolean isApiClass() {
163         return matchAllTypes(ClassType.API.getValue());
164     }
165 
166     /** Returns true if the class is a test class but not an abstract class. */
isNonAbstractTestClass()167     public boolean isNonAbstractTestClass() {
168         return (isTestClass() && !matchAnyTypes(
169                 ClassType.ABSTRACT.getValue() | ClassType.INTERFACE.getValue()));
170     }
171 
172 
173     /** Returns true if it is decided that whether this is a test class or not. */
testClassResolved()174     private boolean testClassResolved() {
175         return matchAnyTypes(
176                 ClassType.JUNIT3.getValue()
177                         | ClassType.JUNIT4.getValue()
178                         | ClassType.COMMON.getValue()
179                         | ClassType.ANNOTATION.getValue()
180                         | ClassType.API.getValue()
181         );
182     }
183 
184     /** Returns true if the class is a Junit4 test class. */
isJunit4Class()185     protected boolean isJunit4Class() {
186         if (testClassResolved()) {
187             return matchAllTypes(ClassType.JUNIT4.getValue());
188         }
189         // Check if the class is marked by a Junit4 runner.
190         for (ClassProfile annotation : annotationManagement.getAnnotations()) {
191             for (String pattern : JUNIT4_ANNOTATION_PATTERNS) {
192                 if (annotation.getClassSignature().matches(pattern)) {
193                     addClassType(ClassType.JUNIT4);
194                     return true;
195                 }
196             }
197         }
198         // Check if any methods are marked by a Junit4 annotation.
199         for (MethodProfile method : mMethods.values()) {
200             if (method.isJunit4Method()) {
201                 addClassType(ClassType.JUNIT4);
202                 return true;
203             }
204         }
205         // Check if the class is extended from a Junit4 class.
206         if (mSuperClass != null && mSuperClass.isJunit4Class()) {
207             addClassType(ClassType.JUNIT4);
208             return true;
209         }
210         return false;
211     }
212 
213     /** Returns true if the class is a Junit3 test class. */
isJunit3Class()214     protected boolean isJunit3Class() {
215         if (testClassResolved()) {
216             return matchAllTypes(ClassType.JUNIT3.getValue());
217         }
218         if (mSuperClass != null) {
219             // Check if the class is extended from a Junit3 base class.
220             for (String pattern : JUNIT3_CLASS_PATTERNS) {
221                 if (mSuperClass.getClassSignature().matches(pattern)) {
222                     addClassType(ClassType.JUNIT3);
223                     return true;
224                 }
225             }
226             // Check if the class is extended from a Junit3 class.
227             if (mSuperClass.isJunit3Class()) {
228                 addClassType(ClassType.JUNIT3);
229                 return true;
230             }
231         }
232         return false;
233     }
234 
235 
matchAnyTypes(int typesValue)236     private boolean matchAnyTypes(int typesValue) {
237         return (mClassType & typesValue) != 0;
238     }
239 
matchAllTypes(int typesValue)240     private boolean matchAllTypes(int typesValue) {
241         return (mClassType & typesValue) == typesValue;
242     }
243 }
244