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());
}
}