Use MessageDigest instead of HashFunction for Fingerprint

This allows us to reset and reuse the underlying digest implementation, which
guava's HashFunction doesn't allow. We do take the clone-if-possible page out
of guava's book.

--
MOS_MIGRATED_REVID=140624836
diff --git a/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
index 75737c3..1ca8ecf 100644
--- a/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
+++ b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
@@ -17,15 +17,15 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.Iterables;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.Map;
 import java.util.UUID;
-
 import javax.annotation.Nullable;
 
 /**
@@ -35,14 +35,26 @@
  */
 public final class Fingerprint {
 
-  private Hasher hasher;
-  private static final HashFunction MD5_HASH_FUNCTION = Hashing.md5();
+  private static final byte[] TRUE_BYTES = new byte[] { 1 };
+  private static final byte[] FALSE_BYTES = new byte[] { 0 };
+
+  private static final MessageDigest MD5_PROTOTYPE;
+  private static final boolean MD5_PROTOTYPE_SUPPORTS_CLONE;
+
+  static {
+    MD5_PROTOTYPE = getMd5Instance();
+    MD5_PROTOTYPE_SUPPORTS_CLONE = supportsClone(MD5_PROTOTYPE);
+  }
+
+  private final ByteBuffer scratch;
+  private final MessageDigest md5;
 
   /**
-   * Creates and initializes a new Hasher.
+   * Creates and initializes a new instance.
    */
   public Fingerprint() {
-    reset();
+    scratch = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
+    md5 = cloneOrCreateMd5();
   }
 
   /**
@@ -54,9 +66,8 @@
    * @see java.security.MessageDigest#digest()
    */
   public byte[] digestAndReset() {
-    byte[] bytes = hasher.hash().asBytes();
-    reset();
-    return bytes;
+    // Reset is implicit.
+    return md5.digest();
   }
 
   /**
@@ -67,9 +78,7 @@
    * @return the MD5 digest as a 32-character string of hexadecimal digits
    */
   public String hexDigestAndReset() {
-    String hexDigest = hasher.hash().toString();
-    reset();
-    return hexDigest;
+    return hexDigest(digestAndReset());
   }
 
   /**
@@ -79,7 +88,7 @@
    * @see java.security.MessageDigest#update(byte[])
    */
   public Fingerprint addBytes(byte[] input) {
-    hasher.putBytes(input);
+    md5.update(input);
     return this;
   }
 
@@ -92,7 +101,7 @@
    * @see java.security.MessageDigest#update(byte[], int, int)
    */
   public Fingerprint addBytes(byte[] input, int offset, int len) {
-    hasher.putBytes(input, offset, len);
+    md5.update(input, offset, len);
     return this;
   }
 
@@ -100,7 +109,7 @@
    * Updates the digest with a boolean value.
    */
   public Fingerprint addBoolean(boolean input) {
-    addBytes(new byte[] { (byte) (input ? 1 : 0) });
+    md5.update(input ? TRUE_BYTES : FALSE_BYTES);
     return this;
   }
 
@@ -108,8 +117,7 @@
    * Updates the digest with a boolean value, correctly handling null.
    */
   public Fingerprint addNullableBoolean(Boolean input) {
-    addInt(input == null ? -1 : (input.booleanValue() ? 1 : 0));
-    return this;
+    return addInt(input == null ? -1 : (input.booleanValue() ? 1 : 0));
   }
 
   /**
@@ -118,7 +126,8 @@
    * @param input the integer with which to update the digest
    */
   public Fingerprint addInt(int input) {
-    hasher.putInt(input);
+    scratch.putInt(input);
+    updateFromScratch(4);
     return this;
   }
 
@@ -128,7 +137,8 @@
    * @param input the long with which to update the digest
    */
   public Fingerprint addLong(long input) {
-    hasher.putLong(input);
+    scratch.putLong(input);
+    updateFromScratch(8);
     return this;
   }
 
@@ -168,8 +178,7 @@
   public Fingerprint addString(String input) {
     byte[] bytes = input.getBytes(UTF_8);
     addInt(bytes.length);
-    // Note that Hasher#putString() would not include the length of {@code input}.
-    hasher.putBytes(bytes);
+    md5.update(bytes);
     return this;
   }
 
@@ -201,7 +210,7 @@
     for (int i = 0; i < input.length(); i++) {
       bytes[i] = (byte) input.charAt(i);
     }
-    hasher.putBytes(bytes);
+    md5.update(bytes);
     return this;
   }
 
