blob: fafab11a2c3e4e48ec947fab621a316c54d4c8ed [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.
*/
#include <string>
#include <sstream>
#include "jni.h"
#include "java-dispatch.h"
#include "trace.h"
#include "JStringWrap.h"
namespace gwt {
/*
* Declarations for private functions.
*/
JSClassRef DispatchObjectClassCreate();
JSClassRef DispatchMethodClassCreate();
JSValueRef DispatchObjectGetProperty(JSContextRef, JSObjectRef, JSStringRef,
JSValueRef*);
JSValueRef DispatchObjectToString(JSContextRef, JSObjectRef, JSObjectRef,
size_t, const JSValueRef*, JSValueRef*);
bool DispatchObjectSetProperty(JSContextRef, JSObjectRef, JSStringRef,
JSValueRef, JSValueRef*);
void DispatchObjectFinalize(JSObjectRef);
JSValueRef DispatchMethodCallAsFunction(JSContextRef, JSObjectRef,
JSObjectRef, size_t,
const JSValueRef*, JSValueRef*);
JSValueRef DispatchMethodGetToString(JSContextRef, JSObjectRef, JSStringRef,
JSValueRef*);
JSValueRef DispatchMethodToString(JSContextRef, JSObjectRef, JSObjectRef,
size_t, const JSValueRef*, JSValueRef*);
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,
kJSClassAttributeNone, "DispatchObject", 0, 0, 0, 0,
DispatchObjectFinalize, 0, DispatchObjectGetProperty,
DispatchObjectSetProperty, 0, 0, 0, 0, 0, 0 };
/*
* The class definition structs for DispatchMethods.
*/
static JSStaticValue _dispatchMethodStaticValues[] = {
{ "toString", DispatchMethodGetToString, 0, kJSPropertyAttributeNone },
{ 0, 0, 0, 0 }
};
static JSClassDefinition _dispatchMethodClassDef = { 0,
kJSClassAttributeNoAutomaticPrototype, "DispatchMethod", 0,
_dispatchMethodStaticValues, 0, 0, DispatchMethodFinalize, 0, 0, 0, 0,
0, DispatchMethodCallAsFunction, 0, 0, 0 };
/*
* The classes used to create DispatchObjects and DispatchMethods.
*/
static JSClassRef _dispatchObjectClass = DispatchObjectClassCreate();
static JSClassRef _dispatchMethodClass = DispatchMethodClassCreate();
/*
* Java class and method references needed to do delegation.
*/
/*
* The main JVM, used by foreign threads to attach.
*/
static JavaVM* _javaVM;
/*
* Only valid for the main thread! WebKit can finalized on a foreign thread.
*/
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.
*
* NOTE: utf8Name is defensively copied.
*/
class DispatchMethodData {
public:
DispatchMethodData(jobject jObject, std::string& utf8Name)
: _jObject(jObject), _utf8Name(utf8Name) { }
~DispatchMethodData() {
ReleaseJavaObject(_jObject);
}
jobject _jObject;
std::string _utf8Name;
};
/*
* The following takes the prototype from the Function constructor, this allows
* us to easily support call and apply on our objects that support CallAsFunction.
*
* NOTE: The return value is not protected.
*/
JSValueRef GetFunctionPrototype(JSContextRef jsContext, JSValueRef* exception) {
TR_ENTER();
JSObjectRef globalObject = JSContextGetGlobalObject(jsContext);
JSStringRef fnPropName= JSStringCreateWithUTF8CString("Function");
JSValueRef fnCtorValue = JSObjectGetProperty(jsContext, globalObject,
fnPropName, exception);
JSStringRelease(fnPropName);
if (!fnCtorValue) {
return JSValueMakeUndefined(jsContext);
}
JSObjectRef fnCtorObject = JSValueToObject(jsContext, fnCtorValue, exception);
if (!fnCtorObject) {
return JSValueMakeUndefined(jsContext);
}
JSStringRef protoPropName = JSStringCreateWithUTF8CString("prototype");
JSValueRef fnPrototype = JSObjectGetProperty(jsContext, fnCtorObject,
protoPropName, exception);
JSStringRelease(protoPropName);
if (!fnPrototype) {
return JSValueMakeUndefined(jsContext);
}
TR_LEAVE();
return fnPrototype;
}
/*
*
*/
JSClassRef GetDispatchObjectClass() {
TR_ENTER();
TR_LEAVE();
return _dispatchObjectClass;
}
/*
*
*/
JSClassRef GetDispatchMethodClass() {
TR_ENTER();
TR_LEAVE();
return _dispatchMethodClass;
}
/*
*
*/
JSClassRef DispatchObjectClassCreate() {
TR_ENTER();
JSClassRef dispClass = JSClassCreate(&_dispatchObjectClassDef);
JSClassRetain(dispClass);
TR_LEAVE();
return dispClass;
}
/*
*
*/
JSClassRef DispatchMethodClassCreate() {
TR_ENTER();
JSClassRef dispClass = JSClassCreate(&_dispatchMethodClassDef);
JSClassRetain(dispClass);
TR_LEAVE();
return dispClass;
}
/*
* NOTE: The object returned from this function is not protected.
*/
JSObjectRef DispatchObjectCreate(JSContextRef jsContext, jobject jObject) {
TR_ENTER();
JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchObjectClass,
_javaEnv->NewGlobalRef(jObject));
TR_LEAVE();
return dispInst;
}
/*
* NOTE: The object returned from this function is not protected.
*/
JSObjectRef DispatchMethodCreate(JSContextRef jsContext, std::string& name,
jobject jObject) {
TR_ENTER();
JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchMethodClass,
new DispatchMethodData(_javaEnv->NewGlobalRef(jObject), name));
// This could only be cached relative to jsContext.
JSValueRef fnProtoValue = GetFunctionPrototype(jsContext, NULL);
JSObjectSetPrototype(jsContext, dispInst, fnProtoValue);
TR_LEAVE();
return dispInst;
}
/*
* NOTE: The value returned from this function is not protected, but all
* JSValues that are passed into Java are protected before the invocation.
*/
JSValueRef DispatchObjectGetProperty(JSContextRef jsContext,
JSObjectRef jsObject, JSStringRef jsPropertyName,
JSValueRef* jsException) {
TR_ENTER();
// If you call toString on a DispatchObject, you should get the results
// of the java object's toString invcation.
if (JSStringIsEqualToUTF8CString(jsPropertyName, "toString")) {
JSObjectRef jsFunction = JSObjectMakeFunctionWithCallback(jsContext,
jsPropertyName, DispatchObjectToString);
return jsFunction;
}
// The class check is omitted because it should not be possible to tear off
// a getter.
jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject));
jstring jPropertyName = _javaEnv->NewString(
static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)),
static_cast<jsize>(JSStringGetLength(jsPropertyName)));
if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) {
TR_FAIL();
_javaEnv->ExceptionClear();
return JSValueMakeUndefined(jsContext);
}
JSValueRef jsResult = reinterpret_cast<JSValueRef>(
_javaEnv->CallIntMethod(jObject, _javaDispatchObjectGetFieldMethod,
reinterpret_cast<jint>(jsContext),
jPropertyName));
if (!jsResult || _javaEnv->ExceptionCheck()) {
TR_FAIL();
_javaEnv->ExceptionClear();
return JSValueMakeUndefined(jsContext);
}
// Java left us an extra reference to eat.
JSValueUnprotectChecked(jsContext, jsResult);
TR_LEAVE();
return jsResult;
}
/*
*
*/
bool DispatchObjectSetProperty(JSContextRef jsContext, JSObjectRef jsObject,
JSStringRef jsPropertyName, JSValueRef jsValue, JSValueRef* jsException) {
TR_ENTER();
// The class check is omitted because it should not be possible to tear off
// a getter.
jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject));
jstring jPropertyName = _javaEnv->NewString(
static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)),
static_cast<jsize>(JSStringGetLength(jsPropertyName)));
if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) {
_javaEnv->ExceptionClear();
return false;
}
JSValueProtectChecked(jsContext, jsValue);
_javaEnv->CallIntMethod(jObject, _javaDispatchObjectSetFieldMethod,
reinterpret_cast<jint>(jsContext), jPropertyName,
reinterpret_cast<jint>(jsValue));
if (_javaEnv->ExceptionCheck()) {
_javaEnv->ExceptionClear();
return false;
}
TR_LEAVE();
return true;
}
/*
*
*/
void DispatchObjectFinalize(JSObjectRef jsObject) {
TR_ENTER();
jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject));
ReleaseJavaObject(jObject);
TR_LEAVE();
}
/*
*
*/
void DispatchMethodFinalize(JSObjectRef jsObject) {
TR_ENTER();
DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>(
JSObjectGetPrivate(jsObject));
delete data;
TR_LEAVE();
}
/*
* NOTE: The value returned from this function is not protected.
*/
JSValueRef DispatchObjectToString(JSContextRef jsContext, JSObjectRef,
JSObjectRef jsThis, size_t, const JSValueRef*, JSValueRef*) {
TR_ENTER();
// This function cannot be torn off and applied to any JSValue. If this does
// not reference a DispatchObject, return undefined.
if (!JSValueIsObjectOfClass(jsContext, jsThis, GetDispatchObjectClass())) {
return JSValueMakeUndefined(jsContext);
}
jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsThis));
jstring jResult = reinterpret_cast<jstring>(
_javaEnv->CallObjectMethod(jObject, _javaDispatchObjectToStringMethod));
if (_javaEnv->ExceptionCheck()) {
return JSValueMakeUndefined(jsContext);
} else if (!jResult) {
return JSValueMakeNull(jsContext);
} else {
JStringWrap result(_javaEnv, jResult);
JSStringRef resultString = JSStringCreateWithCharacters(
static_cast<const JSChar*>(result.jstr()),
static_cast<size_t>(result.length()));
JSValueRef jsResultString = JSValueMakeString(jsContext, resultString);
JSStringRelease(resultString);
return jsResultString;
}
TR_LEAVE();
}
/*
*
*/
JSValueRef DispatchMethodCallAsFunction(JSContextRef jsContext,
JSObjectRef jsFunction, JSObjectRef jsThis, size_t argumentCount,
const JSValueRef arguments[], JSValueRef* exception) {
TR_ENTER();
// We don't need to check the class here because we take the private
// data from jsFunction and not jsThis.
DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>(
JSObjectGetPrivate(jsFunction));
jobject jObject = data->_jObject;
jintArray jArguments = _javaEnv->NewIntArray(argumentCount);
if (!jArguments || _javaEnv->ExceptionCheck()) {
return JSValueMakeUndefined(jsContext);
}
// This single element int array will be passed into the java call to allow the
// called java method to raise an exception. We will check for a non-null value
// after the call is dispatched.
jintArray jException = _javaEnv->NewIntArray(1);
if (!jException || _javaEnv->ExceptionCheck()) {
return JNI_FALSE;
}
for (size_t i = 0; i < argumentCount; ++i) {
JSValueRef arg = arguments[i];
// Java will take ownership of the arguments.
JSValueProtectChecked(jsContext, arg);
_javaEnv->SetIntArrayRegion(jArguments, i, 1, reinterpret_cast<jint*>(&arg));
if (_javaEnv->ExceptionCheck()) {
return JSValueMakeUndefined(jsContext);
}
}
// Java will take ownership of this.
JSValueProtectChecked(jsContext, jsThis);
JSValueRef jsResult = reinterpret_cast<JSValueRef>(_javaEnv->CallIntMethod(jObject,
_javaDispatchMethodInvokeMethod, reinterpret_cast<jint>(jsContext),
reinterpret_cast<jint>(jsThis), jArguments, jException));
if (_javaEnv->ExceptionCheck()) {
return JSValueMakeUndefined(jsContext);
}
JSValueRef jsException = NULL;
_javaEnv->GetIntArrayRegion(jException, 0, 1, reinterpret_cast<jint*>(&jsException));
if (!_javaEnv->ExceptionCheck() && jsException) {
// If the java dispatch set an exception, then we pass it back to our caller.
if (exception) {
*exception = jsException;
}
// Java left us an extra reference to eat.
JSValueUnprotectChecked(jsContext, jsException);
}
// Java left us an extra reference to eat.
JSValueUnprotectChecked(jsContext, jsResult);
TR_LEAVE();
return jsResult;
}
/*
* NOTE: The object returned from this function is not protected.
*/
JSValueRef DispatchMethodToString(JSContextRef jsContext, JSObjectRef,
JSObjectRef thisObject, size_t, const JSValueRef*, JSValueRef*) {
TR_ENTER();
// This function cannot be torn off and applied to any JSValue. If this does
// not reference a DispatchMethod, return undefined.
if (!JSValueIsObjectOfClass(jsContext, thisObject, GetDispatchMethodClass())) {
return JSValueMakeUndefined(jsContext);
}
std::ostringstream ss;
DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>(
JSObjectGetPrivate(thisObject));
ss << "function " << data->_utf8Name << "() {\n [native code]\n}\n";
JSStringRef stringRep = JSStringCreateWithUTF8CString(ss.str().c_str());
JSValueRef jsStringRep = JSValueMakeString(jsContext, stringRep);
JSStringRelease(stringRep);
TR_LEAVE();
return jsStringRep;
}
/*
* NOTE: The object returned from this function is not protected.
*/
JSValueRef DispatchMethodGetToString(JSContextRef jsContext,
JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* jsException) {
TR_ENTER();
JSObjectRef toStringFn = JSObjectMakeFunctionWithCallback(jsContext,
jsPropertyName, DispatchMethodToString);
TR_LEAVE();
return toStringFn;
}
/*
*
*/
bool Initialize(JNIEnv* javaEnv, jclass javaDispatchObjectClass,
jclass javaDispatchMethodClass, jclass lowLevelSafClass) {
TR_ENTER();
if (!javaEnv || !javaDispatchObjectClass || !javaDispatchMethodClass
|| !lowLevelSafClass) {
return false;
}
_javaVM = 0;
javaEnv->GetJavaVM(&_javaVM);
_javaEnv = javaEnv;
_javaDispatchObjectClass = static_cast<jclass>(
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(
javaDispatchObjectClass, "getField", "(ILjava/lang/String;)I");
_javaDispatchMethodInvokeMethod = javaEnv->GetMethodID(
javaDispatchMethodClass, "invoke", "(II[I[I)I");
_javaDispatchObjectToStringMethod = javaEnv->GetMethodID(
javaDispatchObjectClass, "toString", "()Ljava/lang/String;");
_lowLevelSafReleaseObject = javaEnv->GetStaticMethodID(
lowLevelSafClass, "releaseObject", "(Ljava/lang/Object;)V");
if (!_javaVM
|| !_javaDispatchObjectSetFieldMethod || !_javaDispatchObjectGetFieldMethod
|| !_javaDispatchMethodInvokeMethod || !_javaDispatchObjectToStringMethod
|| !_lowLevelSafReleaseObject || javaEnv->ExceptionCheck()) {
return false;
}
TR_LEAVE();
return true;
}
void ReleaseJavaObject(jobject jObject) {
// Tricky: this call may be on a foreign thread.
JNIEnv* javaEnv = 0;
if ((_javaVM->AttachCurrentThreadAsDaemon(reinterpret_cast<void**>(&javaEnv),
NULL) < 0) || !javaEnv) {
TR_FAIL();
return;
}
// 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