summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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.java84
-rw-r--r--src/com/google/doclava/Stubs.java4
-rw-r--r--src/com/google/doclava/apicheck/ApiCheck.java42
-rw-r--r--src/com/google/doclava/apicheck/ApiInfo.java27
6 files changed, 177 insertions, 5 deletions
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 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/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 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;
}