Include the transitive deps when computing the ABI key of a dep with export_deps=True.

Summary: When computing the ABI key of a dependency with export_deps=True,
the ABI keys for the transitive dependencies will be hashed in the final ABI key.

Test Plan: Added new test: DefaultJavaLibraryRuleTest.testGetAbiKeyInThePresenceOfExportDeps().
diff --git a/src/com/facebook/buck/java/DefaultJavaLibraryRule.java b/src/com/facebook/buck/java/DefaultJavaLibraryRule.java
index 5e87343..1e45507 100644
--- a/src/com/facebook/buck/java/DefaultJavaLibraryRule.java
+++ b/src/com/facebook/buck/java/DefaultJavaLibraryRule.java
@@ -309,15 +309,40 @@
       setAbiKey(Suppliers.memoize(new Supplier<Sha1HashCode>() {
         @Override
         public Sha1HashCode get() {
-          return javac.getAbiKey();
+          return createTotalAbiKey(javac.getAbiKey());
         }
       }));
     } else {
       // When there are no .java files to compile, the ABI key should be a constant.
-      setAbiKey(Suppliers.ofInstance(new Sha1HashCode(AbiWriterProtocol.EMPTY_ABI_KEY)));
+      setAbiKey(Suppliers.ofInstance(createTotalAbiKey(
+          new Sha1HashCode(AbiWriterProtocol.EMPTY_ABI_KEY))));
     }
   }
 
+  /**
+   * Creates the total ABI key for this rule. If export_deps is true, the total key is computed by
+   * hashing the ABI keys of the dependencies together with the ABI key of this rule. If export_deps
+   * is false, the standalone ABI key for this rule is used as the total key.
+   * @param abiKey the standalone ABI key for this rule.
+   * @return total ABI key containing also the ABI keys of the dependencies.
+   */
+  protected Sha1HashCode createTotalAbiKey(Sha1HashCode abiKey) {
+    if (!getExportDeps()) {
+      return abiKey;
+    }
+
+    SortedSet<JavaLibraryRule> depsForAbiKey = getDepsForAbiKey();
+    // If there are no deps to consider, just return the ABI Key for this rule.
+    if (depsForAbiKey.isEmpty()) {
+      return abiKey;
+    }
+
+    // Hash the ABI keys of all dependencies together with ABI key for the current rule.
+    Hasher hasher = createHasherWithAbiKeyForDeps(depsForAbiKey);
+    hasher.putUnencodedChars(abiKey.getHash());
+    return new Sha1HashCode(hasher.hash().toString());
+  }
+
   private String getPathToAbiOutputDir() {
     BuildTarget target = getBuildTarget();
     return String.format(
@@ -359,11 +384,18 @@
   }
 
   /**
-   * Finds all deps that implement JavaLibraryRule and hash their ABI keys together. If any dep
-   * lacks an ABI key, then returns {@link Optional#absent()}.
+   * Finds all deps that implement JavaLibraryRule and hash their ABI keys together.
    */
   @Override
   public Sha1HashCode getAbiKeyForDeps() {
+    return new Sha1HashCode(createHasherWithAbiKeyForDeps(getDepsForAbiKey()).hash().toString());
+  }
+
+  /**
+   * Returns a sorted set containing the dependencies which will be hashed in the final ABI key.
+   * @return the dependencies to be hashed in the final ABI key.
+   */
+  private SortedSet<JavaLibraryRule> getDepsForAbiKey() {
     SortedSet<JavaLibraryRule> rulesWithAbiToConsider = Sets.newTreeSet();
     for (BuildRule dep : getDeps()) {
       if (dep instanceof JavaLibraryRule) {
@@ -371,7 +403,16 @@
         rulesWithAbiToConsider.addAll(javaRule.getOutputClasspathEntries().keys());
       }
     }
+    return rulesWithAbiToConsider;
+  }
 
+  /**
+   * Creates a Hasher containing the ABI keys of the dependencies.
+   * @param rulesWithAbiToConsider a sorted set containing the dependencies whose ABI key will be
+   *     added to the hasher.
+   * @return a Hasher containing the ABI keys of the dependencies.
+   */
+  private Hasher createHasherWithAbiKeyForDeps(SortedSet<JavaLibraryRule> rulesWithAbiToConsider) {
     Hasher hasher = Hashing.sha1().newHasher();
     for (JavaLibraryRule ruleWithAbiToConsider : rulesWithAbiToConsider) {
       if (ruleWithAbiToConsider == this) {
@@ -382,7 +423,7 @@
       hasher.putUnencodedChars(abiKey.getHash());
     }
 
-    return new Sha1HashCode(hasher.hash().toString());
+    return hasher;
   }
 
   @Override
diff --git a/test/com/facebook/buck/java/DefaultJavaLibraryRuleTest.java b/test/com/facebook/buck/java/DefaultJavaLibraryRuleTest.java
index e2414fe..ec2dee2 100644
--- a/test/com/facebook/buck/java/DefaultJavaLibraryRuleTest.java
+++ b/test/com/facebook/buck/java/DefaultJavaLibraryRuleTest.java
@@ -683,7 +683,7 @@
         consumerNoExport.getAbiKeyForDeps(),
         not(equalTo(consumerWithExport.getAbiKeyForDeps())));
     String expectedAbiKeyNoDepsHashForConsumerWithExport = Hashing.sha1().newHasher()
-        .putUnencodedChars(commonWithExportAbiKeyHash)
+        .putUnencodedChars(commonWithExport.getAbiKey().getHash())
         .putUnencodedChars(tinyLibAbiKeyHash)
         .hash()
         .toString();
@@ -695,8 +695,201 @@
         observedAbiKeyNoDepsHashForConsumerWithExport);
   }
 
