| /* |
| * 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 |