• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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