diff options
author | Guang Zhu <guangzhu@google.com> | 2015-04-24 16:45:42 -0700 |
---|---|---|
committer | Guang Zhu <guangzhu@google.com> | 2015-04-27 17:01:03 +0000 |
commit | f9b7c1bf84c3d4eeb7b99af30e4ffe159c9fdaee (patch) | |
tree | 7311c76d32d5765445e9e389ed7c5d596438c263 | |
parent | 3061ae5673f1f6c3e028af037134fef09eb68ef8 (diff) | |
download | android_external_doclava-f9b7c1bf84c3d4eeb7b99af30e4ffe159c9fdaee.tar.gz android_external_doclava-f9b7c1bf84c3d4eeb7b99af30e4ffe159c9fdaee.tar.bz2 android_external_doclava-f9b7c1bf84c3d4eeb7b99af30e4ffe159c9fdaee.zip |
add new option to ApiCheck to look for new API methods
When 'newapi' option is needed, ApiCheck will be in "diff" mode:
* when classes are checked for consistency, newly added methods
and constructors are kept in lists
* when packages are checked for consistency, newly added classes
and classes with new API methods are kept in list
* when APIs are checked for consistency, newly added packages
and pakages with new/modified classes are added
* the accumulated deltas are then exported in XML format
Change-Id: I3fed989e2836109e334c0e665639190196f14f4c
-rw-r--r-- | src/com/google/doclava/ClassInfo.java | 17 | ||||
-rw-r--r-- | src/com/google/doclava/PackageInfo.java | 84 | ||||
-rw-r--r-- | src/com/google/doclava/apicheck/ApiCheck.java | 42 | ||||
-rw-r--r-- | src/com/google/doclava/apicheck/ApiInfo.java | 27 |
4 files changed, 167 insertions, 3 deletions
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java index 8dde5c6..374b7b6 100644 --- a/src/com/google/doclava/ClassInfo.java +++ b/src/com/google/doclava/ClassInfo.java @@ -2045,7 +2045,12 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco } public boolean isConsistent(ClassInfo cl) { + return isConsistent(cl, null, null); + } + + public boolean isConsistent(ClassInfo cl, List<MethodInfo> newCtors, List<MethodInfo> newMethods) { boolean consistent = true; + boolean diffMode = (newCtors != null) && (newMethods != null); if (isInterface() != cl.isInterface()) { Errors.error(Errors.CHANGED_CLASS, cl.position(), "Class " + cl.qualifiedName() @@ -2100,10 +2105,16 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco mi.isAbstract() != mInfo.isAbstract()) { Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public method " + mInfo.qualifiedName()); + if (diffMode) { + newMethods.add(mInfo); + } consistent = false; } } } + if (diffMode) { + Collections.sort(newMethods, MethodInfo.comparator); + } for (MethodInfo mInfo : mApiCheckConstructors.values()) { if (cl.mApiCheckConstructors.containsKey(mInfo.getHashableName())) { @@ -2120,9 +2131,15 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco if (!mApiCheckConstructors.containsKey(mInfo.getHashableName())) { Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public constructor " + mInfo.prettySignature()); + if (diffMode) { + newCtors.add(mInfo); + } consistent = false; } } + if (diffMode) { + Collections.sort(newCtors, MethodInfo.comparator); + } for (FieldInfo mInfo : mApiCheckFields.values()) { if (cl.mApiCheckFields.containsKey(mInfo.name())) { diff --git a/src/com/google/doclava/PackageInfo.java b/src/com/google/doclava/PackageInfo.java index 02beaf7..12f18b3 100644 --- a/src/com/google/doclava/PackageInfo.java +++ b/src/com/google/doclava/PackageInfo.java @@ -18,8 +18,8 @@ package com.google.doclava; import com.google.doclava.apicheck.ApiInfo; import com.google.clearsilver.jsilver.data.Data; - import com.sun.javadoc.*; + import java.util.*; public class PackageInfo extends DocInfo implements ContainerInfo { @@ -395,12 +395,85 @@ public class PackageInfo extends DocInfo implements ContainerInfo { } public boolean isConsistent(PackageInfo pInfo) { + return isConsistent(pInfo, null); + } + + /** + * Creates the delta class by copying class signatures from original, but use provided list of + * constructors and methods. + */ + private ClassInfo createDeltaClass(ClassInfo original, + ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods) { + ArrayList<FieldInfo> emptyFields = new ArrayList<>(); + ArrayList<ClassInfo> emptyClasses = new ArrayList<>(); + ArrayList<TypeInfo> emptyTypes = new ArrayList<>(); + ArrayList<MethodInfo> emptyMethods = new ArrayList<>(); + ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(), + original.isPublic(), original.isProtected(), original.isPackagePrivate(), + original.isPrivate(), original.isStatic(), original.isInterface(), + original.isAbstract(), original.isOrdinaryClass(), + original.isException(), original.isError(), original.isEnum(), original.isAnnotation(), + original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(), + original.qualifiedTypeName(), original.isPrimitive()); + ArrayList<ClassInfo> interfaces = original.interfaces(); + // avoid providing null to init method, replace with empty array list when needed + if (interfaces == null) { + interfaces = emptyClasses; + } + ArrayList<TypeInfo> interfaceTypes = original.interfaceTypes(); + if (interfaceTypes == null) { + interfaceTypes = emptyTypes; + } + ArrayList<ClassInfo> innerClasses = original.innerClasses(); + if (innerClasses == null) { + innerClasses = emptyClasses; + } + ArrayList<MethodInfo> annotationElements = original.annotationElements(); + if (annotationElements == null) { + annotationElements = emptyMethods; + } + ArrayList<AnnotationInstanceInfo> annotations = original.annotations(); + if (annotations == null) { + annotations = new ArrayList<>(); + } + ret.init(original.type(), interfaces, interfaceTypes, innerClasses, + constructors, methods, annotationElements, + emptyFields /* fields */, emptyFields /* enum */, + original.containingPackage(), original.containingClass(), original.superclass(), + original.superclassType(), annotations); + return ret; + } + + /** + * Check if packages are consistent, also record class deltas. + * <p> + * <ul>class deltas are: + * <li>brand new classes that are not present in current package + * <li>stripped existing classes stripped where only newly added methods are kept + * @param pInfo + * @param clsInfoDiff + * @return + */ + public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff) { boolean consistent = true; + boolean diffMode = clsInfoDiff != null; for (ClassInfo cInfo : mClasses.values()) { + ArrayList<MethodInfo> newClsApis = null; + ArrayList<MethodInfo> newClsCtors = null; if (pInfo.mClasses.containsKey(cInfo.name())) { - if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()))) { + if (diffMode) { + newClsApis = new ArrayList<>(); + newClsCtors = new ArrayList<>(); + } + if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) { consistent = false; } + // if we are in diff mode, add class to list if there's new ctor or new apis + if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) { + // generate a "delta" class with only added methods and constructors, but no fields etc + ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis); + clsInfoDiff.add(deltaClsInfo); + } } else { Errors.error(Errors.REMOVED_CLASS, cInfo.position(), "Removed public class " + cInfo.qualifiedName()); @@ -412,8 +485,15 @@ public class PackageInfo extends DocInfo implements ContainerInfo { Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name() + " to package " + pInfo.name()); consistent = false; + // brand new class, add everything as is + if (diffMode) { + clsInfoDiff.add(cInfo); + } } } + if (diffMode) { + Collections.sort(clsInfoDiff, ClassInfo.comparator); + } return consistent; } } diff --git a/src/com/google/doclava/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java index 28d7ce0..521ac52 100644 --- a/src/com/google/doclava/apicheck/ApiCheck.java +++ b/src/com/google/doclava/apicheck/ApiCheck.java @@ -23,10 +23,12 @@ import java.io.InputStream; import java.io.PrintStream; import java.net.URL; import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.Stack; import com.google.doclava.Errors; +import com.google.doclava.PackageInfo; import com.google.doclava.Errors.ErrorMessage; import com.google.doclava.Stubs; @@ -62,6 +64,9 @@ public class ApiCheck { System.exit(convertToApi(originalArgs[1], originalArgs[2])); } else if (originalArgs.length == 3 && "-convert2xml".equals(originalArgs[0])) { System.exit(convertToXml(originalArgs[1], originalArgs[2])); + } else if (originalArgs.length == 4 && "-new_api".equals(originalArgs[0])) { + // command syntax: -new_api oldapi.txt newapi.txt diff.xml + System.exit(newApi(originalArgs[1], originalArgs[2], originalArgs[3])); } else { ApiCheck acheck = new ApiCheck(); Report report = acheck.checkApi(originalArgs); @@ -270,4 +275,41 @@ public class ApiCheck { return 0; } + /** + * Generates a "diff": where new API is trimmed down by removing existing methods found in old API + * @param origApiPath path to old API text file + * @param newApiPath path to new API text file + * @param outputPath output XML path for the generated diff + * @return + */ + static int newApi(String origApiPath, String newApiPath, String outputPath) { + ApiInfo origApi, newApi; + try { + origApi = parseApi(origApiPath); + } catch (ApiParseException e) { + e.printStackTrace(); + System.err.println("Error parsing API: " + origApiPath); + return 1; + } + try { + newApi = parseApi(newApiPath); + } catch (ApiParseException e) { + e.printStackTrace(); + System.err.println("Error parsing API: " + newApiPath); + return 1; + } + List<PackageInfo> pkgInfoDiff = new ArrayList<>(); + if (!origApi.isConsistent(newApi, pkgInfoDiff)) { + PrintStream apiWriter = null; + try { + apiWriter = new PrintStream(outputPath); + } catch (FileNotFoundException ex) { + System.err.println("can't open file: " + outputPath); + } + Stubs.writeXml(apiWriter, pkgInfoDiff); + } else { + System.err.println("No API change detected, not generating diff."); + } + return 0; + } } diff --git a/src/com/google/doclava/apicheck/ApiInfo.java b/src/com/google/doclava/apicheck/ApiInfo.java index 711a9f4..148da35 100644 --- a/src/com/google/doclava/apicheck/ApiInfo.java +++ b/src/com/google/doclava/apicheck/ApiInfo.java @@ -19,8 +19,11 @@ package com.google.doclava.apicheck; import com.google.doclava.ClassInfo; import com.google.doclava.Errors; import com.google.doclava.PackageInfo; + import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; public class ApiInfo { @@ -60,12 +63,28 @@ public class ApiInfo { * Checks to see if this api is consistent with a newer version. */ public boolean isConsistent(ApiInfo otherApi) { + return isConsistent(otherApi, null); + } + + public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff) { boolean consistent = true; + boolean diffMode = pkgInfoDiff != null; for (PackageInfo pInfo : mPackages.values()) { + List<ClassInfo> newClsApis = null; if (otherApi.getPackages().containsKey(pInfo.name())) { - if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()))) { + if (diffMode) { + newClsApis = new ArrayList<>(); + } + if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), newClsApis)) { consistent = false; } + if (diffMode && !newClsApis.isEmpty()) { + PackageInfo info = new PackageInfo(pInfo.name(), pInfo.position()); + for (ClassInfo cInfo : newClsApis) { + info.addClass(cInfo); + } + pkgInfoDiff.add(info); + } } else { Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(), "Removed package " + pInfo.name()); consistent = false; @@ -75,8 +94,14 @@ public class ApiInfo { if (!mPackages.containsKey(pInfo.name())) { Errors.error(Errors.ADDED_PACKAGE, pInfo.position(), "Added package " + pInfo.name()); consistent = false; + if (diffMode) { + pkgInfoDiff.add(pInfo); + } } } + if (diffMode) { + Collections.sort(pkgInfoDiff, PackageInfo.comparator); + } return consistent; } |