MacOSX hosted mode now keeps a map of DispatchObjects and DispatchMethods to
underlying jsval's.  Entries are removed when the jsval is garbage
collected.  This change supports stable identity of Java objects in
JavaScript.

Patch by: scottb
Review by: knorton


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2281 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/mac/src/com/google/gwt/dev/shell/mac/LowLevelSaf.java b/dev/mac/src/com/google/gwt/dev/shell/mac/LowLevelSaf.java
index 82825ae..426b2d9 100644
--- a/dev/mac/src/com/google/gwt/dev/shell/mac/LowLevelSaf.java
+++ b/dev/mac/src/com/google/gwt/dev/shell/mac/LowLevelSaf.java
@@ -17,6 +17,8 @@
 
 import com.google.gwt.dev.shell.LowLevel;
 
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Stack;
 
 /**
@@ -50,12 +52,19 @@
     void setField(int jsContext, String name, int value);
   }
 
-  private static boolean jsValueProtectionCheckingEnabled;
+  /**
+   * Stores a map from DispatchObject/DispatchMethod to the live underlying
+   * jsval. This is used to both preserve identity for the same Java Object and
+   * also prevent GC.
+   */
+  static Map<Object, Integer> sObjectToJsval = new IdentityHashMap<Object, Integer>();
 
   private static boolean initialized = false;
 
   private static final ThreadLocal<Stack<Integer>> jsContextStack = new ThreadLocal<Stack<Integer>>();
 
