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) {