@@ -221,8 +230,7 @@
    * @param input the Path with which to update the digest.
    */
   public Fingerprint addPath(PathFragment input) {
-    addStringLatin1(input.getPathString());
-    return this;
+    return addStringLatin1(input.getPathString());
   }
 
   /**
@@ -291,7 +299,51 @@
    * Reset the Fingerprint for additional use as though previous digesting had not been done.
    */
   public void reset() {
-    hasher = MD5_HASH_FUNCTION.newHasher();
+    md5.reset();
+  }
+
+  private void updateFromScratch(int numBytes) {
+    md5.update(scratch.array(), 0, numBytes);
+    scratch.clear();
+  }
+
+  private static MessageDigest cloneOrCreateMd5() {
+    if (MD5_PROTOTYPE_SUPPORTS_CLONE) {
+      try {
+        return (MessageDigest) MD5_PROTOTYPE.clone();
+      } catch (CloneNotSupportedException e) {
+        throw new IllegalStateException("Could not clone md5", e);
+      }
+    } else {
+      return getMd5Instance();
+    }
+  }
+
+  private static String hexDigest(byte[] digest) {
+    StringBuilder b = new StringBuilder(32);
+    for (int i = 0; i < digest.length; i++) {
+      int n = digest[i];
+      b.append("0123456789abcdef".charAt((n >> 4) & 0xF));
+      b.append("0123456789abcdef".charAt(n & 0xF));
+    }
+    return b.toString();
+  }
+
+  private static MessageDigest getMd5Instance() {
+    try {
+      return MessageDigest.getInstance("md5");
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalStateException("md5 not available", e);
+    }
+  }
+
+  private static boolean supportsClone(MessageDigest toCheck) {
+    try {
+      toCheck.clone();
+      return true;
+    } catch (CloneNotSupportedException e) {
+      return false;
+    }
   }
 
   // -------- Convenience methods ----------------------------
@@ -303,6 +355,6 @@
    * @param input the String from which to compute the digest
    */
   public static String md5Digest(String input) {
-    return MD5_HASH_FUNCTION.hashString(input, UTF_8).toString();
+    return hexDigest(cloneOrCreateMd5().digest(input.getBytes(StandardCharsets.UTF_8)));
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java b/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
index 15a8264..22e9fe7 100644
--- a/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
+++ b/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
@@ -20,14 +20,12 @@
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 /**
  * Tests for Fingerprint.
@@ -56,9 +54,10 @@
   // echo -n 'Hello World!'| md5sum
   @Test
   public void bytesFingerprint() {
-    assertThat("ed076287532e86365e841e92bfc50d8c").isEqualTo(
-        new Fingerprint().addBytes("Hello World!".getBytes(UTF_8)).hexDigestAndReset());
-    assertThat("ed076287532e86365e841e92bfc50d8c").isEqualTo(Fingerprint.md5Digest("Hello World!"));
+    assertThat(new Fingerprint().addBytes("Hello World!".getBytes(UTF_8)).hexDigestAndReset())
+        .isEqualTo("ed076287532e86365e841e92bfc50d8c");
+    assertThat(Fingerprint.md5Digest("Hello World!"))
+        .isEqualTo("ed076287532e86365e841e92bfc50d8c");
   }
 
   @Test
@@ -113,11 +112,11 @@
   @Test
   public void addPath() throws Exception {
     PathFragment pf = new PathFragment("/etc/pwd");
-    assertThat("01cc3eeea3a2f58e447e824f9f62d3d1").isEqualTo(
-        new Fingerprint().addPath(pf).hexDigestAndReset());
+    assertThat(new Fingerprint().addPath(pf).hexDigestAndReset())
+        .isEqualTo("01cc3eeea3a2f58e447e824f9f62d3d1");
     Path p = new InMemoryFileSystem(BlazeClock.instance()).getPath(pf);
-    assertThat("01cc3eeea3a2f58e447e824f9f62d3d1").isEqualTo(
-        new Fingerprint().addPath(p).hexDigestAndReset());
+    assertThat(new Fingerprint().addPath(p).hexDigestAndReset())
+        .isEqualTo("01cc3eeea3a2f58e447e824f9f62d3d1");
   }
 
   @Test