Refactor graph enhancement logic into AndroidBinaryGraphEnhancer.

Summary:
This makes the graph enhancement logic easier to unit test, and helps
ensure that it does not modify the `AndroidBinaryRule.Builder` from which
it is used. Its only side-effect is that it updates the `BuildRuleResolver`
that is passed in, but now that distinction is clearer.

Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/android/AndroidBinaryGraphEnhancer.java b/src/com/facebook/buck/android/AndroidBinaryGraphEnhancer.java
new file mode 100644
index 0000000..4c2ff26
--- /dev/null
+++ b/src/com/facebook/buck/android/AndroidBinaryGraphEnhancer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.android;
+
+import com.facebook.buck.java.AccumulateClassNames;
+import com.facebook.buck.java.Classpaths;
+import com.facebook.buck.java.JavaLibraryRule;
+import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.model.BuildTargetPattern;
+import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
+import com.facebook.buck.rules.BuildRule;
+import com.facebook.buck.rules.BuildRuleResolver;
+import com.facebook.buck.rules.DefaultBuildRuleBuilderParams;
+import com.facebook.buck.rules.RuleKeyBuilderFactory;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+
+import java.nio.file.Path;
+
+public class AndroidBinaryGraphEnhancer {
+
+  private final ImmutableSortedSet<BuildRule> originalDeps;
+  private final ImmutableSet<BuildTarget> buildRulesToExcludeFromDex;
+  private final AbstractBuildRuleBuilderParams buildRuleBuilderParams;
+
+  AndroidBinaryGraphEnhancer(
+      ImmutableSortedSet<BuildRule> originalDeps,
+      ImmutableSet<BuildTarget> buildRulesToExcludeFromDex,
+      Function<String, Path> pathRelativizer,
+      RuleKeyBuilderFactory ruleKeyBuilderFactory) {
+    this.originalDeps = Preconditions.checkNotNull(originalDeps);
+    this.buildRulesToExcludeFromDex = Preconditions.checkNotNull(buildRulesToExcludeFromDex);
+    this.buildRuleBuilderParams = new DefaultBuildRuleBuilderParams(
+        pathRelativizer, ruleKeyBuilderFactory);
+  }
+
+  /**
+   * Creates/finds the set of build rules that correspond to pre-dex'd artifacts that should be
+   * merged to create the final classes.dex for the APK.
+   * <p>
+   * This method may modify {@code ruleResolver}, inserting new rules into its index.
+   */
+  ImmutableSet<IntermediateDexRule> createDepsForPreDexing(BuildRuleResolver ruleResolver) {
+    ImmutableSet.Builder<IntermediateDexRule> preDexDeps = ImmutableSet.builder();
+    ImmutableSet<JavaLibraryRule> transitiveJavaDeps = Classpaths
+        .getClasspathEntries(originalDeps).keySet();
+    for (JavaLibraryRule javaLibraryRule : transitiveJavaDeps) {
+      // If the rule has no output file (which happens when a java_library has no srcs or
+      // resources, but export_deps is true), then there will not be anything to dx.
+      if (javaLibraryRule.getPathToOutputFile() == null) {
+        continue;
+      }
+
+      // If the rule is in the no_dx list, then do not pre-dex it.
+      if (buildRulesToExcludeFromDex.contains(javaLibraryRule.getBuildTarget())) {
+        continue;
+      }
+
+      // See whether the corresponding IntermediateDexRule has already been added to the
+      // ruleResolver.
+      BuildTarget originalTarget = javaLibraryRule.getBuildTarget();
+      BuildTarget preDexTarget = createBuildTargetWithDexFlavor(originalTarget);
+      IntermediateDexRule preDexRule = (IntermediateDexRule) ruleResolver.get(preDexTarget);
+      if (preDexRule != null) {
+        preDexDeps.add(preDexRule);
+        continue;
+      }
+
+      // Create a rule to get the list of the classes in the JavaLibraryRule.
+      BuildTarget accumulateClassNamesBuildTarget = new BuildTarget(
+          originalTarget.getBaseName(), originalTarget.getShortName(), "class_names");
+      AccumulateClassNames.Builder accumulateClassNamesBuilder = AccumulateClassNames
+          .newAccumulateClassNamesBuilder(buildRuleBuilderParams)
+          .setBuildTarget(accumulateClassNamesBuildTarget)
+          .setJavaLibraryToDex(javaLibraryRule)
+          .addDep(originalTarget)
+          .addVisibilityPattern(BuildTargetPattern.MATCH_ALL);
+      BuildRule accumulateClassNamesRule = ruleResolver.buildAndAddToIndex(
+          accumulateClassNamesBuilder);
+      AccumulateClassNames accumulateClassNames =
+          (AccumulateClassNames) accumulateClassNamesRule.getBuildable();
+
+      // Create the IntermediateDexRule and add it to both the ruleResolver and preDexDeps.
+      IntermediateDexRule.Builder preDexBuilder = IntermediateDexRule
+          .newPreDexBuilder(buildRuleBuilderParams)
+          .setBuildTarget(preDexTarget)
+          .setAccumulateClassNamesDep(accumulateClassNames)
+          .addDep(accumulateClassNamesBuildTarget)
+          .addVisibilityPattern(BuildTargetPattern.MATCH_ALL);
+      IntermediateDexRule preDex = ruleResolver.buildAndAddToIndex(preDexBuilder);
+      preDexDeps.add(preDex);
+    }
+    return preDexDeps.build();
+  }
+
+  private static BuildTarget createBuildTargetWithDexFlavor(BuildTarget originalTarget) {
+    return new BuildTarget(originalTarget.getBaseName(),
+        originalTarget.getShortName(),
+        "dex");
+  }
+}
diff --git a/src/com/facebook/buck/android/AndroidBinaryRule.java b/src/com/facebook/buck/android/AndroidBinaryRule.java
index 28ae645..f6ece50 100644
--- a/src/com/facebook/buck/android/AndroidBinaryRule.java
+++ b/src/com/facebook/buck/android/AndroidBinaryRule.java
@@ -22,7 +22,6 @@
 import com.android.common.SdkConstants;
 import com.facebook.buck.android.FilterResourcesStep.ResourceFilter;
 import com.facebook.buck.dalvik.ZipSplitter;
