release-request-68cc9b2a-98ae-4fbf-8b56-3e535855f399-for-git_oc-mr1-release-4269864 snap-temp-L25700000092502312

Change-Id: I396782341da824d0234f17222080aea5e1c3049a
diff --git a/res/assets/templates-sdk/page_info.cs b/res/assets/templates-sdk/page_info.cs
index fad1274..4bdda67 100644
--- a/res/assets/templates-sdk/page_info.cs
+++ b/res/assets/templates-sdk/page_info.cs
@@ -23,11 +23,15 @@
 ?>
 <div id="api-info-block">
 <div class="api-level">
-  <?cs call:since_tags(class) ?><?cs
-  if:class.deprecatedsince
-    ?><br>Deprecated since <a href="<?cs var:toroot ?>guide/topics/manifest/uses-sdk-element.html#ApiLevels"
-        >API level <?cs var:class.deprecatedsince ?></a><?cs
-  /if ?>
+  <?cs call:since_tags(class) ?>
+  <?cs if:class.artifact ?>
+    <br><?cs call:artifact_tags(class) ?>
+  <?cs /if ?>
+  <?cs if:class.deprecatedsince ?>
+    <br>Deprecated since
+    <a href="<?cs var:toroot ?>guide/topics/manifest/uses-sdk-element.html#ApiLevels">API level
+      <?cs var:class.deprecatedsince ?></a>
+  <?cs /if ?>
   <?cs call:federated_refs(class) ?>
 </div>
 
diff --git a/res/assets/templates/macros.cs b/res/assets/templates/macros.cs
index 3bf36e2..cf8c86e 100644
--- a/res/assets/templates/macros.cs
+++ b/res/assets/templates/macros.cs
@@ -274,6 +274,13 @@
 /if ?><?cs
 /def ?><?cs
 
+# print the artifact ?><?cs
+def:artifact_tags(obj) ?><?cs
+if:reference.artifacts && obj.artifact ?>
+  belongs to Maven artifact <?cs var:obj.artifact ?></a><?cs
+/if ?><?cs
+/def ?><?cs
+
 def:federated_refs(obj) ?>
   <?cs if:subcount(obj.federated) ?>
     <div>
