blob: ed739ceaa0a5daaf6548bac68f069c4298a493f2 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.moz;
import com.google.gwt.dev.shell.CompilingClassLoader;
import com.google.gwt.dev.shell.JsValue;
import com.google.gwt.dev.shell.moz.LowLevelMoz.DispatchMethod;
import com.google.gwt.dev.shell.moz.LowLevelMoz.DispatchObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a Mozilla JavaScript value.
*
* TODO(jat): 64-bit compatibility - currently underlying pointers are passed
* around in a Java int, which only works on standard 32-bit platforms where
* sizeof(void*)=4
*/
public class JsValueMoz extends JsValue {
/**
* Records debug information, only used when {@link JsValueMoz#debugFlag} is
* <code>true</code>.
*/
private static class DebugLogging {
private Map<Integer, Throwable> alreadyCleanedJsRootedValues = Collections.synchronizedMap(new HashMap<Integer, Throwable>());
private int maxActive = 0;
private int numActive = 0;
private Map<Integer, Throwable> seenJsRootedValues = Collections.synchronizedMap(new HashMap<Integer, Throwable>());
private int totAlloc = 0;
/**
* Count a JsValueMoz instance being created.
*
* Verify that the underlying JsRootedValue is not currently active, and
* mark that it is active.
*
* This is debug code that is only executed if debugFlag is true. Since this
* is a private static final field, the compiler should optimize out all
* this code. It is useful to have for tracking down problems, so it is
* being left in but disabled.
*/
public void createInstance(int jsRootedValue) {
Integer jsrv = new Integer(jsRootedValue);
if (seenJsRootedValues.containsKey(jsrv)) {
Throwable t = seenJsRootedValues.get(jsrv);
String msg = hexString(jsRootedValue);
System.err.println(msg + ", original caller stacktrace:");
t.printStackTrace();
throw new RuntimeException(msg);
}
Throwable t = new Throwable();
seenJsRootedValues.put(jsrv, t);
if (alreadyCleanedJsRootedValues.containsKey(jsrv)) {
alreadyCleanedJsRootedValues.remove(jsrv);
}
if (++numActive > maxActive) {
maxActive = numActive;
}
++totAlloc;
}
/**
* Count a JsValueMoz instance being destroyed.
*
* Verify that this instance hasn't already been destroyed, that it has
* previously been created, and that the underlying JsRootedValue is only
* being cleaned once.
*
* This is debug code that is only executed if debugFlag is true. Since this
* is a private static final field, the compiler should optimize out all
* this code. It is useful to have for tracking down problems, so it is
* being left in but disabled.
*/
public void destroyInstance(int jsRootedValue) {
if (jsRootedValue == 0) {
throw new RuntimeException("Cleaning already-cleaned JsValueMoz");
}
Integer jsrv = new Integer(jsRootedValue);
if (!seenJsRootedValues.containsKey(jsrv)) {
throw new RuntimeException("cleaning up 0x" + hexString(jsRootedValue)
+ ", not active");
}
if (alreadyCleanedJsRootedValues.containsKey(jsrv)) {
Throwable t = seenJsRootedValues.get(jsrv);
String msg = "Already cleaned 0x" + hexString(jsRootedValue);
System.err.println(msg + ", original allocator stacktrace:");
t.printStackTrace();
throw new RuntimeException(msg);
}
Throwable t = new Throwable();
alreadyCleanedJsRootedValues.put(jsrv, t);
seenJsRootedValues.remove(jsrv);
--numActive;
}
/**
* Print collected statistics on JsValueMoz usage.
*/
public void dumpStatistics() {
System.gc();
System.out.println("JsValueMoz usage:");
System.out.println(" " + totAlloc + " total instances created");
System.out.println(" " + maxActive + " at any one time");
System.out.println(" " + seenJsRootedValues.size() + " uncleaned entries");
}
}
private static class JsCleanupMoz implements JsCleanup {
private final int jsRootedValue;
public JsCleanupMoz(int jsRootedValue) {
this.jsRootedValue = jsRootedValue;
}
public void doCleanup() {
_destroyJsRootedValue(jsRootedValue);
}
}
/**
* Flag to enable debug checks on underlying JsRootedValues.
*/
private static final boolean debugFlag = false;
/**
* Flag to enable debug checks on underlying JsRootedValues.
*/
private static final DebugLogging debugInfo = debugFlag ? new DebugLogging()
: null;
/**
* This must match the value from jsapi.h.
*/
private static final int JSVAL_VOID = 0x80000001;
// CHECKSTYLE_NAMING_OFF -- native methods start with '_'
protected static native boolean _getBoolean(int jsRootedValue);
protected static native int _getInt(int jsRootedValue);
protected static native int _getJsval(int jsRootedValue);
protected static native double _getNumber(int jsRootedValue);
protected static native String _getString(int jsRootedValue);
protected static native String _getTypeString(int jsRootedValue);
protected static native DispatchObject _getWrappedJavaObject(int jsRootedValue);
protected static native boolean _isBoolean(int jsRootedValue);
protected static native boolean _isInt(int jsRootedValue);
protected static native boolean _isJavaScriptObject(int jsRootedValue);
protected static native boolean _isJavaScriptString(int jsRootedValue);
protected static native boolean _isNull(int jsRootedValue);
protected static native boolean _isNumber(int jsRootedValue);
protected static native boolean _isString(int jsRootedValue);
protected static native boolean _isUndefined(int jsRootedValue);
protected static native boolean _isWrappedJavaObject(int jsRootedValue);
protected static native void _setBoolean(int jsRootedValue, boolean val);
protected static native void _setDouble(int jsRootedValue, double val);
protected static native void _setInt(int jsRootedValue, int val);
protected static native void _setJsRootedValue(int jsRootedValue,
int jsOtherRootedValue);
protected static native void _setJsval(int jsRootedValue, int jsval);
protected static native void _setNull(int jsRootedValue);
protected static native void _setString(int jsRootedValue, String val);
protected static native void _setUndefined(int jsRootedValue);
protected static native void _setWrappedFunction(int jsRootedValue,
String methodName, DispatchMethod dispatchMethod);
protected static native void _setWrappedJavaObject(int jsRootedValue,
DispatchObject val);
private static native int _copyJsRootedValue(int jsRootedValue);
/**
* Create a JsRootedValue and return a pointer to it as a Java int.
*
* @param jsval JavaScript jsval for initial value
* @return pointer to JsRootedValue object as an integer
*/
private static native int _createJsRootedValue(int jsval);
/**
* Destroy a JsRootedValue.
*
* @param jsRootedValue pointer to underlying JsRootedValue as an integer.
*/
private static native void _destroyJsRootedValue(int jsRootedValue);
// CHECKSTYLE_NAMING_ON
/**
* Convert an address to a hex string.
*
* @param jsRootedValue underlying JavaScript value as an opaque integer
* @return a string with the JavaScript value represented as hex
*/
private static String hexString(int jsRootedValue) {
long l = jsRootedValue;
l = l & 0xffffffffL;
return Long.toHexString(l);
}
// pointer to underlying JsRootedValue object as an integer
private int jsRootedValue;
/**
* Create a JsValueMoz object representing the undefined value.
*/
public JsValueMoz() {
this.jsRootedValue = _createJsRootedValue(JSVAL_VOID);
if (debugFlag) {
debugInfo.createInstance(jsRootedValue);
}
}
/**
* Create a JsValueMoz object wrapping a JsRootedValue object given the
* pointer to it as an integer.
*
* @param jsRootedValue pointer to underlying JsRootedValue as an integer.
*/
public JsValueMoz(int jsRootedValue) {
this.jsRootedValue = jsRootedValue;
if (debugFlag) {
debugInfo.createInstance(jsRootedValue);
}
}
/**
* Copy constructor.
*
* @param other JsValueMoz instance to copy
*/
public JsValueMoz(JsValueMoz other) {
jsRootedValue = _copyJsRootedValue(other.jsRootedValue);
if (debugFlag) {
debugInfo.createInstance(jsRootedValue);
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getBoolean()
*/
@Override
public boolean getBoolean() {
return _getBoolean(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getInt()
*/
@Override
public int getInt() {
return _getInt(jsRootedValue);
}
@Override
public int getJavaScriptObjectPointer() {
assert isJavaScriptObject();
return _getJsval(jsRootedValue);
}
/**
* Returns the underlying JavaScript object pointer as an integer.
*/
public int getJsRootedValue() {
return jsRootedValue;
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getNumber()
*/
@Override
public double getNumber() {
return _getNumber(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getString()
*/
@Override
public String getString() {
return _getString(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getTypeString()
*/
@Override
public String getTypeString() {
return _getTypeString(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#getWrappedJavaObject()
*/
@Override
public Object getWrappedJavaObject() {
DispatchObject obj = _getWrappedJavaObject(jsRootedValue);
return obj.getTarget();
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isBoolean()
*/
@Override
public boolean isBoolean() {
return _isBoolean(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isInt()
*/
@Override
public boolean isInt() {
return _isInt(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isJavaScriptObject()
*/
@Override
public boolean isJavaScriptObject() {
return _isJavaScriptObject(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isNull()
*/
@Override
public boolean isNull() {
return _isNull(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isNumber()
*/
@Override
public boolean isNumber() {
return _isNumber(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isString()
*/
@Override
public boolean isString() {
// String objects are acceptable for String value returns
return _isString(jsRootedValue) || _isJavaScriptString(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isUndefined()
*/
@Override
public boolean isUndefined() {
return _isUndefined(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#isWrappedJavaObject()
*/
@Override
public boolean isWrappedJavaObject() {
return _isWrappedJavaObject(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setBoolean(boolean)
*/
@Override
public void setBoolean(boolean val) {
_setBoolean(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setByte(byte)
*/
@Override
public void setByte(byte val) {
_setInt(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setChar(char)
*/
@Override
public void setChar(char val) {
_setInt(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setDouble(double)
*/
@Override
public void setDouble(double val) {
_setDouble(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setInt(int)
*/
@Override
public void setInt(int val) {
_setInt(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setNull()
*/
@Override
public void setNull() {
_setNull(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setShort(short)
*/
@Override
public void setShort(short val) {
_setInt(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setString(java.lang.String)
*/
@Override
public void setString(String val) {
_setString(jsRootedValue, val);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setUndefined()
*/
@Override
public void setUndefined() {
_setUndefined(jsRootedValue);
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setValue(com.google.gwt.dev.shell.JsValue)
*/
@Override
public void setValue(JsValue other) {
_setJsRootedValue(jsRootedValue, ((JsValueMoz) other).jsRootedValue);
}
/**
* Wrap a function call to a Java method in this JavaScript value.
*
* @param methodName the name of the method to invoke
* @param dispatchMethod the wrapper object
*/
public void setWrappedFunction(String methodName,
DispatchMethod dispatchMethod) {
Integer jsval = LowLevelMoz.sObjectToJsval.get(dispatchMethod);
if (jsval != null) {
_setJsval(jsRootedValue, jsval);
} else {
_setWrappedFunction(jsRootedValue, methodName, dispatchMethod);
LowLevelMoz.sObjectToJsval.put(dispatchMethod, _getJsval(jsRootedValue));
}
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.dev.shell.JsValue#setWrappedJavaObject(com.google.gwt.dev.shell.CompilingClassLoader,
* java.lang.Object)
*/
@Override
public <T> void setWrappedJavaObject(CompilingClassLoader cl, T val) {
if (val == null) {
setNull();
return;
}
DispatchObject dispObj;
if (val instanceof DispatchObject) {
dispObj = (DispatchObject) val;
} else {
dispObj = (DispatchObject) cl.getWrapperForObject(val);
if (dispObj == null) {
dispObj = new GeckoDispatchAdapter(cl, val);
cl.putWrapperForObject(val, dispObj);
}
}
Integer cached = LowLevelMoz.sObjectToJsval.get(dispObj);
if (cached != null) {
_setJsval(jsRootedValue, cached);
} else {
_setWrappedJavaObject(jsRootedValue, dispObj);
LowLevelMoz.sObjectToJsval.put(dispObj, _getJsval(jsRootedValue));
}
}
/**
* Create a cleanup object that will free the underlying JsRootedValue object.
*/
@Override
protected JsCleanup createCleanupObject() {
JsCleanup cleanup = new JsCleanupMoz(jsRootedValue);
if (debugFlag) {
debugInfo.destroyInstance(jsRootedValue);
jsRootedValue = 0;
}
return cleanup;
}
}