Home | History | Annotate | Download | only in docs
      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 
     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 
     45     public DeprecatedAPIChecker(Set<APIInfo> apiInfoSet, PrintWriter pw) {
     46         this.apiInfoSet = apiInfoSet;
     47         this.pw = pw;
     48     }
     49 
     50     public int errorCount() {
     51         return errCount;
     52     }
     53 
     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 
     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 
    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 
    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 
    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
    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 
    257     private static boolean isAPIDeprecated(APIInfo api) {
    258         return api.isDeprecated() || api.isInternal() || api.isObsolete();
    259     }
    260 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    397     private static List<String> getParamNames(Constructor<?> ctor) {
    398         return toTypeNameList(ctor.getGenericParameterTypes());
    399     }
    400 
    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 
    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 
    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