/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.google.gwt.dev.shell.mac;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gdt.eclipse.designer.mac.BrowserShellMac;
import com.google.gdt.eclipse.designer.mac.BrowserShellMac.DispatchObject;
import com.google.gwt.dev.shell.CompilingClassLoader;
import com.google.gwt.dev.shell.JsValue;
/**
* Represents a Safari JavaScript value.
*
* The basic rule is that any JSValue passed to Java code from native code will
* always be GC-protected in the native code and Java will always unprotect it
* when the value is finalized. It should always be stored in a JsValue object
* immediately to make sure it is cleaned up properly when it is no longer
* needed. This approach is required to avoid a race condition where the value
* is allocated in JNI code but could be garbage collected before Java takes
* ownership of the value. Java values passed into JavaScript store a GlobalRef
* of a WebKitDispatchAdapter or MethodDispatch objects, which are freed when
* the JS value is finalized.
*/
public class JsValueSaf extends JsValue {
private static class JsCleanupSaf implements JsCleanup {
private final long jsval;
/**
* Create a cleanup object which takes care of cleaning up the underlying JS
* object.
*
* @param jsval JSValue pointer as a long
*/
public JsCleanupSaf(long jsval) {
this.jsval = jsval;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue.JsCleanup#doCleanup()
*/
public void doCleanup() {
BrowserShellMac.objcRelease(jsval);
}
}
/*
* Underlying JSValue* as a long.
*/
private long jsval;
/**
* Create a Java wrapper around an undefined JSValue.
*/
public JsValueSaf() {
init(BrowserShellMac.jsUndefined());
}
/**
* Create a Java wrapper around the underlying JSValue.
*
* @param jsval a pointer to the underlying JSValue object as a long
*/
public JsValueSaf(long jsval) {
init(jsval);
}
@Override
public boolean getBoolean() {
return BrowserShellMac.coerceToBoolean(jsval);
}
@Override
public int getInt() {
return BrowserShellMac.coerceToInt(jsval);
}
@Override
public long getJavaScriptObjectPointer() {
assert isJavaScriptObject();
return jsval;
}
public long getJsValue() {
return jsval;
}
@Override
public double getNumber() {
return BrowserShellMac.coerceToDouble(jsval);
}
@Override
public String getString() {
return BrowserShellMac.coerceToString(jsval);
}
@Override
public String getTypeString() {
return BrowserShellMac.getTypeString(jsval);
}
@Override
public Object getWrappedJavaObject() {
DispatchObject obj = BrowserShellMac.unwrapDispatch(jsval);
return obj.getTarget();
}
@Override
public boolean isBoolean() {
return BrowserShellMac.isBoolean(jsval);
}
@Override
public boolean isInt() {
// Safari doesn't have integers, so this is always false
return false;
}
@Override
public boolean isJavaScriptObject() {
return BrowserShellMac.isObject(jsval) && !BrowserShellMac.isWrappedDispatch(jsval);
}
@Override
public boolean isNull() {
return BrowserShellMac.isNull(jsval);
}
@Override
public boolean isNumber() {
return BrowserShellMac.isNumber(jsval);
}
@Override
public boolean isString() {
return BrowserShellMac.isString(jsval);
}
@Override
public boolean isUndefined() {
return BrowserShellMac.isUndefined(jsval);
}
@Override
public boolean isWrappedJavaObject() {
return BrowserShellMac.isWrappedDispatch(jsval);
}
@Override
public void setBoolean(boolean val) {
setJsValNoRetain(BrowserShellMac.convertBoolean(val));
}
@Override
public void setByte(byte val) {
setJsValNoRetain(BrowserShellMac.convertDouble(val));
}
@Override
public void setChar(char val) {
setJsValNoRetain(BrowserShellMac.convertDouble(val));
}
@Override
public void setDouble(double val) {
setJsValNoRetain(BrowserShellMac.convertDouble(val));
}
@Override
public void setInt(int val) {
setJsValNoRetain(BrowserShellMac.convertDouble(val));
}
@Override
public void setNull() {
setJsValNoRetain(BrowserShellMac.jsNull());
}
@Override
public void setShort(short val) {
setJsValNoRetain(BrowserShellMac.convertDouble(val));
}
@Override
public void setString(String val) {
setJsValNoRetain(BrowserShellMac.convertString(val));
}
@Override
public void setUndefined() {
setJsValNoRetain(BrowserShellMac.jsUndefined());
}
@Override
public void setValue(JsValue other) {
long jsvalOther = ((JsValueSaf) other).jsval;
/*
* Add another lock to this jsval, since both the other object and this one
* will eventually release it.
*/
// LowLevelSaf.gcProtect(LowLevelSaf.getCurrentJsContext(), jsvalOther);
// XXX should we add the BrowserShell.gcLock()?
setJsVal(jsvalOther);
}
@Override
public <T> void setWrappedJavaObject(CompilingClassLoader cl, T val) {
DispatchObject dispObj;
if (val == null) {
setNull();
return;
} else if (val instanceof DispatchObject) {
dispObj = (DispatchObject) val;
} else {
dispObj = (DispatchObject) cl.getWrapperForObject(val);
if (dispObj == null) {
dispObj = new WebKitDispatchAdapter(cl, val);
cl.putWrapperForObject(val, dispObj);
}
}
long jsVal = BrowserShellMac.wrapDispatch(dispObj);
setJsValNoRetain(jsVal);
}
@Override
protected JsCleanup createCleanupObject() {
return new JsCleanupSaf(jsval);
}
/**
* Initialization helper method.
*
* @param jsval underlying JSValue*
*/
private void init(long jsval) {
this.jsval = jsval;
BrowserShellMac.objcRetain(this.jsval);
}
/**
* 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(long jsval) {
BrowserShellMac.objcRelease(this.jsval);
init(jsval);
}
private void setJsValNoRetain(long jsval) {
BrowserShellMac.objcRelease(this.jsval);
this.jsval = jsval;
}
////////////////////////////////////////////////////////////////////////////
//
// obj-c -> java refs
//
////////////////////////////////////////////////////////////////////////////
protected static Map<Long, Object> m_dispatchRefs = new HashMap<Long, Object>();
/**
* Invoked by native code at the moment when new wrapper objc object created for {@link DispatchObject}.
*
* @param ref
* the native pointer to objc object instance.
* @param refObj
* the {@link DispatchObject} instance.
*/
protected static void putDispatchObjectRef(long ref, Object refObj) {
synchronized (m_dispatchRefs) {
m_dispatchRefs.put(ref, refObj);
}
}
/**
* Invoked by native code every time when the objc wrapper needs the appropriate {@link DispatchObject}
* instance.
*
* @param ref
* the native pointer to objc object instance.
*/
protected static Object getDispatchObjectRef(long ref) {
synchronized (m_dispatchRefs) {
return m_dispatchRefs.get(ref);
}
}
/**
* Invoked by native code when the objc wrapper deallocated.
*
* @param ref
* the native pointer to objc object instance.
*/
protected static void removeDispatchObjectRef(long ref) {
synchronized (m_dispatchRefs) {
m_dispatchRefs.remove(ref);
}
}
/**
* Clean up {@link DispatchObject} instances when disposing current {@link ModuleSpaceSaf}.
*/
static void clearDispatchObjectRefs(CompilingClassLoader cl) {
synchronized (m_dispatchRefs) {
List<Long> removingRefs = new ArrayList<Long>();
for (Long ref : m_dispatchRefs.keySet()) {
Object dispObj = m_dispatchRefs.get(ref);
if (dispObj instanceof WebKitDispatchAdapter) {
WebKitDispatchAdapter dispatchAdapter = (WebKitDispatchAdapter) dispObj;
if (dispatchAdapter.getClassLoader() == cl) {
removingRefs.add(ref);
}
}
}
for (Long ref : removingRefs) {
m_dispatchRefs.remove(ref);
}
}
}
}