Fixes memory leak in String.hashCode(); instead of permanently pinning every string that's ever had its hashcode taken, we just keep the last 256.

Review by: ispeters, jat, bobv


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2190 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 e404e09..92b2f45 100644
--- a/user/super/com/google/gwt/emul/java/lang/String.java
+++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -82,14 +82,79 @@
     }
   };
 
-  /**
-   * Accesses need to be prefixed with ':' to prevent conflict with built-in
-   * JavaScript properties.
-   * 
-   * @skip
-   */
-  static JavaScriptObject hashCache;
+  static final class HashCache {
+    /**
+     * Pulled this number out of thin air.
+     */
+    static final int MAX_CACHE = 256;
+    /**
+     * The "old" cache; it will be dumped when front is full.
+     */
+    static JavaScriptObject back = JavaScriptObject.createObject();
+    /**
+     * The "new" cache; it will become back when it becomes full.
+     */
+    static JavaScriptObject front = JavaScriptObject.createObject();
+    /**
+     * Tracks the number of entries in front.
+     */
+    static int count = 0;
 
+    public static native int getHashCode(String str) /*-{
+      // Accesses must to be prefixed with ':' to prevent conflict with built-in
+      // JavaScript properties.
+      var key = ':' + str;
+
+      // Check the front store.
+      var result = @java.lang.String.HashCache::front[key];
+      if (result != null) {
+        return result;
+      }
+
+      // Check the back store.
+      result = @java.lang.String.HashCache::back[key];
+      if (result == null) {
+        // Compute the value.
+        result = @java.lang.String.HashCache::compute(Ljava/lang/String;)(str);
+      }
+      // Increment can trigger the swap/flush; call after checking back but
+      // before writing to front.
+      @java.lang.String.HashCache::increment()();
+      return @java.lang.String.HashCache::front[key] = result;
+    }-*/;
+    
+    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);
+      }
+      // force to 32-bits
+      hashCode |= 0;
+      return hashCode;
+    }
+
+    static void increment() {
+      if (count == MAX_CACHE) {
+        back = front;
+        front = JavaScriptObject.createObject();
+        count = 0;
+      }
+      ++count;
+    }
+  }
+  
   public static String valueOf(boolean x) {
     return "" + x;
   };
@@ -395,36 +460,9 @@
   }
 
   @Override
-  public native int hashCode() /*-{
-    var hashCache = @java.lang.String::hashCache;
-    if (!hashCache) {
-      hashCache = @java.lang.String::hashCache = {};
-    }
-  
-    // Prefix needed to prevent conflict with built-in JavaScript properties.
-    var key  = ':' + this;
-    var hashCode = hashCache[key];
-    // Must check null/undefined because 0 is a legal hashCode
-    if (hashCode == null) {
-      hashCode = 0;
-      var n = this.length;
-      // 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.
-      var inc = (n < 64) ? 1 : ((n / 32) | 0);
-      for (var i = 0; i < n; i += inc) {
-        hashCode <<= 1;
-        hashCode += this.charCodeAt(i);
-      }
-      hashCode |= 0; // force to 32-bits
-      hashCache[key] = hashCode
-    }
-    return hashCode;
-  }-*/;
+  public int hashCode() {
+    return HashCache.getHashCode(this);
+  }
 
   public int indexOf(int codePoint) {
     return indexOf(fromCodePoint(codePoint));