-import com.facebook.buck.java.AccumulateClassNames;
 import com.facebook.buck.java.Classpaths;
 import com.facebook.buck.java.HasClasspathEntries;
 import com.facebook.buck.java.JavaLibraryRule;
@@ -39,13 +38,11 @@
 import com.facebook.buck.rules.Buildable;
 import com.facebook.buck.rules.BuildableContext;
 import com.facebook.buck.rules.BuildableProperties;
-import com.facebook.buck.rules.DefaultBuildRuleBuilderParams;
 import com.facebook.buck.rules.DependencyGraph;
 import com.facebook.buck.rules.DoNotUseAbstractBuildable;
 import com.facebook.buck.rules.FileSourcePath;
 import com.facebook.buck.rules.InstallableBuildRule;
 import com.facebook.buck.rules.RuleKey;
-import com.facebook.buck.rules.RuleKeyBuilderFactory;
 import com.facebook.buck.rules.SourcePath;
 import com.facebook.buck.shell.AbstractGenruleStep;
 import com.facebook.buck.shell.EchoStep;
@@ -78,7 +75,6 @@
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
 import com.google.common.io.Files;
 
 import java.io.File;
@@ -198,7 +194,7 @@
 
   private final FilterResourcesStep.ResourceFilter resourceFilter;
   private final ImmutableSet<TargetCpuType> cpuFilters;
-  private final ImmutableSet<DexProducedFromJavaLibraryThatContainsClassFiles> preDexDeps;
+  private final ImmutableSet<IntermediateDexRule> preDexDeps;
   private final ImmutableSortedSet<BuildRule> preprocessJavaClassesDeps;
   private final Optional<String> preprocessJavaClassesBash;
   private final AndroidTransitiveDependencyGraph transitiveDependencyGraph;
@@ -228,7 +224,7 @@
       Optional<SourcePath> primaryDexClassesFile,
       FilterResourcesStep.ResourceFilter resourceFilter,
       Set<TargetCpuType> cpuFilters,
-      Set<DexProducedFromJavaLibraryThatContainsClassFiles> preDexDeps,
+      Set<IntermediateDexRule> preDexDeps,
       Set<BuildRule> preprocessJavaClassesDeps,
       Optional<String> preprocessJavaClassesBash) {
     super(buildRuleParams);
@@ -333,7 +329,7 @@
     return this.cpuFilters;
   }
 