diff --git a/src/com/google/doclava/ArtifactTagger.java b/src/com/google/doclava/ArtifactTagger.java
new file mode 100644
index 0000000..3c5ee06
--- /dev/null
+++ b/src/com/google/doclava/ArtifactTagger.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.doclava.apicheck.ApiCheck;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Applies version information to the Doclava class model from apicheck XML files.
+ * <p>
+ * Sample usage:
+ * <pre>
+ *   ClassInfo[] classInfos = ...
+ *
+ *   ArtifactTagger artifactTagger = new ArtifactTagger()
+ *   artifactTagger.addArtifact("frameworks/support/core-ui/api/current.xml",
+ *       "com.android.support:support-core-ui:26.0.0")
+ *   artifactTagger.addArtifact("frameworks/support/design/api/current.xml",
+ *       "com.android.support:support-design:26.0.0")
+ *   artifactTagger.tagAll(...);
+ * </pre>
+ */
+public class ArtifactTagger {
+
+  private final Map<String, String> xmlToArtifact = new LinkedHashMap<>();
+
+  /**
+   * Specifies the apicheck XML file associated with an artifact.
+   * <p>
+   * This method should only be called once per artifact.
+   *
+   * @param file an apicheck XML file
+   * @param mavenSpec the Maven spec for the artifact to which the XML file belongs
+   */
+  public void addArtifact(String file, String mavenSpec) {
+    xmlToArtifact.put(file, mavenSpec);
+  }
+
+  /**
+   * Tags the specified docs with artifact information.
+   *
+   * @param classDocs the docs to tag
+   */
+  public void tagAll(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()) {
+      String xmlFile = artifactSpec.getKey();
+      String artifactName = artifactSpec.getValue();
+
+      ApiInfo specApi;
+      try {
+        specApi = new ApiCheck().parseApi(xmlFile);
+      } catch (ApiParseException e) {
+        StringWriter stackTraceWriter = new StringWriter();
+        e.printStackTrace(new PrintWriter(stackTraceWriter));
+        Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null,
+            "Failed to parse " + xmlFile + " for " + artifactName + " artifact data.\n"
+                + stackTraceWriter.toString());
+        continue;
+      }
+
+      applyArtifactsFromSpec(artifactName, specApi, classDocs);
+    }
+
+    if (!xmlToArtifact.isEmpty()) {
+      warnForMissingArtifacts(classDocs);
+    }
+  }
+
+  /**
+   * Returns {@code true} if any artifact mappings are specified.
+   */
+  public boolean hasArtifacts() {
+    return !xmlToArtifact.isEmpty();
+  }
+
+  /**
+   * Writes an index of the artifact names to {@code data}.
+   */
+  public void writeArtifactNames(Data data) {
+    int index = 1;
+    for (String artifact : xmlToArtifact.values()) {
+      data.setValue("artifact." + index + ".name", artifact);
+      index++;
+    }
+  }
+
+  /**
+   * Applies artifact information to {@code classDocs} where not already present.
+   *
+   * @param mavenSpec the Maven spec for the artifact
+   * @param specApi the spec for this artifact
+   * @param classDocs the docs to update
+   */
+  private void applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi, ClassInfo[] classDocs) {
+    for (ClassInfo classDoc : classDocs) {
+      PackageInfo packageSpec = specApi.getPackages().get(classDoc.containingPackage().name());
+      if (packageSpec != null) {
+        ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
+        if (classSpec != null) {
+          if (classDoc.getArtifact() == null) {
+            classDoc.setArtifact(mavenSpec);
+          } else {
+            Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null, "Class "
+                + classDoc.name() + " belongs to multiple artifacts: " + classDoc.getArtifact()
+                + " and " + mavenSpec);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Warns if any symbols are missing artifact information. When configured properly, this will
+   * yield zero warnings because {@code apicheck} guarantees that all symbols are present in the
+   * most recent API.
+   *
+   * @param classDocs the docs to verify
+   */
+  private void warnForMissingArtifacts(ClassInfo[] classDocs) {
+    for (ClassInfo claz : classDocs) {
+      if (checkLevelRecursive(claz) && claz.getArtifact() == null) {
+        Errors.error(Errors.NO_ARTIFACT_DATA, claz.position(), "XML missing class "
+            + claz.qualifiedName());
+      }
+    }
+  }
+
+  /**
+   * Returns true if {@code claz} and all containing classes are documented. The result may be used
+   * to filter out members that exist in the API data structure but aren't a part of the API.
+   */
+  private boolean checkLevelRecursive(ClassInfo claz) {
+    for (ClassInfo c = claz; c != null; c = c.containingClass()) {
+      if (!c.checkLevel()) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index b152508..3a4090c 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -1123,6 +1123,7 @@
     if (isDeprecated()) {
       data.setValue(base + ".deprecatedsince", getDeprecatedSince());
     }
+    data.setValue(base + ".artifact", getArtifact());
 
     ArrayList<AnnotationInstanceInfo> showAnnos = getShowAnnotationsIncludeOuters();
     AnnotationInstanceInfo.makeLinkListHDF(
@@ -1193,6 +1194,7 @@
     if (isDeprecated()) {
       data.setValue("class.deprecatedsince", getDeprecatedSince());
     }
+    data.setValue("class.artifact", getArtifact());
     setFederatedReferences(data, "class");
 
     // the containing package -- note that this can be passed to type_link,
@@ -1510,9 +1512,6 @@
   public boolean isRemovedImpl() {
     ClassInfo cl = this;
     while (cl != null) {
-      if (cl.hasShowAnnotation()) {
-        return false;
-      }
       PackageInfo pkg = cl.containingPackage();
       if (pkg != null && pkg.hasRemovedComment()) {
         return true;
diff --git a/src/com/google/doclava/DocInfo.java b/src/com/google/doclava/DocInfo.java
index d8a1961..650ce0d 100644
--- a/src/com/google/doclava/DocInfo.java
+++ b/src/com/google/doclava/DocInfo.java
@@ -104,6 +104,26 @@
     return mSince;
   }
 
+  /**
+   * Sets the artifact in which the class resides.
+   * <p>
+   * This property should be specified as a full Maven dependency spec. For example, a Support
+   * Library core utility class may use "com.android.support:support-core-utils:26.0.1".
+   *
+   * @param artifact the artifact in which the class resides
+   * @return
+   */
+  public void setArtifact(String artifact) {
+    mArtifact = artifact;
+  }
+
+  /**
+   * Returns the artifact in which the class resides.
+   */
+  public String getArtifact() {
+    return mArtifact;
+  }
+
   public void setDeprecatedSince(String since) {
     mDeprecatedSince = since;
   }
@@ -137,6 +157,7 @@
   Comment mComment;
   SourcePositionInfo mPosition;
   private String mSince;
+  private String mArtifact;
   private String mDeprecatedSince;
   private Set<FederatedSite> mFederatedReferences = new LinkedHashSet<FederatedSite>();
 }
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index 0327114..e1c92f1 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -101,6 +101,7 @@
   public static Map<Character, String> escapeChars = new HashMap<Character, String>();
   public static String title = "";
   public static SinceTagger sinceTagger = new SinceTagger();
+  public static ArtifactTagger artifactTagger = new ArtifactTagger();
   public static HashSet<String> knownTags = new HashSet<String>();
   public static FederationTagger federationTagger = new FederationTagger();
   public static Set<String> showAnnotations = new HashSet<String>();
@@ -309,6 +310,8 @@
         parseComments = true;
       } else if (a[0].equals("-since")) {
         sinceTagger.addVersion(a[1], a[2]);
+      } else if (a[0].equals("-artifact")) {
+        artifactTagger.addArtifact(a[1], a[2]);
       } else if (a[0].equals("-offlinemode")) {
         offlineMode = true;
       } else if (a[0].equals("-metadataDebug")) {
@@ -433,6 +436,9 @@
         // Apply @since tags from the XML file
         sinceTagger.tagAll(Converter.rootClasses());
 
+        // Apply @artifact tags from the XML file
+        artifactTagger.tagAll(Converter.rootClasses());
+
         // Apply details of federated documentation
         federationTagger.tagAll(Converter.rootClasses());
 
@@ -833,6 +839,9 @@
     if (option.equals("-since")) {
       return 3;
     }
+    if (option.equals("-artifact")) {
+      return 3;
+    }
     if (option.equals("-offlinemode")) {
       return 1;
     }
@@ -979,6 +988,7 @@
       }
       data.setValue("reference", "1");
       data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
+      data.setValue("reference.artifacts", artifactTagger.hasArtifacts() ? "1" : "0");
       data.setValue("docs.packages." + i + ".name", s);
       data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
       data.setValue("docs.packages." + i + ".since", pkg.getSince());
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
index 48202a3..9edd239 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -311,6 +311,8 @@
   public static final Error BROADCAST_BEHAVIOR = new Error(126, LINT);
   public static final Error SDK_CONSTANT = new Error(127, LINT);
   public static final Error TODO = new Error(128, LINT);
+  public static final Error NO_ARTIFACT_DATA = new Error(129, HIDDEN);
+  public static final Error BROKEN_ARTIFACT_FILE = new Error(130, ERROR);
 
   public static boolean setErrorLevel(int code, int level) {
     for (Error e : sErrors) {
diff --git a/src/com/google/doclava/MemberInfo.java b/src/com/google/doclava/MemberInfo.java
index 8c88648..8e7863e 100644
--- a/src/com/google/doclava/MemberInfo.java
+++ b/src/com/google/doclava/MemberInfo.java
@@ -53,14 +53,6 @@
   }
 
   @Override
-  public boolean isRemoved() {
-    if (mShowAnnotations.size() > 0) {
-      return false;
-    }
-    return super.isRemoved();
-  }
-
-  @Override
   public boolean isHiddenOrRemoved() {
     return isHidden() || isRemoved();
   }
diff --git a/src/com/google/doclava/NavTree.java b/src/com/google/doclava/NavTree.java
index 03926b2..5d055db 100644
--- a/src/com/google/doclava/NavTree.java
+++ b/src/com/google/doclava/NavTree.java
@@ -165,7 +165,7 @@
 
     for (ClassInfo cl : classes) {
       if (cl.checkLevel()) {
-        children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince()));
+        children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince(), cl.getArtifact()));
       }
     }
 
@@ -179,12 +179,18 @@
     private String mLink;
     List<Node> mChildren;
     private String mSince;
+    private String mArtifact;
 
     Node(String label, String link, List<Node> children, String since) {
+      this(label, link, children, since, null);
+    }
+
+    Node(String label, String link, List<Node> children, String since, String artifact) {
       mLabel = label;
       mLink = link;
       mChildren = children;
       mSince = since;
+      mArtifact = artifact;
     }
 
     static void renderString(StringBuilder buf, String s) {
@@ -243,6 +249,8 @@
       renderChildren(buf);
       buf.append(", ");
       renderString(buf, mSince);
+      buf.append(", ");
+      renderString(buf, mArtifact);
       buf.append(" ]");
     }
   }
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 479b5ae..4dc859d 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -1230,14 +1230,16 @@
       ClassInfo clazz = member.containingClass();
 
       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();
       }
 
-      if (visible && removed) {
+      if (visible && !hidden && removed) {
         if (member instanceof MethodInfo) {
           final MethodInfo method = (MethodInfo) member;
           return (method.findOverriddenMethod(method.name(), method.signature()) == null);
@@ -1257,15 +1259,17 @@
 
       boolean visible = member.isPublic() || member.isProtected();
       boolean hasShowAnnotation = member.hasShowAnnotation();
-      boolean hiddenOrRemoved = member.isHiddenOrRemoved();
+      boolean hidden = member.isHidden();
+      boolean removed = member.isRemoved();
       while (clazz != null) {
         visible &= clazz.isPublic() || clazz.isProtected();
         hasShowAnnotation |= clazz.hasShowAnnotation();
-        hiddenOrRemoved |= clazz.isHiddenOrRemoved();
+        hidden |= clazz.isHidden();
+        removed |= clazz.isRemoved();
         clazz = clazz.containingClass();
       }
 
-      if (visible && hasShowAnnotation && !hiddenOrRemoved) {
+      if (visible && hasShowAnnotation && !hidden && !removed) {
         if (member instanceof MethodInfo) {
           final MethodInfo method = (MethodInfo) member;
           return (method.findOverriddenMethod(method.name(), method.signature()) == null);