--- /dev/null
+/*\r
+ *******************************************************************************\r
+ * Copyright (C) 2014, International Business Machines Corporation and *\r
+ * others. All Rights Reserved. *\r
+ *******************************************************************************\r
+ */\r
+package com.ibm.icu.dev.tool.docs;\r
+\r
+import java.io.File;\r
+import java.io.PrintWriter;\r
+import java.lang.reflect.Constructor;\r
+import java.lang.reflect.Field;\r
+import java.lang.reflect.GenericArrayType;\r
+import java.lang.reflect.Method;\r
+import java.lang.reflect.Modifier;\r
+import java.lang.reflect.ParameterizedType;\r
+import java.lang.reflect.Type;\r
+import java.lang.reflect.TypeVariable;\r
+import java.lang.reflect.WildcardType;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Map.Entry;\r
+import java.util.Set;\r
+import java.util.TreeMap;\r
+\r
+public class DeprecatedAPIChecker {\r
+\r
+ public static void main(String[] args) {\r
+ if (args.length != 1) {\r
+ System.err.println("Illegal command argument. Specify the API signature file path.");\r
+ }\r
+ // Load the ICU4J API signature file\r
+ Set<APIInfo> apiInfoSet = APIData.read(new File(args[0]), true).getAPIInfoSet();\r
+\r
+ DeprecatedAPIChecker checker = new DeprecatedAPIChecker(apiInfoSet, new PrintWriter(System.err, true));\r
+ checker.checkDeprecated();\r
+ System.exit(checker.errCount);\r
+ }\r
+\r
+ private int errCount = 0;\r
+ private Set<APIInfo> apiInfoSet;\r
+ private PrintWriter pw;\r
+\r
+ public DeprecatedAPIChecker(Set<APIInfo> apiInfoSet, PrintWriter pw) {\r
+ this.apiInfoSet = apiInfoSet;\r
+ this.pw = pw;\r
+ }\r
+\r
+ public int errorCount() {\r
+ return errCount;\r
+ }\r
+\r
+ public void checkDeprecated() {\r
+ // Gather API class/enum names and its names that can be\r
+ // used for Class.forName()\r
+ Map<String, String> apiClassNameMap = new TreeMap<String, String>();\r
+ for (APIInfo api : apiInfoSet) {\r
+ if (!api.isPublic() && !api.isProtected()) {\r
+ continue;\r
+ }\r
+ if (!api.isClass() && !api.isEnum()) {\r
+ continue;\r
+ }\r
+ String packageName = api.getPackageName();\r
+ String className = api.getName();\r
+\r
+ // Replacing separator for nested class/enum (replacing '.' with\r
+ // '$'), so we can use the name for Class.forName(String)\r
+ String classNamePath = className.contains(".") ? className.replace('.', '$') : className;\r
+\r
+ apiClassNameMap.put(packageName + "." + classNamePath, packageName + "." + className);\r
+ }\r
+\r
+ // Walk through API classes using reflection\r
+ for (Entry<String, String> classEntry : apiClassNameMap.entrySet()) {\r
+ String classNamePath = classEntry.getKey();\r
+ try {\r
+ Class<?> cls = Class.forName(classNamePath);\r
+ if (cls.isEnum()) {\r
+ checkEnum(cls, apiClassNameMap);\r
+ } else {\r
+ checkClass(cls, apiClassNameMap);\r
+ }\r
+ } catch (ClassNotFoundException e) {\r
+ pw.println("## Error ## Class " + classNamePath + " is not found.");\r
+ errCount++;\r
+ }\r
+ }\r
+ }\r
+\r
+ private void checkClass(Class<?> cls, Map<String, String> clsNameMap) {\r
+ assert !cls.isEnum();\r
+\r
+ String clsPath = cls.getName();\r
+ String clsName = clsNameMap.get(clsPath);\r
+ APIInfo api = null;\r
+\r
+ if (clsName != null) {\r
+ api = findClassInfo(apiInfoSet, clsName);\r
+ }\r
+ if (api == null) {\r
+ pw.println("## Error ## Class " + clsName + " is not found in the API signature data.");\r
+ errCount++;\r
+ }\r
+\r
+ // check class\r
+ compareDeprecated(isAPIDeprecated(api), cls.isAnnotationPresent(Deprecated.class), clsName, null, "Class");\r
+\r
+ // check fields\r
+ for (Field f : cls.getDeclaredFields()) {\r
+ if (!isPublicOrProtected(f.getModifiers())) {\r
+ continue;\r
+ }\r
+\r
+ String fName = f.getName();\r
+ api = findFieldInfo(apiInfoSet, clsName, fName);\r
+ if (api == null) {\r
+ pw.println("## Error ## Field " + clsName + "." + fName + " is not found in the API signature data.");\r
+ errCount++;\r
+ continue;\r
+ }\r
+\r
+ compareDeprecated(isAPIDeprecated(api), f.isAnnotationPresent(Deprecated.class), clsName, fName, "Field");\r
+ }\r
+\r
+ // check constructors\r
+ for (Constructor<?> ctor : cls.getDeclaredConstructors()) {\r
+ if (!isPublicOrProtected(ctor.getModifiers())) {\r
+ continue;\r
+ }\r
+\r
+ List<String> paramNames = getParamNames(ctor);\r
+ api = findConstructorInfo(apiInfoSet, clsName, paramNames);\r
+\r
+ if (api == null) {\r
+ pw.println("## Error ## Constructor " + clsName + formatParams(paramNames)\r
+ + " is not found in the API signature data.");\r
+ errCount++;\r
+ continue;\r
+ }\r
+\r
+ compareDeprecated(isAPIDeprecated(api), ctor.isAnnotationPresent(Deprecated.class), clsName,\r
+ api.getClassName() + formatParams(paramNames), "Constructor");\r
+ }\r
+\r
+ // check methods\r
+ for (Method mtd : cls.getDeclaredMethods()) {\r
+ // Note: We exclude synthetic method.\r
+ if (!isPublicOrProtected(mtd.getModifiers()) || mtd.isSynthetic()) {\r
+ continue;\r
+ }\r
+\r
+ String mtdName = mtd.getName();\r
+ List<String> paramNames = getParamNames(mtd);\r
+ api = findMethodInfo(apiInfoSet, clsName, mtdName, paramNames);\r
+\r
+ if (api == null) {\r
+ pw.println("## Error ## Method " + clsName + "#" + mtdName + formatParams(paramNames)\r
+ + " is not found in the API signature data.");\r
+ errCount++;\r
+ continue;\r
+ }\r
+\r
+ compareDeprecated(isAPIDeprecated(api), mtd.isAnnotationPresent(Deprecated.class), clsName, mtdName\r
+ + formatParams(paramNames), "Method");\r
+\r
+ }\r
+ }\r
+\r
+ private void checkEnum(Class<?> cls, Map<String, String> clsNameMap) {\r
+ assert cls.isEnum();\r
+\r
+ String enumPath = cls.getName();\r
+ String enumName = clsNameMap.get(enumPath);\r
+ APIInfo api = null;\r
+\r
+ if (enumName != null) {\r
+ api = findEnumInfo(apiInfoSet, enumName);\r
+ }\r
+ if (api == null) {\r
+ pw.println("## Error ## Enum " + enumName + " is not found in the API signature data.");\r
+ errCount++;\r
+ }\r
+\r
+ // check enum\r
+ compareDeprecated(isAPIDeprecated(api), cls.isAnnotationPresent(Deprecated.class), enumName, null, "Enum");\r
+\r
+ // check enum constants\r
+ for (Field ec : cls.getDeclaredFields()) {\r
+ if (!ec.isEnumConstant()) {\r
+ continue;\r
+ }\r
+ String ecName = ec.getName();\r
+ api = findEnumConstantInfo(apiInfoSet, enumName, ecName);\r
+ if (api == null) {\r
+ pw.println("## Error ## Enum constant " + enumName + "." + ecName\r
+ + " is not found in the API signature data.");\r
+ errCount++;\r
+ continue;\r
+ }\r
+\r
+ compareDeprecated(isAPIDeprecated(api), ec.isAnnotationPresent(Deprecated.class), enumName, ecName,\r
+ "Enum Constant");\r
+ }\r
+ }\r
+\r
+ private void compareDeprecated(boolean depTag, boolean depAnt, String cls, String name, String type) {\r
+ if (depTag != depAnt) {\r
+ String apiName = cls;\r
+ if (name != null) {\r
+ apiName += "." + name;\r
+ }\r
+ if (depTag) {\r
+ pw.println("No @Deprecated annotation: [" + type + "] " + apiName);\r
+ } else {\r
+ pw.println("No @deprecated JavaDoc tag: [" + type + "] " + apiName);\r
+ }\r
+ errCount++;\r
+ }\r
+ }\r
+\r
+ private static boolean isPublicOrProtected(int modifier) {\r
+ return ((modifier & Modifier.PUBLIC) != 0) || ((modifier & Modifier.PROTECTED) != 0);\r
+ }\r
+\r
+ private static boolean isAPIDeprecated(APIInfo api) {\r
+ return api.isDeprecated() || api.isInternal() || api.isObsolete();\r
+ }\r
+\r
+ private static APIInfo findClassInfo(Set<APIInfo> apis, String cls) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getName();\r
+ if (api.isClass() && clsName.equals(cls)) {\r
+ return api;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static APIInfo findFieldInfo(Set<APIInfo> apis, String cls, String field) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getClassName();\r
+ if (api.isField() && clsName.equals(cls) && api.getName().equals(field)) {\r
+ return api;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static APIInfo findConstructorInfo(Set<APIInfo> apis, String cls, List<String> params) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getClassName();\r
+ if (api.isConstructor() && clsName.equals(cls)) {\r
+ // check params\r
+ List<String> paramsFromApi = getParamNames(api);\r
+ if (paramsFromApi.size() == params.size()) {\r
+ boolean match = true;\r
+ for (int i = 0; i < params.size(); i++) {\r
+ if (!params.get(i).equals(paramsFromApi.get(i))) {\r
+ match = false;\r
+ break;\r
+ }\r
+ }\r
+ if (match) {\r
+ return api;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static APIInfo findMethodInfo(Set<APIInfo> apis, String cls, String method, List<String> params) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getClassName();\r
+ if (api.isMethod() && clsName.equals(cls) && api.getName().equals(method)) {\r
+ // check params\r
+ List<String> paramsFromApi = getParamNames(api);\r
+ if (paramsFromApi.size() == params.size()) {\r
+ boolean match = true;\r
+ for (int i = 0; i < params.size(); i++) {\r
+ if (!params.get(i).equals(paramsFromApi.get(i))) {\r
+ match = false;\r
+ break;\r
+ }\r
+ }\r
+ if (match) {\r
+ return api;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static APIInfo findEnumInfo(Set<APIInfo> apis, String ecls) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getName();\r
+ if (api.isEnum() && clsName.equals(ecls)) {\r
+ return api;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static APIInfo findEnumConstantInfo(Set<APIInfo> apis, String ecls, String econst) {\r
+ for (APIInfo api : apis) {\r
+ String clsName = api.getPackageName() + "." + api.getClassName();\r
+ if (api.isEnumConstant() && clsName.equals(ecls) && api.getName().equals(econst)) {\r
+ return api;\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+ private static List<String> getParamNames(APIInfo api) {\r
+ if (!api.isMethod() && !api.isConstructor()) {\r
+ throw new IllegalArgumentException(api.toString() + " is not a constructor or a method.");\r
+ }\r
+\r
+ List<String> nameList = new ArrayList<String>();\r
+ String signature = api.getSignature();\r
+ int start = signature.indexOf('(');\r
+ int end = signature.indexOf(')');\r
+\r
+ if (start < 0 || end < 0 || start > end) {\r
+ throw new RuntimeException(api.toString() + " has bad API signature: " + signature);\r
+ }\r
+\r
+ String paramsSegment = signature.substring(start + 1, end);\r
+ // erase generic args\r
+ if (paramsSegment.indexOf('<') >= 0) {\r
+ StringBuilder buf = new StringBuilder();\r
+ boolean inGenericsParams = false;\r
+ for (int i = 0; i < paramsSegment.length(); i++) {\r
+ char c = paramsSegment.charAt(i);\r
+ if (inGenericsParams) {\r
+ if (c == '>') {\r
+ inGenericsParams = false;\r
+ }\r
+ } else {\r
+ if (c == '<') {\r
+ inGenericsParams = true;\r
+ } else {\r
+ buf.append(c);\r
+ }\r
+ }\r
+ }\r
+ paramsSegment = buf.toString();\r
+ }\r
+\r
+ if (!paramsSegment.isEmpty()) {\r
+ String[] params = paramsSegment.split("\\s*,\\s*");\r
+ for (String p : params) {\r
+ if (p.endsWith("...")) {\r
+ // varargs to array\r
+ p = p.substring(0, p.length() - 3) + "[]";\r
+ }\r
+ nameList.add(p);\r
+ }\r
+ }\r
+\r
+ return nameList;\r
+ }\r
+\r
+ private static List<String> getParamNames(Constructor<?> ctor) {\r
+ return toTypeNameList(ctor.getGenericParameterTypes());\r
+ }\r
+\r
+ private static List<String> getParamNames(Method method) {\r
+ return toTypeNameList(method.getGenericParameterTypes());\r
+ }\r
+\r
+ private static final String[] PRIMITIVES = { "byte", "short", "int", "long", "float", "double", "boolean", "char" };\r
+ private static char[] PRIMITIVE_SIGNATURES = { 'B', 'S', 'I', 'J', 'F', 'D', 'Z', 'C' };\r
+\r
+ private static List<String> toTypeNameList(Type[] types) {\r
+ List<String> nameList = new ArrayList<String>();\r
+\r
+ for (Type t : types) {\r
+ StringBuilder s = new StringBuilder();\r
+ if (t instanceof ParameterizedType) {\r
+ // throw away generics parameters\r
+ ParameterizedType prdType = (ParameterizedType) t;\r
+ Class<?> rawType = (Class<?>) prdType.getRawType();\r
+ s.append(rawType.getCanonicalName());\r
+ } else if (t instanceof WildcardType) {\r
+ // we don't need to worry about WildcardType,\r
+ // because this tool erases generics parameters\r
+ // for comparing method/constructor parameters\r
+ throw new RuntimeException("WildcardType not supported by this tool");\r
+ } else if (t instanceof TypeVariable) {\r
+ // this tool does not try to resolve actual parameter\r
+ // type - for example, "<T extends Object> void foo(T in)"\r
+ // this tool just use the type variable "T" for API signature\r
+ // comparison. This is actually not perfect, but should be\r
+ // sufficient for our purpose.\r
+ TypeVariable<?> tVar = (TypeVariable<?>) t;\r
+ s.append(tVar.getName());\r
+ } else if (t instanceof GenericArrayType) {\r
+ // same as TypeVariable. "T[]" is sufficient enough.\r
+ GenericArrayType tGenArray = (GenericArrayType) t;\r
+ s.append(tGenArray.toString());\r
+ } else if (t instanceof Class) {\r
+ Class<?> tClass = (Class<?>) t;\r
+ String tName = tClass.getCanonicalName();\r
+\r
+ if (tName.charAt(0) == '[') {\r
+ // Array type\r
+ int idx = 0;\r
+ for (; idx < tName.length(); idx++) {\r
+ if (tName.charAt(idx) != '[') {\r
+ break;\r
+ }\r
+ }\r
+ int dimension = idx;\r
+ char sigChar = tName.charAt(dimension);\r
+\r
+ String elemType = null;\r
+ if (sigChar == 'L') {\r
+ // class\r
+ elemType = tName.substring(dimension + 1, tName.length() - 1);\r
+ } else {\r
+ // primitive\r
+ for (int i = 0; i < PRIMITIVE_SIGNATURES.length; i++) {\r
+ if (sigChar == PRIMITIVE_SIGNATURES[i]) {\r
+ elemType = PRIMITIVES[i];\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (elemType == null) {\r
+ throw new RuntimeException("Unexpected array type: " + tName);\r
+ }\r
+\r
+ s.append(elemType);\r
+ for (int i = 0; i < dimension; i++) {\r
+ s.append("[]");\r
+ }\r
+ } else {\r
+ s.append(tName);\r
+ }\r
+ } else {\r
+ throw new IllegalArgumentException("Unknown type: " + t);\r
+ }\r
+\r
+ nameList.add(s.toString());\r
+ }\r
+\r
+ return nameList;\r
+ }\r
+\r
+ private static String formatParams(List<String> paramNames) {\r
+ StringBuilder buf = new StringBuilder("(");\r
+ boolean isFirst = true;\r
+ for (String p : paramNames) {\r
+ if (isFirst) {\r
+ isFirst = false;\r
+ } else {\r
+ buf.append(", ");\r
+ }\r
+ buf.append(p);\r
+ }\r
+ buf.append(")");\r
+\r
+ return buf.toString();\r
+ }\r
+}\r
/**
*******************************************************************************
- * Copyright (C) 2004-2013, International Business Machines Corporation and *
+ * Copyright (C) 2004-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
// care if we didn't properly document the draft status of
// default constructors for abstract classes.
+ // Update: We mandate a no-arg synthetic constructor with explicit
+ // javadoc comments by the policy. So, we no longer ignore abstract
+ // class's no-arg constructor blindly. -Yoshito 2014-05-21
+
private boolean isAbstractClassDefaultConstructor(ProgramElementDoc doc) {
return doc.isConstructor()
&& doc.containingClass().isAbstract()
&& "()".equals(((ConstructorDoc) doc).signature());
}
+ private static final boolean IGNORE_NO_ARG_ABSTRACT_CTOR = false;
+
private boolean ignore(ProgramElementDoc doc) {
if (doc == null) return true;
if (doc.isPrivate() || doc.isPackagePrivate()) return true;
if (isIgnoredEnumMethod(doc)) {
return true;
}
- if (isAbstractClassDefaultConstructor(doc)) {
+
+ if (IGNORE_NO_ARG_ABSTRACT_CTOR && isAbstractClassDefaultConstructor(doc)) {
return true;
}
private int tagStatus(final ProgramElementDoc doc, String[] version) {
class Result {
+ boolean deprecatedFlag = false;
int res = -1;
void set(int val) {
if (res != -1) {
+ boolean isValid = true;
if (val == APIInfo.STA_DEPRECATED) {
- // ok to have both a 'standard' tag and deprecated
- return;
- } else if (res != APIInfo.STA_DEPRECATED) {
- // if already not deprecated, this is an error
+ // @internal and @obsolete should be always used along with @deprecated.
+ // no change for status
+ isValid = (res == APIInfo.STA_INTERNAL || res == APIInfo.STA_OBSOLETE);
+ deprecatedFlag = true;
+ } else if (val == APIInfo.STA_INTERNAL) {
+ // @deprecated should be always used along with @internal.
+ // update status
+ if (res == APIInfo.STA_DEPRECATED) {
+ res = val; // APIInfo.STA_INTERNAL
+ } else {
+ isValid = false;
+ }
+ } else if (val == APIInfo.STA_OBSOLETE) {
+ // @deprecated should be always used along with @obsolete.
+ // update status
+ if (res == APIInfo.STA_DEPRECATED) {
+ res = val; // APIInfo.STA_OBSOLETE
+ } else {
+ isValid = false;
+ }
+ } else {
+ // two different status tags must not co-exist, except for
+ // following two cases:
+ // 1. @internal and @deprecated
+ // 2. @obsolete and @deprecated
+ isValid = false;
+ }
+ if (!isValid) {
System.err.println("bad doc: " + doc + " both: "
+ APIInfo.getTypeValName(APIInfo.STA, res) + " and: "
+ APIInfo.getTypeValName(APIInfo.STA, val));
return;
}
+ } else {
+ // ok to replace with new tag
+ res = val;
+ if (val == APIInfo.STA_DEPRECATED) {
+ deprecatedFlag = true;
+ }
}
- // ok to replace with new tag
- res = val;
}
int get() {
if (res == -1) {
System.err.println("warning: no tag for " + doc);
return 0;
+ } else if (res == APIInfo.STA_INTERNAL && !deprecatedFlag) {
+ System.err.println("warning: no @deprecated tag for @internal API: " + doc);
}
return res;
}