1 /*
2  * Copyright (C) 2023 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.car.tool.apibuilder;
18 
19 import com.android.car.tool.data.AnnotationData;
20 import com.android.car.tool.data.ClassData;
21 import com.android.car.tool.data.ConstructorData;
22 import com.android.car.tool.data.FieldData;
23 import com.android.car.tool.data.MethodData;
24 import com.android.car.tool.data.PackageData;
25 import com.android.car.tool.data.ParsedData;
26 
27 import com.github.javaparser.StaticJavaParser;
28 import com.github.javaparser.ast.CompilationUnit;
29 import com.github.javaparser.ast.NodeList;
30 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
31 import com.github.javaparser.ast.body.ConstructorDeclaration;
32 import com.github.javaparser.ast.body.FieldDeclaration;
33 import com.github.javaparser.ast.body.MethodDeclaration;
34 import com.github.javaparser.ast.body.Parameter;
35 import com.github.javaparser.ast.expr.AnnotationExpr;
36 import com.github.javaparser.ast.expr.MemberValuePair;
37 import com.github.javaparser.ast.stmt.Statement;
38 import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
39 
40 import java.io.File;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.Objects;
44 
45 public final class ParsedDataBuilder {
46 
47     public static List<String> sErrors = new ArrayList<>();
48 
populateParsedData(List<File> files, ParsedData data)49     public static void populateParsedData(List<File> files, ParsedData data) throws Exception {
50         if (data == null) {
51             throw new NullPointerException("Parsed data object can't be null.");
52         }
53 
54         for (File file : files) {
55             populateParsedDataForFile(file, data);
56         }
57     }
58 
populateParsedDataForFile(File file, ParsedData data)59     public static void populateParsedDataForFile(File file, ParsedData data) throws Exception {
60         CompilationUnit cu = StaticJavaParser.parse(file);
61         String packageName = cu.getPackageDeclaration().get().getNameAsString();
62 
63         PackageData packageData = data.getPackageData(packageName);
64 
65         new VoidVisitorAdapter<PackageData>() {
66             @Override
67             public void visit(ClassOrInterfaceDeclaration n, PackageData packageData) {
68 
69                 ClassData classData = getClassData(n, packageName);
70 
71                 if (classData == null) {
72                     super.visit(n, packageData);
73                     return;
74                 }
75 
76                 // update constructor info
77                 List<ConstructorDeclaration> constructors = n.getConstructors();
78                 for (int i = 0; i < constructors.size(); i++) {
79                     ConstructorDeclaration constructor = constructors.get(i);
80                     ConstructorData constructorData = getConstructorData(n, constructor);
81                     if (constructorData == null) {
82                         continue;
83                     }
84 
85                     constructorData.annotationData = getAnnotationData(
86                             constructor.getAnnotations());
87                     classData.constructors.put(constructorData.fullConstructorName,
88                             constructorData);
89                 }
90 
91                 // update field info
92                 List<FieldDeclaration> fields = n.getFields();
93                 for (int i = 0; i < fields.size(); i++) {
94                     FieldDeclaration field = fields.get(i);
95                     FieldData fieldData = getFieldData(n, field);
96                     if (fieldData == null) {
97                         continue;
98                     }
99 
100                     fieldData.annotationData = getAnnotationData(field.getAnnotations());
101                     classData.fields.put(fieldData.fieldName, fieldData);
102                 }
103 
104                 // update method info
105                 List<MethodDeclaration> methods = n.getMethods();
106                 for (int i = 0; i < methods.size(); i++) {
107                     MethodDeclaration method = methods.get(i);
108                     MethodData methodData = getMethodData(n, method);
109                     if (methodData == null) {
110                         continue;
111                     }
112 
113                     methodData.annotationData = getAnnotationData(method.getAnnotations());
114                     CompilationUnit.Storage storage = cu.getStorage().get();
115                     methodData.fileName = storage.getDirectory() + "/" + storage.getFileName();
116                     classData.methods.put(methodData.fullMethodname, methodData);
117                 }
118 
119                 packageData.addClass(classData);
120                 super.visit(n, packageData);
121             }
122         }.visit(cu, packageData);
123 
124     }
125 
getClassData(ClassOrInterfaceDeclaration n, String packageName)126     public static ClassData getClassData(ClassOrInterfaceDeclaration n, String packageName) {
127         // Don't need any data for the private or package-private classes.
128         if (!n.isPublic()
129                 && !n.isProtected()) {
130             return null;
131         }
132 
133         String fullyQualifiedClasName = n.getFullyQualifiedName()
134                 .get();
135         String onlyClassName = n.getFullyQualifiedName().get()
136                 .substring(packageName.length() + 1);
137         String useableClassName = packageName + "." + onlyClassName.replace(".", "$");
138 
139         boolean isClassHidden = false;
140 
141         if (!n.getJavadoc().isEmpty()) {
142             isClassHidden = n.getJavadoc().get().toText().contains("@hide");
143         }
144 
145         ClassData classData = new ClassData();
146         classData.fullyQualifiedClassName = fullyQualifiedClasName;
147         classData.onlyClassName = onlyClassName;
148         classData.useableClassName = useableClassName;
149         classData.isClassHidden = isClassHidden;
150         classData.isInterface = n.isInterface();
151         classData.annotationData = getAnnotationData(n.getAnnotations());
152         return classData;
153     }
154 
getVersion(AnnotationExpr annotationExpr, String parameterName)155     public static String getVersion(AnnotationExpr annotationExpr, String parameterName) {
156         List<MemberValuePair> children = annotationExpr
157                 .getChildNodesByType(MemberValuePair.class);
158         for (MemberValuePair memberValuePair : children) {
159             if (parameterName.equals(memberValuePair.getNameAsString())) {
160                 if (memberValuePair.getValue() == null) {
161                     return "0";
162                 }
163                 return memberValuePair.getValue().toString();
164             }
165         }
166         return "0";
167     }
168 
getFieldData(ClassOrInterfaceDeclaration n, FieldDeclaration field)169     public static FieldData getFieldData(ClassOrInterfaceDeclaration n, FieldDeclaration field) {
170         // No need for private fields of interface
171         if (n.isInterface() && field.isPrivate()) {
172             return null;
173         }
174 
175         // No need for the private or package-private field of class
176         if (!n.isInterface() && !field.isPublic() && !field.isProtected()) {
177             return null;
178         }
179 
180         String fieldName = field.getVariables().get(0).getName().asString();
181         String fieldType = field.getVariables().get(0).getTypeAsString();
182         boolean fieldInitialized = !field.getVariables().get(0).getInitializer()
183                 .isEmpty();
184 
185         String fieldInitializedValue = "";
186         if (fieldInitialized) {
187             fieldInitializedValue = field.getVariables().get(0).getInitializer().get()
188                     .toString();
189         }
190 
191         boolean isHidden = false;
192         if (!field.getJavadoc().isEmpty()) {
193             isHidden = field.getJavadoc().get().toText().contains("@hide");
194         }
195 
196         FieldData fieldData = new FieldData(fieldName, fieldType);
197         fieldData.isFieldInitialized = fieldInitialized;
198         fieldData.fieldInitializedValue = fieldInitializedValue;
199         fieldData.isHidden = isHidden;
200         return fieldData;
201     }
202 
getMethodData(ClassOrInterfaceDeclaration n, MethodDeclaration method)203     public static MethodData getMethodData(ClassOrInterfaceDeclaration n,
204             MethodDeclaration method) {
205         if (n.isInterface() && method.isPrivate()) {
206             return null;
207         }
208         if (!n.isInterface() && !method.isPublic() && !method.isProtected()) {
209             return null;
210         }
211         String returnType = method.getTypeAsString();
212         String methodName = method.getName().asString();
213 
214         boolean isHidden = false;
215         if (!method.getJavadoc().isEmpty()) {
216             isHidden = method.getJavadoc().get().toText().contains("@hide");
217         }
218 
219         StringBuilder fullMethodNameString = new StringBuilder();
220 
221         fullMethodNameString.append(methodName);
222         fullMethodNameString.append("(");
223 
224         List<Parameter> parameters = method.getParameters();
225         for (int k = 0; k < parameters.size(); k++) {
226             Parameter parameter = parameters.get(k);
227             fullMethodNameString.append(parameter.getTypeAsString());
228             fullMethodNameString.append(" ");
229             fullMethodNameString.append(parameter.getNameAsString());
230             if (k < parameters.size() - 1) {
231                 fullMethodNameString.append(", ");
232             }
233         }
234         fullMethodNameString.append(")");
235 
236         MethodData methodData = new MethodData(methodName, returnType);
237         methodData.isHidden = isHidden;
238         methodData.fullMethodname = fullMethodNameString.toString();
239         methodData.firstBodyStatement = getFirstBodyStatement(method);
240         return methodData;
241     }
242 
getAnnotationData(NodeList<AnnotationExpr> annotations)243     public static AnnotationData getAnnotationData(NodeList<AnnotationExpr> annotations) {
244         boolean isSystem = false;
245         boolean hasAddedInOrBefore = false;
246         int addedInOrBeforeMajorVersion = 0;
247         int addedInOrBeforeMinorVersion = 0;
248         boolean hasDeprecatedAddedInAnnotation = false;
249         boolean hasAddedInAnnotation = false;
250         String addedInPlatformVersion = "";
251         boolean hasApiRequirementAnnotation = false;
252         String minPlatformVersion = "";
253         String minCarVersion = "";
254         boolean hasRequiresApiAnnotation = false;
255         String requiresApiVersion = "";
256 
257         for (int j = 0; j < annotations.size(); j++) {
258             String annotationString = annotations.get(j).getName().asString();
259 
260             if (annotationString.contains("SystemApi")) {
261                 isSystem = true;
262             }
263 
264             if (annotationString.contains("AddedInOrBefore")) {
265                 hasAddedInOrBefore = true;
266                 addedInOrBeforeMajorVersion = Integer
267                         .parseInt(getVersion(annotations.get(j), "majorVersion"));
268                 if (addedInOrBeforeMajorVersion == 0) {
269                     sErrors.add("Incorrect Annotation. annotationString: " + annotationString);
270                 }
271                 addedInOrBeforeMinorVersion = Integer
272                         .parseInt(getVersion(annotations.get(j), "minorVersion"));
273             }
274 
275             if (annotationString.contains("AddedIn")
276                     && !annotationString.contains("AddedInOrBefore")) {
277                 // It may be old AddedIn which is deprecated. Chances are low as it is
278                 // no longer used but check.
279                 if (Integer.parseInt(
280                         getVersion(annotations.get(j), "majorVersion")) != 0) {
281                     hasDeprecatedAddedInAnnotation = true;
282                     sErrors.add("Incorrect Annotation. annotationString: " + annotationString);
283                 } else {
284                     sErrors.add("Incorrect Annotation. annotationString: " + annotationString);
285                     hasAddedInAnnotation = true;
286                     String fullPlatformVersion = getVersion(annotations.get(j),
287                             "value");
288                     if (Objects.equals(fullPlatformVersion, "0")) {
289                         fullPlatformVersion = annotations.get(j).toString()
290                                 .split("\\(")[1].split("\\)")[0];
291                     }
292                     addedInPlatformVersion = getLastToken(fullPlatformVersion, "\\.");
293                 }
294             }
295 
296             if (annotationString.contains("ApiRequirements")) {
297                 hasApiRequirementAnnotation = true;
298                 String fullCarVersion = getVersion(annotations.get(j), "minCarVersion");
299                 minCarVersion = getLastToken(fullCarVersion, "\\.");
300 
301                 String fullplatformVersion = getVersion(annotations.get(j),
302                         "minPlatformVersion");
303                 minPlatformVersion = getLastToken(fullplatformVersion, "\\.");
304             }
305 
306             if (annotationString.contains("RequiresApi")) {
307                 hasRequiresApiAnnotation = true;
308                 String fullRequiresApi = getVersion(annotations.get(j), "api");
309 
310                 // if RequiresApi doesn't have "api" parameter
311                 if (Objects.equals(fullRequiresApi, "0")) {
312                     fullRequiresApi = getVersion(annotations.get(j), "value");
313                 }
314 
315                 // if RequiresApi doesn't have "value" parameter. Means no parameter
316                 if (Objects.equals(fullRequiresApi, "0")) {
317                     fullRequiresApi = annotations.get(j).toString()
318                             .split("\\(")[1].split("\\)")[0];
319                 }
320 
321                 requiresApiVersion = getLastToken(fullRequiresApi, "\\.");
322             }
323 
324         }
325 
326         AnnotationData annotationData = new AnnotationData();
327         annotationData.isSystemApi = isSystem;
328         annotationData.hasAddedInOrBefore = hasAddedInOrBefore;
329         annotationData.addedInOrBeforeMajorVersion = addedInOrBeforeMajorVersion;
330         annotationData.addedInOrBeforeMinorVersion = addedInOrBeforeMinorVersion;
331         annotationData.hasDeprecatedAddedInAnnotation = hasDeprecatedAddedInAnnotation;
332         annotationData.hasAddedInAnnotation = hasAddedInAnnotation;
333         annotationData.addedInPlatformVersion = addedInPlatformVersion;
334         annotationData.hasApiRequirementAnnotation = hasApiRequirementAnnotation;
335         annotationData.minPlatformVersion = minPlatformVersion;
336         annotationData.minCarVersion = minCarVersion;
337         annotationData.hasRequiresApiAnnotation = hasRequiresApiAnnotation;
338         annotationData.requiresApiVersion = requiresApiVersion;
339         return annotationData;
340     }
341 
getLastToken(String s, String pattern)342     private static String getLastToken(String s, String pattern) {
343         return s.split(pattern)[(s.split(pattern).length - 1)];
344     }
345 
getConstructorData(ClassOrInterfaceDeclaration n, ConstructorDeclaration constructor)346     public static ConstructorData getConstructorData(ClassOrInterfaceDeclaration n,
347             ConstructorDeclaration constructor) {
348         if (!n.isInterface() && !constructor.isPublic() && !constructor.isProtected()) {
349             return null;
350         }
351 
352         String constructorName = constructor.getName().asString();
353 
354         StringBuilder parametersString = new StringBuilder();
355 
356         parametersString.append(constructorName);
357         parametersString.append("(");
358 
359         List<Parameter> parameters = constructor.getParameters();
360         for (int k = 0; k < parameters.size(); k++) {
361             Parameter parameter = parameters.get(k);
362             parametersString.append(parameter.getTypeAsString());
363             parametersString.append(" ");
364             parametersString.append(parameter.getNameAsString());
365             if (k < parameters.size() - 1) {
366                 parametersString.append(", ");
367             }
368         }
369         parametersString.append(")");
370 
371         boolean isHidden = false;
372 
373         if (!constructor.getJavadoc().isEmpty()) {
374             isHidden = constructor.getJavadoc().get().toText().contains("@hide");
375         }
376 
377         ConstructorData constructorData = new ConstructorData(constructorName);
378         constructorData.isHidden = isHidden;
379         constructorData.fullConstructorName = parametersString.toString();
380 
381         return constructorData;
382     }
383 
getFirstBodyStatement(MethodDeclaration method)384     private static Statement getFirstBodyStatement(MethodDeclaration method) {
385         if (method.getBody().isEmpty() || method.getBody().get().isEmpty()) {
386             return null;
387         }
388         return method.getBody().get().getStatement(0);
389     }
390 }
391