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.ClassData; 20 import com.android.car.tool.data.ConstructorData; 21 import com.android.car.tool.data.FieldData; 22 import com.android.car.tool.data.MethodData; 23 import com.android.car.tool.data.PackageData; 24 import com.android.car.tool.data.ParsedData; 25 26 import com.github.javaparser.ast.expr.MethodCallExpr; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 public final class ParsedDataHelper { 32 33 private static final ArrayList<String> EXEMPT_METHODS = new ArrayList<>( 34 List.of("toString", "equals", "hashCode", "finalize", "writeToParcel", 35 "describeContents")); 36 getClassNamesOnly(ParsedData parsedData)37 public static List<String> getClassNamesOnly(ParsedData parsedData) { 38 List<String> classes = new ArrayList<>(); 39 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 40 .forEach((classData) -> classes.add(classData.useableClassName))); 41 return classes; 42 } 43 44 /** 45 * Returns all the non-hidden classes 46 */ getNonHiddenClassNamesOnly(ParsedData parsedData)47 public static List<String> getNonHiddenClassNamesOnly(ParsedData parsedData) { 48 List<String> classes = new ArrayList<>(); 49 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 50 .forEach((classData) -> { 51 if (!classData.annotationData.isSystemApi && classData.isClassHidden) { 52 return; 53 } 54 classes.add(classData.useableClassName); 55 })); 56 return classes; 57 } 58 getAddedInOrBeforeApisOnly(ParsedData parsedData)59 public static List<String> getAddedInOrBeforeApisOnly(ParsedData parsedData) { 60 List<String> fieldsAndMethods = new ArrayList<>(); 61 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 62 .forEach((classData) -> classData.fields.values().forEach( 63 (field) -> { 64 if (field.annotationData.hasAddedInOrBefore) { 65 fieldsAndMethods.add(packageData.packageName + "." 66 + classData.onlyClassName + "." + field.fieldName); 67 } 68 }))); 69 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 70 .forEach((classData) -> classData.methods.values().forEach( 71 (method) -> { 72 if (method.annotationData.hasAddedInOrBefore) { 73 fieldsAndMethods.add(packageData.packageName + "." 74 + classData.onlyClassName + "." + method.methodName); 75 } 76 }))); 77 return fieldsAndMethods; 78 } 79 getHiddenApisOnly(ParsedData parsedData)80 public static List<String> getHiddenApisOnly(ParsedData parsedData) { 81 List<String> fieldsAndMethods = new ArrayList<>(); 82 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 83 .forEach((classData) -> classData.fields.values().forEach( 84 (field) -> { 85 if ((!field.annotationData.isSystemApi && field.isHidden) 86 || (classData.isClassHidden 87 && !classData.annotationData.isSystemApi)) { 88 fieldsAndMethods 89 .add(formatFieldString(packageData, classData, field)); 90 } 91 }))); 92 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 93 .forEach((classData) -> classData.methods.values().forEach( 94 (method) -> { 95 if ((!method.annotationData.isSystemApi && method.isHidden) 96 || (classData.isClassHidden 97 && !classData.annotationData.isSystemApi)) { 98 fieldsAndMethods 99 .add(formatMethodString(packageData, classData, method)); 100 } 101 }))); 102 return fieldsAndMethods; 103 } 104 getHiddenApisWithHiddenConstructor(ParsedData parsedData)105 public static List<String> getHiddenApisWithHiddenConstructor(ParsedData parsedData) { 106 List<String> allHiddenApis = getHiddenApisOnly(parsedData); 107 108 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 109 .forEach((classData) -> classData.constructors.values() 110 .forEach((constructorData) -> { 111 if ((constructorData.isHidden 112 && !constructorData.annotationData.isSystemApi) 113 || (classData.isClassHidden 114 && !classData.annotationData.isSystemApi)) { 115 allHiddenApis.add(formatConstructorString(packageData, classData, 116 constructorData)); 117 } 118 }))); 119 120 return allHiddenApis; 121 } 122 getAllApis(ParsedData parsedData)123 public static List<String> getAllApis(ParsedData parsedData) { 124 List<String> fieldsAndMethods = new ArrayList<>(); 125 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 126 .forEach((classData) -> classData.fields.values().forEach( 127 (field) -> { 128 fieldsAndMethods.add(formatFieldString(packageData, classData, field)); 129 }))); 130 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 131 .forEach((classData) -> classData.methods.values().forEach( 132 (method) -> { 133 fieldsAndMethods 134 .add(formatMethodString(packageData, classData, method)); 135 }))); 136 return fieldsAndMethods; 137 } 138 getAllApisWithConstructor(ParsedData parsedData)139 public static List<String> getAllApisWithConstructor(ParsedData parsedData) { 140 List<String> allApis = getAllApis(parsedData); 141 142 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 143 .forEach((classData) -> classData.constructors.values() 144 .forEach((constructorData) -> { 145 allApis.add(formatConstructorString(packageData, classData, 146 constructorData)); 147 }))); 148 149 return allApis; 150 } 151 checkAssertPlatformVersionAtLeast( ParsedData parsedData)152 public static List<String> checkAssertPlatformVersionAtLeast( 153 ParsedData parsedData) { 154 List<String> apis = new ArrayList<>(); 155 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 156 .forEach((classData) -> classData.methods.values().forEach( 157 (method) -> { 158 // Only check that assertPlatformVersionAtLeast is present for APIs 159 // added after TIRAMISU_x. 160 if (!method.annotationData.hasApiRequirementAnnotation 161 || method.annotationData.minPlatformVersion.contains( 162 "TIRAMISU") || method.firstBodyStatement == null) { 163 return; 164 } 165 166 for (String exempt : EXEMPT_METHODS) { 167 if (method.methodName.contains(exempt)) { 168 return; 169 } 170 } 171 172 int line = 0; 173 if (method.firstBodyStatement.getBegin().isPresent()) { 174 line = method.firstBodyStatement.getBegin().get().line; 175 } 176 177 // Case where the first body statement is not a method call expression. 178 if (!method.firstBodyStatement.isExpressionStmt() 179 || !method.firstBodyStatement.asExpressionStmt() 180 .getExpression().isMethodCallExpr()) { 181 apis.add(formatMethodString(packageData, classData, method) + " | " 182 + line + " | " + method.fileName); 183 return; 184 } 185 186 MethodCallExpr methodCallExpr = (MethodCallExpr) 187 method.firstBodyStatement.asExpressionStmt().getExpression(); 188 189 // Case where no `assertPlatformVersionAtLeastU` method call exists in 190 // the first line of the method body. 191 // TODO(b/280357275): Add the version (ex. U, V, ...) as an argument 192 if (!methodCallExpr.getName().asString().contains( 193 "assertPlatformVersionAtLeastU")) { 194 apis.add(formatMethodString(packageData, classData, method) + " | " 195 + line + " | " + method.fileName); 196 } 197 }))); 198 return apis; 199 } 200 getApisWithVersion(ParsedData parsedData)201 public static List<String> getApisWithVersion(ParsedData parsedData) { 202 List<String> apisWithVersion = new ArrayList<>(); 203 204 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 205 .forEach((classData) -> classData.fields.values().forEach( 206 (field) -> { 207 String minCarVersion = ""; 208 if (field.annotationData.hasAddedInAnnotation) { 209 minCarVersion = field.annotationData.addedInPlatformVersion; 210 } else if (field.annotationData.hasAddedInOrBefore) { 211 // The only car version for @AddedInOrBefore is TIRAMISU_0. 212 minCarVersion = "TIRAMISU_0"; 213 } else { 214 minCarVersion = field.annotationData.minCarVersion; 215 } 216 apisWithVersion.add( 217 formatFieldString(packageData, classData, field) + " | " 218 + minCarVersion); 219 }))); 220 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 221 .forEach((classData) -> classData.methods.values().forEach( 222 (method) -> { 223 String minCarVersion = ""; 224 if (method.annotationData.hasAddedInAnnotation) { 225 minCarVersion = method.annotationData.addedInPlatformVersion; 226 } else if (method.annotationData.hasAddedInOrBefore) { 227 // The only car version for @AddedInOrBefore is TIRAMISU_0. 228 minCarVersion = "TIRAMISU_0"; 229 } else { 230 minCarVersion = method.annotationData.minCarVersion; 231 } 232 apisWithVersion.add( 233 formatMethodString(packageData, classData, method) + " | " 234 + minCarVersion); 235 }))); 236 237 return apisWithVersion; 238 } 239 240 /** 241 * Gives incorrect usage of requiresApi annotation in Car Service. 242 */ 243 // TODO(b/277617236): add tests for this getIncorrectRequiresApiUsage(ParsedData parsedData)244 public static List<String> getIncorrectRequiresApiUsage(ParsedData parsedData) { 245 List<String> incorrectRequiresApiUsage = new ArrayList<>(); 246 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 247 .forEach((classData) -> { 248 if (classData.annotationData.hasRequiresApiAnnotation) { 249 incorrectRequiresApiUsage.add(classData.useableClassName + " " 250 + classData.annotationData.requiresApiVersion); 251 } 252 })); 253 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 254 .forEach((classData) -> classData.methods.values().forEach( 255 (method) -> { 256 if (method.annotationData.hasRequiresApiAnnotation) { 257 incorrectRequiresApiUsage 258 .add(formatMethodString(packageData, classData, method) 259 + " " + method.annotationData.requiresApiVersion); 260 } 261 }))); 262 return incorrectRequiresApiUsage; 263 } 264 265 /** 266 * Gives incorrect usage of AddedIn annotation in Car built-in library. 267 */ 268 // TODO(b/277617236): add tests for this getIncorrectRequiresApi(ParsedData parsedData)269 public static List<String> getIncorrectRequiresApi(ParsedData parsedData) { 270 List<String> incorrectRequiresApi = new ArrayList<>(); 271 parsedData.packages.values().forEach((packageData) -> packageData.classes.values() 272 .forEach((classData) -> classData.methods.values().forEach( 273 (method) -> { 274 if (method.annotationData.hasAddedInAnnotation 275 && !method.annotationData.addedInPlatformVersion 276 .contains("TIRAMISU")) { 277 if (!method.annotationData.hasRequiresApiAnnotation) { 278 // Require API annotation is missing. 279 incorrectRequiresApi.add( 280 formatMethodString(packageData, classData, method)); 281 } 282 String platformVersion = 283 method.annotationData.addedInPlatformVersion; 284 String platformVersionWithoutMinorVersion = platformVersion 285 .substring(0, platformVersion.length() - 2); 286 if (method.annotationData.hasRequiresApiAnnotation 287 && !method.annotationData.requiresApiVersion 288 .equals(platformVersionWithoutMinorVersion)) { 289 // requires Api annotation is wrong 290 incorrectRequiresApi.add( 291 formatMethodString(packageData, classData, method)); 292 } 293 } 294 }))); 295 return incorrectRequiresApi; 296 } 297 formatMethodString(PackageData packageData, ClassData classData, MethodData method)298 private static String formatMethodString(PackageData packageData, ClassData classData, 299 MethodData method) { 300 return packageData.packageName + " " 301 + classData.onlyClassName + " " + method.returnType + " " 302 + method.fullMethodname; 303 } 304 formatFieldString(PackageData packageData, ClassData classData, FieldData field)305 private static String formatFieldString(PackageData packageData, ClassData classData, 306 FieldData field) { 307 return packageData.packageName + " " 308 + classData.onlyClassName + " " + field.fieldType + " " 309 + field.fieldName; 310 } 311 formatConstructorString(PackageData packageData, ClassData classData, ConstructorData constructorData)312 private static String formatConstructorString(PackageData packageData, ClassData classData, 313 ConstructorData constructorData) { 314 return packageData.packageName + " " 315 + classData.onlyClassName + " " 316 + constructorData.constructorName + " " 317 + constructorData.fullConstructorName; 318 } 319 } 320