• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  *******************************************************************************
5  * Copyright (C) 2014, International Business Machines Corporation and         *
6  * others. All Rights Reserved.                                                *
7  *******************************************************************************
8  */
9 package com.ibm.icu.dev.tool.docs;
10 
11 import java.io.File;
12 import java.io.PrintWriter;
13 import java.lang.reflect.Constructor;
14 import java.lang.reflect.Field;
15 import java.lang.reflect.GenericArrayType;
16 import java.lang.reflect.Method;
17 import java.lang.reflect.Modifier;
18 import java.lang.reflect.ParameterizedType;
19 import java.lang.reflect.Type;
20 import java.lang.reflect.TypeVariable;
21 import java.lang.reflect.WildcardType;
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27 import java.util.TreeMap;
28 
29 public class DeprecatedAPIChecker {
30 
main(String[] args)31     public static void main(String[] args) {
32         if (args.length != 1) {
33             System.err.println("Illegal command argument. Specify the API signature file path.");
34         }
35         // Load the ICU4J API signature file
36         Set<APIInfo> apiInfoSet = APIData.read(new File(args[0]), true).getAPIInfoSet();
37 
38         DeprecatedAPIChecker checker = new DeprecatedAPIChecker(apiInfoSet, new PrintWriter(System.err, true));
39         checker.checkDeprecated();
40         System.exit(checker.errCount);
41     }
42 
43     private int errCount = 0;
44     private Set<APIInfo> apiInfoSet;
45     private PrintWriter pw;
46 
DeprecatedAPIChecker(Set<APIInfo> apiInfoSet, PrintWriter pw)47     public DeprecatedAPIChecker(Set<APIInfo> apiInfoSet, PrintWriter pw) {
48         this.apiInfoSet = apiInfoSet;
49         this.pw = pw;
50     }
51 
errorCount()52     public int errorCount() {
53         return errCount;
54     }
55 
checkDeprecated()56     public void checkDeprecated() {
57         // Gather API class/enum names and its names that can be
58         // used for Class.forName()
59         Map<String, String> apiClassNameMap = new TreeMap<>();
60         for (APIInfo api : apiInfoSet) {
61             if (!api.isPublic() && !api.isProtected()) {
62                 continue;
63             }
64             if (!api.isClass() && !api.isEnum()) {
65                 continue;
66             }
67             String packageName = api.getPackageName();
68             String className = api.getName();
69 
70             // Replacing separator for nested class/enum (replacing '.' with
71             // '$'), so we can use the name for Class.forName(String)
72             String classNamePath = className.contains(".") ? className.replace('.', '$') : className;
73 
74             apiClassNameMap.put(packageName + "." + classNamePath, packageName + "." + className);
75         }
76 
77         // Walk through API classes using reflection
78         for (Entry<String, String> classEntry : apiClassNameMap.entrySet()) {
79             String classNamePath = classEntry.getKey();
80             try {
81                 Class<?> cls = Class.forName(classNamePath);
82                 if (cls.isEnum()) {
83                     checkEnum(cls, apiClassNameMap);
84                 } else {
85                     checkClass(cls, apiClassNameMap);
86                 }
87             } catch (ClassNotFoundException e) {
88                 pw.println("## Error ## Class " + classNamePath + " is not found.");
89                 errCount++;
90             }
91         }
92     }
93 
checkClass(Class<?> cls, Map<String, String> clsNameMap)94     private void checkClass(Class<?> cls, Map<String, String> clsNameMap) {
95         assert !cls.isEnum();
96 
97         String clsPath = cls.getName();
98         String clsName = clsNameMap.get(clsPath);
99         APIInfo api = null;
100 
101         if (clsName != null) {
102             api = findClassInfo(apiInfoSet, clsName);
103         }
104         if (api == null) {
105             pw.println("## Error ## Class " + clsName + " is not found in the API signature data.");
106             errCount++;
107         }
108 
109         // check class
110         compareDeprecated(isAPIDeprecated(api), cls.isAnnotationPresent(Deprecated.class), clsName, null, "Class");
111 
112         // check fields
113         for (Field f : cls.getDeclaredFields()) {
114             if (!isPublicOrProtected(f.getModifiers())) {
115                 continue;
116             }
117 
118             String fName = f.getName();
119             api = findFieldInfo(apiInfoSet, clsName, fName);
120             if (api == null) {
121                 pw.println("## Error ## Field " + clsName + "." + fName + " is not found in the API signature data.");
122                 errCount++;
123                 continue;
124             }
125 
126             compareDeprecated(isAPIDeprecated(api), f.isAnnotationPresent(Deprecated.class), clsName, fName, "Field");
127         }
128 
129         // check constructors
130         for (Constructor<?> ctor : cls.getDeclaredConstructors()) {
131             if (!isPublicOrProtected(ctor.getModifiers())) {
132                 continue;
133             }
134 
135             List<String> paramNames = getParamNames(ctor);
136 
137             Class<?> declClass = cls.getDeclaringClass();
138             if (declClass != null && !Modifier.isStatic(cls.getModifiers())) {
139                 // This is non-static inner class's constructor.
140                 // javac automatically injects instance of declaring class
141                 // as the first param of the constructor, but ICU's API
142                 // signature is based on javadoc and it generates signature
143                 // without the implicit parameter.
144                 assert paramNames.get(0).equals(declClass.getName());
145                 paramNames.remove(0);
146             }
147 
148             api = findConstructorInfo(apiInfoSet, clsName, paramNames);
149 
150             if (api == null) {
151                 pw.println("## Error ## Constructor " + clsName + formatParams(paramNames)
152                         + " is not found in the API signature data.");
153                 errCount++;
154                 continue;
155             }
156 
157             compareDeprecated(isAPIDeprecated(api), ctor.isAnnotationPresent(Deprecated.class), clsName,
158                     api.getClassName() + formatParams(paramNames), "Constructor");
159         }
160 
161         // check methods
162         for (Method mtd : cls.getDeclaredMethods()) {
163             // Note: We exclude synthetic method.
164             if (!isPublicOrProtected(mtd.getModifiers()) || mtd.isSynthetic()) {
165                 continue;
166             }
167 
168             String mtdName = mtd.getName();
169             List<String> paramNames = getParamNames(mtd);
170             api = findMethodInfo(apiInfoSet, clsName, mtdName, paramNames);
171 
172             if (api == null) {
173                 pw.println("## Error ## Method " + clsName + "#" + mtdName + formatParams(paramNames)
174                         + " is not found in the API signature data.");
175                 errCount++;
176                 continue;
177             }
178 
179             compareDeprecated(isAPIDeprecated(api), mtd.isAnnotationPresent(Deprecated.class), clsName, mtdName
180                     + formatParams(paramNames), "Method");
181 
182         }
183     }
184 
checkEnum(Class<?> cls, Map<String, String> clsNameMap)185     private void checkEnum(Class<?> cls, Map<String, String> clsNameMap) {
186         assert cls.isEnum();
187 
188         String enumPath = cls.getName();
189         String enumName = clsNameMap.get(enumPath);
190         APIInfo api = null;
191 
192         if (enumName != null) {
193             api = findEnumInfo(apiInfoSet, enumName);
194         }
195         if (api == null) {
196             pw.println("## Error ## Enum " + enumName + " is not found in the API signature data.");
197             errCount++;
198         }
199 
200         // check enum
201         compareDeprecated(isAPIDeprecated(api), cls.isAnnotationPresent(Deprecated.class), enumName, null, "Enum");
202 
203         // check enum constants
204         for (Field ec : cls.getDeclaredFields()) {
205             if (!ec.isEnumConstant()) {
206                 continue;
207             }
208             String ecName = ec.getName();
209             api = findEnumConstantInfo(apiInfoSet, enumName, ecName);
210             if (api == null) {
211                 pw.println("## Error ## Enum constant " + enumName + "." + ecName
212                         + " is not found in the API signature data.");
213                 errCount++;
214                 continue;
215             }
216 
217             compareDeprecated(isAPIDeprecated(api), ec.isAnnotationPresent(Deprecated.class), enumName, ecName,
218                     "Enum Constant");
219         }
220 
221         // check methods
222         for (Method mtd : cls.getDeclaredMethods()) {
223             // Note: We exclude built-in methods in a Java Enum instance
224             if (!isPublicOrProtected(mtd.getModifiers()) || isBuiltinEnumMethod(mtd)) {
225                 continue;
226             }
227 
228             String mtdName = mtd.getName();
229             List<String> paramNames = getParamNames(mtd);
230             api = findMethodInfo(apiInfoSet, enumName, mtdName, paramNames);
231 
232             if (api == null) {
233                 pw.println("## Error ## Method " + enumName + "#" + mtdName + formatParams(paramNames)
234                         + " is not found in the API signature data.");
235                 errCount++;
236                 continue;
237             }
238 
239             compareDeprecated(isAPIDeprecated(api), mtd.isAnnotationPresent(Deprecated.class), enumName, mtdName
240                     + formatParams(paramNames), "Method");
241 
242         }
243     }
244 
compareDeprecated(boolean depTag, boolean depAnt, String cls, String name, String type)245     private void compareDeprecated(boolean depTag, boolean depAnt, String cls, String name, String type) {
246         if (depTag != depAnt) {
247             String apiName = cls;
248             if (name != null) {
249                 apiName += "." + name;
250             }
251             if (depTag) {
252                 pw.println("No @Deprecated annotation: [" + type + "] " + apiName);
253             } else {
254                 pw.println("No @deprecated JavaDoc tag: [" + type + "] " + apiName);
255             }
256             errCount++;
257         }
258     }
259 
isPublicOrProtected(int modifier)260     private static boolean isPublicOrProtected(int modifier) {
261         return ((modifier & Modifier.PUBLIC) != 0) || ((modifier & Modifier.PROTECTED) != 0);
262     }
263 
264     // Check if a method is automatically generated for a each Enum
isBuiltinEnumMethod(Method mtd)265     private static boolean isBuiltinEnumMethod(Method mtd) {
266         // Just check method name for now
267         String name = mtd.getName();
268         return name.equals("values") || name.equals("valueOf");
269     }
270 
isAPIDeprecated(APIInfo api)271     private static boolean isAPIDeprecated(APIInfo api) {
272         return api.isDeprecated() || api.isInternal() || api.isObsolete();
273     }
274 
findClassInfo(Set<APIInfo> apis, String cls)275     private static APIInfo findClassInfo(Set<APIInfo> apis, String cls) {
276         for (APIInfo api : apis) {
277             String clsName = api.getPackageName() + "." + api.getName();
278             if (api.isClass() && clsName.equals(cls)) {
279                 return api;
280             }
281         }
282         return null;
283     }
284 
findFieldInfo(Set<APIInfo> apis, String cls, String field)285     private static APIInfo findFieldInfo(Set<APIInfo> apis, String cls, String field) {
286         for (APIInfo api : apis) {
287             String clsName = api.getPackageName() + "." + api.getClassName();
288             if (api.isField() && clsName.equals(cls) && api.getName().equals(field)) {
289                 return api;
290             }
291         }
292         return null;
293     }
294 
findConstructorInfo(Set<APIInfo> apis, String cls, List<String> params)295     private static APIInfo findConstructorInfo(Set<APIInfo> apis, String cls, List<String> params) {
296         for (APIInfo api : apis) {
297             String clsName = api.getPackageName() + "." + api.getClassName();
298             if (api.isConstructor() && clsName.equals(cls)) {
299                 // check params
300                 List<String> paramsFromApi = getParamNames(api);
301                 if (paramsFromApi.size() == params.size()) {
302                     boolean match = true;
303                     for (int i = 0; i < params.size(); i++) {
304                         if (!params.get(i).equals(paramsFromApi.get(i))) {
305                             match = false;
306                             break;
307                         }
308                     }
309                     if (match) {
310                         return api;
311                     }
312                 }
313             }
314         }
315         return null;
316     }
317 
findMethodInfo(Set<APIInfo> apis, String cls, String method, List<String> params)318     private static APIInfo findMethodInfo(Set<APIInfo> apis, String cls, String method, List<String> params) {
319         for (APIInfo api : apis) {
320             String clsName = api.getPackageName() + "." + api.getClassName();
321             if (api.isMethod() && clsName.equals(cls) && api.getName().equals(method)) {
322                 // check params
323                 List<String> paramsFromApi = getParamNames(api);
324                 if (paramsFromApi.size() == params.size()) {
325                     boolean match = true;
326                     for (int i = 0; i < params.size(); i++) {
327                         if (!params.get(i).equals(paramsFromApi.get(i))) {
328                             match = false;
329                             break;
330                         }
331                     }
332                     if (match) {
333                         return api;
334                     }
335                 }
336             }
337         }
338         return null;
339     }
340 
findEnumInfo(Set<APIInfo> apis, String ecls)341     private static APIInfo findEnumInfo(Set<APIInfo> apis, String ecls) {
342         for (APIInfo api : apis) {
343             String clsName = api.getPackageName() + "." + api.getName();
344             if (api.isEnum() && clsName.equals(ecls)) {
345                 return api;
346             }
347         }
348         return null;
349     }
350 
findEnumConstantInfo(Set<APIInfo> apis, String ecls, String econst)351     private static APIInfo findEnumConstantInfo(Set<APIInfo> apis, String ecls, String econst) {
352         for (APIInfo api : apis) {
353             String clsName = api.getPackageName() + "." + api.getClassName();
354             if (api.isEnumConstant() && clsName.equals(ecls) && api.getName().equals(econst)) {
355                 return api;
356             }
357         }
358         return null;
359     }
360 
getParamNames(APIInfo api)361     private static List<String> getParamNames(APIInfo api) {
362         if (!api.isMethod() && !api.isConstructor()) {
363             throw new IllegalArgumentException(api.toString() + " is not a constructor or a method.");
364         }
365 
366         List<String> nameList = new ArrayList<>();
367         String signature = api.getSignature();
368         int start = signature.indexOf('(');
369         int end = signature.indexOf(')');
370 
371         if (start < 0 || end < 0 || start > end) {
372             throw new RuntimeException(api.toString() + " has bad API signature: " + signature);
373         }
374 
375         String paramsSegment = signature.substring(start + 1, end);
376         // erase generic args
377         if (paramsSegment.indexOf('<') >= 0) {
378             StringBuilder buf = new StringBuilder();
379             int genericsNestLevel = 0;
380             for (int i = 0; i < paramsSegment.length(); i++) {
381                 char c = paramsSegment.charAt(i);
382                 if (genericsNestLevel > 0) {
383                     if (c == '<') {
384                         genericsNestLevel++;
385                     } else if (c == '>') {
386                         genericsNestLevel--;
387                     }
388                 } else {
389                     if (c == '<') {
390                         genericsNestLevel++;
391                     } else {
392                         buf.append(c);
393                     }
394                 }
395             }
396             paramsSegment = buf.toString();
397         }
398 
399         if (paramsSegment.length() > 0) {
400             String[] params = paramsSegment.split("\\s*,\\s*");
401             for (String p : params) {
402                 if (p.endsWith("...")) {
403                     // varargs to array
404                     p = p.substring(0, p.length() - 3) + "[]";
405                 }
406                 nameList.add(p);
407             }
408         }
409 
410         return nameList;
411     }
412 
getParamNames(Constructor<?> ctor)413     private static List<String> getParamNames(Constructor<?> ctor) {
414         return toTypeNameList(ctor.getGenericParameterTypes());
415     }
416 
getParamNames(Method method)417     private static List<String> getParamNames(Method method) {
418         return toTypeNameList(method.getGenericParameterTypes());
419     }
420 
421     private static final String[] PRIMITIVES = { "byte", "short", "int", "long", "float", "double", "boolean", "char" };
422     private static char[] PRIMITIVE_SIGNATURES = { 'B', 'S', 'I', 'J', 'F', 'D', 'Z', 'C' };
423 
toTypeNameList(Type[] types)424     private static List<String> toTypeNameList(Type[] types) {
425         List<String> nameList = new ArrayList<>();
426 
427         for (Type t : types) {
428             StringBuilder s = new StringBuilder();
429             if (t instanceof ParameterizedType) {
430                 // throw away generics parameters
431                 ParameterizedType prdType = (ParameterizedType) t;
432                 Class<?> rawType = (Class<?>) prdType.getRawType();
433                 s.append(rawType.getCanonicalName());
434             } else if (t instanceof WildcardType) {
435                 // we don't need to worry about WildcardType,
436                 // because this tool erases generics parameters
437                 // for comparing method/constructor parameters
438                 throw new RuntimeException("WildcardType not supported by this tool");
439             } else if (t instanceof TypeVariable) {
440                 // this tool does not try to resolve actual parameter
441                 // type - for example, "<T extends Object> void foo(T in)"
442                 // this tool just use the type variable "T" for API signature
443                 // comparison. This is actually not perfect, but should be
444                 // sufficient for our purpose.
445                 TypeVariable<?> tVar = (TypeVariable<?>) t;
446                 s.append(tVar.getName());
447             } else if (t instanceof GenericArrayType) {
448                 // same as TypeVariable. "T[]" is sufficient enough.
449                 GenericArrayType tGenArray = (GenericArrayType) t;
450                 s.append(tGenArray.toString());
451             } else if (t instanceof Class) {
452                 Class<?> tClass = (Class<?>) t;
453                 String tName = tClass.getCanonicalName();
454 
455                 if (tName.charAt(0) == '[') {
456                     // Array type
457                     int idx = 0;
458                     for (; idx < tName.length(); idx++) {
459                         if (tName.charAt(idx) != '[') {
460                             break;
461                         }
462                     }
463                     int dimension = idx;
464                     char sigChar = tName.charAt(dimension);
465 
466                     String elemType = null;
467                     if (sigChar == 'L') {
468                         // class
469                         elemType = tName.substring(dimension + 1, tName.length() - 1);
470                     } else {
471                         // primitive
472                         for (int i = 0; i < PRIMITIVE_SIGNATURES.length; i++) {
473                             if (sigChar == PRIMITIVE_SIGNATURES[i]) {
474                                 elemType = PRIMITIVES[i];
475                                 break;
476                             }
477                         }
478                     }
479 
480                     if (elemType == null) {
481                         throw new RuntimeException("Unexpected array type: " + tName);
482                     }
483 
484                     s.append(elemType);
485                     for (int i = 0; i < dimension; i++) {
486                         s.append("[]");
487                     }
488                 } else {
489                     s.append(tName);
490                 }
491             } else {
492                 throw new IllegalArgumentException("Unknown type: " + t);
493             }
494 
495             nameList.add(s.toString());
496         }
497 
498         return nameList;
499     }
500 
formatParams(List<String> paramNames)501     private static String formatParams(List<String> paramNames) {
502         StringBuilder buf = new StringBuilder("(");
503         boolean isFirst = true;
504         for (String p : paramNames) {
505             if (isFirst) {
506                 isFirst = false;
507             } else {
508                 buf.append(", ");
509             }
510             buf.append(p);
511         }
512         buf.append(")");
513 
514         return buf.toString();
515     }
516 }
517