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