blob: 022d51fba1f0989c11bc53fec04b4c9ae4dc77d2 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.tool.apibuilder;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.MemberValuePair;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Class to generate API txt file.
*/
public final class GenerateAPI {
private static final boolean DBG = false;
private static final String ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP";
private static final String CAR_API_PATH =
"/packages/services/Car/car-lib/src/android/car";
private static final String CAR_BUILT_IN_API_PATH =
"/packages/services/Car/car-builtin-lib/src/android/car/builtin";
private static final String CAR_API_ANNOTATION_TEST_FILE =
"/packages/services/Car/tests/carservice_unit_test/res/raw/car_api_classes.txt";
private static final String CAR_BUILT_IN_ANNOTATION_TEST_FILE =
"/packages/services/Car/tests/carservice_unit_test/res/raw/"
+ "car_built_in_api_classes.txt";
private static final String CAR_HIDDEN_API_FILE =
"/packages/services/Car/tests/carservice_unit_test/res/raw/"
+ "car_hidden_apis.txt";
private static final String API_TXT_SAVE_PATH =
"/packages/services/Car/tools/GenericCarApiBuilder/";
private static final String COMPLETE_CAR_API_LIST = "complete_car_api_list.txt";
private static final String COMPLETE_CAR_BUILT_IN_API_LIST =
"complete_car_built_in_api_list.txt";
private static final String TAB = " ";
// Arguments:
private static final String PRINT_CLASSES_ONLY = "--print-classes-only";
private static final String UPDATE_CLASSES_FOR_TEST = "--update-classes-for-test";
private static final String GENERATE_FULL_API_LIST = "--generate-full-api-list";
private static final String UPDATE_HIDDEN_API_FOR_TEST = "--update-hidden-api-for-test";
private static final String PRINT_HIDDEN_API_FOR_TEST = "--print-hidden-api-for-test";
private static final String PRINT_SHORTFORM_FULL_API_FOR_TEST =
"--print-shortform-full-api-for-test";
// Print Level: Describes desired print level for the tool
// PRINT_SHORT prints only a condensed version of the APIs.
// PRINT_HIDDEN_ONLY prints only hidden APIs.
private static final int PRINT_DEFAULT = 0;
private static final int PRINT_SHORT = 1;
private static final int PRINT_HIDDEN_ONLY = 2;
/**
* Main method for generate API txt file.
*/
public static void main(final String[] args) throws Exception {
try {
if (args.length == 0) {
printHelp();
return;
}
String rootDir = System.getenv(ANDROID_BUILD_TOP);
if (rootDir == null || rootDir.isEmpty()) {
// check for second optional argument
if (args.length > 2 && args[1].equalsIgnoreCase("--android-build-top")) {
rootDir = args[2];
} else {
printHelp();
return;
}
}
List<File> allJavaFiles_carLib = getAllFiles(new File(rootDir + CAR_API_PATH));
List<File> allJavaFiles_carBuiltInLib = getAllFiles(
new File(rootDir + CAR_BUILT_IN_API_PATH));
if (args.length > 0 && args[0].equalsIgnoreCase(PRINT_CLASSES_ONLY)) {
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
printOrUpdateAllClasses(allJavaFiles_carLib.get(i), true, null);
}
for (int i = 0; i < allJavaFiles_carBuiltInLib.size(); i++) {
printOrUpdateAllClasses(allJavaFiles_carBuiltInLib.get(i), true, null);
}
return;
}
if (args.length > 0 && args[0].equalsIgnoreCase(UPDATE_CLASSES_FOR_TEST)) {
List<String> api_list = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
printOrUpdateAllClasses(allJavaFiles_carLib.get(i), false, api_list);
}
writeListToFile(rootDir + CAR_API_ANNOTATION_TEST_FILE, api_list);
List<String> built_in_api_list = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carBuiltInLib.size(); i++) {
printOrUpdateAllClasses(allJavaFiles_carBuiltInLib.get(i), false,
built_in_api_list);
}
writeListToFile(rootDir + CAR_BUILT_IN_ANNOTATION_TEST_FILE, built_in_api_list);
return;
}
if (args.length > 0 && args[0].equalsIgnoreCase(UPDATE_HIDDEN_API_FOR_TEST)) {
List<String> allCarAPIs = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
allCarAPIs.addAll(
parseJavaFile(allJavaFiles_carLib.get(i), PRINT_HIDDEN_ONLY));
}
writeListToFile(rootDir + CAR_HIDDEN_API_FILE, allCarAPIs);
return;
}
if (args.length > 0 && args[0].equalsIgnoreCase(PRINT_HIDDEN_API_FOR_TEST)) {
List<String> allCarAPIs = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
allCarAPIs.addAll(
parseJavaFile(allJavaFiles_carLib.get(i), PRINT_HIDDEN_ONLY));
}
print(allCarAPIs);
return;
}
if (args.length > 0 && args[0].equalsIgnoreCase(PRINT_SHORTFORM_FULL_API_FOR_TEST)) {
List<String> allCarAPIs = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
allCarAPIs.addAll(
parseJavaFile(allJavaFiles_carLib.get(i), PRINT_SHORT));
}
print(allCarAPIs);
return;
}
if (args.length > 0 && args[0].equalsIgnoreCase(GENERATE_FULL_API_LIST)) {
List<String> allCarAPIs = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carLib.size(); i++) {
allCarAPIs.addAll(
parseJavaFile(allJavaFiles_carLib.get(i), PRINT_DEFAULT));
}
writeListToFile(rootDir + API_TXT_SAVE_PATH + COMPLETE_CAR_API_LIST, allCarAPIs);
List<String> allCarBuiltInAPIs = new ArrayList<>();
for (int i = 0; i < allJavaFiles_carBuiltInLib.size(); i++) {
allCarBuiltInAPIs.addAll(
parseJavaFile(allJavaFiles_carBuiltInLib.get(i), PRINT_DEFAULT));
}
writeListToFile(rootDir + API_TXT_SAVE_PATH + COMPLETE_CAR_BUILT_IN_API_LIST,
allCarBuiltInAPIs);
return;
}
} catch (Exception e) {
throw e;
}
}
private static void print(List<String> data) {
for (String string : data) {
System.out.println(string);
}
}
private static void writeListToFile(String filePath, List<String> data) throws IOException {
Path file = Paths.get(filePath);
Files.write(file, data, StandardCharsets.UTF_8);
}
private static void printHelp() {
System.out.println("**** Help ****");
System.out.println("At least one argument is required. Supported arguments - ");
System.out.println(PRINT_CLASSES_ONLY + " : Would print the list of valid class and"
+ " interfaces.");
System.out.println(UPDATE_CLASSES_FOR_TEST + " : Would update the test file with the list"
+ " of valid class and interfaces. These files are updated"
+ " tests/carservice_unit_test/res/raw/car_api_classes.txt and"
+ " tests/carservice_unit_test/res/raw/car_built_in_api_classes.txt");
System.out.println(GENERATE_FULL_API_LIST + " : Would generate full api list including the"
+ " hidden APIs. Results would be saved in ");
System.out.println(PRINT_HIDDEN_API_FOR_TEST + " : Would generate hidden api list for"
+ " testing. Results would be printed.");
System.out.println(UPDATE_HIDDEN_API_FOR_TEST + " : Would generate hidden api list for"
+ " testing. Results would be updated in " + CAR_HIDDEN_API_FILE);
System.out.println("Second optional argument is value of Git Root Directory. By default, "
+ "it is environment variable ANDROID_BUILD_TOP. If environment variable is not set"
+ "then provide using --android-build-top <directory>");
}
private static List<File> getAllFiles(File folderName) {
List<File> allFiles = new ArrayList<>();
File[] files = folderName.listFiles();
for (int i = 0; i < files.length; i++) {
if (files[i].isFile() && files[i].getName().endsWith(".java")) {
if (DBG) {
System.out.printf("File added %s\n", files[i]);
}
allFiles.add(files[i]);
}
if (files[i].isDirectory()) {
allFiles.addAll(getAllFiles(files[i]));
}
}
// List files doesn't guarantee fixed order on all systems. It is better to sort the list.
Collections.sort(allFiles);
return allFiles;
}
private static void printOrUpdateAllClasses(File file, boolean print, List<String> updateList)
throws Exception {
if (!print && updateList == null) {
throw new Exception("update list should not be null if not printing.");
}
CompilationUnit cu = StaticJavaParser.parse(file);
String packageName = cu.getPackageDeclaration().get().getNameAsString();
new VoidVisitorAdapter<Object>() {
@Override
public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, Object arg) {
if (!classOrInterfaceDeclaration.isPublic()
&& !classOrInterfaceDeclaration.isProtected()) {
return;
}
String className = classOrInterfaceDeclaration.getFullyQualifiedName().get()
.substring(packageName.length() + 1);
String useableClassName = packageName + "." + className.replace(".", "$");
if (print) {
System.out.println(useableClassName);
} else {
updateList.add(useableClassName);
}
super.visit(classOrInterfaceDeclaration, arg);
}
}.visit(cu, null);
}
private static List<String> parseJavaFile(File file, int printLevel)
throws Exception {
List<String> parsedList = new ArrayList<>();
// Add code to parse file
CompilationUnit cu = StaticJavaParser.parse(file);
String packageName = cu.getPackageDeclaration().get().getNameAsString();
new VoidVisitorAdapter<Object>() {
@Override
public void visit(ClassOrInterfaceDeclaration n, Object arg) {
if (!n.isPublic() && !n.isProtected()) {
return;
}
String className = n.getFullyQualifiedName().get()
.substring(packageName.length() + 1);
String classType = n.isInterface() ? "interface" : "class";
boolean hiddenClass = false;
if (!n.getJavadoc().isEmpty()) {
hiddenClass = n.getJavadoc().get().toText().contains("@hide");
}
boolean isClassSystemAPI = false;
NodeList<AnnotationExpr> classAnnotations = n.getAnnotations();
for (int j = 0; j < classAnnotations.size(); j++) {
if (classAnnotations.get(j).getName().asString().contains("SystemApi")) {
isClassSystemAPI = true;
}
}
String classDeclaration = classType + " "
+ (hiddenClass && !isClassSystemAPI ? "@hiddenOnly " : "")
+ (hiddenClass ? "@hide " : "")
+ (isClassSystemAPI ? "@SystemApi " : "") + className + " package "
+ packageName;
boolean wholeClassIsHidden = hiddenClass && !isClassSystemAPI;
if (printLevel == PRINT_DEFAULT) {
parsedList.add(classDeclaration);
}
if (DBG) {
System.out.println(classDeclaration);
}
List<FieldDeclaration> fields = n.getFields();
for (int i = 0; i < fields.size(); i++) {
FieldDeclaration field = fields.get(i);
if (!field.isPublic() && !field.isProtected()) {
continue;
}
String fieldName = field.getVariables().get(0).getName().asString();
String fieldType = field.getVariables().get(0).getTypeAsString();
boolean fieldInitialized = !field.getVariables().get(0).getInitializer()
.isEmpty();
String fieldInitializedValue = "";
if (fieldInitialized) {
fieldInitializedValue = field.getVariables().get(0).getInitializer().get()
.toString();
}
// special case
if (fieldName.equalsIgnoreCase("CREATOR")) {
fieldInitialized = false;
}
boolean isSystem = false;
boolean isHidden = false;
String version = "";
if (!field.getJavadoc().isEmpty()) {
isHidden = field.getJavadoc().get().toText().contains("@hide");
}
NodeList<AnnotationExpr> annotations = field.getAnnotations();
for (int j = 0; j < annotations.size(); j++) {
String annotationString = annotations.get(j).getName().asString();
if (annotationString.contains("SystemApi")) {
isSystem = true;
}
if (annotationString.contains("AddedInOrBefore")) {
String major = getVersion(annotations.get(j), "majorVersion");
String minor = getVersion(annotations.get(j), "minorVersion");
if (!minor.equals("0")) {
System.out.println("ERROR: minor should be zero for " + field);
}
if (!major.equals("33")) {
System.out.println("ERROR: major should be 33 for " + field);
}
version = "TIRAMISU_0";
}
if (annotationString.equals("AddedIn")) {
String major = getVersion(annotations.get(j), "majorVersion");
String minor = getVersion(annotations.get(j), "minorVersion");
if (!major.equals("33")) {
System.out.println("ERROR: major should be 33 for " + field);
}
version = "TIRAMISU_" + minor;
}
if (annotationString.equals("ApiRequirements")) {
String major = getVersion(annotations.get(j), "minCarVersion");
version = major.split("\\.")[major.split("\\.").length - 1];
}
}
StringBuilder sb = new StringBuilder();
sb.append("field ");
sb.append(version + " ");
if (isHidden && !isSystem) {
sb.append("@hiddenOnly ");
}
sb.append(fieldType);
sb.append(" ");
sb.append(fieldName);
if (fieldInitialized) {
sb.append(" = ");
sb.append(fieldInitializedValue);
}
sb.append(";");
if (DBG) {
System.out.printf("%s%s\n", TAB, sb);
}
String parsedName = packageName + " " + className + " "
+ fieldType + " " + fieldName;
switch (printLevel) {
case PRINT_DEFAULT:
parsedList.add(TAB + sb);
break;
case PRINT_SHORT:
parsedList.add(parsedName);
break;
case PRINT_HIDDEN_ONLY:
if (wholeClassIsHidden || (isHidden && !isSystem)) {
parsedList.add(parsedName);
}
break;
default:
System.err.println("Unknown print level specified");
break;
}
}
// get all the methods
List<MethodDeclaration> methods = n.getMethods();
for (int i = 0; i < methods.size(); i++) {
MethodDeclaration method = methods.get(i);
if (!method.isPublic() && !method.isProtected()) {
continue;
}
String returnType = method.getTypeAsString();
String methodName = method.getName().asString();
boolean isSystem = false;
boolean isHidden = false;
String version = "";
if (!method.getJavadoc().isEmpty()) {
isHidden = method.getJavadoc().get().toText().contains("@hide");
}
NodeList<AnnotationExpr> annotations = method.getAnnotations();
for (int j = 0; j < annotations.size(); j++) {
String annotationString = annotations.get(j).getName().asString();
if (annotationString.contains("SystemApi")) {
isSystem = true;
}
if (annotationString.contains("AddedInOrBefore")) {
String major = getVersion(annotations.get(j), "majorVersion");
String minor = getVersion(annotations.get(j), "minorVersion");
if (!minor.equals("0")) {
System.out.println("ERROR: minor should be zero for " + method);
}
if (!major.equals("33")) {
System.out.println("ERROR: major should be 33 for " + method);
}
version = "TIRAMISU_0";
}
if (annotationString.equals("AddedIn")) {
String major = getVersion(annotations.get(j), "majorVersion");
String minor = getVersion(annotations.get(j), "minorVersion");
if (!major.equals("33")) {
System.out.println("ERROR: major should be 33 for " + method);
}
version = "TIRAMISU_" + minor;
}
if (annotationString.equals("ApiRequirements")) {
String major = getVersion(annotations.get(j), "minCarVersion");
version = major.split("\\.")[major.split("\\.").length - 1];
}
}
StringBuilder sb = new StringBuilder();
sb.append("method ");
sb.append(version + " ");
if (isHidden && !isSystem) {
sb.append("@hiddenOnly ");
}
sb.append(returnType);
sb.append(" ");
sb.append(methodName);
StringBuilder parametersString = new StringBuilder();
parametersString.append("(");
List<Parameter> parameters = method.getParameters();
for (int k = 0; k < parameters.size(); k++) {
Parameter parameter = parameters.get(k);
parametersString.append(parameter.getTypeAsString());
parametersString.append(" ");
parametersString.append(parameter.getNameAsString());
if (k < parameters.size() - 1) {
parametersString.append(", ");
}
}
parametersString.append(")");
sb.append(parametersString);
if (DBG) {
System.out.printf("%s%s\n", TAB, sb);
}
String parsedName = packageName + " " + className + " "
+ returnType + " " + methodName + parametersString;
switch (printLevel) {
case PRINT_DEFAULT:
parsedList.add(TAB + sb);
break;
case PRINT_SHORT:
parsedList.add(parsedName);
break;
case PRINT_HIDDEN_ONLY:
if (wholeClassIsHidden || (isHidden && !isSystem)) {
parsedList.add(parsedName);
}
break;
default:
System.err.println("Unknown print level specified");
break;
}
}
super.visit(n, arg);
}
private String getVersion(AnnotationExpr annotationExpr, String parameterName) {
List<MemberValuePair> children = annotationExpr
.getChildNodesByType(MemberValuePair.class);
for (MemberValuePair memberValuePair : children) {
if (parameterName.equals(memberValuePair.getNameAsString())) {
if (memberValuePair.getValue() == null) {
return "0";
}
return memberValuePair.getValue().toString();
}
}
return "0";
}
}.visit(cu, null);
return parsedList;
}
}