+  private static boolean jsValueProtectionCheckingEnabled;
+
   public static int executeScript(int jsContext, String script) {
     final int[] rval = new int[1];
     if (!executeScriptWithInfoImpl(jsContext, script, null, 0, rval)) {
@@ -119,7 +128,7 @@
     }
 
     LowLevel.init();
-    if (!initImpl(DispatchObject.class, DispatchMethod.class)) {
+    if (!initImpl(DispatchObject.class, DispatchMethod.class, LowLevelSaf.class)) {
       throw new RuntimeException("Unable to initialize LowLevelSaf");
     }
 
@@ -271,19 +280,39 @@
 
   public static int wrapDispatchMethod(int jsContext, String name,
       DispatchMethod dispatch) {
-    final int[] rval = new int[1];
-    if (!wrapDispatchMethodImpl(jsContext, name, dispatch, rval)) {
-      throw new RuntimeException("Failed to wrap DispatchMethod.");
+    Integer cached = LowLevelSaf.sObjectToJsval.get(dispatch);
+    if (cached != null) {
+      /*
+       * Add another lock to the cached jsval, since it will not have any.
+       */
+      LowLevelSaf.gcProtect(LowLevelSaf.getCurrentJsContext(), cached);
+      return cached;
+    } else {
+      final int[] rval = new int[1];
+      if (!wrapDispatchMethodImpl(jsContext, name, dispatch, rval)) {
+        throw new RuntimeException("Failed to wrap DispatchMethod.");
+      }
+      LowLevelSaf.sObjectToJsval.put(dispatch, rval[0]);
+      return rval[0];
     }
-    return rval[0];
   }
 
   public static int wrapDispatchObject(int jsContext, DispatchObject dispatcher) {
-    final int[] rval = new int[1];
-    if (!wrapDispatchObjectImpl(jsContext, dispatcher, rval)) {
-      throw new RuntimeException("Failed to wrap DispatchObject.");
+    Integer cached = LowLevelSaf.sObjectToJsval.get(dispatcher);
+    if (cached != null) {
+      /*
+       * Add another lock to the cached jsval, since it will not have any.
+       */
+      LowLevelSaf.gcProtect(LowLevelSaf.getCurrentJsContext(), cached);
+      return cached;
+    } else {
+      final int[] rval = new int[1];
+      if (!wrapDispatchObjectImpl(jsContext, dispatcher, rval)) {
+        throw new RuntimeException("Failed to wrap DispatchObject.");
+      }
+      LowLevelSaf.sObjectToJsval.put(dispatcher, rval[0]);
+      return rval[0];
     }
-    return rval[0];
   }
 
   static native boolean isGcProtected(int jsValue);
@@ -299,6 +328,13 @@
     return jsValueProtectionCheckingEnabled;
   }
 
+  /**
+   * Native code accessor to remove the mapping upon GC.
+   */
+  static void releaseObject(Object o) {
+    sObjectToJsval.remove(o);
+  }
+
   private static native boolean executeScriptWithInfoImpl(int jsContext,
       String script, String url, int line, int[] rval);
 
@@ -306,7 +342,8 @@
 
   private static native boolean initImpl(
       Class<DispatchObject> dispatchObjectClass,
-      Class<DispatchMethod> dispatchMethodClass);
+      Class<DispatchMethod> dispatchMethodClass,
+      Class<LowLevelSaf> lowLevelSafClass);
 
   private static native boolean invokeImpl(int jsContext, int jsScriptObject,
       String methodName, int thisObj, int[] args, int argsLength, int[] rval);
diff --git a/jni/mac/gwt-webkit.cpp b/jni/mac/gwt-webkit.cpp
index 293a2bc..477b312 100644
--- a/jni/mac/gwt-webkit.cpp
+++ b/jni/mac/gwt-webkit.cpp
@@ -677,10 +677,10 @@
  */
 JNIEXPORT jboolean JNICALL Java_com_google_gwt_dev_shell_mac_LowLevelSaf_initImpl
     (JNIEnv *env, jclass klass, jclass dispatchObjectClass,
-    jclass dispatchMethodClass) {
+    jclass dispatchMethodClass, jclass lowLevelSafClass) {
   TR_ENTER();
   TR_LEAVE();
-  return static_cast<jboolean>(gwt::Initialize(env, dispatchObjectClass, dispatchMethodClass));
+  return static_cast<jboolean>(gwt::Initialize(env, dispatchObjectClass, dispatchMethodClass, lowLevelSafClass));
 }
 
 /*
diff --git a/jni/mac/java-dispatch.cpp b/jni/mac/java-dispatch.cpp
index 60ec10a..9cdef44 100644
--- a/jni/mac/java-dispatch.cpp
+++ b/jni/mac/java-dispatch.cpp
@@ -54,6 +54,12 @@
   void DispatchMethodFinalize(JSObjectRef);
 
   /*
+   * Call this when an underlying Java Object should be freed.
+   */
+  void ReleaseJavaObject(jobject jObject);
+
+
+  /*
    * The class definition stuct for DispatchObjects.
    */
   static JSClassDefinition _dispatchObjectClassDef = { 0,
@@ -85,10 +91,12 @@
   static JNIEnv* _javaEnv;
   static jclass _javaDispatchObjectClass;
   static jclass _javaDispatchMethodClass;
+  static jclass _lowLevelSafClass;
   static jmethodID _javaDispatchObjectSetFieldMethod;
   static jmethodID _javaDispatchObjectGetFieldMethod;
   static jmethodID _javaDispatchMethodInvokeMethod;
   static jmethodID _javaDispatchObjectToStringMethod;
+  static jmethodID _lowLevelSafReleaseObject;
 
   /*
    * Structure to hold DispatchMethod private data.
@@ -100,7 +108,7 @@
     DispatchMethodData(jobject jObject, std::string& utf8Name)
         : _jObject(jObject), _utf8Name(utf8Name) { }
     ~DispatchMethodData() {
-       _javaEnv->DeleteGlobalRef(_jObject);
+      ReleaseJavaObject(_jObject);
     }
     jobject _jObject;
     std::string _utf8Name;
@@ -293,7 +301,7 @@
 void DispatchObjectFinalize(JSObjectRef jsObject) {
   TR_ENTER();
   jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject));
-  _javaEnv->DeleteGlobalRef(jObject);
+  ReleaseJavaObject(jObject);
   TR_LEAVE();
 }
 
@@ -445,9 +453,10 @@
  *
  */
 bool Initialize(JNIEnv* javaEnv, jclass javaDispatchObjectClass,
-    jclass javaDispatchMethodClass) {
+    jclass javaDispatchMethodClass, jclass lowLevelSafClass) {
   TR_ENTER();
-  if (!javaEnv || !javaDispatchObjectClass || !javaDispatchMethodClass) {
+  if (!javaEnv || !javaDispatchObjectClass || !javaDispatchMethodClass
+      || !lowLevelSafClass) {
     return false;
   }
 
@@ -456,6 +465,8 @@
       javaEnv->NewGlobalRef(javaDispatchObjectClass));
   _javaDispatchMethodClass = static_cast<jclass>(
       javaEnv->NewGlobalRef(javaDispatchMethodClass));
+  _lowLevelSafClass = static_cast<jclass>(
+      javaEnv->NewGlobalRef(lowLevelSafClass));
   _javaDispatchObjectSetFieldMethod = javaEnv->GetMethodID(
       javaDispatchObjectClass, "setField", "(ILjava/lang/String;I)V");
   _javaDispatchObjectGetFieldMethod = javaEnv->GetMethodID(
@@ -464,10 +475,12 @@
       javaDispatchMethodClass, "invoke", "(II[I[I)I");
   _javaDispatchObjectToStringMethod = javaEnv->GetMethodID(
       javaDispatchObjectClass, "toString", "()Ljava/lang/String;");
+  _lowLevelSafReleaseObject = javaEnv->GetStaticMethodID(
+      lowLevelSafClass, "releaseObject", "(Ljava/lang/Object;)V");
 
   if (!_javaDispatchObjectSetFieldMethod || !_javaDispatchObjectGetFieldMethod
       || !_javaDispatchMethodInvokeMethod || !_javaDispatchObjectToStringMethod
-      || javaEnv->ExceptionCheck()) {
+      || !_lowLevelSafReleaseObject || javaEnv->ExceptionCheck()) {
     return false;
   }
 
@@ -475,4 +488,15 @@
   return true;
 }
 
+void ReleaseJavaObject(jobject jObject) {
+  // Tell the Java code we're done with this object.
+  _javaEnv->CallStaticVoidMethod(_lowLevelSafClass, _lowLevelSafReleaseObject,
+      jObject);
+  if (_javaEnv->ExceptionCheck()) {
+    TR_FAIL();
+    return;
+  }
+  _javaEnv->DeleteGlobalRef(jObject);
+}
+
 } // namespace gwt
diff --git a/jni/mac/java-dispatch.h b/jni/mac/java-dispatch.h
index 4ff15c9..f8fa80b 100644
--- a/jni/mac/java-dispatch.h
+++ b/jni/mac/java-dispatch.h
@@ -23,11 +23,11 @@
 namespace gwt {
 
 /*
- * Initializes static members needed by DispatchObjects and DispatchMethods.
- * This should be called before before calling either DispatchMethodCreate or
- * DispatchObjectCreate.
+ * Initializes static members needed by DispatchObject, DispatchMethod,
+ * and LowLevelSaf.  This should be called before before calling anything
+ * else.
  */
-bool Initialize(JNIEnv*, jclass, jclass);
+bool Initialize(JNIEnv*, jclass, jclass, jclass);
 
 /*
  * Returns a shared reference to the DispatchObject class
diff --git a/jni/mac/prebuilt/libgwt-ll.jnilib b/jni/mac/prebuilt/libgwt-ll.jnilib
index 4f62d3d..c9db8c5 100755
--- a/jni/mac/prebuilt/libgwt-ll.jnilib
+++ b/jni/mac/prebuilt/libgwt-ll.jnilib
Binary files differ