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