Add support for --json for `audit classpath`.

Summary:
Return the classpath for multiple targets, grouped by
target; sort stuff to make sure the output is stable.

Note: this doesn't throw if one of the targets
provided does not have a classpath; it's omitted from the output
instead.
diff --git a/src/com/facebook/buck/cli/AuditClasspathCommand.java b/src/com/facebook/buck/cli/AuditClasspathCommand.java
index 4672a92..1fa829e 100644
--- a/src/com/facebook/buck/cli/AuditClasspathCommand.java
+++ b/src/com/facebook/buck/cli/AuditClasspathCommand.java
@@ -27,10 +27,13 @@
 import com.facebook.buck.rules.BuildRuleType;
 import com.facebook.buck.rules.DependencyGraph;
 import com.facebook.buck.util.HumanReadableException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
+import com.google.common.collect.TreeMultimap;
 
 import java.io.IOException;
 import java.util.List;
@@ -83,6 +86,8 @@
 
     if (options.shouldGenerateDotOutput()) {
       return printDotOutput(partialGraph.getDependencyGraph());
+    } else if (options.shouldGenerateJsonOutput()) {
+      return printJsonClasspath(partialGraph);
     } else {
       return printClasspath(partialGraph);
     }
@@ -132,6 +137,35 @@
     return 0;
   }
 
+  @VisibleForTesting
+  int printJsonClasspath(PartialGraph partialGraph) throws IOException {
+    DependencyGraph graph = partialGraph.getDependencyGraph();
+    List<BuildTarget> targets = partialGraph.getTargets();
+    Multimap<String, String> targetClasspaths = TreeMultimap.create();
+
+    for (BuildTarget target : targets) {
+      BuildRule rule = graph.findBuildRuleByTarget(target);
+      if (!(rule instanceof HasClasspathEntries)) {
+        continue;
+      }
+
+      HasClasspathEntries classpathEntries = (HasClasspathEntries) rule;
+      targetClasspaths.putAll(
+          target.getFullyQualifiedName(),
+          classpathEntries.getTransitiveClasspathEntries().values());
+    }
+
+    ObjectMapper mapper = new ObjectMapper();
+
+    // Note: using `asMap` here ensures that the keys are sorted
+    mapper.writeValue(
+        console.getStdOut(),
+        targetClasspaths.asMap());
+
+    return 0;
+  }
+
+
   @Override
   String getUsageIntro() {
     return "provides facilities to audit build targets' classpaths";
diff --git a/src/com/facebook/buck/cli/AuditCommandOptions.java b/src/com/facebook/buck/cli/AuditCommandOptions.java
index fdcd1b1..5667a27 100644
--- a/src/com/facebook/buck/cli/AuditCommandOptions.java
+++ b/src/com/facebook/buck/cli/AuditCommandOptions.java
@@ -36,6 +36,10 @@
       usage = "Print dependencies as Dot graph")
   private boolean generateDotOutput;
 
+  @Option(name = "--json",
+	    usage = "Output in JSON format")
+  private boolean generateJsonOutput;
+
   @Argument
   private List<String> arguments = Lists.newArrayList();
 
@@ -54,4 +58,8 @@
   public boolean shouldGenerateDotOutput() {
     return generateDotOutput;
   }
+
+  public boolean shouldGenerateJsonOutput() {
+    return generateJsonOutput;
+  }
 }
diff --git a/test/com/facebook/buck/cli/AuditClasspathCommandTest.java b/test/com/facebook/buck/cli/AuditClasspathCommandTest.java
index a0fc18a..8f31a96 100644
--- a/test/com/facebook/buck/cli/AuditClasspathCommandTest.java
+++ b/test/com/facebook/buck/cli/AuditClasspathCommandTest.java
@@ -42,6 +42,7 @@
 import com.facebook.buck.util.environment.Platform;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -50,6 +51,7 @@
 import org.junit.Test;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.SortedSet;
@@ -176,4 +178,43 @@
     assertEquals(expectedClasspath, console.getTextWrittenToStdOut());
     assertEquals("", console.getTextWrittenToStdErr());
   }
+
+  private static final String EXPECTED_JSON = Joiner.on("").join(
+      "{",
+      "\"//:test-android-library\":",
+      "[",
+      "\"buck-out/gen/lib__test-android-library__output/test-android-library.jar\",",
+      "\"buck-out/gen/lib__test-java-library__output/test-java-library.jar\"",
+      "],",
+      "\"//:test-java-library\":",
+      "[",
+      "\"buck-out/gen/lib__test-java-library__output/test-java-library.jar\"",
+      "]",
+      "}");
+
+  @Test
+  public void testJsonClassPathOutput() throws IOException {
+    // Build a DependencyGraph of build rules manually.
+    BuildRuleResolver ruleResolver = new BuildRuleResolver();
+    ImmutableList<String> targets = ImmutableList.of(
+        "//:test-android-library",
+        "//:test-java-library");
+
+    ruleResolver.buildAndAddToIndex(
+        DefaultJavaLibraryRule.newJavaLibraryRuleBuilder(new FakeAbstractBuildRuleBuilderParams())
+            .setBuildTarget(BuildTargetFactory.newInstance("//:test-java-library"))
+            .addSrc("src/com/facebook/TestJavaLibrary.java"));
+    ruleResolver.buildAndAddToIndex(
+        DefaultJavaLibraryRule.newJavaLibraryRuleBuilder(new FakeAbstractBuildRuleBuilderParams())
+            .setBuildTarget(BuildTargetFactory.newInstance("//:test-android-library"))
+            .addSrc("src/com/facebook/TestAndroidLibrary.java")
+            .addDep(BuildTargetFactory.newInstance("//:test-java-library")));
+
+    PartialGraph partialGraph = createGraphFromBuildRules(ruleResolver, targets);
+    auditClasspathCommand.printJsonClasspath(partialGraph);
+
+    assertEquals(EXPECTED_JSON, console.getTextWrittenToStdOut());
+    assertEquals("", console.getTextWrittenToStdErr());
+  }
+
 }