Offer to emit exact APIs instead of superset.
am: ef8f3ec4f2

Change-Id: I730e5214a9c9755be07ebfe567d00fd46690ca15
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index b549167..b2e3344 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -764,78 +764,20 @@
     mRemovedMethods = Collections.unmodifiableList(removedMethods);
   }
 
-  /**
-   * @param allMethods all methods regardless of access levels. Selects the
-   * removed, public/protected ones and store them. If a class is removed, all its members
-   * are removed, even if the member may not have a @removed tag.
-   */
-  public void setRemovedSelfMethods(List<MethodInfo> allMethods) {
-    List<MethodInfo> removedSelfMethods = new ArrayList<MethodInfo>();
-    for (MethodInfo method : allMethods) {
-      if ((this.isRemoved() || method.isRemoved()) && (method.isPublic() || method.isProtected()) &&
-          (this.isPublic() || this.isProtected()) &&
-          (method.findOverriddenMethod(method.name(), method.signature()) == null)) {
-        removedSelfMethods.add(method);
-      }
-    }
-
-    Collections.sort(removedSelfMethods, MethodInfo.comparator);
-    mRemovedSelfMethods = Collections.unmodifiableList(removedSelfMethods);
+  public void setExhaustiveConstructors(List<MethodInfo> constructors) {
+    mExhaustiveConstructors = constructors;
   }
 
-  /**
-   * @param allCtors all constructors regardless of access levels.
-   * But only the public/protected removed constructors will be stored by the method.
-   * Removed constructors should never be deleted from source code because
-   * they were once public API.
-   */
-  public void setRemovedConstructors(List<MethodInfo> allCtors) {
-    List<MethodInfo> ctors = new ArrayList<MethodInfo>();
-    for (MethodInfo ctor : allCtors) {
-      if ((this.isRemoved() || ctor.isRemoved()) && (ctor.isPublic() || ctor.isProtected()) &&
-          (this.isPublic() || this.isProtected())) {
-        ctors.add(ctor);
-      }
-    }
-
-    Collections.sort(ctors, MethodInfo.comparator);
-    mRemovedConstructors = Collections.unmodifiableList(ctors);
+  public void setExhaustiveMethods(List<MethodInfo> methods) {
+    mExhaustiveMethods = methods;
   }
 
-  /**
-   * @param allFields all fields regardless of access levels.  Selects the
-   * removed, public/protected ones and store them. If a class is removed, all its members
-   * are removed, even if the member may not have a @removed tag.
-   */
-  public void setRemovedSelfFields(List<FieldInfo> allFields) {
-    List<FieldInfo> fields = new ArrayList<FieldInfo>();
-    for (FieldInfo field : allFields) {
-      if ((this.isRemoved() || field.isRemoved()) && (field.isPublic() || field.isProtected()) &&
-          (this.isPublic() || this.isProtected())) {
-        fields.add(field);
-      }
-    }
-
-    Collections.sort(fields, FieldInfo.comparator);
-    mRemovedSelfFields = Collections.unmodifiableList(fields);
+  public void setExhaustiveEnumConstants(List<FieldInfo> enumConstants) {
+    mExhaustiveEnumConstants = enumConstants;
   }
 
-  /**
-   * @param allEnumConstants all enum constants regardless of access levels. Selects the
-   * removed, public/protected ones and store them. If a class is removed, all its members
-   * are removed, even if the member may not have a @removed tag.
-   */
-  public void setRemovedEnumConstants(List<FieldInfo> allEnumConstants) {
-    List<FieldInfo> enums = new ArrayList<FieldInfo>();
-    for (FieldInfo field : allEnumConstants) {
-      if ((this.isRemoved() || field.isRemoved()) && (field.isPublic() || field.isProtected()) &&
-          (this.isPublic() || this.isProtected())) {
-        enums.add(field);
-      }
-    }
-
-    Collections.sort(enums, FieldInfo.comparator);
-    mRemovedEnumConstants = Collections.unmodifiableList(enums);
+  public void setExhaustiveFields(List<FieldInfo> fields) {
+    mExhaustiveFields = fields;
   }
 
   /**
@@ -846,58 +788,20 @@
     return mRemovedMethods;
   }
 
-  /**
-   * @return all public/protected methods that are removed. @removed methods should never be
-   * deleted from source code because they were once public API. Methods that override
-   * a parent method will not be included, because deleting them does not break the API.
-   */
-  public List<MethodInfo> getRemovedSelfMethods() {
-    return mRemovedSelfMethods;
+  public List<MethodInfo> getExhaustiveConstructors() {
+    return mExhaustiveConstructors;
   }
 
-  /**
-   * @return all public constructors that are removed.
-   * removed constructors should never be deleted from source code because they
-   * were once public API.
-   * The returned list is sorted and unmodifiable.
-   */
-  public List<MethodInfo> getRemovedConstructors() {
-    return mRemovedConstructors;
+  public List<MethodInfo> getExhaustiveMethods() {
+    return mExhaustiveMethods;
   }
 
-  /**
-   * @return all public/protected fields that are removed.
-   * removed members should never be deleted from source code because they were once public API.
-   * The returned list is sorted and unmodifiable.
-   */
-  public List<FieldInfo> getRemovedSelfFields() {
-    return mRemovedSelfFields;
+  public List<FieldInfo> getExhaustiveEnumConstants() {
+    return mExhaustiveEnumConstants;
   }
 
-  /**
-   * @return all public/protected enumConstants that are removed.
-   * removed members should never be deleted from source code
-   * because they were once public API.
-   * The returned list is sorted and unmodifiable.
-   */
-  public List<FieldInfo> getRemovedSelfEnumConstants() {
-    return mRemovedEnumConstants;
-  }
-
-  /**
-   * @return true if this class contains any self members that are removed
-   */
-  public boolean hasRemovedSelfMembers() {
-    List<FieldInfo> removedSelfFields = getRemovedSelfFields();
-    List<FieldInfo> removedSelfEnumConstants = getRemovedSelfEnumConstants();
-    List<MethodInfo> removedSelfMethods = getRemovedSelfMethods();
-    List<MethodInfo> removedConstructors = getRemovedConstructors();
-    if (removedSelfFields.size() + removedSelfEnumConstants.size()
-        + removedSelfMethods.size() + removedConstructors.size() == 0) {
-      return false;
-    } else {
-      return true;
-    }
+  public List<FieldInfo> getExhaustiveFields() {
+    return mExhaustiveFields;
   }
 
   public void addMethod(MethodInfo method) {
@@ -1950,12 +1854,12 @@
   // Resolutions
   private ArrayList<Resolution> mResolutions;
 
-  private List<MethodInfo> mRemovedConstructors; // immutable after you set its value.
-  // @removed self methods that do not override any parent methods
-  private List<MethodInfo> mRemovedSelfMethods; // immutable after you set its value.
   private List<MethodInfo> mRemovedMethods; // immutable after you set its value.
-  private List<FieldInfo> mRemovedSelfFields; // immutable after you set its value.
-  private List<FieldInfo> mRemovedEnumConstants; // immutable after you set its value.
+
+  private List<MethodInfo> mExhaustiveConstructors; // immutable after you set its value.
+  private List<MethodInfo> mExhaustiveMethods; // immutable after you set its value.
+  private List<FieldInfo> mExhaustiveEnumConstants; // immutable after you set its value.
+  private List<FieldInfo> mExhaustiveFields; // immutable after you set its value.
 
   /**
    * Returns true if {@code cl} implements the interface {@code iface} either by either being that
diff --git a/src/com/google/doclava/Converter.java b/src/com/google/doclava/Converter.java
index 1bc3563..ee91960 100644
--- a/src/com/google/doclava/Converter.java
+++ b/src/com/google/doclava/Converter.java
@@ -137,14 +137,14 @@
     cl.setRemovedMethods(
             new ArrayList<MethodInfo>(Arrays.asList(Converter.getRemovedMethods(c.methods(false)))));
 
-    cl.setRemovedSelfMethods(
-        new ArrayList<MethodInfo>(Converter.convertAllMethods(c.methods(false))));
-    cl.setRemovedConstructors(
+    cl.setExhaustiveConstructors(
         new ArrayList<MethodInfo>(Converter.convertAllMethods(c.constructors(false))));
-    cl.setRemovedSelfFields(
-        new ArrayList<FieldInfo>(Converter.convertAllFields(c.fields(false))));
-    cl.setRemovedEnumConstants(
+    cl.setExhaustiveMethods(
+        new ArrayList<MethodInfo>(Converter.convertAllMethods(c.methods(false))));
+    cl.setExhaustiveEnumConstants(
         new ArrayList<FieldInfo>(Converter.convertAllFields(c.enumConstants())));
+    cl.setExhaustiveFields(
+        new ArrayList<FieldInfo>(Converter.convertAllFields(c.fields(false))));
 
     cl.setNonWrittenConstructors(
             new ArrayList<MethodInfo>(Arrays.asList(Converter.convertNonWrittenConstructors(
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index 8c29ac7..9359930 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -176,6 +176,7 @@
     boolean offlineMode = false;
     String apiFile = null;
     String removedApiFile = null;
+    String exactApiFile = null;
     String debugStubsFile = "";
     HashSet<String> stubPackages = null;
     ArrayList<String> knownTagsFiles = new ArrayList<String>();
@@ -280,8 +281,9 @@
         apiFile = a[1];
       } else if (a[0].equals("-removedApi")) {
         removedApiFile = a[1];
-      }
-      else if (a[0].equals("-nodocs")) {
+      } else if (a[0].equals("-exactApi")) {
+        exactApiFile = a[1];
+      } else if (a[0].equals("-nodocs")) {
         generateDocs = false;
       } else if (a[0].equals("-noassets")) {
         includeAssets = false;
@@ -496,8 +498,10 @@
     }
 
     // Stubs
-    if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null) {
-      Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, stubPackages);
+    if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null
+        || exactApiFile != null) {
+      Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, exactApiFile,
+          stubPackages);
     }
 
     Errors.printErrors();
@@ -751,6 +755,9 @@
     if (option.equals("-removedApi")) {
       return 2;
     }
+    if (option.equals("-exactApi")) {
+      return 2;
+    }
     if (option.equals("-nodocs")) {
       return 1;
     }
diff --git a/src/com/google/doclava/MemberInfo.java b/src/com/google/doclava/MemberInfo.java
index 2d4a682..6c5aad3 100644
--- a/src/com/google/doclava/MemberInfo.java
+++ b/src/com/google/doclava/MemberInfo.java
@@ -169,6 +169,10 @@
     return mAnnotations;
   }
 
+  public boolean hasShowAnnotation() {
+    return mShowAnnotations != null && mShowAnnotations.size() > 0;
+  }
+
   public ArrayList<AnnotationInstanceInfo> showAnnotations() {
     return mShowAnnotations;
   }
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index e1ada09..2010315 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -37,17 +37,20 @@
 import java.util.List;
 import java.util.Scanner;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 public class Stubs {
   public static void writeStubsAndApi(String stubsDir, String apiFile, String keepListFile,
-      String removedApiFile, HashSet<String> stubPackages) {
+      String removedApiFile, String exactApiFile, HashSet<String> stubPackages) {
     // figure out which classes we need
     final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
     ClassInfo[] all = Converter.allClasses();
     PrintStream apiWriter = null;
     PrintStream keepListWriter = null;
     PrintStream removedApiWriter = null;
+    PrintStream exactApiWriter = null;
 
     if (apiFile != null) {
       try {
@@ -80,6 +83,17 @@
             "Cannot open file for write");
       }
     }
+    if (exactApiFile != null) {
+      try {
+        File exactApi = new File(exactApiFile);
+        exactApi.getParentFile().mkdirs();
+        exactApiWriter = new PrintStream(
+            new BufferedOutputStream(new FileOutputStream(exactApi)));
+      } catch (FileNotFoundException e) {
+        Errors.error(Errors.IO_ERROR, new SourcePositionInfo(exactApiFile, 0, 0),
+            "Cannot open file for write");
+      }
+    }
     // If a class is public or protected, not hidden, and marked as included,
     // then we can't strip it
     for (ClassInfo cl : all) {
@@ -214,11 +228,18 @@
         allPackageClassMap.put(cl.containingPackage(), classes);
       }
     }
-    // write out the removed Api
+    // Write out the removed API
     if (removedApiWriter != null) {
-      writeRemovedApi(removedApiWriter, allPackageClassMap, notStrippable);
+      writePredicateApi(removedApiWriter, allPackageClassMap, notStrippable,
+          new RemovedPredicate());
       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,
@@ -1175,8 +1196,52 @@
     return returnString;
   }
 
-  static void writeRemovedApi(PrintStream apiWriter, HashMap<PackageInfo,
-      List<ClassInfo>> allPackageClassMap, Set<ClassInfo> notStrippable) {
+  public static class RemovedPredicate implements Predicate<MemberInfo> {
+    @Override
+    public boolean test(MemberInfo member) {
+      final ClassInfo clazz = member.containingClass();
+      final boolean removed = clazz.isRemoved() || member.isRemoved();
+      final boolean clazzVisible = clazz.isPublic() || clazz.isProtected();
+      final boolean memberVisible = member.isPublic() || member.isProtected();
+
+      if (removed && clazzVisible && memberVisible) {
+        if (member instanceof MethodInfo) {
+          final MethodInfo method = (MethodInfo) member;
+          return (method.findOverriddenMethod(method.name(), method.signature()) == null);
+        } else {
+          return true;
+        }
+      } else {
+        return false;
+      }
+    }
+  }
+
+  public static class ExactPredicate implements Predicate<MemberInfo> {
+    @Override
+    public boolean test(MemberInfo member) {
+      final ClassInfo clazz = member.containingClass();
+      final boolean hasShowAnnotation = member.hasShowAnnotation()
+          || member.containingClass().hasShowAnnotation();
+      final boolean clazzVisible = clazz.isPublic() || clazz.isProtected();
+      final boolean memberVisible = member.isPublic() || member.isProtected();
+
+      if (hasShowAnnotation && !member.isHiddenOrRemoved() && clazzVisible && memberVisible) {
+        if (member instanceof MethodInfo) {
+          final MethodInfo method = (MethodInfo) member;
+          return (method.findOverriddenMethod(method.name(), method.signature()) == null);
+        } else {
+          return true;
+        }
+      } else {
+        return false;
+      }
+    }
+  }
+
+  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) {
@@ -1185,15 +1250,8 @@
       Collections.sort(classes, ClassInfo.comparator);
       boolean hasWrittenPackageHead = false;
       for (ClassInfo cl : classes) {
-        if (cl.hasRemovedSelfMembers()) {
-          if (!hasWrittenPackageHead) {
-            hasWrittenPackageHead = true;
-            apiWriter.print("package ");
-            apiWriter.print(pkg.qualifiedName());
-            apiWriter.print(" {\n\n");
-          }
-          writeClassRemovedSelfMembers(apiWriter, cl, notStrippable);
-        }
+        hasWrittenPackageHead = writeClassPredicateSelfMembers(apiWriter, cl, notStrippable,
+            predicate, hasWrittenPackageHead);
       }
 
       // the package contains some classes with some removed members
@@ -1206,8 +1264,30 @@
   /**
    * Write the removed members of the class to removed.txt
    */
-  private static void writeClassRemovedSelfMembers(PrintStream apiWriter, ClassInfo cl,
-      Set<ClassInfo> notStrippable) {
+  private static boolean writeClassPredicateSelfMembers(PrintStream apiWriter, ClassInfo cl,
+      Set<ClassInfo> notStrippable, Predicate<MemberInfo> predicate,
+      boolean hasWrittenPackageHead) {
+
+    List<MethodInfo> constructors = cl.getExhaustiveConstructors().stream().filter(predicate)
+        .sorted(MethodInfo.comparator).collect(Collectors.toList());
+    List<MethodInfo> methods = cl.getExhaustiveMethods().stream().filter(predicate)
+        .sorted(MethodInfo.comparator).collect(Collectors.toList());
+    List<FieldInfo> enums = cl.getExhaustiveEnumConstants().stream().filter(predicate)
+        .sorted(FieldInfo.comparator).collect(Collectors.toList());
+    List<FieldInfo> fields = cl.getExhaustiveFields().stream().filter(predicate)
+        .sorted(FieldInfo.comparator).collect(Collectors.toList());
+
+    if (constructors.isEmpty() && methods.isEmpty() && enums.isEmpty() && fields.isEmpty()) {
+      return hasWrittenPackageHead;
+    }
+
+    if (!hasWrittenPackageHead) {
+      hasWrittenPackageHead = true;
+      apiWriter.print("package ");
+      apiWriter.print(cl.containingPackage().qualifiedName());
+      apiWriter.print(" {\n\n");
+    }
+
     apiWriter.print("  ");
     apiWriter.print(cl.scope());
     if (cl.isStatic()) {
@@ -1251,27 +1331,21 @@
 
     apiWriter.print(" {\n");
 
-    List<MethodInfo> constructors = cl.getRemovedConstructors();
     for (MethodInfo mi : constructors) {
       writeConstructorApi(apiWriter, mi);
     }
-
-    List<MethodInfo> methods = cl.getRemovedSelfMethods();
     for (MethodInfo mi : methods) {
       writeMethodApi(apiWriter, mi);
     }
-
-    List<FieldInfo> enums = cl.getRemovedSelfEnumConstants();
     for (FieldInfo fi : enums) {
       writeFieldApi(apiWriter, fi, "enum_constant");
     }
-
-    List<FieldInfo> fields = cl.getRemovedSelfFields();
     for (FieldInfo fi : fields) {
       writeFieldApi(apiWriter, fi, "field");
     }
 
     apiWriter.print("  }\n\n");
+    return hasWrittenPackageHead;
   }
 
   public static void writeApi(PrintStream apiWriter, Collection<PackageInfo> pkgs) {
diff --git a/src/com/google/doclava/TypeInfo.java b/src/com/google/doclava/TypeInfo.java
index 689ff88..7bba560 100644
--- a/src/com/google/doclava/TypeInfo.java
+++ b/src/com/google/doclava/TypeInfo.java
@@ -524,9 +524,11 @@
    */
   public static Map<String, TypeInfo> getTypeArgumentMapping(TypeInfo generic, TypeInfo typed) {
     Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
-    for (int i = 0; i < generic.typeArguments().size(); i++) {
-      if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
-        map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
+    if (generic != null && generic.typeArguments() != null) {
+      for (int i = 0; i < generic.typeArguments().size(); i++) {
+        if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
+          map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
+        }
       }
     }
     return map;