summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuang Zhu <guangzhu@google.com>2015-04-24 16:45:42 -0700
committerGuang Zhu <guangzhu@google.com>2015-04-27 17:01:03 +0000
commitf9b7c1bf84c3d4eeb7b99af30e4ffe159c9fdaee (patch)
tree7311c76d32d5765445e9e389ed7c5d596438c263
parent3061ae5673f1f6c3e028af037134fef09eb68ef8 (diff)
downloadandroid_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.java17
-rw-r--r--src/com/google/doclava/PackageInfo.java84
-rw-r--r--src/com/google/doclava/apicheck/ApiCheck.java42
-rw-r--r--src/com/google/doclava/apicheck/ApiInfo.java27
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;
}