summaryrefslogtreecommitdiffstats
path: root/src/com/google/doclava/Stubs.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/google/doclava/Stubs.java')
-rw-r--r--src/com/google/doclava/Stubs.java145
1 files changed, 96 insertions, 49 deletions
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index fbcff97..59ca505 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -18,6 +18,7 @@ package com.google.doclava;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -27,6 +28,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -43,7 +46,9 @@ import java.util.stream.Collectors;
public class Stubs {
public static void writeStubsAndApi(String stubsDir, String apiFile, String keepListFile,
- String removedApiFile, String exactApiFile, HashSet<String> stubPackages) {
+ String removedApiFile, String exactApiFile, HashSet<String> stubPackages,
+ HashSet<String> stubImportPackages,
+ boolean stubSourceOnly) {
// figure out which classes we need
final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
ClassInfo[] all = Converter.allClasses();
@@ -94,11 +99,11 @@ public class Stubs {
"Cannot open file for write");
}
}
- // If a class is public or protected, not hidden, and marked as included,
+ // If a class is public or protected, not hidden, not imported and marked as included,
// then we can't strip it
for (ClassInfo cl : all) {
if (cl.checkLevel() && cl.isIncluded()) {
- cantStripThis(cl, notStrippable, "0:0");
+ cantStripThis(cl, notStrippable, "0:0", stubImportPackages);
}
}
@@ -117,7 +122,7 @@ public class Stubs {
+ m.name() + " is deprecated");
}
- ClassInfo hiddenClass = findHiddenClasses(m.returnType());
+ ClassInfo hiddenClass = findHiddenClasses(m.returnType(), stubImportPackages);
if (null != hiddenClass) {
if (hiddenClass.qualifiedName() == m.returnType().asClassInfo().qualifiedName()) {
// Return type is hidden
@@ -134,7 +139,7 @@ public class Stubs {
for (ParameterInfo p : m.parameters()) {
TypeInfo t = p.type();
if (!t.isPrimitive()) {
- hiddenClass = findHiddenClasses(t);
+ hiddenClass = findHiddenClasses(t, stubImportPackages);
if (null != hiddenClass) {
if (hiddenClass.qualifiedName() == t.asClassInfo().qualifiedName()) {
// Parameter type is hidden
@@ -187,6 +192,9 @@ public class Stubs {
final HashSet<Pattern> stubPackageWildcards = extractWildcards(stubPackages);
for (ClassInfo cl : notStrippable) {
if (!cl.isDocOnly()) {
+ if (stubSourceOnly && !Files.exists(Paths.get(cl.position().file))) {
+ continue;
+ }
if (shouldWriteStub(cl.containingPackage().name(), stubPackages, stubPackageWildcards)) {
// write out the stubs
if (stubsDir != null) {
@@ -282,15 +290,34 @@ public class Stubs {
return wildcards;
}
- private static ClassInfo findHiddenClasses(TypeInfo ti) {
+ /**
+ * Find references to hidden classes.
+ *
+ * <p>This finds hidden classes that are used by public parts of the API in order to ensure the
+ * API is self consistent and does not reference classes that are not included in
+ * the stubs. Any such references cause an error to be reported.
+ *
+ * <p>A reference to an imported class is not treated as an error, even though imported classes
+ * are hidden from the stub generation. That is because imported classes are, by definition,
+ * excluded from the set of classes for which stubs are required.
+ *
+ * @param ti the type information to examine for references to hidden classes.
+ * @param stubImportPackages the possibly null set of imported package names.
+ * @return a reference to a hidden class or null if there are none
+ */
+ private static ClassInfo findHiddenClasses(TypeInfo ti, HashSet<String> stubImportPackages) {
ClassInfo ci = ti.asClassInfo();
if (ci == null) return null;
+ if (stubImportPackages != null
+ && stubImportPackages.contains(ci.containingPackage().qualifiedName())) {
+ return null;
+ }
if (ci.isHiddenOrRemoved()) return ci;
if (ti.typeArguments() != null) {
for (TypeInfo tii : ti.typeArguments()) {
// Avoid infinite recursion in the case of Foo<T extends Foo>
if (tii.qualifiedTypeName() != ti.qualifiedTypeName()) {
- ClassInfo hiddenClass = findHiddenClasses(tii);
+ ClassInfo hiddenClass = findHiddenClasses(tii, stubImportPackages);
if (hiddenClass != null) return hiddenClass;
}
}
@@ -298,7 +325,14 @@ public class Stubs {
return null;
}
- public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
+ public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why,
+ HashSet<String> stubImportPackages) {
+
+ if (stubImportPackages != null
+ && stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
+ // if the package is imported then it does not need stubbing.
+ return;
+ }
if (!notStrippable.add(cl)) {
// slight optimization: if it already contains cl, it already contains
@@ -318,12 +352,14 @@ public class Stubs {
for (FieldInfo fInfo : cl.selfFields()) {
if (fInfo.type() != null) {
if (fInfo.type().asClassInfo() != null) {
- cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName());
+ cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName(),
+ stubImportPackages);
}
if (fInfo.type().typeArguments() != null) {
for (TypeInfo tTypeInfo : fInfo.type().typeArguments()) {
if (tTypeInfo.asClassInfo() != null) {
- cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName());
+ cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName(),
+ stubImportPackages);
}
}
}
@@ -335,7 +371,8 @@ public class Stubs {
if (cl.asTypeInfo().typeArguments() != null) {
for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()) {
if (tInfo.asClassInfo() != null) {
- cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName());
+ cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName(),
+ stubImportPackages);
}
}
}
@@ -343,11 +380,12 @@ public class Stubs {
// cant strip any of the annotation elements
// cantStripThis(cl.annotationElements(), notStrippable);
// take care of methods
- cantStripThis(cl.allSelfMethods(), notStrippable);
- cantStripThis(cl.allConstructors(), notStrippable);
+ cantStripThis(cl.allSelfMethods(), notStrippable, stubImportPackages);
+ cantStripThis(cl.allConstructors(), notStrippable, stubImportPackages);
// blow the outer class open if this is an inner class
if (cl.containingClass() != null) {
- cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName());
+ cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName(),
+ stubImportPackages);
}
// blow open super class and interfaces
ClassInfo supr = cl.realSuperclass();
@@ -365,7 +403,8 @@ public class Stubs {
Errors.error(Errors.HIDDEN_SUPERCLASS, cl.position(), "Public class " + cl.qualifiedName()
+ " stripped of unavailable superclass " + supr.qualifiedName());
} else {
- cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName());
+ cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName(),
+ stubImportPackages);
if (supr.isPrivate()) {
Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
+ cl.qualifiedName() + " extends private class " + supr.qualifiedName());
@@ -374,7 +413,8 @@ public class Stubs {
}
}
- private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable) {
+ private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable,
+ HashSet<String> stubImportPackages) {
// for each method, blow open the parameters, throws and return types. also blow open their
// generics
if (mInfos != null) {
@@ -383,7 +423,8 @@ public class Stubs {
for (TypeInfo tInfo : mInfo.getTypeParameters()) {
if (tInfo.asClassInfo() != null) {
cantStripThis(tInfo.asClassInfo(), notStrippable, "8:"
- + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+ + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+ stubImportPackages);
}
}
}
@@ -391,7 +432,8 @@ public class Stubs {
for (ParameterInfo pInfo : mInfo.parameters()) {
if (pInfo.type() != null && pInfo.type().asClassInfo() != null) {
cantStripThis(pInfo.type().asClassInfo(), notStrippable, "9:"
- + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+ + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+ stubImportPackages);
if (pInfo.type().typeArguments() != null) {
for (TypeInfo tInfoType : pInfo.type().typeArguments()) {
if (tInfoType.asClassInfo() != null) {
@@ -404,7 +446,8 @@ public class Stubs {
+ "()");
} else {
cantStripThis(tcl, notStrippable, "10:"
- + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+ + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+ stubImportPackages);
}
}
}
@@ -414,16 +457,18 @@ public class Stubs {
}
for (ClassInfo thrown : mInfo.thrownExceptions()) {
cantStripThis(thrown, notStrippable, "11:" + mInfo.realContainingClass().qualifiedName()
- + ":" + mInfo.name());
+ + ":" + mInfo.name(), stubImportPackages);
}
if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null) {
cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, "12:"
- + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+ + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+ stubImportPackages);
if (mInfo.returnType().typeArguments() != null) {
for (TypeInfo tyInfo : mInfo.returnType().typeArguments()) {
if (tyInfo.asClassInfo() != null) {
cantStripThis(tyInfo.asClassInfo(), notStrippable, "13:"
- + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+ + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+ stubImportPackages);
}
}
}
@@ -811,40 +856,32 @@ public class Stubs {
|| !field.type().dimension().equals("") || field.containingClass().isInterface();
}
- // Returns 'true' if the method is an @Override of a visible parent
- // method implementation, and thus does not affect the API.
+ /**
+ * Test if the given method has a concrete implementation in a superclass or
+ * interface that has no differences in its public API representation.
+ *
+ * @return {@code true} if the tested method can be safely elided from the
+ * public API to conserve space.
+ */
static boolean methodIsOverride(HashSet<ClassInfo> notStrippable, MethodInfo mi) {
// Abstract/static/final methods are always listed in the API description
if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
return false;
}
- // Find any relevant ancestor declaration and inspect it
- MethodInfo om = mi;
- do {
- MethodInfo superMethod = om.findSuperclassImplementation(notStrippable);
- if (om.equals(superMethod)) {
- break;
- }
- om = superMethod;
- } while (om != null && (om.isHiddenOrRemoved() || om.containingClass().isHiddenOrRemoved()));
- if (om != null) {
- // Visibility mismatch is an API change, so check for it
- if (mi.mIsPrivate == om.mIsPrivate && mi.mIsPublic == om.mIsPublic
- && mi.mIsProtected == om.mIsProtected) {
- // Look only for overrides of an ancestor class implementation,
- // not of e.g. an abstract or interface method declaration
- if (!om.isAbstract()) {
- // If the only "override" turns out to be in our own class
- // (which sometimes happens in concrete subclasses of
- // abstract base classes), it's not really an override
- if (!mi.mContainingClass.equals(om.mContainingClass)) {
- return true;
- }
+ final String api = writeMethodApiWithoutDefault(mi);
+ final MethodInfo overridden = mi.findPredicateOverriddenMethod(new Predicate<MethodInfo>() {
+ @Override
+ public boolean test(MethodInfo test) {
+ if (test.isHiddenOrRemoved() || test.containingClass().isHiddenOrRemoved()) {
+ return false;
}
+
+ final String testApi = writeMethodApiWithoutDefault(test);
+ return api.equals(testApi);
}
- }
- return false;
+ });
+ return (overridden != null);
}
static boolean canCallMethod(ClassInfo from, MethodInfo m) {
@@ -1573,10 +1610,20 @@ public class Stubs {
apiWriter.print(";\n");
}
+ static String writeMethodApiWithoutDefault(MethodInfo mi) {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writeMethodApi(new PrintStream(out), mi, false);
+ return out.toString();
+ }
+
static void writeMethodApi(PrintStream apiWriter, MethodInfo mi) {
+ writeMethodApi(apiWriter, mi, true);
+ }
+
+ static void writeMethodApi(PrintStream apiWriter, MethodInfo mi, boolean withDefault) {
apiWriter.print(" method ");
apiWriter.print(mi.scope());
- if (mi.isDefault()) {
+ if (mi.isDefault() && withDefault) {
apiWriter.print(" default");
}
if (mi.isStatic()) {