+  /**
+   * @see DefaultJavaLibraryRule#getAbiKey()
+   */
+  @Test
+  public void testGetAbiKeyInThePresenceOfExportDeps() throws IOException {
+    // Create a java_library named //:commonlib with a hardcoded ABI key.
+    String commonLibAbiKeyHash = Strings.repeat("a", 40);
+    JavaLibraryRule commonLibrary = createDefaultJavaLibaryRuleWithAbiKey(
+        commonLibAbiKeyHash,
+        BuildTargetFactory.newInstance("//:commonlib"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(),
+        /* exportDeps */ false);
+
+    // Create two java_library rules, each of which depends on //:commonlib, but only one of which
+    // exports its deps.
+    String libWithExportAbiKeyHash = Strings.repeat("b", 40);
+    DefaultJavaLibraryRule libWithExport = createDefaultJavaLibaryRuleWithAbiKey(
+        libWithExportAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_with_export"),
+        /* srcs */ ImmutableSet.<String>of(),
+        /* deps */ ImmutableSet.<BuildRule>of(commonLibrary),
+        /* exportDeps */ true);
+    String libNoExportAbiKeyHash = Strings.repeat("c", 40);
+    DefaultJavaLibraryRule libNoExport = createDefaultJavaLibaryRuleWithAbiKey(
+        /* abiHash */ libNoExportAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_no_export"),
+        /* srcs */ ImmutableSet.<String>of(),
+        /* deps */ ImmutableSet.<BuildRule>of(commonLibrary),
+        /* exportDeps */ false);
+
+    // Verify getAbiKey() for the two //:lib_XXX rules.
+    String expectedLibWithExportAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(commonLibAbiKeyHash)
+        .putUnencodedChars(libWithExportAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "getAbiKey() should include the dependencies' ABI keys for the rule with export_deps=true.",
+        expectedLibWithExportAbiKeyHash,
+        libWithExport.getAbiKey().getHash());
+
+    assertEquals(
+        "getAbiKey() should not include the dependencies' ABI keys for the rule with export_deps=false.",
+        libNoExportAbiKeyHash,
+        libNoExport.getAbiKey().getHash());
+  }
+
+  /**
+   * @see DefaultJavaLibraryRule#getAbiKey()
+   */
+  @Test
+  public void testGetAbiKeyRecursiveExportDeps() throws IOException {
+    String libAAbiKeyHash = Strings.repeat("a", 40);
+    String libBAbiKeyHash = Strings.repeat("b", 40);
+    String libCAbiKeyHash = Strings.repeat("c", 40);
+    String libDAbiKeyHash = Strings.repeat("d", 40);
+
+    // Test the following dependency graph:
+    // a(export_deps=true) -> b(export_deps=true) -> c(export_deps=true) -> d(export_deps=true)
+    JavaLibraryRule libD = createDefaultJavaLibaryRuleWithAbiKey(
+        libDAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_d"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(),
+        /* exportDeps */ true);
+    JavaLibraryRule libC = createDefaultJavaLibaryRuleWithAbiKey(
+        libCAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_c"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libD),
+        /* exportDeps */ true);
+    JavaLibraryRule libB = createDefaultJavaLibaryRuleWithAbiKey(
+        libBAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_b"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libC),
+        /* exportDeps */ true);
+    JavaLibraryRule libA = createDefaultJavaLibaryRuleWithAbiKey(
+        libAAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_a"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libB),
+        /* exportDeps */ true);
+
+    assertEquals(
+        "If a rule has no dependencies its final ABI key should be the rule's own ABI key.",
+        libDAbiKeyHash,
+        libD.getAbiKey().getHash());
+
+    String expectedLibCAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(libDAbiKeyHash)
+        .putUnencodedChars(libCAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "The ABI key for lib_c should contain lib_d's ABI key.",
+        expectedLibCAbiKeyHash,
+        libC.getAbiKey().getHash());
+
+    String expectedLibBAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(expectedLibCAbiKeyHash)
+        .putUnencodedChars(libDAbiKeyHash)
+        .putUnencodedChars(libBAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "The ABI key for lib_b should contain lib_c's and lib_d's ABI keys.",
+        expectedLibBAbiKeyHash,
+        libB.getAbiKey().getHash());
+
+    String expectedLibAAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(expectedLibBAbiKeyHash)
+        .putUnencodedChars(expectedLibCAbiKeyHash)
+        .putUnencodedChars(libDAbiKeyHash)
+        .putUnencodedChars(libAAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "The ABI key for lib_a should contain lib_b's, lib_c's and lib_d's ABI keys.",
+        expectedLibAAbiKeyHash,
+        libA.getAbiKey().getHash());
+
+    // Test the following dependency graph:
+    // a(export_deps=true) -> b(export_deps=true) -> c(export_deps=false) -> d(export_deps=false)
+    libD = createDefaultJavaLibaryRuleWithAbiKey(
+        libDAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_d2"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(),
+        /* exportDeps */ false);
+    libC = createDefaultJavaLibaryRuleWithAbiKey(
+        libCAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_c2"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libD),
+        /* exportDeps */ false);
+    libB = createDefaultJavaLibaryRuleWithAbiKey(
+        libBAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_b2"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libC),
+        /* exportDeps */ true);
+    libA = createDefaultJavaLibaryRuleWithAbiKey(
+        libAAbiKeyHash,
+        BuildTargetFactory.newInstance("//:lib_a2"),
+        // Must have a source file or else its ABI will be AbiWriterProtocol.EMPTY_ABI_KEY.
+        /* srcs */ ImmutableSet.of("foo/Bar.java"),
+        /* deps */ ImmutableSet.<BuildRule>of(libB),
+        /* exportDeps */ true);
+
+    assertEquals(
+        "If export_deps is false, the final ABI key should be the rule's own ABI key.",
+        libDAbiKeyHash,
+        libD.getAbiKey().getHash());
+
+    assertEquals(
+        "If export_deps is false, the final ABI key should be the rule's own ABI key.",
+        libCAbiKeyHash,
+        libC.getAbiKey().getHash());
+
+    expectedLibBAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(libCAbiKeyHash)
+        .putUnencodedChars(libBAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "The ABI key for lib_b should contain lib_c's ABI key.",
+        expectedLibBAbiKeyHash,
+        libB.getAbiKey().getHash());
+
+    expectedLibAAbiKeyHash = Hashing.sha1().newHasher()
+        .putUnencodedChars(expectedLibBAbiKeyHash)
+        .putUnencodedChars(libCAbiKeyHash)
+        .putUnencodedChars(libDAbiKeyHash)
+        .putUnencodedChars(libAAbiKeyHash)
+        .hash()
+        .toString();
+    assertEquals(
+        "The ABI key for lib_a should contain lib_b's, lib_c's and lib_d's ABI keys.",
+        expectedLibAAbiKeyHash,
+        libA.getAbiKey().getHash());
+
+  }
+
   private static DefaultJavaLibraryRule createDefaultJavaLibaryRuleWithAbiKey(
-      @Nullable final String abiHash,
+      @Nullable final String partialAbiHash,
       BuildTarget buildTarget,
       ImmutableSet<String> srcs,
       ImmutableSet<BuildRule> deps,
@@ -711,10 +904,10 @@
         ) {
       @Override
       public Sha1HashCode getAbiKey() {
-        if (abiHash == null) {
+        if (partialAbiHash == null) {
           return super.getAbiKey();
         } else {
-          return new Sha1HashCode(abiHash);
+          return createTotalAbiKey(new Sha1HashCode(partialAbiHash));
         }
       }
     };