1 // © 2018 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 package com.ibm.icu.dev.tool.docs; 4 5 import java.io.File; 6 import java.io.PrintWriter; 7 import java.util.Arrays; 8 import java.util.Collections; 9 import java.util.List; 10 import java.util.Map; 11 import java.util.Objects; 12 import java.util.Set; 13 import java.util.TreeMap; 14 15 /** 16 * Checks if API status of equals/hashCode is same with its containing class. 17 * 18 * @author Yoshito 19 */ 20 public class APIStatusConsistencyChecker { main(String[] args)21 public static void main(String[] args) { 22 // args[0] API signature file path 23 // args[1] (Optional) List of classes to be skipped, separated by semicolon 24 if (args.length < 1) { 25 System.err.println("Missing API signature file path."); 26 } else if (args.length > 2) { 27 System.err.println("Too many command arguments"); 28 } 29 30 List<String> skipClasses = Collections.emptyList(); 31 if (args.length == 2) { 32 String[] classes = args[1].split(";"); 33 skipClasses = Arrays.asList(classes); 34 } 35 36 // Load the ICU4J API signature file 37 Set<APIInfo> apiInfoSet = APIData.read(new File(args[0]), true).getAPIInfoSet(); 38 APIStatusConsistencyChecker checker = new APIStatusConsistencyChecker(apiInfoSet, skipClasses, new PrintWriter(System.err, true)); 39 checker.checkConsistency(); 40 System.exit(checker.errCount); 41 } 42 43 private int errCount = 0; 44 private Set<APIInfo> apiInfoSet; 45 private PrintWriter pw; 46 private List<String> skipClasses; 47 APIStatusConsistencyChecker(Set<APIInfo> apiInfoSet, List<String> skipClasses, PrintWriter pw)48 public APIStatusConsistencyChecker(Set<APIInfo> apiInfoSet, List<String> skipClasses, PrintWriter pw) { 49 this.apiInfoSet = apiInfoSet; 50 this.skipClasses = skipClasses; 51 this.pw = pw; 52 } 53 errorCount()54 public int errorCount() { 55 return errCount; 56 } 57 58 // Methods that should have same API status with a containing class 59 static final String[][] METHODS = { 60 //{"<method name>", "<method signature in APIInfo data>"}, 61 {"equals", "boolean(java.lang.Object)"}, 62 {"hashCode", "int()"}, 63 {"toString", "java.lang.String()"}, 64 {"clone", "java.lang.Object()"}, 65 }; 66 checkConsistency()67 public void checkConsistency() { 68 Map<String, APIInfo> classMap = new TreeMap<>(); 69 // Build a map of APIInfo for classes, indexed by class name 70 for (APIInfo api : apiInfoSet) { 71 if (!api.isPublic() && !api.isProtected()) { 72 continue; 73 } 74 if (!api.isClass() && !api.isEnum()) { 75 continue; 76 } 77 String fullClassName = api.getPackageName() + "." + api.getName(); 78 classMap.put(fullClassName, api); 79 } 80 81 // Walk through methods 82 for (APIInfo api : apiInfoSet) { 83 if (!api.isMethod()) { 84 continue; 85 } 86 87 String fullClassName = api.getPackageName() + "." + api.getClassName(); 88 if (skipClasses.contains(fullClassName)) { 89 continue; 90 } 91 92 boolean checkWithClass = false; 93 String methodName = api.getName(); 94 String methodSig = api.getSignature(); 95 96 for (String[] method : METHODS) { 97 if (method[0].equals(methodName) && method[1].equals(methodSig)) { 98 checkWithClass = true; 99 } 100 } 101 102 if (!checkWithClass) { 103 continue; 104 } 105 106 // Check if this method has same API status with the containing class 107 APIInfo clsApi = classMap.get(fullClassName); 108 if (clsApi == null) { 109 pw.println("## Error ## Class " + fullClassName + " is not found."); 110 errCount++; 111 } 112 113 int methodStatus = api.getVal(APIInfo.STA); 114 String methodVer = api.getStatusVersion(); 115 int classStatus = clsApi.getVal(APIInfo.STA); 116 String classVer = clsApi.getStatusVersion(); 117 118 if (methodStatus != classStatus || !Objects.equals(methodVer, classVer)) { 119 pw.println("## Error ## " + methodName + " in " + fullClassName); 120 errCount++; 121 } 122 } 123 } 124 } 125