Java objects now have stable identities in JavaScript in hosted mode. (They always had them in web mode.)
Suggested by: jaimeyap
Review by: knorton
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2282 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index ba0102e..9304833 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -28,6 +28,8 @@
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.util.tools.Utility;
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.apache.commons.collections.map.ReferenceIdentityMap;
import org.apache.commons.collections.map.ReferenceMap;
import java.io.IOException;
@@ -327,13 +329,15 @@
private final TreeLogger logger;
- private final Map<MethodAdaptor, Object> methodToDispatch = new HashMap<MethodAdaptor, Object>();
-
private final TypeOracle typeOracle;
@SuppressWarnings("unchecked")
+ private final Map<Object, Object> weakJavaWrapperCache = new ReferenceIdentityMap(
+ AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK);
+
+ @SuppressWarnings("unchecked")
private final Map<Integer, Object> weakJsoCache = new ReferenceMap(
- ReferenceMap.HARD, ReferenceMap.WEAK);
+ AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
public CompilingClassLoader(TreeLogger logger, ByteCodeCompiler compiler,
TypeOracle typeOracle) throws UnableToCompleteException {
@@ -434,10 +438,16 @@
return dispClassInfoOracle.getDispId(jsniMemberRef);
}
- public Object getMethodDispatch(MethodAdaptor method) {
- synchronized (methodToDispatch) {
- return methodToDispatch.get(method);
- }
+ /**
+ * Retrieves the mapped wrapper for a given Java Object, provided the wrapper
+ * was previously cached and has not been garbage collected.
+ *
+ * @param javaObject the Object being wrapped
+ * @return the mapped wrapper, or <code>null</code> if the Java object
+ * mapped or if the wrapper has been garbage collected
+ */
+ public Object getWrapperForObject(Object javaObject) {
+ return weakJavaWrapperCache.get(javaObject);
}
/**
@@ -451,10 +461,14 @@
weakJsoCache.put(uniqueId, jso);
}
- public void putMethodDispatch(MethodAdaptor method, Object methodDispatch) {
- synchronized (methodToDispatch) {
- methodToDispatch.put(method, methodDispatch);
- }
+ /**
+ * Weakly caches a wrapper for a given Java Object.
+ *
+ * @param javaObject the Object being wrapped
+ * @param wrapper the mapped wrapper
+ */
+ public void putWrapperForObject(Object javaObject, Object wrapper) {
+ weakJavaWrapperCache.put(javaObject, wrapper);
}
@Override
@@ -499,11 +513,8 @@
void clear() {
weakJsoCache.clear();
+ weakJavaWrapperCache.clear();
dispClassInfoOracle.clear();
-
- synchronized (methodToDispatch) {
- methodToDispatch.clear();
- }
}
private String getBinaryName(JClassType type) {
diff --git a/dev/core/src/com/google/gwt/dev/shell/MethodAdaptor.java b/dev/core/src/com/google/gwt/dev/shell/MethodAdaptor.java
index 80ad04d..3be16a4 100644
--- a/dev/core/src/com/google/gwt/dev/shell/MethodAdaptor.java
+++ b/dev/core/src/com/google/gwt/dev/shell/MethodAdaptor.java
@@ -15,6 +15,7 @@
*/
package com.google.gwt.dev.shell;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -86,6 +87,10 @@
return returnType;
}
+ public AccessibleObject getUnderlyingObject() {
+ return (method != null) ? method : constructor;
+ }
+
@Override
public int hashCode() {
return hashCode;
diff --git a/dev/linux/src/com/google/gwt/dev/shell/moz/GeckoDispatchAdapter.java b/dev/linux/src/com/google/gwt/dev/shell/moz/GeckoDispatchAdapter.java
index 7c98a96..fbec94e 100644
--- a/dev/linux/src/com/google/gwt/dev/shell/moz/GeckoDispatchAdapter.java
+++ b/dev/linux/src/com/google/gwt/dev/shell/moz/GeckoDispatchAdapter.java
@@ -24,6 +24,7 @@
import com.google.gwt.dev.shell.moz.LowLevelMoz.DispatchMethod;
import com.google.gwt.dev.shell.moz.LowLevelMoz.DispatchObject;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
/**
@@ -85,10 +86,11 @@
return;
} else {
MethodAdaptor method = javaDispatch.getMethod(dispId);
- DispatchMethod dispMethod = (DispatchMethod) classLoader.getMethodDispatch(method);
+ AccessibleObject obj = method.getUnderlyingObject();
+ DispatchMethod dispMethod = (DispatchMethod) classLoader.getWrapperForObject(obj);
if (dispMethod == null) {
dispMethod = new MethodDispatch(classLoader, method);
- classLoader.putMethodDispatch(method, dispMethod);
+ classLoader.putWrapperForObject(obj, dispMethod);
}
jsValue.setWrappedFunction(method.toString(), dispMethod);
}
diff --git a/dev/linux/src/com/google/gwt/dev/shell/moz/JsValueMoz.java b/dev/linux/src/com/google/gwt/dev/shell/moz/JsValueMoz.java
index da4173c..ed739ce 100644
--- a/dev/linux/src/com/google/gwt/dev/shell/moz/JsValueMoz.java
+++ b/dev/linux/src/com/google/gwt/dev/shell/moz/JsValueMoz.java
@@ -562,11 +562,15 @@
if (val instanceof DispatchObject) {
dispObj = (DispatchObject) val;
} else {
- dispObj = new GeckoDispatchAdapter(cl, val);
+ dispObj = (DispatchObject) cl.getWrapperForObject(val);
+ if (dispObj == null) {
+ dispObj = new GeckoDispatchAdapter(cl, val);
+ cl.putWrapperForObject(val, dispObj);
+ }
}
- Integer jsval = LowLevelMoz.sObjectToJsval.get(dispObj);
- if (jsval != null) {
- _setJsval(jsRootedValue, jsval);
+ Integer cached = LowLevelMoz.sObjectToJsval.get(dispObj);
+ if (cached != null) {
+ _setJsval(jsRootedValue, cached);
} else {
_setWrappedJavaObject(jsRootedValue, dispObj);
LowLevelMoz.sObjectToJsval.put(dispObj, _getJsval(jsRootedValue));
diff --git a/dev/linux/src/com/google/gwt/dev/shell/moz/LowLevelMoz.java b/dev/linux/src/com/google/gwt/dev/shell/moz/LowLevelMoz.java
index c06b166..d102fe8 100644
--- a/dev/linux/src/com/google/gwt/dev/shell/moz/LowLevelMoz.java
+++ b/dev/linux/src/com/google/gwt/dev/shell/moz/LowLevelMoz.java
@@ -17,7 +17,6 @@
import com.google.gwt.dev.shell.LowLevel;
-import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Vector;
@@ -80,10 +79,11 @@
}
/**
- * 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.
+ * 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 = Collections.synchronizedMap(new IdentityHashMap<Object, Integer>());
+ static Map<Object, Integer> sObjectToJsval = new IdentityHashMap<Object, Integer>();
private static Vector<ExternalFactory> sExternalFactories = new Vector<ExternalFactory>();
private static boolean sInitialized = false;
diff --git a/dev/mac/src/com/google/gwt/dev/shell/mac/JsValueSaf.java b/dev/mac/src/com/google/gwt/dev/shell/mac/JsValueSaf.java
index 36faef1..de7b7b6 100644
--- a/dev/mac/src/com/google/gwt/dev/shell/mac/JsValueSaf.java
+++ b/dev/mac/src/com/google/gwt/dev/shell/mac/JsValueSaf.java
@@ -92,11 +92,8 @@
@Override
public int getJavaScriptObjectPointer() {
- if (isJavaScriptObject()) {
- return jsval;
- } else {
- return 0;
- }
+ assert isJavaScriptObject();
+ return jsval;
}
public int getJsValue() {
@@ -196,17 +193,6 @@
setJsVal(LowLevelSaf.toJsNumber(LowLevelSaf.getCurrentJsContext(), val));
}
- /**
- * Set a new value. Unlock the previous value, but do *not* lock the new value
- * (see class comment).
- *
- * @param jsval the new value to set
- */
- public void setJsVal(int jsval) {
- LowLevelSaf.gcUnprotect(LowLevelSaf.getCurrentJsContext(), this.jsval);
- init(jsval);
- }
-
@Override
public void setNull() {
setJsVal(LowLevelSaf.getJsNull(LowLevelSaf.getCurrentJsContext()));
@@ -247,7 +233,11 @@
} else if (val instanceof DispatchObject) {
dispObj = (DispatchObject) val;
} else {
- dispObj = new WebKitDispatchAdapter(cl, val);
+ dispObj = (DispatchObject) cl.getWrapperForObject(val);
+ if (dispObj == null) {
+ dispObj = new WebKitDispatchAdapter(cl, val);
+ cl.putWrapperForObject(val, dispObj);
+ }
}
setJsVal(LowLevelSaf.wrapDispatchObject(LowLevelSaf.getCurrentJsContext(),
dispObj));
@@ -277,4 +267,15 @@
}
}
+ /**
+ * Set a new value. Unlock the previous value, but do *not* lock the new value
+ * (see class comment).
+ *
+ * @param jsval the new value to set
+ */
+ private void setJsVal(int jsval) {
+ LowLevelSaf.gcUnprotect(LowLevelSaf.getCurrentJsContext(), this.jsval);
+ init(jsval);
+ }
+
}
diff --git a/dev/mac/src/com/google/gwt/dev/shell/mac/ModuleSpaceSaf.java b/dev/mac/src/com/google/gwt/dev/shell/mac/ModuleSpaceSaf.java
index b8a8b11..1959d43 100644
--- a/dev/mac/src/com/google/gwt/dev/shell/mac/ModuleSpaceSaf.java
+++ b/dev/mac/src/com/google/gwt/dev/shell/mac/ModuleSpaceSaf.java
@@ -20,7 +20,6 @@
import com.google.gwt.dev.shell.JsValueGlue;
import com.google.gwt.dev.shell.ModuleSpace;
import com.google.gwt.dev.shell.ModuleSpaceHost;
-import com.google.gwt.dev.shell.mac.LowLevelSaf.DispatchObject;
/**
* An implementation of {@link com.google.gwt.dev.shell.ModuleSpace} for Safari.
@@ -105,19 +104,4 @@
protected Object getStaticDispatcher() {
return new WebKitDispatchAdapter(getIsolatedClassLoader());
}
-
- protected int wrapObjectAsJSObject(Object o) {
- if (o == null) {
- return LowLevelSaf.getJsNull(LowLevelSaf.getCurrentJsContext());
- }
-
- DispatchObject dispObj;
- if (o instanceof DispatchObject) {
- dispObj = (DispatchObject) o;
- } else {
- dispObj = new WebKitDispatchAdapter(getIsolatedClassLoader(), o);
- }
- return LowLevelSaf.wrapDispatchObject(LowLevelSaf.getCurrentJsContext(),
- dispObj);
- }
}
diff --git a/dev/mac/src/com/google/gwt/dev/shell/mac/WebKitDispatchAdapter.java b/dev/mac/src/com/google/gwt/dev/shell/mac/WebKitDispatchAdapter.java
index bc25905..69a0240 100644
--- a/dev/mac/src/com/google/gwt/dev/shell/mac/WebKitDispatchAdapter.java
+++ b/dev/mac/src/com/google/gwt/dev/shell/mac/WebKitDispatchAdapter.java
@@ -24,6 +24,7 @@
import com.google.gwt.dev.shell.mac.LowLevelSaf.DispatchMethod;
import com.google.gwt.dev.shell.mac.LowLevelSaf.DispatchObject;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
/**
@@ -79,10 +80,11 @@
return jsval;
} else {
MethodAdaptor method = javaDispatch.getMethod(dispId);
- DispatchMethod dispMethod = (DispatchMethod) classLoader.getMethodDispatch(method);
+ AccessibleObject obj = method.getUnderlyingObject();
+ DispatchMethod dispMethod = (DispatchMethod) classLoader.getWrapperForObject(obj);
if (dispMethod == null) {
dispMethod = new MethodDispatch(classLoader, method);
- classLoader.putMethodDispatch(method, dispMethod);
+ classLoader.putWrapperForObject(obj, dispMethod);
}
// Native code eats the same ref it gave us.
return LowLevelSaf.wrapDispatchMethod(jsContext, method.toString(),
diff --git a/dev/windows/src/com/google/gwt/dev/shell/ie/IDispatchProxy.java b/dev/windows/src/com/google/gwt/dev/shell/ie/IDispatchProxy.java
index f30963a..f23df9d 100644
--- a/dev/windows/src/com/google/gwt/dev/shell/ie/IDispatchProxy.java
+++ b/dev/windows/src/com/google/gwt/dev/shell/ie/IDispatchProxy.java
@@ -26,6 +26,7 @@
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.ole.win32.Variant;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -154,10 +155,11 @@
return callMethod(classLoader, getTarget(), params, method);
} else if (flags == COM.DISPATCH_PROPERTYGET) {
// The function is being accessed as a property.
- IDispatchImpl dispMethod = (IDispatchImpl) classLoader.getMethodDispatch(method);
+ AccessibleObject obj = method.getUnderlyingObject();
+ IDispatchImpl dispMethod = (IDispatchImpl) classLoader.getWrapperForObject(obj);
if (dispMethod == null || dispMethod.refCount < 1) {
dispMethod = new MethodDispatch(classLoader, method);
- classLoader.putMethodDispatch(method, dispMethod);
+ classLoader.putWrapperForObject(obj, dispMethod);
}
IDispatch disp = new IDispatch(dispMethod.getAddress());
disp.AddRef();
diff --git a/dev/windows/src/com/google/gwt/dev/shell/ie/JsValueIE6.java b/dev/windows/src/com/google/gwt/dev/shell/ie/JsValueIE6.java
index 175d3e9..89cded6 100644
--- a/dev/windows/src/com/google/gwt/dev/shell/ie/JsValueIE6.java
+++ b/dev/windows/src/com/google/gwt/dev/shell/ie/JsValueIE6.java
@@ -496,7 +496,11 @@
} else if (val instanceof IDispatchImpl) {
dispObj = (IDispatchImpl) val;
} else {
- dispObj = new IDispatchProxy(cl, val);
+ dispObj = (IDispatchImpl) cl.getWrapperForObject(val);
+ if (dispObj == null || dispObj.refCount < 1) {
+ dispObj = new IDispatchProxy(cl, val);
+ cl.putWrapperForObject(val, dispObj);
+ }
}
IDispatch disp = new IDispatch(dispObj.getAddress());
disp.AddRef();
diff --git a/dev/windows/src/com/google/gwt/dev/shell/ie/MethodDispatch.java b/dev/windows/src/com/google/gwt/dev/shell/ie/MethodDispatch.java
index 2d642e9..b6eeb57 100644
--- a/dev/windows/src/com/google/gwt/dev/shell/ie/MethodDispatch.java
+++ b/dev/windows/src/com/google/gwt/dev/shell/ie/MethodDispatch.java
@@ -23,6 +23,7 @@
import org.eclipse.swt.internal.ole.win32.IDispatch;
import org.eclipse.swt.ole.win32.Variant;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationTargetException;
/**
@@ -114,10 +115,11 @@
throw new RuntimeException(
"Failed to get Object.toString() method", e);
}
- IDispatchImpl dispMethod = (IDispatchImpl) classLoader.getMethodDispatch(toStringMethod);
- if (dispMethod == null) {
+ AccessibleObject obj = toStringMethod.getUnderlyingObject();
+ IDispatchImpl dispMethod = (IDispatchImpl) classLoader.getWrapperForObject(obj);
+ if (dispMethod == null || dispMethod.refCount < 1) {
dispMethod = new MethodDispatch(classLoader, toStringMethod);
- classLoader.putMethodDispatch(toStringMethod, dispMethod);
+ classLoader.putWrapperForObject(obj, dispMethod);
}
IDispatch disp = new IDispatch(dispMethod.getAddress());
disp.AddRef();
diff --git a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
index d5409e5..efe1e2c 100644
--- a/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/HostedTest.java
@@ -144,6 +144,18 @@
return s.length;
}-*/;
+ private static native boolean jsIdentical(Object o1, Object o2) /*-{
+ return o1 === o2;
+ }-*/;
+
+ private static native boolean jsniInstanceFunctionsIdentical(Object o) /*-{
+ return o.@java.lang.Object::toString() === o.@java.lang.Object::toString();
+ }-*/;
+
+ private static native boolean jsniStaticFunctionsIdentical() /*-{
+ return @com.google.gwt.dev.jjs.test.HostedTest::sFoo(Ljava/lang/String;) === @com.google.gwt.dev.jjs.test.HostedTest::sFoo(Ljava/lang/String;);
+ }-*/;
+
private static native int passThroughInt(int val) /*-{
return val;
}-*/;
@@ -200,7 +212,7 @@
assertTrue(HostedTest.class.desiredAssertionStatus());
}
}
-
+
/*
* Test that returning JavaScript boxed primitives works as expected. Note
* that Boolean and Number cannot be supported properly in web mode, so we do
@@ -322,6 +334,13 @@
assertEquals(1, b.getUsingBinaryRef());
}
+ public void testJavaObjectIdentityInJS() {
+ Object o = new Object();
+ assertTrue(jsIdentical(o, o));
+ assertTrue(jsniInstanceFunctionsIdentical(o));
+ assertTrue(jsniStaticFunctionsIdentical());
+ }
+
public void testJsniFormats() {
jsniA();
jsniB();