-  public ImmutableSet<DexProducedFromJavaLibraryThatContainsClassFiles> getPreDexDeps() {
+  public ImmutableSet<IntermediateDexRule> getPreDexDeps() {
     return preDexDeps;
   }
 
@@ -772,10 +768,12 @@
     } else {
       Iterable<Path> filesToDex = FluentIterable.from(preDexDeps)
           .transform(
-              new Function<DexProducedFromJavaLibraryThatContainsClassFiles, Path>() {
+              new Function<IntermediateDexRule, Path>() {
                   @Override
                   @Nullable
-                  public Path apply(DexProducedFromJavaLibraryThatContainsClassFiles preDex) {
+                  public Path apply(IntermediateDexRule preDexDep) {
+                    DexProducedFromJavaLibraryThatContainsClassFiles preDex = preDexDep
+                        .getBuildable();
                     if (preDex.hasOutput()) {
                       return preDex.getPathToDex();
                     } else {
@@ -1239,7 +1237,8 @@
 
     private BuildTarget keystoreTarget;
     private PackageType packageType = DEFAULT_PACKAGE_TYPE;
-    private Set<BuildTarget> buildRulesToExcludeFromDex = Sets.newHashSet();
+    private ImmutableSet.Builder<BuildTarget> buildRulesToExcludeFromDexBuilder =
+        ImmutableSet.builder();
     private boolean disablePreDex = false;
     private DexSplitMode dexSplitMode = new DexSplitMode(
         /* shouldSplitDex */ false,
@@ -1278,28 +1277,22 @@
 
       BuildRuleParams originalParams = createBuildRuleParams(ruleResolver);
       ImmutableSortedSet<BuildRule> originalDeps = originalParams.getDeps();
-      ImmutableSet<BuildRule> preDexDepsAsBuildRules;
-      ImmutableSet<DexProducedFromJavaLibraryThatContainsClassFiles> preDexDeps;
+      ImmutableSet<IntermediateDexRule> preDexDeps;
+      ImmutableSet<BuildTarget> buildRulesToExcludeFromDex =
+          buildRulesToExcludeFromDexBuilder.build();
       if (!disablePreDex
           && PackageType.DEBUG.equals(packageType)
           && !dexSplitMode.isShouldSplitDex() // TODO(mbolin): Support predex for split dex.
           && !preprocessJavaClassesBash.isPresent() // TODO(mbolin): Support predex post-preprocess.
           ) {
-        preDexDepsAsBuildRules = enhanceGraphToLeveragePreDexing(originalDeps,
+        AndroidBinaryGraphEnhancer graphEnhancer = new AndroidBinaryGraphEnhancer(
+            originalDeps,
             buildRulesToExcludeFromDex,
-            ruleResolver,
             originalParams.getPathRelativizer(),
             originalParams.getRuleKeyBuilderFactory());
-        ImmutableSet.Builder<DexProducedFromJavaLibraryThatContainsClassFiles> preDexDepsBuilder =
-            ImmutableSet.builder();
-        for (BuildRule preDexDepBuildRule : preDexDepsAsBuildRules) {
-          preDexDepsBuilder.add(
-              (DexProducedFromJavaLibraryThatContainsClassFiles) preDexDepBuildRule.getBuildable());
-        }
-        preDexDeps = preDexDepsBuilder.build();
+        preDexDeps = graphEnhancer.createDepsForPreDexing(ruleResolver);
       } else {
-        preDexDepsAsBuildRules = ImmutableSet.of();
-        preDexDeps = ImmutableSortedSet.of();
+        preDexDeps = ImmutableSet.of();
       }
 
       boolean allowNonExistentRule =
@@ -1309,7 +1302,7 @@
       // createBuildRuleParams(ruleResolver).
       ImmutableSortedSet<BuildRule> totalDeps = ImmutableSortedSet.<BuildRule>naturalOrder()
           .addAll(originalDeps)
-          .addAll(preDexDepsAsBuildRules)
+          .addAll(preDexDeps)
           .build();
       BuildRuleParams finalParams = new BuildRuleParams(getBuildTarget(),
           totalDeps,
@@ -1341,75 +1334,6 @@
           preprocessJavaClassesBash);
     }
 
-    /**
-     * @return The set of build rules that correspond to pre-dex'd artifacts that should be merged
-     *     to create the final classes.dex for the APK. For every element in the set, its
-     *     {@link BuildRule#getBuildable()} method will return an instance of
-     *     {@link DexProducedFromJavaLibraryThatContainsClassFiles}.
-     */
-    private ImmutableSet<BuildRule> enhanceGraphToLeveragePreDexing(
-            ImmutableSortedSet<BuildRule> originalDeps,
-            Set<BuildTarget> buildRulesToExcludeFromDex,
-            BuildRuleResolver ruleResolver,
-            Function<String, Path> pathRelativizer,
-            RuleKeyBuilderFactory ruleKeyBuilderFactory) {
-      ImmutableSet.Builder<BuildRule> preDexDeps = ImmutableSet.builder();
-      ImmutableSet<JavaLibraryRule> transitiveJavaDeps = Classpaths
-          .getClasspathEntries(originalDeps).keySet();
-      for (JavaLibraryRule javaLibraryRule : transitiveJavaDeps) {
-        // If the rule has no output file (which happens when a java_library has no srcs or
-        // resources, but export_deps is true), then there will not be anything to dx.
-        if (javaLibraryRule.getPathToOutputFile() == null) {
-          continue;
-        }
-
-        // If the rule is in the no_dx list, then do not pre-dex it.
-        if (buildRulesToExcludeFromDex.contains(javaLibraryRule.getBuildTarget())) {
-          continue;
-        }
-
-        // See whether the corresponding PreDex has already been added to the ruleResolver.
-        BuildTarget originalTarget = javaLibraryRule.getBuildTarget();
-        BuildTarget preDexTarget = new BuildTarget(originalTarget.getBaseName(),
-            originalTarget.getShortName(),
-            "dex");
-        BuildRule preDexRule = ruleResolver.get(preDexTarget);
-        if (preDexRule != null) {
-          preDexDeps.add(preDexRule);
-          continue;
-        }
-
-        // Create a rule to get the list of the classes in the JavaLibraryRule.
-        AccumulateClassNames.Builder accumulateClassNamesBuilder = AccumulateClassNames
-            .newAccumulateClassNamesBuilder(new DefaultBuildRuleBuilderParams(
-                pathRelativizer, ruleKeyBuilderFactory));
-        BuildTarget accumulateClassNamesBuildTarget = new BuildTarget(
-            originalTarget.getBaseName(), originalTarget.getShortName(), "class_names");
-        accumulateClassNamesBuilder.setBuildTarget(accumulateClassNamesBuildTarget);
-        accumulateClassNamesBuilder.setJavaLibraryToDex(javaLibraryRule);
-        accumulateClassNamesBuilder.addDep(originalTarget);
-        accumulateClassNamesBuilder.addVisibilityPattern(BuildTargetPattern.MATCH_ALL);
-        BuildRule accumulateClassNamesRule = ruleResolver.buildAndAddToIndex(
-            accumulateClassNamesBuilder);
-        AccumulateClassNames accumulateClassNames =
-            (AccumulateClassNames) accumulateClassNamesRule.getBuildable();
-
-        // Create the PreDex and add it to both the ruleResolver and preDexDeps.
-        IntermediateDexRule.Builder preDexBuilder =
-            IntermediateDexRule.newPreDexBuilder(
-                new DefaultBuildRuleBuilderParams(
-                    pathRelativizer,
-                    ruleKeyBuilderFactory));
-        preDexBuilder.setBuildTarget(preDexTarget);
-        preDexBuilder.setAccumulateClassNamesDep(accumulateClassNames);
-        preDexBuilder.addDep(accumulateClassNamesBuildTarget);
-        preDexBuilder.addVisibilityPattern(BuildTargetPattern.MATCH_ALL);
-        BuildRule preDex = ruleResolver.buildAndAddToIndex(preDexBuilder);
-        preDexDeps.add(preDex);
-      }
-      return preDexDeps.build();
-    }
-
     @Override
     public Builder setBuildTarget(BuildTarget buildTarget) {
       super.setBuildTarget(buildTarget);
@@ -1460,7 +1384,7 @@
     }
 
     public Builder addBuildRuleToExcludeFromDex(BuildTarget entry) {
-      this.buildRulesToExcludeFromDex.add(entry);
+      this.buildRulesToExcludeFromDexBuilder.add(entry);
       return this;
     }
 
diff --git a/src/com/facebook/buck/android/BUCK b/src/com/facebook/buck/android/BUCK
index 7270ae2..a807bee 100644
--- a/src/com/facebook/buck/android/BUCK
+++ b/src/com/facebook/buck/android/BUCK
@@ -41,6 +41,7 @@
 
 RULES_SRCS = [
   'AndroidBinaryBuildRuleFactory.java',
+  'AndroidBinaryGraphEnhancer.java',
   'AndroidBinaryRule.java',
   'AndroidDexTransitiveDependencies.java',
   'AndroidInstrumentationApk.java',
diff --git a/src/com/facebook/buck/android/IntermediateDexRule.java b/src/com/facebook/buck/android/IntermediateDexRule.java
index 8d5b301..cdd2bc9 100644
--- a/src/com/facebook/buck/android/IntermediateDexRule.java
+++ b/src/com/facebook/buck/android/IntermediateDexRule.java
@@ -17,6 +17,8 @@
 package com.facebook.buck.android;
 
 import com.facebook.buck.java.AccumulateClassNames;
+import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.model.BuildTargetPattern;
 import com.facebook.buck.rules.AbiRule;
 import com.facebook.buck.rules.AbstractBuildRuleBuilder;
 import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
@@ -24,7 +26,6 @@
 import com.facebook.buck.rules.BuildRuleParams;
 import com.facebook.buck.rules.BuildRuleResolver;
 import com.facebook.buck.rules.BuildRuleType;
-import com.facebook.buck.rules.Buildable;
 import com.facebook.buck.rules.Sha1HashCode;
 import com.google.common.base.Preconditions;
 
@@ -39,7 +40,7 @@
   }
 
   @Override
-  public Buildable getBuildable() {
+  public DexProducedFromJavaLibraryThatContainsClassFiles getBuildable() {
     return buildable;
   }
 
@@ -82,5 +83,23 @@
       this.javaLibraryWithClassesList = javaLibraryWithClassesList;
       return this;
     }
+
+    @Override
+    public Builder setBuildTarget(BuildTarget buildTarget) {
+      super.setBuildTarget(buildTarget);
+      return this;
+    }
+
+    @Override
+    public Builder addDep(BuildTarget buildTarget) {
+      super.addDep(buildTarget);
+      return this;
+    }
+
+    @Override
+    public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) {
+      super.addVisibilityPattern(visibilityPattern);
+      return this;
+    }
   }
 }
diff --git a/src/com/facebook/buck/java/AccumulateClassNames.java b/src/com/facebook/buck/java/AccumulateClassNames.java
index a0069cc..0f9d6ff 100644
--- a/src/com/facebook/buck/java/AccumulateClassNames.java
+++ b/src/com/facebook/buck/java/AccumulateClassNames.java
@@ -17,6 +17,7 @@
 package com.facebook.buck.java;
 
 import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.model.BuildTargetPattern;
 import com.facebook.buck.rules.AbiRule;
 import com.facebook.buck.rules.AbstractBuildRuleBuilderParams;
 import com.facebook.buck.rules.AbstractBuildable;
@@ -214,5 +215,22 @@
       return this;
     }
 
+    @Override
+    public Builder setBuildTarget(BuildTarget buildTarget) {
+      super.setBuildTarget(buildTarget);
+      return this;
+    }
+
+    @Override
+    public Builder addDep(BuildTarget buildTarget) {
+      super.addDep(buildTarget);
+      return this;
+    }
+
+    @Override
+    public Builder addVisibilityPattern(BuildTargetPattern visibilityPattern) {
+      super.addVisibilityPattern(visibilityPattern);
+      return this;
+    }
   }
 }
diff --git a/test/com/facebook/buck/android/AndroidBinaryGraphEnhancerTest.java b/test/com/facebook/buck/android/AndroidBinaryGraphEnhancerTest.java
new file mode 100644
index 0000000..dcea3ff
--- /dev/null
+++ b/test/com/facebook/buck/android/AndroidBinaryGraphEnhancerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013-present Facebook, Inc.
+ *
+ * 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.facebook.buck.android;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.facebook.buck.java.DefaultJavaLibraryRule;
+import com.facebook.buck.model.BuildTarget;
+import com.facebook.buck.rules.BuildRule;
+import com.facebook.buck.rules.BuildRuleResolver;
+import com.facebook.buck.rules.DefaultBuildRuleBuilderParams;
+import com.facebook.buck.rules.FakeRuleKeyBuilderFactory;
+import com.facebook.buck.rules.RuleKeyBuilderFactory;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+
+import org.junit.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Iterator;
+
+public class AndroidBinaryGraphEnhancerTest {
+
+  @Test
+  public void testCreateDepsForPreDexing() {
+    BuildRuleResolver ruleResolver = new BuildRuleResolver();
+    Function<String, Path> pathRelativizer = new Function<String, Path>() {
+      @Override
+      public Path apply(String input) {
+        return Paths.get(input);
+      }
+    };
+    RuleKeyBuilderFactory ruleKeyBuilderFactory = new FakeRuleKeyBuilderFactory();
+
+    // Create three Java rules, :dep1, :dep2, and :lib. :lib depends on :dep1 and :dep2.
+    BuildTarget javaDep1BuildTarget = new BuildTarget("//java/com/example", "dep1");
+    ruleResolver.buildAndAddToIndex(
+        DefaultJavaLibraryRule.newJavaLibraryRuleBuilder(
+            new DefaultBuildRuleBuilderParams(pathRelativizer, ruleKeyBuilderFactory))
+            .setBuildTarget(javaDep1BuildTarget)
+            .addSrc("java/com/example/Dep1.java"));
+
+    BuildTarget javaDep2BuildTarget = new BuildTarget("//java/com/example", "dep2");
+    ruleResolver.buildAndAddToIndex(
+        DefaultJavaLibraryRule.newJavaLibraryRuleBuilder(
+            new DefaultBuildRuleBuilderParams(pathRelativizer, ruleKeyBuilderFactory))
+            .setBuildTarget(javaDep2BuildTarget)
+            .addSrc("java/com/example/Dep2.java"));
+
+    BuildTarget javaLibBuildTarget = new BuildTarget("//java/com/example", "lib");
+    DefaultJavaLibraryRule javaLib = ruleResolver.buildAndAddToIndex(
+        DefaultJavaLibraryRule.newJavaLibraryRuleBuilder(
+            new DefaultBuildRuleBuilderParams(pathRelativizer, ruleKeyBuilderFactory))
+            .setBuildTarget(javaLibBuildTarget)
+            .addSrc("java/com/example/Lib.java")
+            .addDep(javaDep1BuildTarget)
+            .addDep(javaDep2BuildTarget));
+
+    // Assume we are enhancing an android_binary rule whose only dep
+    // is //java/com/example:lib, and that //java/com/example:dep2 is in its no_dx list.
+    ImmutableSortedSet<BuildRule> originalDeps = ImmutableSortedSet.<BuildRule>of(javaLib);
+    ImmutableSet<BuildTarget> buildRulesToExcludeFromDex = ImmutableSet.of(javaDep2BuildTarget);
+    AndroidBinaryGraphEnhancer graphEnhancer = new AndroidBinaryGraphEnhancer(
+        originalDeps,
+        buildRulesToExcludeFromDex,
+        pathRelativizer,
+        ruleKeyBuilderFactory);
+    ImmutableSet<IntermediateDexRule> depsForPreDexing = graphEnhancer.createDepsForPreDexing(
+        ruleResolver);
+    assertEquals(
+        "There should be a #dex rule for dep1 and lib, but not dep2 because it is in the no_dx " +
+            "list.",
+        2,
+    		depsForPreDexing.size());
+
+    Iterator<IntermediateDexRule> depsForPreDexingIter = depsForPreDexing.iterator();
+    BuildRule preDexRule1 = depsForPreDexingIter.next();
+    assertEquals("//java/com/example:dep1#dex", preDexRule1.getBuildTarget().toString());
+    assertNotNull(ruleResolver.get(preDexRule1.getBuildTarget()));
+
+    BuildRule preDexRule2 = depsForPreDexingIter.next();
+    assertEquals("//java/com/example:lib#dex", preDexRule2.getBuildTarget().toString());
+    assertNotNull(ruleResolver.get(preDexRule2.getBuildTarget()));
+  }
+}