Fix for issue 3025 -- Gwt's String.hashCode() now matches Jre specifications. 

Patch by: rdamazio
Review by: scottb, jat, amitmanjhi



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5424 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java
index 1190b9e..d966b87 100644
--- a/user/super/com/google/gwt/emul/java/lang/String.java
+++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -124,26 +124,33 @@
     }-*/;
 
     static int compute(String str) {
-      /*
-       * In our hash code calculation, only 32 characters will actually affect
-       * the final value, so there's no need to sample more than 32 characters.
-       * To get a better hash code, we'd like to evenly distribute these
-       * characters throughout the string. That means that for lengths between 0
-       * and 63 (inclusive), we increment by 1. For 64-95, 2; 96-127, 3; and so
-       * on. The complicated formula below computes just that. The "| 0"
-       * operation is a fast way to coerce the division result to an integer.
-       */
-      int n = str.length();
-      int inc = (n < 64) ? 1 : (n / 32);
       int hashCode = 0;
-      for (int i = 0; i < n; i += inc) {
-        hashCode <<= 1;
-        hashCode += str.charAt(i);
+      int n = str.length();
+      int nBatch = n - 4;
+      int i = 0;
+
+      // Process batches of 4 characters at a time
+      while (i < nBatch) {
+        // Add the next 4 characters to the hash.
+        // After every 4 characters, we force the result to fit into 32 bits
+        // by doing a bitwise operation on it.
+        hashCode = (str.charAt(i + 3)
+            + 31 * (str.charAt(i + 2)
+            + 31 * (str.charAt(i + 1)
+            + 31 * (str.charAt(i)
+            + 31 * hashCode)))) | 0;
+
+        i += 4;
       }
-      // force to 32-bits
+
+      // Now process the leftovers
+      while (i < n) {
+        hashCode = hashCode * 31 + str.charAt(i++);
+      }
+      
       // TODO: make a JSNI call in case JDT gets smart about removing this
-      hashCode |= 0;
-      return hashCode;
+      // Do a final fitting to 32 bits
+      return hashCode | 0;
     }
 
     static void increment() {
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
index a20a0b6..5a99183 100644
--- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
@@ -212,25 +212,33 @@
   public void testHashCode() {
     String[] testStrings = {
         "watch", "unwatch", "toString", "toSource", "eval", "valueOf",
-        "constructor", "__proto__"};
-    int[] savedHash = new int[testStrings.length];
+        "constructor", "__proto__", "polygenelubricants", "xy", "x", "" };
+    int[] javaHashes = {
+        112903375, -274141738, -1776922004, -1781441930, 3125404, 231605032,
+        -1588406278, 2139739112, Integer.MIN_VALUE, 3841, 120, 0 };
+
     for (int i = 0; i < testStrings.length; ++i) {
-      savedHash[i] = testStrings[i].hashCode();
+      String testString = testStrings[i];
+      int expectedHash = javaHashes[i];
+
+      // verify that the hash codes of these strings match their java
+      // counterparts
+      assertEquals("Unexpected hash for string " + testString, expectedHash,
+          testString.hashCode());
 
       /*
        * Verify that the resulting hash code is numeric, since this is not
        * enforced in web mode.
        */
-      String str = Integer.toString(savedHash[i]);
+      String str = Integer.toString(expectedHash);
       for (int j = 0; j < str.length(); ++j) {
         char ch = str.charAt(j);
         assertTrue("Bad character '" + ch + "' (U+0" + Integer.toHexString(ch)
             + ")", ch == '-' || ch == ' ' || Character.isDigit(ch));
       }
-    }
-    // verify the hash codes are constant for a given string
-    for (int i = 0; i < testStrings.length; ++i) {
-      assertEquals(savedHash[i], testStrings[i].hashCode());
+
+      // get hashes again to verify the values are constant for a given string
+      assertEquals(expectedHash, testStrings[i].hashCode());
     }
   }