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