AccumulateClassNamesStep now includes the hashes of the .class files in classes.txt.

Summary:
This is the first step in making it so that
`DexProducedFromJavaLibraryThatContainsClassFiles` does not re-dex if the `.class`
files from its dependent `JavaLibraryRule` are unchanged.

Test Plan: Sandcastle builds.
diff --git a/src/com/facebook/buck/java/AccumulateClassNamesStep.java b/src/com/facebook/buck/java/AccumulateClassNamesStep.java
index 0eb7c2f..fd1ccbc 100644
--- a/src/com/facebook/buck/java/AccumulateClassNamesStep.java
+++ b/src/com/facebook/buck/java/AccumulateClassNamesStep.java
@@ -23,12 +23,21 @@
 import com.facebook.buck.step.AbstractExecutionStep;
 import com.facebook.buck.step.ExecutionContext;
 import com.facebook.buck.step.Step;
+import com.google.common.base.Function;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Iterables;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.InputSupplier;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * {@link Step} that takes a directory or zip of {@code .class} files and traverses it to get the
@@ -37,6 +46,12 @@
 public class AccumulateClassNamesStep extends AbstractExecutionStep {
 
   /**
+   * In the generated {@code classes.txt} file, each line will contain the path to a {@code .class}
+   * file (without its suffix) and the SHA-1 hash of its contents, separated by this separator.
+   */
+  static final String CLASS_NAME_HASH_CODE_SEPARATOR = " ";
+
+  /**
    * Entries in the {@link #pathToJarOrClassesDirectory} that end with this suffix will be written
    * to {@link #whereClassNamesShouldBeWritten}. Since they all share the same suffix, the suffix
    * will be stripped when written to {@link #whereClassNamesShouldBeWritten}.
@@ -55,17 +70,26 @@
 
   @Override
   public int execute(ExecutionContext context) {
-    final ImmutableSortedSet.Builder<String> classNamesBuilder = ImmutableSortedSet.naturalOrder();
+    final ImmutableSortedMap.Builder<String, HashCode> classNamesBuilder =
+        ImmutableSortedMap.naturalOrder();
     Path path = context.getProjectFilesystem().resolve(pathToJarOrClassesDirectory);
     ClasspathTraversal traversal = new ClasspathTraversal(Collections.singleton(path)) {
       @Override
-      public void visit(FileLike fileLike) throws IOException {
+      public void visit(final FileLike fileLike) throws IOException {
         String name = fileLike.getRelativePath();
 
         // When traversing a JAR file, it may have resources or directory entries that do not end
         // in .class, which should be ignored.
         if (name.endsWith(CLASS_NAME_SUFFIX)) {
-          classNamesBuilder.add(name.substring(0, name.length() - CLASS_NAME_SUFFIX.length()));
+          String key = name.substring(0, name.length() - CLASS_NAME_SUFFIX.length());
+          InputSupplier<InputStream> input = new InputSupplier<InputStream>() {
+            @Override
+            public InputStream getInput() throws IOException {
+              return fileLike.getInput();
+            }
+          };
+          HashCode value = ByteStreams.hash(input, Hashing.sha1());
+          classNamesBuilder.put(key, value);
         }
       }
     };
@@ -77,9 +101,17 @@
       return 1;
     }
 
-    ImmutableSortedSet<String> classNames = classNamesBuilder.build();
+    ImmutableSortedMap<String, HashCode> classNames = classNamesBuilder.build();
     try {
-      context.getProjectFilesystem().writeLinesToPath(classNames, whereClassNamesShouldBeWritten);
+      context.getProjectFilesystem().writeLinesToPath(
+          Iterables.transform(classNames.entrySet(),
+              new Function<Map.Entry<String, HashCode>, String>() {
+            @Override
+            public String apply(Entry<String, HashCode> entry) {
+              return entry.getKey() + CLASS_NAME_HASH_CODE_SEPARATOR + entry.getValue();
+            }
+          }),
+          whereClassNamesShouldBeWritten);
     } catch (IOException e) {
       context.getBuckEventBus().post(ThrowableLogEvent.create(e,
           "There was an error writing the list of .class files to %s.",
diff --git a/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java b/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java
index 85e46b8..60de63a 100644
--- a/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java
+++ b/test/com/facebook/buck/java/AccumulateClassNamesStepTest.java
@@ -39,6 +39,8 @@
 
 public class AccumulateClassNamesStepTest {
 
+  private static final String SHA1_FOR_EMPTY_STRING = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
+
   @Rule
   public TemporaryFolder tmp = new TemporaryFolder();
 
@@ -70,12 +72,13 @@
     accumulateClassNamesStep.execute(context);
 
     String contents = Files.toString(new File(tmp.getRoot(), "output.txt"), Charsets.UTF_8);
+    String separator = AccumulateClassNamesStep.CLASS_NAME_HASH_CODE_SEPARATOR;
     assertEquals(
         "Verify that the contents are sorted alphabetically and ignore non-.class files.",
         Joiner.on('\n').join(
-            "com/example/Bar",
-            "com/example/Foo",
-            "com/example/subpackage/Baz") + '\n',
+            "com/example/Bar" + separator + SHA1_FOR_EMPTY_STRING,
+            "com/example/Foo" + separator + SHA1_FOR_EMPTY_STRING,
+            "com/example/subpackage/Baz" + separator + SHA1_FOR_EMPTY_STRING) + '\n',
         contents);
   }
 
@@ -104,12 +107,13 @@
     accumulateClassNamesStep.execute(context);
 
     String contents = Files.toString(new File(tmp.getRoot(), "output.txt"), Charsets.UTF_8);
+    String separator = AccumulateClassNamesStep.CLASS_NAME_HASH_CODE_SEPARATOR;
     assertEquals(
         "Verify that the contents are sorted alphabetically and ignore non-.class files.",
         Joiner.on('\n').join(
-            "com/example/Bar",
-            "com/example/Foo",
-            "com/example/subpackage/Baz") + '\n',
+            "com/example/Bar" + separator + SHA1_FOR_EMPTY_STRING,
+            "com/example/Foo" + separator + SHA1_FOR_EMPTY_STRING,
+            "com/example/subpackage/Baz" + separator + SHA1_FOR_EMPTY_STRING) + '\n',
         contents);
   }
 }