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