blob: 3a15a40891bb86f51a85e00c6c506b9e566c565d [file] [log] [blame]
// © 2018 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
package com.ibm.icu.dev.tool.docs;
import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
/**
* Checks if API status of equals/hashCode is same with its containing class.
*
* @author Yoshito
*/
public class APIStatusConsistencyChecker {
public static void main(String[] args) {
// args[0] API signature file path
// args[1] (Optional) List of classes to be skipped, separated by semicolon
if (args.length < 1) {
System.err.println("Missing API signature file path.");
} else if (args.length > 2) {
System.err.println("Too many command arguments");
}
List<String> skipClasses = Collections.emptyList();
if (args.length == 2) {
String[] classes = args[1].split(";");
skipClasses = Arrays.asList(classes);
}
// Load the ICU4J API signature file
Set<APIInfo> apiInfoSet = APIData.read(new File(args[0]), true).getAPIInfoSet();
APIStatusConsistencyChecker checker = new APIStatusConsistencyChecker(apiInfoSet, skipClasses, new PrintWriter(System.err, true));
checker.checkConsistency();
System.exit(checker.errCount);
}
private int errCount = 0;
private Set<APIInfo> apiInfoSet;
private PrintWriter pw;
private List<String> skipClasses;
public APIStatusConsistencyChecker(Set<APIInfo> apiInfoSet, List<String> skipClasses, PrintWriter pw) {
this.apiInfoSet = apiInfoSet;
this.skipClasses = skipClasses;
this.pw = pw;
}
public int errorCount() {
return errCount;
}
// Methods that should have same API status with a containing class
static final String[][] METHODS = {
//{"<method name>", "<method signature in APIInfo data>"},
{"equals", "boolean(java.lang.Object)"},
{"hashCode", "int()"},
{"toString", "java.lang.String()"},
{"clone", "java.lang.Object()"},
};
public void checkConsistency() {
Map<String, APIInfo> classMap = new TreeMap<>();
// Build a map of APIInfo for classes, indexed by class name
for (APIInfo api : apiInfoSet) {
if (!api.isPublic() && !api.isProtected()) {
continue;
}
if (!api.isClass() && !api.isEnum()) {
continue;
}
String fullClassName = api.getPackageName() + "." + api.getName();
classMap.put(fullClassName, api);
}
// Walk through methods
for (APIInfo api : apiInfoSet) {
if (!api.isMethod()) {
continue;
}
String fullClassName = api.getPackageName() + "." + api.getClassName();
if (skipClasses.contains(fullClassName)) {
continue;
}
boolean checkWithClass = false;
String methodName = api.getName();
String methodSig = api.getSignature();
for (String[] method : METHODS) {
if (method[0].equals(methodName) && method[1].equals(methodSig)) {
checkWithClass = true;
}
}
if (!checkWithClass) {
continue;
}
// Check if this method has same API status with the containing class
APIInfo clsApi = classMap.get(fullClassName);
if (clsApi == null) {
pw.println("## Error ## Class " + fullClassName + " is not found.");
errCount++;
}
int methodStatus = api.getVal(APIInfo.STA);
String methodVer = api.getStatusVersion();
int classStatus = clsApi.getVal(APIInfo.STA);
String classVer = clsApi.getStatusVersion();
if (methodStatus != classStatus || !Objects.equals(methodVer, classVer)) {
pw.println("## Error ## " + methodName + " in " + fullClassName);
errCount++;
}
}
}
}