summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.gradle32
-rw-r--r--src/com/google/doclava/ClassInfo.java22
-rw-r--r--src/com/google/doclava/Errors.java3
-rw-r--r--src/com/google/doclava/PackageInfo.java107
-rw-r--r--src/com/google/doclava/Stubs.java4
-rw-r--r--src/com/google/doclava/apicheck/ApiCheck.java47
-rw-r--r--src/com/google/doclava/apicheck/ApiInfo.java47
7 files changed, 235 insertions, 27 deletions
diff --git a/build.gradle b/build.gradle
index 79d856e..382e10d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,7 +14,7 @@ sourceSets {
String findToolsJar() {
new ByteArrayOutputStream().withStream { os ->
project.exec {
- executable "$rootDir/build/core/find-jdk-tools-jar.sh"
+ executable "../../build/core/find-jdk-tools-jar.sh"
standardOutput = os
}
@@ -22,9 +22,27 @@ String findToolsJar() {
}
}
-dependencies {
- compile files(findToolsJar())
- compile project(path: ':antlr', configuration: 'antlrRuntime')
- compile project(':jsilver')
- compile project(':tagsoup')
-} \ No newline at end of file
+
+if (project.hasProperty("usePrebuilts") && project.usePrebuilts == "true") {
+ repositories {
+ maven { url '../../prebuilts/tools/common/m2/repository' }
+ }
+
+ dependencies {
+ compile files(findToolsJar())
+ compile files('../../prebuilts/misc/common/antlr/antlr-3.4-complete.jar')
+ compile 'com.google.jsilver:jsilver:1.0.0'
+ // TODO add tagsoup to prebuils to fully support building using prebuilts
+ compile project(':tagsoup')
+ // required by jsilver
+ compile 'com.google.guava:guava:15.0'
+ //compile project(path: ':junit', configuration: 'target')
+ }
+} else {
+ dependencies {
+ compile files(findToolsJar())
+ compile project(path: ':antlr', configuration: 'antlrRuntime')
+ compile project(':jsilver')
+ compile project(':tagsoup')
+ }
+}
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 616cf23..8f0199e 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()
@@ -2092,15 +2097,24 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco
/*
* Similarly to the above, do not fail if this "new" method is really an override of an
* existing superclass method.
+ * But we should fail if this is overriding an abstract method, because method's
+ * abstractness affects how users use it. See also Stubs.methodIsOverride().
*/
MethodInfo mi = ClassInfo.overriddenMethod(mInfo, this);
- if (mi == null) {
+ if (mi == null ||
+ mi.isAbstract() != mInfo.isAbstract()) {
Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public method "
+ mInfo.prettyQualifiedSignature());
+ 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())) {
@@ -2117,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.prettyQualifiedSignature());
+ 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/Errors.java b/src/com/google/doclava/Errors.java
index 84df16b..156de66 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -172,6 +172,7 @@ public class Errors {
public static final Error INVALID_CONTENT_TYPE = new Error(119, ERROR);
public static final Error INVALID_SAMPLE_INDEX = new Error(120, ERROR);
public static final Error HIDDEN_TYPE_PARAMETER = new Error(121, HIDDEN);
+ public static final Error PRIVATE_SUPERCLASS = new Error(122, ERROR);
public static final Error[] ERRORS =
{UNRESOLVED_LINK, BAD_INCLUDE_TAG, UNKNOWN_TAG, UNKNOWN_PARAM_TAG_NAME,
@@ -183,7 +184,7 @@ public class Errors {
CHANGED_TRANSIENT, CHANGED_VOLATILE, CHANGED_TYPE, CHANGED_VALUE, CHANGED_SUPERCLASS,
CHANGED_SCOPE, CHANGED_ABSTRACT, CHANGED_THROWS, CHANGED_NATIVE, CHANGED_CLASS,
CHANGED_DEPRECATED, CHANGED_SYNCHRONIZED, ADDED_FINAL_UNINSTANTIABLE, REMOVED_FINAL,
- BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE, HIDDEN_TYPE_PARAMETER};
+ BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE, HIDDEN_TYPE_PARAMETER, PRIVATE_SUPERCLASS};
public static boolean setErrorLevel(int code, int level) {
for (Error e : ERRORS) {
diff --git a/src/com/google/doclava/PackageInfo.java b/src/com/google/doclava/PackageInfo.java
index c0f10da..46b5b8f 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 {
@@ -394,9 +394,89 @@ public class PackageInfo extends DocInfo implements ContainerInfo {
return mClasses;
}
- public boolean isConsistent(PackageInfo pInfo, Collection<String> ignoredClasses) {
+ 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) {
+ return isConsistent(pInfo, clsInfoDiff, null);
+ }
+
+ /**
+ * 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
+ * @param ignoredClasses
+ * @return
+ */
+ public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff,
+ Collection<String> ignoredClasses) {
boolean consistent = true;
+ boolean diffMode = clsInfoDiff != null;
for (ClassInfo cInfo : mClasses.values()) {
+ ArrayList<MethodInfo> newClsApis = null;
+ ArrayList<MethodInfo> newClsCtors = null;
+
// TODO: Add support for matching inner classes (e.g, something like
// example.Type.* should match example.Type.InnerType)
if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
@@ -404,9 +484,19 @@ public class PackageInfo extends DocInfo implements ContainerInfo {
continue;
}
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());
@@ -422,12 +512,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;
}
-
- public boolean isConsistent(PackageInfo pInfo) {
- return isConsistent(pInfo, null);
- }
}
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 647c921..5ae1dd0 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -337,6 +337,10 @@ public class Stubs {
+ " stripped of unavailable superclass " + supr.qualifiedName());
} else {
cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName());
+ if (supr.isPrivate()) {
+ Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
+ + cl.qualifiedName() + " extends private class " + supr.qualifiedName());
+ }
}
}
}
diff --git a/src/com/google/doclava/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java
index 9698c89..47a8b5e 100644
--- a/src/com/google/doclava/apicheck/ApiCheck.java
+++ b/src/com/google/doclava/apicheck/ApiCheck.java
@@ -23,11 +23,13 @@ import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
+import java.util.List;
import java.util.HashSet;
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;
@@ -64,6 +66,10 @@ 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
+ // TODO: Support reading in other options for new_api, such as ignored classes/packages.
+ System.exit(newApi(originalArgs[1], originalArgs[2], originalArgs[3]));
} else {
ApiCheck acheck = new ApiCheck();
Report report = acheck.checkApi(originalArgs);
@@ -138,11 +144,11 @@ public class ApiCheck {
// only run the consistency check if we haven't had XML parse errors
if (!Errors.hadError) {
- oldApi.isConsistent(newApi, ignoredPackages, ignoredClasses);
+ oldApi.isConsistent(newApi, null, ignoredPackages, ignoredClasses);
}
if (!Errors.hadError) {
- oldRemovedApi.isConsistent(newRemovedApi, ignoredPackages, ignoredClasses);
+ oldRemovedApi.isConsistent(newRemovedApi, null, ignoredPackages, ignoredClasses);
}
return new Report(Errors.hadError ? 1 : 0, Errors.getErrors());
@@ -287,4 +293,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 2752f3a..fa51e8b 100644
--- a/src/com/google/doclava/apicheck/ApiInfo.java
+++ b/src/com/google/doclava/apicheck/ApiInfo.java
@@ -19,9 +19,12 @@ 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.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class ApiInfo {
@@ -59,16 +62,31 @@ 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) {
+ return isConsistent(otherApi, pkgInfoDiff, null, null);
+ }
+
+ /**
+ * Checks to see if this api is consistent with a newer version.
*
* @param otherApi the other api to test consistency against
+ * @param pkgInfoDiff
* @param ignoredPackages packages to skip consistency checks (will match by exact name)
* @param ignoredClasses classes to skip consistency checks (will match by exact fully qualified
* name)
*/
- public boolean isConsistent(ApiInfo otherApi,
+ public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff,
Collection<String> ignoredPackages, Collection<String> ignoredClasses) {
boolean consistent = true;
+ boolean diffMode = pkgInfoDiff != null;
for (PackageInfo pInfo : mPackages.values()) {
+ List<ClassInfo> newClsApis = null;
+
// TODO: Add support for matching subpackages (e.g, something like
// test.example.* should match test.example.subpackage, and
// test.example.** should match the above AND test.example.subpackage.more)
@@ -77,9 +95,21 @@ public class ApiInfo {
continue;
}
if (otherApi.getPackages().containsKey(pInfo.name())) {
- if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), ignoredClasses)) {
+ if (diffMode) {
+ newClsApis = new ArrayList<>();
+ }
+ if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), newClsApis, ignoredClasses)) {
consistent = false;
}
+ if (diffMode && !newClsApis.isEmpty()) {
+ PackageInfo info = new PackageInfo(pInfo.name(), pInfo.position());
+ for (ClassInfo cInfo : newClsApis) {
+ if (ignoredClasses == null || !ignoredClasses.contains(cInfo.qualifiedName())) {
+ info.addClass(cInfo);
+ }
+ }
+ pkgInfoDiff.add(info);
+ }
} else {
Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(), "Removed package " + pInfo.name());
consistent = false;
@@ -93,18 +123,17 @@ 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;
}
- /**
- * Checks to see if this api is consistent with a newer version.
- */
- public boolean isConsistent(ApiInfo otherApi) {
- return isConsistent(otherApi, null, null);
- }
-
public HashMap<String, PackageInfo> getPackages() {
return mPackages;
}