Emit all APIs using Predicates. am: 1563953aaf
am: 7660c29299
Change-Id: I07ca20db5d485cf081325a584e43aca16c9021f2
diff --git a/src/com/google/doclava/ArtifactTagger.java b/src/com/google/doclava/ArtifactTagger.java
index 3c5ee06..eabf22b 100644
--- a/src/com/google/doclava/ArtifactTagger.java
+++ b/src/com/google/doclava/ArtifactTagger.java
@@ -23,6 +23,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -62,7 +63,7 @@
*
* @param classDocs the docs to tag
*/
- public void tagAll(ClassInfo[] classDocs) {
+ public void tagAll(Collection<ClassInfo> classDocs) {
// Read through the XML files in order, applying their artifact information
// to the Javadoc models.
for (Map.Entry<String, String> artifactSpec : xmlToArtifact.entrySet()) {
@@ -114,7 +115,8 @@
* @param specApi the spec for this artifact
* @param classDocs the docs to update
*/
- private void applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi, ClassInfo[] classDocs) {
+ private void applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi,
+ Collection<ClassInfo> classDocs) {
for (ClassInfo classDoc : classDocs) {
PackageInfo packageSpec = specApi.getPackages().get(classDoc.containingPackage().name());
if (packageSpec != null) {
@@ -139,7 +141,7 @@
*
* @param classDocs the docs to verify
*/
- private void warnForMissingArtifacts(ClassInfo[] classDocs) {
+ private void warnForMissingArtifacts(Collection<ClassInfo> classDocs) {
for (ClassInfo claz : classDocs) {
if (checkLevelRecursive(claz) && claz.getArtifact() == null) {
Errors.error(Errors.NO_ARTIFACT_DATA, claz.position(), "XML missing class "
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 3a4090c..f195246 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -22,16 +22,21 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.TreeMap;
+import java.util.function.Predicate;
public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Scoped, Resolvable {
@@ -199,6 +204,29 @@
mRealInnerClasses = realInnerClasses;
}
+ public class ClassMemberInfo extends MemberInfo {
+ public ClassMemberInfo() {
+ super(ClassInfo.this.getRawCommentText(), ClassInfo.this.name(), ClassInfo.this.name(),
+ ClassInfo.this, ClassInfo.this, ClassInfo.this.isPublic(), ClassInfo.this.isProtected(),
+ ClassInfo.this.isPackagePrivate(), ClassInfo.this.isPrivate(), ClassInfo.this.isFinal(),
+ ClassInfo.this.isStatic(), false, ClassInfo.this.kind(), ClassInfo.this.position(),
+ ClassInfo.this.annotations());
+ }
+
+ @Override
+ public boolean isExecutable() {
+ return false;
+ }
+ }
+
+ /**
+ * Return representation of this class as {@link MemberInfo}. This normally
+ * doesn't make any sense, but it's useful for {@link Predicate} testing.
+ */
+ public MemberInfo asMemberInfo() {
+ return new ClassMemberInfo();
+ }
+
public ArrayList<ClassInfo> getRealInnerClasses() {
return mRealInnerClasses;
}
@@ -806,6 +834,114 @@
return mExhaustiveFields;
}
+ /**
+ * Return list of ancestor classes that contribute to this class through
+ * inheritance. Ordered from most general to most specific with all interfaces
+ * listed before concrete classes.
+ */
+ public List<ClassInfo> gatherAncestorClasses() {
+ LinkedList<ClassInfo> classes = gatherAncestorClasses(new LinkedList<>());
+ classes.removeLast();
+ return classes;
+ }
+
+ private LinkedList<ClassInfo> gatherAncestorClasses(LinkedList<ClassInfo> classes) {
+ classes.add(0, this);
+ if (mRealSuperclass != null) {
+ mRealSuperclass.gatherAncestorClasses(classes);
+ }
+ if (mRealInterfaces != null) {
+ for (ClassInfo clazz : mRealInterfaces) {
+ clazz.gatherAncestorClasses(classes);
+ }
+ }
+ return classes;
+ }
+
+ /**
+ * Return superclass matching the given predicate. When a superclass doesn't
+ * match, we'll keep crawling up the tree until we find someone who matches.
+ */
+ public ClassInfo filteredSuperclass(Predicate<MemberInfo> predicate) {
+ if (mRealSuperclass == null) {
+ return null;
+ } else if (predicate.test(mRealSuperclass.asMemberInfo())) {
+ return mRealSuperclass;
+ } else {
+ return mRealSuperclass.filteredSuperclass(predicate);
+ }
+ }
+
+ /**
+ * Return interfaces matching the given predicate. When a superclass or
+ * interface doesn't match, we'll keep crawling up the tree until we find
+ * someone who matches.
+ */
+ public Collection<ClassInfo> filteredInterfaces(Predicate<MemberInfo> predicate) {
+ return filteredInterfaces(predicate, new LinkedHashSet<>());
+ }
+
+ private LinkedHashSet<ClassInfo> filteredInterfaces(Predicate<MemberInfo> predicate,
+ LinkedHashSet<ClassInfo> classes) {
+ if (mRealSuperclass != null && !predicate.test(mRealSuperclass.asMemberInfo())) {
+ mRealSuperclass.filteredInterfaces(predicate, classes);
+ }
+ if (mRealInterfaces != null) {
+ for (ClassInfo clazz : mRealInterfaces) {
+ if (predicate.test(clazz.asMemberInfo())) {
+ classes.add(clazz);
+ } else {
+ clazz.filteredInterfaces(predicate, classes);
+ }
+ }
+ }
+ return classes;
+ }
+
+ /**
+ * Return methods matching the given predicate. Forcibly includes local
+ * methods that override a matching method in an ancestor class.
+ */
+ public Collection<MethodInfo> filteredMethods(Predicate<MemberInfo> predicate) {
+ Set<MethodInfo> methods = new LinkedHashSet<>();
+ for (MethodInfo method : getExhaustiveMethods()) {
+ if (predicate.test(method) || (method.findPredicateOverriddenMethod(predicate) != null)) {
+ methods.remove(method);
+ methods.add(method);
+ }
+ }
+ return methods;
+ }
+
+ /**
+ * Return fields matching the given predicate. Also clones fields from
+ * ancestors that would match had they been defined in this class.
+ */
+ public Collection<FieldInfo> filteredFields(Predicate<MemberInfo> predicate) {
+ Set<FieldInfo> fields = new LinkedHashSet<>();
+ if (Doclava.showAnnotations.isEmpty()) {
+ for (ClassInfo clazz : gatherAncestorClasses()) {
+ if (!clazz.isInterface()) continue;
+ for (FieldInfo field : clazz.getExhaustiveFields()) {
+ if (!predicate.test(field)) {
+ field = field.cloneForClass(this);
+ if (predicate.test(field)) {
+ fields.remove(field);
+ fields.add(field);
+ }
+ }
+ }
+ }
+ }
+ for (FieldInfo field : getExhaustiveFields()) {
+ if (predicate.test(field)) {
+ fields.remove(field);
+ fields.add(field);
+ }
+ }
+ return fields;
+ }
+
public void addMethod(MethodInfo method) {
mApiCheckMethods.put(method.getHashableName(), method);
@@ -1227,7 +1363,7 @@
// known subclasses
TreeMap<String, ClassInfo> direct = new TreeMap<String, ClassInfo>();
TreeMap<String, ClassInfo> indirect = new TreeMap<String, ClassInfo>();
- ClassInfo[] all = Converter.rootClasses();
+ Collection<ClassInfo> all = Converter.rootClasses();
for (ClassInfo cl : all) {
if (cl.superclass() != null && cl.superclass().equals(this)) {
direct.put(cl.name(), cl);
@@ -1424,7 +1560,7 @@
if (ctp.classInfo().mIsIncluded) {
data.setValue(base + ".included", "true");
} else {
- Doclava.federationTagger.tagAll(new ClassInfo[] {ctp.classInfo()});
+ Doclava.federationTagger.tagAll(Arrays.asList(ctp.classInfo()));
if (!ctp.classInfo().getFederatedReferences().isEmpty()) {
FederatedSite site = ctp.classInfo().getFederatedReferences().iterator().next();
data.setValue(base + ".link", site.linkFor(ctp.classInfo().htmlPage()));
@@ -1719,14 +1855,6 @@
return rv;
}
- public boolean equals(ClassInfo that) {
- if (that != null) {
- return this.qualifiedName().equals(that.qualifiedName());
- } else {
- return false;
- }
- }
-
public void setNonWrittenConstructors(ArrayList<MethodInfo> nonWritten) {
mNonWrittenConstructors = nonWritten;
}
@@ -1779,6 +1907,23 @@
return this.qualifiedName();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof ClassInfo) {
+ final ClassInfo c = (ClassInfo) o;
+ return mQualifiedName.equals(c.mQualifiedName);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mQualifiedName.hashCode();
+ }
+
public void setReasonIncluded(String reason) {
mReasonIncluded = reason;
}
diff --git a/src/com/google/doclava/Converter.java b/src/com/google/doclava/Converter.java
index 71c7acb..269c860 100644
--- a/src/com/google/doclava/Converter.java
+++ b/src/com/google/doclava/Converter.java
@@ -39,6 +39,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -111,12 +112,12 @@
private static ClassInfo[] mRootClasses;
- public static ClassInfo[] rootClasses() {
- return mRootClasses;
+ public static Collection<ClassInfo> rootClasses() {
+ return Arrays.asList(mRootClasses);
}
- public static ClassInfo[] allClasses() {
- return (ClassInfo[]) mClasses.all();
+ public static Collection<ClassInfo> allClasses() {
+ return (Collection<ClassInfo>) mClasses.all();
}
private static final MethodDoc[] EMPTY_METHOD_DOC = new MethodDoc[0];
@@ -351,8 +352,8 @@
}
@Override
- ClassInfo[] all() {
- return mCache.values().toArray(new ClassInfo[mCache.size()]);
+ Collection<?> all() {
+ return mCache.values();
}
};
@@ -758,7 +759,7 @@
return o;
}
- Object[] all() {
+ Collection<?> all() {
return null;
}
}
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index e1c92f1..fdfc6c9 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -29,6 +29,7 @@
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import java.io.*;
import java.lang.reflect.Proxy;
import java.lang.reflect.Array;
@@ -918,7 +919,7 @@
public static Data makePackageHDF() {
Data data = makeHDF();
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
for (ClassInfo cl : classes) {
@@ -1100,7 +1101,7 @@
// Write the lists for API references
Data data = makeHDF();
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
SortedMap<String, Object> sorted = new TreeMap<String, Object>();
for (ClassInfo cl : classes) {
@@ -1288,8 +1289,8 @@
*/
public static void writeKeepList(String filename) {
HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
- ClassInfo[] all = Converter.allClasses();
- Arrays.sort(all); // just to make the file a little more readable
+ Collection<ClassInfo> all = Converter.allClasses().stream().sorted(ClassInfo.comparator)
+ .collect(Collectors.toList());
// If a class is public and not hidden, then it and everything it derives
// from cannot be stripped. Otherwise we can strip it.
@@ -1320,7 +1321,7 @@
return sVisiblePackages;
}
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
SortedMap<String, PackageInfo> sorted = new TreeMap<String, PackageInfo>();
for (ClassInfo cl : classes) {
PackageInfo pkg = cl.containingPackage();
@@ -1536,7 +1537,7 @@
*/
public static void writeHierarchy() {
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
ArrayList<ClassInfo> info = new ArrayList<ClassInfo>();
for (ClassInfo cl : classes) {
if (!cl.isHiddenOrRemoved()) {
@@ -1550,7 +1551,7 @@
}
public static void writeClasses() {
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
for (ClassInfo cl : classes) {
Data data = makePackageHDF();
@@ -1750,7 +1751,7 @@
ArrayList<ClassInfo> widgets = new ArrayList<ClassInfo>();
ArrayList<ClassInfo> layoutParams = new ArrayList<ClassInfo>();
- ClassInfo[] classes = Converter.allClasses();
+ Collection<ClassInfo> classes = Converter.allClasses();
// The topmost LayoutParams class - android.view.ViewGroup.LayoutParams
ClassInfo topLayoutParams = null;
diff --git a/src/com/google/doclava/FederationTagger.java b/src/com/google/doclava/FederationTagger.java
index a83bb20..52c7549 100644
--- a/src/com/google/doclava/FederationTagger.java
+++ b/src/com/google/doclava/FederationTagger.java
@@ -19,6 +19,8 @@
import com.google.doclava.apicheck.ApiParseException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -72,11 +74,11 @@
public void tag(ClassInfo classDoc) {
initialize();
for (FederatedSite site : federatedSites) {
- applyFederation(site, new ClassInfo[] { classDoc });
+ applyFederation(site, Arrays.asList(classDoc));
}
}
- public void tagAll(ClassInfo[] classDocs) {
+ public void tagAll(Collection<ClassInfo> classDocs) {
initialize();
for (FederatedSite site : federatedSites) {
applyFederation(site, classDocs);
@@ -124,7 +126,7 @@
initialized = true;
}
- private void applyFederation(FederatedSite federationSource, ClassInfo[] classDocs) {
+ private void applyFederation(FederatedSite federationSource, Collection<ClassInfo> classDocs) {
for (ClassInfo classDoc : classDocs) {
PackageInfo packageSpec
= federationSource.apiInfo().getPackages().get(classDoc.containingPackage().name());
diff --git a/src/com/google/doclava/FieldInfo.java b/src/com/google/doclava/FieldInfo.java
index b535ec0..b3e2308 100644
--- a/src/com/google/doclava/FieldInfo.java
+++ b/src/com/google/doclava/FieldInfo.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.Objects;
public class FieldInfo extends MemberInfo {
public static final Comparator<FieldInfo> comparator = new Comparator<FieldInfo>() {
@@ -44,6 +45,9 @@
}
public FieldInfo cloneForClass(ClassInfo newContainingClass) {
+ if (newContainingClass == containingClass()) {
+ return this;
+ }
return new FieldInfo(name(), newContainingClass, realContainingClass(), isPublic(),
isProtected(), isPackagePrivate(), isPrivate(), isFinal(), isStatic(), isTransient(),
isVolatile(), isSynthetic(), mType, getRawCommentText(), mConstantValue, position(),
@@ -55,6 +59,28 @@
return isConstant(isFinal, isStatic, constantValue) ? "constant" : "field";
}
+ @Override
+ public String toString() {
+ return this.name();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof FieldInfo) {
+ final FieldInfo f = (FieldInfo) o;
+ return mName.equals(f.mName);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode();
+ }
+
public String qualifiedName() {
String parentQName
= (containingClass() != null) ? (containingClass().qualifiedName() + ".") : "";
diff --git a/src/com/google/doclava/MethodInfo.java b/src/com/google/doclava/MethodInfo.java
index e5761c1..58f9f90 100644
--- a/src/com/google/doclava/MethodInfo.java
+++ b/src/com/google/doclava/MethodInfo.java
@@ -27,13 +27,16 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Queue;
import java.util.function.Predicate;
public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable {
public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
+ @Override
public int compare(MethodInfo a, MethodInfo b) {
- return a.name().compareTo(b.name());
+ // TODO: expand to compare signature for better sorting
+ return a.name().compareTo(b.name());
}
};
@@ -124,27 +127,22 @@
return null;
}
- public MethodInfo findPredicateOverriddenMethod(Predicate<MethodInfo> predicate) {
+ public MethodInfo findPredicateOverriddenMethod(Predicate<MemberInfo> predicate) {
if (mReturnType == null) {
// ctor
return null;
}
if (mOverriddenMethod != null) {
- if (predicate.test(mOverriddenMethod)) {
+ if (equals(mOverriddenMethod) && !mOverriddenMethod.isStatic()
+ && predicate.test(mOverriddenMethod)) {
return mOverriddenMethod;
}
}
- ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
- if (containingClass().realSuperclass() != null
- && containingClass().realSuperclass().isAbstract()) {
- queue.add(containingClass().realSuperclass());
- }
- addInterfaces(containingClass().realInterfaces(), queue);
- for (ClassInfo iface : queue) {
- for (MethodInfo me : iface.methods()) {
- if (predicate.test(me)) {
- return me;
+ for (ClassInfo clazz : containingClass().gatherAncestorClasses()) {
+ for (MethodInfo method : clazz.getExhaustiveMethods()) {
+ if (equals(method) && !method.isStatic() && predicate.test(method)) {
+ return method;
}
}
}
@@ -258,7 +256,12 @@
*/
public MethodInfo cloneForClass(ClassInfo newContainingClass,
Map<String, TypeInfo> typeArgumentMapping) {
- TypeInfo returnType = mReturnType.getTypeWithArguments(typeArgumentMapping);
+ if (newContainingClass == containingClass()) {
+ return this;
+ }
+ TypeInfo returnType = (mReturnType != null)
+ ? mReturnType.getTypeWithArguments(typeArgumentMapping)
+ : null;
ArrayList<ParameterInfo> parameters = new ArrayList<ParameterInfo>();
for (ParameterInfo pi : mParameters) {
parameters.add(pi.cloneWithTypeArguments(typeArgumentMapping));
@@ -730,6 +733,23 @@
return this.name();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof MethodInfo) {
+ final MethodInfo m = (MethodInfo) o;
+ return mName.equals(m.mName) && signature().equals(m.signature());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, signature());
+ }
+
public void setReason(String reason) {
mReasonOpened = reason;
}
diff --git a/src/com/google/doclava/NavTree.java b/src/com/google/doclava/NavTree.java
index 5d055db..de9dd4e 100644
--- a/src/com/google/doclava/NavTree.java
+++ b/src/com/google/doclava/NavTree.java
@@ -19,6 +19,7 @@
import com.google.clearsilver.jsilver.data.Data;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -63,7 +64,7 @@
*/
public static void writeYamlTree(String dir, String fileName){
Data data = Doclava.makeHDF();
- ClassInfo[] classes = Converter.rootClasses();
+ Collection<ClassInfo> classes = Converter.rootClasses();
SortedMap<String, Object> sorted = new TreeMap<String, Object>();
for (ClassInfo cl : classes) {
diff --git a/src/com/google/doclava/PackageInfo.java b/src/com/google/doclava/PackageInfo.java
index 2cdfe4a..25f229e 100644
--- a/src/com/google/doclava/PackageInfo.java
+++ b/src/com/google/doclava/PackageInfo.java
@@ -107,6 +107,10 @@
return mHidden;
}
+ public void setHidden(boolean hidden) {
+ mHidden = hidden;
+ }
+
@Override
public boolean isRemoved() {
if (mRemoved == null) {
@@ -277,7 +281,23 @@
mContainingApi = api;
}
- // in hashed containers, treat the name as the key
+ @Override
+ public String toString() {
+ return this.name();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o instanceof PackageInfo) {
+ final PackageInfo p = (PackageInfo) o;
+ return mName.equals(p.mName);
+ } else {
+ return false;
+ }
+ }
+
@Override
public int hashCode() {
return mName.hashCode();
diff --git a/src/com/google/doclava/SinceTagger.java b/src/com/google/doclava/SinceTagger.java
index ce2cee3..2d5eed6 100644
--- a/src/com/google/doclava/SinceTagger.java
+++ b/src/com/google/doclava/SinceTagger.java
@@ -23,6 +23,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@@ -53,7 +54,7 @@
xmlToName.put(file, name);
}
- public void tagAll(ClassInfo[] classDocs) {
+ public void tagAll(Collection<ClassInfo> classDocs) {
// read through the XML files in order, applying their since information
// to the Javadoc models
for (Map.Entry<String, String> versionSpec : xmlToName.entrySet()) {
@@ -103,7 +104,8 @@
* named version
* @param classDocs the doc model to update
*/
- private void applyVersionsFromSpec(String versionName, ApiInfo specApi, ClassInfo[] classDocs) {
+ private void applyVersionsFromSpec(String versionName, ApiInfo specApi,
+ Collection<ClassInfo> classDocs) {
for (ClassInfo classDoc : classDocs) {
PackageInfo packageSpec
= specApi.getPackages().get(classDoc.containingPackage().name());
@@ -232,7 +234,7 @@
* zero warnings because {@code apicheck} guarantees that all symbols are present in the most
* recent API.
*/
- private void warnForMissingVersions(ClassInfo[] classDocs) {
+ private void warnForMissingVersions(Collection<ClassInfo> classDocs) {
for (ClassInfo claz : classDocs) {
if (!checkLevelRecursive(claz)) {
continue;
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 4dc859d..a618dca 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -38,6 +38,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.function.Predicate;
@@ -51,7 +52,7 @@
boolean stubSourceOnly) {
// figure out which classes we need
final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
- ClassInfo[] all = Converter.allClasses();
+ Collection<ClassInfo> all = Converter.allClasses();
PrintStream apiWriter = null;
PrintStream keepListWriter = null;
PrintStream removedApiWriter = null;
@@ -213,41 +214,33 @@
}
}
}
- // write out the Api
+
+ Map<PackageInfo, List<ClassInfo>> allClassesByPackage = Converter.allClasses().stream()
+ .collect(Collectors.groupingBy(ClassInfo::containingPackage));
+
+ final boolean ignoreShown = Doclava.showAnnotations.isEmpty();
+
+ // Write out the current API
if (apiWriter != null) {
- writeApi(apiWriter, packages, notStrippable);
+ writeApi(apiWriter, packages,
+ new ApiPredicate().setIgnoreShown(ignoreShown),
+ new ApiPredicate().setIgnoreShown(true));
apiWriter.close();
}
- // write out the keep list
+ // Write out the keep list
if (keepListWriter != null) {
writeKeepList(keepListWriter, packages, notStrippable);
keepListWriter.close();
}
- HashMap<PackageInfo, List<ClassInfo>> allPackageClassMap =
- new HashMap<PackageInfo, List<ClassInfo>>();
- for (ClassInfo cl : Converter.allClasses()) {
- if (allPackageClassMap.containsKey(cl.containingPackage())) {
- allPackageClassMap.get(cl.containingPackage()).add(cl);
- } else {
- ArrayList<ClassInfo> classes = new ArrayList<ClassInfo>();
- classes.add(cl);
- allPackageClassMap.put(cl.containingPackage(), classes);
- }
- }
// Write out the removed API
if (removedApiWriter != null) {
- writePredicateApi(removedApiWriter, allPackageClassMap, notStrippable,
- new RemovedPredicate());
+ writeApi(removedApiWriter, allClassesByPackage,
+ new ApiPredicate().setIgnoreShown(ignoreShown).setMatchRemoved(true),
+ new ApiPredicate().setIgnoreShown(true).setIgnoreRemoved(true));
removedApiWriter.close();
}
- // Write out the exact API
- if (exactApiWriter != null) {
- writePredicateApi(exactApiWriter, allPackageClassMap, notStrippable,
- new ExactPredicate());
- exactApiWriter.close();
- }
}
private static boolean shouldWriteStub(final String packageName,
@@ -856,34 +849,6 @@
|| !field.type().dimension().equals("") || field.containingClass().isInterface();
}
- /**
- * 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(MethodInfo mi) {
- // Abstract/static/final methods are always listed in the API description
- if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
- return false;
- }
-
- 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 (overridden != null);
- }
-
static boolean canCallMethod(ClassInfo from, MethodInfo m) {
if (m.isPublic() || m.isProtected()) {
return true;
@@ -1098,9 +1063,7 @@
ArrayList<MethodInfo> methods = cl.allSelfMethods();
Collections.sort(methods, MethodInfo.comparator);
for (MethodInfo mi : methods) {
- if (!methodIsOverride(mi)) {
- writeMethodXML(xmlWriter, mi);
- }
+ writeMethodXML(xmlWriter, mi);
}
ArrayList<FieldInfo> fields = cl.selfFields();
@@ -1224,80 +1187,148 @@
return returnString;
}
- public static class RemovedPredicate implements Predicate<MemberInfo> {
- @Override
- public boolean test(MemberInfo member) {
- ClassInfo clazz = member.containingClass();
+ /**
+ * Predicate that decides if the given member should be considered part of an
+ * API surface area. To make the most accurate decision, it searches for
+ * signals on the member, all containing classes, and all containing packages.
+ */
+ public static class ApiPredicate implements Predicate<MemberInfo> {
+ public boolean ignoreShown;
+ public boolean ignoreRemoved;
+ public boolean matchRemoved;
- boolean visible = member.isPublic() || member.isProtected();
- boolean hidden = member.isHidden();
- boolean removed = member.isRemoved();
- while (clazz != null) {
- visible &= clazz.isPublic() || clazz.isProtected();
- hidden |= clazz.isHidden();
- removed |= clazz.isRemoved();
- clazz = clazz.containingClass();
- }
+ /**
+ * Set if the value of {@link MemberInfo#hasShowAnnotation()} should be
+ * ignored. That is, this predicate will assume that all encountered members
+ * match the "shown" requirement.
+ * <p>
+ * This is typically useful when generating "current.txt", when no
+ * {@link Doclava#showAnnotations} have been defined.
+ */
+ public ApiPredicate setIgnoreShown(boolean ignoreShown) {
+ this.ignoreShown = ignoreShown;
+ return this;
+ }
- if (visible && !hidden && removed) {
- if (member instanceof MethodInfo) {
- final MethodInfo method = (MethodInfo) member;
- return (method.findOverriddenMethod(method.name(), method.signature()) == null);
- } else {
- return true;
- }
+ /**
+ * Set if the value of {@link MemberInfo#isRemoved()} should be ignored.
+ * That is, this predicate will assume that all encountered members match
+ * the "removed" requirement.
+ * <p>
+ * This is typically useful when generating "removed.txt", when it's okay to
+ * reference both current and removed APIs.
+ */
+ public ApiPredicate setIgnoreRemoved(boolean ignoreRemoved) {
+ this.ignoreRemoved = ignoreRemoved;
+ return this;
+ }
+
+ /**
+ * Set what the value of {@link MemberInfo#isRemoved()} must be equal to in
+ * order for a member to match.
+ * <p>
+ * This is typically useful when generating "removed.txt", when you only
+ * want to match members that have actually been removed.
+ */
+ public ApiPredicate setMatchRemoved(boolean matchRemoved) {
+ this.matchRemoved = matchRemoved;
+ return this;
+ }
+
+ private static PackageInfo containingPackage(PackageInfo pkg) {
+ String name = pkg.name();
+ final int lastDot = name.lastIndexOf('.');
+ if (lastDot == -1) {
+ return null;
} else {
- return false;
+ name = name.substring(0, lastDot);
+ return Converter.obtainPackage(name);
}
}
- }
- public static class ExactPredicate implements Predicate<MemberInfo> {
@Override
public boolean test(MemberInfo member) {
- ClassInfo clazz = member.containingClass();
-
boolean visible = member.isPublic() || member.isProtected();
boolean hasShowAnnotation = member.hasShowAnnotation();
boolean hidden = member.isHidden();
+ boolean docOnly = member.isDocOnly();
boolean removed = member.isRemoved();
+
+ ClassInfo clazz = member.containingClass();
+ if (clazz != null) {
+ PackageInfo pkg = clazz.containingPackage();
+ while (pkg != null) {
+ hidden |= pkg.isHidden();
+ docOnly |= pkg.isDocOnly();
+ removed |= pkg.isRemoved();
+ pkg = containingPackage(pkg);
+ }
+ }
while (clazz != null) {
visible &= clazz.isPublic() || clazz.isProtected();
hasShowAnnotation |= clazz.hasShowAnnotation();
hidden |= clazz.isHidden();
+ docOnly |= clazz.isDocOnly();
removed |= clazz.isRemoved();
clazz = clazz.containingClass();
}
- if (visible && hasShowAnnotation && !hidden && !removed) {
- if (member instanceof MethodInfo) {
- final MethodInfo method = (MethodInfo) member;
- return (method.findOverriddenMethod(method.name(), method.signature()) == null);
- } else {
- return true;
- }
+ if (ignoreShown) {
+ hasShowAnnotation = true;
+ }
+ if (ignoreRemoved) {
+ removed = matchRemoved;
+ }
+
+ return visible && hasShowAnnotation && !hidden && !docOnly && (removed == matchRemoved);
+ }
+ }
+
+ /**
+ * Filter that will elide exact duplicate members that are already included
+ * in another superclass/interfaces.
+ */
+ public static class ElidingPredicate implements Predicate<MemberInfo> {
+ private final Predicate<MemberInfo> wrapped;
+
+ public ElidingPredicate(Predicate<MemberInfo> wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public boolean test(MemberInfo member) {
+ // This member should be included, but if it's an exact duplicate
+ // override then we can elide it.
+ if (member instanceof MethodInfo) {
+ MethodInfo method = (MethodInfo) member;
+ String methodRaw = writeMethodApiWithoutDefault(method);
+ return (method.findPredicateOverriddenMethod(new Predicate<MemberInfo>() {
+ @Override
+ public boolean test(MemberInfo test) {
+ // We're looking for included and perfect signature
+ return (wrapped.test(test)
+ && writeMethodApiWithoutDefault((MethodInfo) test).equals(methodRaw));
+ }
+ }) == null);
} else {
- return false;
+ return true;
}
}
}
- static void writePredicateApi(PrintStream apiWriter,
- HashMap<PackageInfo, List<ClassInfo>> allPackageClassMap, Set<ClassInfo> notStrippable,
- Predicate<MemberInfo> predicate) {
- final PackageInfo[] packages = allPackageClassMap.keySet().toArray(new PackageInfo[0]);
- Arrays.sort(packages, PackageInfo.comparator);
- for (PackageInfo pkg : packages) {
- // beware that pkg.allClasses() has no class in it at the moment
- final List<ClassInfo> classes = allPackageClassMap.get(pkg);
- Collections.sort(classes, ClassInfo.comparator);
+ static void writeApi(PrintStream apiWriter, Map<PackageInfo, List<ClassInfo>> classesByPackage,
+ Predicate<MemberInfo> filterEmit, Predicate<MemberInfo> filterReference) {
+ for (PackageInfo pkg : classesByPackage.keySet().stream().sorted(PackageInfo.comparator)
+ .collect(Collectors.toList())) {
+ if (pkg.name().equals(PackageInfo.DEFAULT_PACKAGE)) continue;
+
boolean hasWrittenPackageHead = false;
- for (ClassInfo cl : classes) {
- hasWrittenPackageHead = writeClassPredicateSelfMembers(apiWriter, cl, notStrippable,
- predicate, hasWrittenPackageHead);
+ for (ClassInfo clazz : classesByPackage.get(pkg).stream().sorted(ClassInfo.comparator)
+ .collect(Collectors.toList())) {
+ hasWrittenPackageHead = writeClassApi(apiWriter, clazz, filterEmit, filterReference,
+ hasWrittenPackageHead);
}
- // the package contains some classes with some removed members
if (hasWrittenPackageHead) {
apiWriter.print("}\n\n");
}
@@ -1307,27 +1338,37 @@
/**
* Write the removed members of the class to removed.txt
*/
- private static boolean writeClassPredicateSelfMembers(PrintStream apiWriter, ClassInfo cl,
- Set<ClassInfo> notStrippable, Predicate<MemberInfo> predicate,
+ private static boolean writeClassApi(PrintStream apiWriter, ClassInfo cl,
+ Predicate<MemberInfo> filterEmit, Predicate<MemberInfo> filterReference,
boolean hasWrittenPackageHead) {
- List<MethodInfo> constructors = cl.getExhaustiveConstructors().stream().filter(predicate)
+ List<MethodInfo> constructors = cl.getExhaustiveConstructors().stream().filter(filterEmit)
.sorted(MethodInfo.comparator).collect(Collectors.toList());
- List<MethodInfo> methods = cl.getExhaustiveMethods().stream().filter(predicate)
+ List<MethodInfo> methods = cl.filteredMethods(filterEmit).stream()
+ .filter(new ElidingPredicate(filterReference))
.sorted(MethodInfo.comparator).collect(Collectors.toList());
- List<FieldInfo> enums = cl.getExhaustiveEnumConstants().stream().filter(predicate)
+ List<FieldInfo> enums = cl.getExhaustiveEnumConstants().stream().filter(filterEmit)
.sorted(FieldInfo.comparator).collect(Collectors.toList());
- List<FieldInfo> fields = cl.getExhaustiveFields().stream().filter(predicate)
+ List<FieldInfo> fields = cl.filteredFields(filterEmit).stream()
.sorted(FieldInfo.comparator).collect(Collectors.toList());
- if (constructors.isEmpty() && methods.isEmpty() && enums.isEmpty() && fields.isEmpty()) {
+ final boolean classEmpty = (constructors.isEmpty() && methods.isEmpty() && enums.isEmpty()
+ && fields.isEmpty());
+ final boolean emit;
+ if (filterEmit.test(cl.asMemberInfo())) {
+ emit = true;
+ } else if (!classEmpty) {
+ emit = filterReference.test(cl.asMemberInfo());
+ } else {
+ emit = false;
+ }
+ if (!emit) {
return hasWrittenPackageHead;
}
// Look for Android @SystemApi exposed outside the normal SDK; we require
// that they're protected with a system permission.
- if (Doclava.android && Doclava.showAnnotations.contains("android.annotation.SystemApi")
- && !(predicate instanceof RemovedPredicate)) {
+ if (Doclava.android && Doclava.showAnnotations.contains("android.annotation.SystemApi")) {
boolean systemService = "android.content.pm.PackageManager".equals(cl.qualifiedName());
for (AnnotationInstanceInfo a : cl.annotations()) {
if (a.type().qualifiedNameMatches("android", "annotation.SystemService")) {
@@ -1366,27 +1407,30 @@
apiWriter.print(cl.isInterface() ? "interface" : "class");
apiWriter.print(" ");
apiWriter.print(cl.name());
-
- if (!cl.isInterface()
- && !"java.lang.Object".equals(cl.qualifiedName())
- && cl.realSuperclass() != null
- && !"java.lang.Object".equals(cl.realSuperclass().qualifiedName())) {
- apiWriter.print(" extends ");
- apiWriter.print(cl.realSuperclass().qualifiedName());
+ if (cl.hasTypeParameters()) {
+ apiWriter.print(TypeInfo.typeArgumentsName(cl.asTypeInfo().typeArguments(),
+ new HashSet<String>()));
}
- ArrayList<ClassInfo> interfaces = cl.realInterfaces();
- Collections.sort(interfaces, ClassInfo.comparator);
+ if (!cl.isInterface()
+ && !"java.lang.Object".equals(cl.qualifiedName())) {
+ final ClassInfo superclass = cl.filteredSuperclass(filterReference);
+ if (superclass != null && !"java.lang.Object".equals(superclass.qualifiedName())) {
+ apiWriter.print(" extends ");
+ apiWriter.print(superclass.qualifiedName());
+ }
+ }
+
+ List<ClassInfo> interfaces = cl.filteredInterfaces(filterReference).stream()
+ .sorted(ClassInfo.comparator).collect(Collectors.toList());
boolean first = true;
for (ClassInfo iface : interfaces) {
- if (notStrippable.contains(iface)) {
- if (first) {
- apiWriter.print(" implements");
- first = false;
- }
- apiWriter.print(" ");
- apiWriter.print(iface.qualifiedName());
+ if (first) {
+ apiWriter.print(" implements");
+ first = false;
}
+ apiWriter.print(" ");
+ apiWriter.print(iface.qualifiedName());
}
apiWriter.print(" {\n");
@@ -1464,133 +1508,6 @@
}
}
- public static void writeApi(PrintStream apiWriter, Collection<PackageInfo> pkgs) {
- final PackageInfo[] packages = pkgs.toArray(new PackageInfo[pkgs.size()]);
- Arrays.sort(packages, PackageInfo.comparator);
-
- HashSet<ClassInfo> notStrippable = new HashSet();
- for (PackageInfo pkg: packages) {
- for (ClassInfo cl: pkg.allClasses().values()) {
- notStrippable.add(cl);
- }
- }
- for (PackageInfo pkg: packages) {
- writePackageApi(apiWriter, pkg, pkg.allClasses().values(), notStrippable);
- }
- }
-
- static void writeApi(PrintStream apiWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
- HashSet<ClassInfo> notStrippable) {
- // extract the set of packages, sort them by name, and write them out in that order
- Set<PackageInfo> allClassKeys = allClasses.keySet();
- PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
- Arrays.sort(allPackages, PackageInfo.comparator);
-
- for (PackageInfo pack : allPackages) {
- writePackageApi(apiWriter, pack, allClasses.get(pack), notStrippable);
- }
- }
-
- static void writePackageApi(PrintStream apiWriter, PackageInfo pack,
- Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
- // Work around the bogus "Array" class we invent for
- // Arrays.copyOf's Class<? extends T[]> newType parameter. (http://b/2715505)
- if (pack.name().equals(PackageInfo.DEFAULT_PACKAGE)) {
- return;
- }
-
- apiWriter.print("package ");
- apiWriter.print(pack.qualifiedName());
- apiWriter.print(" {\n\n");
-
- ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
- Arrays.sort(classes, ClassInfo.comparator);
- for (ClassInfo cl : classes) {
- writeClassApi(apiWriter, cl, notStrippable);
- }
-
- apiWriter.print("}\n\n");
- }
-
- static void writeClassApi(PrintStream apiWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable) {
- boolean first;
-
- apiWriter.print(" ");
- apiWriter.print(cl.scope());
- if (cl.isStatic()) {
- apiWriter.print(" static");
- }
- if (cl.isFinal()) {
- apiWriter.print(" final");
- }
- if (cl.isAbstract()) {
- apiWriter.print(" abstract");
- }
- if (cl.isDeprecated()) {
- apiWriter.print(" deprecated");
- }
- apiWriter.print(" ");
- apiWriter.print(cl.isInterface() ? "interface" : "class");
- apiWriter.print(" ");
- apiWriter.print(cl.name());
- if (cl.hasTypeParameters()) {
- apiWriter.print(TypeInfo.typeArgumentsName(cl.asTypeInfo().typeArguments(),
- new HashSet<String>()));
- }
-
- if (!cl.isInterface()
- && !"java.lang.Object".equals(cl.qualifiedName())
- && cl.realSuperclass() != null
- && !"java.lang.Object".equals(cl.realSuperclass().qualifiedName())) {
- apiWriter.print(" extends ");
- apiWriter.print(cl.realSuperclass().qualifiedName());
- }
-
- ArrayList<ClassInfo> interfaces = cl.realInterfaces();
- Collections.sort(interfaces, ClassInfo.comparator);
- first = true;
- for (ClassInfo iface : interfaces) {
- if (notStrippable.contains(iface)) {
- if (first) {
- apiWriter.print(" implements");
- first = false;
- }
- apiWriter.print(" ");
- apiWriter.print(iface.qualifiedName());
- }
- }
-
- apiWriter.print(" {\n");
-
- ArrayList<MethodInfo> constructors = cl.constructors();
- Collections.sort(constructors, MethodInfo.comparator);
- for (MethodInfo mi : constructors) {
- writeConstructorApi(apiWriter, mi);
- }
-
- ArrayList<MethodInfo> methods = cl.allSelfMethods();
- Collections.sort(methods, MethodInfo.comparator);
- for (MethodInfo mi : methods) {
- if (!methodIsOverride(mi)) {
- writeMethodApi(apiWriter, mi);
- }
- }
-
- ArrayList<FieldInfo> enums = cl.enumConstants();
- Collections.sort(enums, FieldInfo.comparator);
- for (FieldInfo fi : enums) {
- writeFieldApi(apiWriter, fi, "enum_constant");
- }
-
- ArrayList<FieldInfo> fields = cl.selfFields();
- Collections.sort(fields, FieldInfo.comparator);
- for (FieldInfo fi : fields) {
- writeFieldApi(apiWriter, fi, "field");
- }
-
- apiWriter.print(" }\n\n");
- }
-
static void writeConstructorApi(PrintStream apiWriter, MethodInfo mi) {
apiWriter.print(" ctor ");
apiWriter.print(mi.scope());
diff --git a/src/com/google/doclava/TodoFile.java b/src/com/google/doclava/TodoFile.java
index 36df2c7..efd3bb5 100644
--- a/src/com/google/doclava/TodoFile.java
+++ b/src/com/google/doclava/TodoFile.java
@@ -19,6 +19,7 @@
import com.google.clearsilver.jsilver.data.Data;
import java.util.*;
+import java.util.stream.Collectors;
public class TodoFile {
@@ -68,8 +69,8 @@
Doclava.setPageTitle(data, "Missing Documentation");
TreeMap<String, PackageStats> packageStats = new TreeMap<String, PackageStats>();
- ClassInfo[] classes = Converter.rootClasses();
- Arrays.sort(classes);
+ Collection<ClassInfo> classes = Converter.rootClasses().stream().sorted(ClassInfo.comparator)
+ .collect(Collectors.toList());
int classIndex = 0;
diff --git a/src/com/google/doclava/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java
index 5dda8d2..4828720 100644
--- a/src/com/google/doclava/apicheck/ApiCheck.java
+++ b/src/com/google/doclava/apicheck/ApiCheck.java
@@ -250,25 +250,9 @@
}
static int convertToApi(String src, String dst) {
- ApiInfo api;
- try {
- api = parseApi(src);
- } catch (ApiParseException e) {
- e.printStackTrace();
- System.err.println("Error parsing API: " + src);
- return 1;
- }
-
- PrintStream apiWriter = null;
- try {
- apiWriter = new PrintStream(dst);
- } catch (FileNotFoundException ex) {
- System.err.println("can't open file: " + dst);
- }
-
- Stubs.writeApi(apiWriter, api.getPackages().values());
-
- return 0;
+ // This was historically used to convert XML to TXT format, which was a
+ // one-time migration.
+ throw new UnsupportedOperationException();
}
static int convertToXml(String src, String dst, boolean strip) {