1 // © 2018 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 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 67 // Exceptions - API status of these methods are different from the parent 68 // by intent. 69 // 70 // TODO: We should revisit this and see if we want to continue 71 // to handle these as exceptions, or update our policy. 72 static final String[][] CONSISTENCY_EXCEPTIONS = { 73 //{"<class>", "<method>"}, 74 {"com.ibm.icu.text.Normalizer", "clone"}, 75 {"com.ibm.icu.text.PersonNameFormatter", "toString"}, 76 {"com.ibm.icu.text.SimplePersonName", "toString"}, 77 }; 78 checkConsistency()79 public void checkConsistency() { 80 Map<String, APIInfo> classMap = new TreeMap<>(); 81 // Build a map of APIInfo for classes, indexed by class name 82 for (APIInfo api : apiInfoSet) { 83 if (!api.isPublic() && !api.isProtected()) { 84 continue; 85 } 86 if (!api.isClass() && !api.isEnum()) { 87 continue; 88 } 89 String fullClassName = api.getPackageName() + "." + api.getName(); 90 classMap.put(fullClassName, api); 91 } 92 93 // Walk through methods 94 for (APIInfo api : apiInfoSet) { 95 if (!api.isMethod()) { 96 continue; 97 } 98 99 String fullClassName = api.getPackageName() + "." + api.getClassName(); 100 if (skipClasses.contains(fullClassName)) { 101 continue; 102 } 103 104 boolean checkWithClass = false; 105 String methodName = api.getName(); 106 String methodSig = api.getSignature(); 107 108 for (String[] method : METHODS) { 109 if (method[0].equals(methodName) && method[1].equals(methodSig)) { 110 checkWithClass = true; 111 } 112 } 113 114 if (!checkWithClass) { 115 continue; 116 } 117 118 // Check if this method has same API status with the containing class 119 APIInfo clsApi = classMap.get(fullClassName); 120 if (clsApi == null) { 121 pw.println("## Error ## Class " + fullClassName + " is not found."); 122 errCount++; 123 } 124 125 int methodStatus = api.getVal(APIInfo.STA); 126 String methodVer = api.getStatusVersion(); 127 int classStatus = clsApi.getVal(APIInfo.STA); 128 String classVer = clsApi.getStatusVersion(); 129 130 if (methodStatus != classStatus || !Objects.equals(methodVer, classVer)) { 131 boolean isExcepted = false; 132 for (String[] exceptMethod : CONSISTENCY_EXCEPTIONS) { 133 if (exceptMethod[0].equals(fullClassName) && exceptMethod[1].equals(methodName)) { 134 isExcepted = true; 135 break; 136 } 137 } 138 if (isExcepted) { 139 pw.println("## Info ## " + methodName + " in " + fullClassName + " (included in the exception list)"); 140 } else { 141 pw.println("## Error ## " + methodName + " in " + fullClassName); 142 errCount++; 143 } 144 } 145 } 146 } 147 } 148