#include "java.h" #include #ifdef WIN32 #else #include #endif #include "javaObject.h" #include "javaScope.h" #include "methodCallBaton.h" #include "node_NodeDynamicProxyClass.h" #include #include #include #include #define DYNAMIC_PROXY_JS_ERROR -4 #ifdef WIN32 typedef long threadId; #else typedef pthread_t threadId; #endif threadId v8ThreadId; std::queue queue_dynamicProxyJsCallData; uv_mutex_t uvMutex_dynamicProxyJsCall; uv_async_t uvAsync_dynamicProxyJsCall; /*static*/ Nan::Persistent<:functiontemplate> Java::s_ct; /*static*/ std::string Java::s_nativeBindingLocation; void my_sleep(int dur) { #ifdef WIN32 Sleep(dur); #else usleep(dur); #endif } threadId my_getThreadId() { #ifdef WIN32 return (long)GetCurrentThreadId(); #else return pthread_self(); #endif } bool v8ThreadIdEquals(threadId a, threadId b) { #ifdef WIN32 return a == b; #else return pthread_equal(a, b); #endif } void EIO_CallJs(DynamicProxyJsCallData *callData); void uvAsyncCb_dynamicProxyJsCall(uv_async_t *handle) { DynamicProxyJsCallData *callData; do { uv_mutex_lock(&uvMutex_dynamicProxyJsCall); if(!queue_dynamicProxyJsCallData.empty()) { callData = queue_dynamicProxyJsCallData.front(); queue_dynamicProxyJsCallData.pop(); } else { callData = NULL; } uv_mutex_unlock(&uvMutex_dynamicProxyJsCall); if(callData) { EIO_CallJs(callData); } } while(callData); } /*static*/ void Java::Init(v8::Local<:object> target) { Nan::HandleScope scope; v8ThreadId = my_getThreadId(); uv_mutex_init(&uvMutex_dynamicProxyJsCall); uv_async_init(uv_default_loop(), &uvAsync_dynamicProxyJsCall, uvAsyncCb_dynamicProxyJsCall); v8::Local<:functiontemplate> t = Nan::New<:functiontemplate>(New); s_ct.Reset(t); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(Nan::New<:string>("Java").ToLocalChecked()); Nan::SetPrototypeMethod(t, "getClassLoader", getClassLoader); Nan::SetPrototypeMethod(t, "newInstance", newInstance); Nan::SetPrototypeMethod(t, "newInstanceSync", newInstanceSync); Nan::SetPrototypeMethod(t, "newProxy", newProxy); Nan::SetPrototypeMethod(t, "callStaticMethod", callStaticMethod); Nan::SetPrototypeMethod(t, "callStaticMethodSync", callStaticMethodSync); Nan::SetPrototypeMethod(t, "callMethod", callMethod); Nan::SetPrototypeMethod(t, "callMethodSync", callMethodSync); Nan::SetPrototypeMethod(t, "findClassSync", findClassSync); Nan::SetPrototypeMethod(t, "newArray", newArray); Nan::SetPrototypeMethod(t, "newByte", newByte); Nan::SetPrototypeMethod(t, "newShort", newShort); Nan::SetPrototypeMethod(t, "newLong", newLong); Nan::SetPrototypeMethod(t, "newChar", newChar); Nan::SetPrototypeMethod(t, "newFloat", newFloat); Nan::SetPrototypeMethod(t, "newDouble", newDouble); Nan::SetPrototypeMethod(t, "getStaticFieldValue", getStaticFieldValue); Nan::SetPrototypeMethod(t, "setStaticFieldValue", setStaticFieldValue); Nan::SetPrototypeMethod(t, "instanceOf", instanceOf); Nan::Set(target, Nan::New<:string>("Java").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); JavaProxyObject::init(); } NAN_METHOD(Java::New) { Nan::HandleScope scope; Java *self = new Java(); self->Wrap(info.This()); Nan::Set(self->handle(), Nan::New<:string>("classpath").ToLocalChecked(), Nan::New<:array>()); Nan::Set(self->handle(), Nan::New<:string>("options").ToLocalChecked(), Nan::New<:array>()); Nan::Set(self->handle(), Nan::New<:string>("nativeBindingLocation").ToLocalChecked(), Nan::New<:string>("Not Set").ToLocalChecked()); Nan::Set(self->handle(), Nan::New<:string>("asyncOptions").ToLocalChecked(), Nan::Null()); info.GetReturnValue().Set(info.This()); } Java::Java() { this->m_jvm = NULL; this->m_env = NULL; m_SyncSuffix = "Sync"; m_AsyncSuffix = ""; doSync = true; doAsync = true; doPromise = false; } Java::~Java() { this->destroyJVM(&this->m_jvm, &this->m_env); } v8::Local<:value> Java::ensureJvm() { if(!m_jvm) { v8::Local<:value> result = createJVM(&this->m_jvm, &this->m_env); assert(result->IsNull()); return result; } return Nan::Null(); } void Java::configureAsync(v8::Local<:value>& asyncOptions) { v8::Local<:object> asyncOptionsObj = asyncOptions.As<:object>(); m_SyncSuffix = "invalid"; m_AsyncSuffix = "invalid"; m_PromiseSuffix = "invalid"; doSync = false; doAsync = false; doPromise = false; v8::MaybeLocal<:value> maybeSuffixValue = Nan::Get(asyncOptionsObj, Nan::New<:string>("syncSuffix").ToLocalChecked()); v8::Local<:value> suffixValue; if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { v8::Local<:string> suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); Nan::Utf8String utf8(suffix); m_SyncSuffix.assign(*utf8); doSync = true; } maybeSuffixValue = Nan::Get(asyncOptionsObj, Nan::New<:string>("asyncSuffix").ToLocalChecked()); if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { v8::Local<:string> suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); Nan::Utf8String utf8(suffix); m_AsyncSuffix.assign(*utf8); doAsync = true; } maybeSuffixValue = Nan::Get(asyncOptionsObj, Nan::New<:string>("promiseSuffix").ToLocalChecked()); if (maybeSuffixValue.ToLocal(&suffixValue) && suffixValue->IsString()) { v8::Local<:string> suffix = suffixValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); Nan::Utf8String utf8(suffix); m_PromiseSuffix.assign(*utf8); v8::MaybeLocal<:value> maybePromisify = Nan::Get(asyncOptionsObj, Nan::New<:string>("promisify").ToLocalChecked()); v8::Local<:value> promisify; if (maybePromisify.ToLocal(&promisify) && !promisify->IsFunction()) { fprintf(stderr, "asyncOptions.promisify must be a function"); assert(promisify->IsFunction()); } doPromise = true; } if (doSync && doAsync) { assert(m_SyncSuffix != m_AsyncSuffix); } if (doSync && doPromise) { assert(m_SyncSuffix != m_PromiseSuffix); } if (doAsync && doPromise) { assert(m_AsyncSuffix != m_PromiseSuffix); } m_asyncOptions.Reset(asyncOptionsObj); } v8::Local<:value> Java::createJVM(JavaVM** jvm, JNIEnv** env) { v8::MaybeLocal<:value> maybeAsyncOptions = Nan::Get(this->handle(), Nan::New<:string>("asyncOptions").ToLocalChecked()); v8::Local<:value> asyncOptions; if (maybeAsyncOptions.ToLocal(&asyncOptions) && asyncOptions->IsObject()) { configureAsync(asyncOptions); } // setup classpath std::ostringstream classPath; classPath maybeClassPathValue = Nan::Get(this->handle(), Nan::New<:string>("classpath").ToLocalChecked()); v8::Local<:value> classPathValue; if(!maybeClassPathValue.ToLocal(&classPathValue) || !classPathValue->IsArray()) { return Nan::TypeError("Classpath must be an array"); } v8::Local<:array> classPathArrayTemp = v8::Local<:array>::Cast(classPathValue); m_classPathArray.Reset(classPathArrayTemp); for(uint32_t i=0; iLength(); i++) { if(i != 0) { #ifdef WIN32 classPath arrayItemValue = classPathArrayTemp->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); if(!arrayItemValue->IsString()) { return Nan::TypeError("Classpath must only contain strings"); } v8::Local<:string> arrayItem = arrayItemValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); Nan::Utf8String arrayItemStr(arrayItem); classPath v8NativeBindingLocation = Nan::Get(this->handle(), Nan::New<:string>("nativeBindingLocation").ToLocalChecked()).FromMaybe(v8::Local<:value>()); Nan::Utf8String nativeBindingLocationStr(v8NativeBindingLocation); s_nativeBindingLocation = *nativeBindingLocationStr; // get other options v8::Local<:value> optionsValue = Nan::Get(this->handle(), Nan::New<:string>("options").ToLocalChecked()).FromMaybe(v8::Local<:value>()); if(!optionsValue->IsArray()) { return Nan::TypeError("options must be an array"); } v8::Local<:array> optionsArrayTemp = v8::Local<:array>::Cast(optionsValue); m_optionsArray.Reset(optionsArrayTemp); // create vm options int vmOptionsCount = optionsArrayTemp->Length() + 1; JavaVMOption* vmOptions = new JavaVMOption[vmOptionsCount]; //printf("classPath: %s\n", classPath.str().c_str()); vmOptions[0].optionString = strdup(classPath.str().c_str()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> arrayItemValue = optionsArrayTemp->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); if(!arrayItemValue->IsString()) { delete[] vmOptions; return Nan::TypeError("options must only contain strings"); } v8::Local<:string> arrayItem = arrayItemValue->ToString(Nan::GetCurrentContext()).ToLocalChecked(); Nan::Utf8String arrayItemStr(arrayItem); vmOptions[i+1].optionString = strdup(*arrayItemStr); } JavaVMInitArgs args; // The JNI invocation is documented to include a function JNI_GetDefaultJavaVMInitArgs that // was formerly called here. But the documentation from Oracle is confusing/contradictory. // 1) It claims that the caller must set args.version before calling JNI_GetDefaultJavaVMInitArgs, which // we did not do. // 2) The sample code provide at the top of the doc doesn't even call JNI_GetDefaultJavaVMInitArgs. // 3) The Oracle documentation for Java 6 through Java 8 all contain a comment "Note that in the JDK/JRE, there is no // longer any need to call JNI_GetDefaultJavaVMInitArgs." // 4) It seems that some platforms don't implement JNI_GetDefaultJavaVMInitArgs, or have // marked it deprecated. // Omitting the call to JNI_GetDefaultJavaVMInitArgs works fine on Mac and Linux with Java 7 and Java 8. // The Oracle documentation is here: // http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/invocation.html // http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html // http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html args.version = JNI_BEST_VERSION; // JNI_GetDefaultJavaVMInitArgs(&args); // If this turns out to be necessary, it should be called here. args.ignoreUnrecognized = false; args.options = vmOptions; args.nOptions = vmOptionsCount; JavaVM* jvmTemp; JNI_CreateJavaVM(&jvmTemp, (void **)env, &args); *jvm = jvmTemp; delete [] vmOptions; m_classLoader = getSystemClassLoader(*env); v8::Local<:value> onJvmCreated = Nan::Get(this->handle(), Nan::New<:string>("onJvmCreated").ToLocalChecked()).FromMaybe(v8::Local<:value>()); // TODO: this handles sets put doesn't prevent modifing the underlying data. So java.classpath.push will still work which is invalid. Nan::SetAccessor(this->handle(), Nan::New<:string>("classpath").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); Nan::SetAccessor(this->handle(), Nan::New<:string>("options").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); Nan::SetAccessor(this->handle(), Nan::New<:string>("nativeBindingLocation").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); Nan::SetAccessor(this->handle(), Nan::New<:string>("asyncOptions").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); Nan::SetAccessor(this->handle(), Nan::New<:string>("onJvmCreated").ToLocalChecked(), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter); if (onJvmCreated->IsFunction()) { v8::Local<:function> onJvmCreatedFunc = onJvmCreated.As<:function>(); v8::Local<:object> context = Nan::New<:object>(); Nan::Call(onJvmCreatedFunc, context, 0, NULL); } return Nan::Null(); } NAN_GETTER(Java::AccessorProhibitsOverwritingGetter) { Java* self = Nan::ObjectWrap::Unwrap(info.This()); Nan::HandleScope scope; Nan::Utf8String nameStr(property); if(!strcmp("classpath", *nameStr)) { info.GetReturnValue().Set(Nan::New(self->m_classPathArray)); return; } else if(!strcmp("options", *nameStr)) { info.GetReturnValue().Set(Nan::New(self->m_optionsArray)); return; } else if(!strcmp("nativeBindingLocation", *nameStr)) { info.GetReturnValue().Set(Nan::New(Java::s_nativeBindingLocation.c_str()).ToLocalChecked()); return; } else if(!strcmp("asyncOptions", *nameStr)) { info.GetReturnValue().Set(Nan::New(self->m_asyncOptions)); return; } else if(!strcmp("onJvmCreated", *nameStr)) { // There is no good reason to get onJvmCreated, so just fall through to error below. } std::ostringstream errStr; errStr DestroyJavaVM(); *jvm = NULL; *env = NULL; } NAN_METHOD(Java::getClassLoader) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); jclass classClazz = env->FindClass("java/lang/ClassLoader"); jmethodID class_getClassLoader = env->GetStaticMethodID(classClazz, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); jobject classLoader = env->CallStaticObjectMethod(classClazz, class_getClassLoader); checkJavaException(env); jobject result = env->NewGlobalRef(classLoader); info.GetReturnValue().Set(javaToV8(self, env, result)); } NAN_METHOD(Java::newInstance) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_CLASSNAME(); ARGS_BACK_CALLBACK(); // find class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { EXCEPTION_CALL_CALLBACK(self, "Could not find class " run(); END_CALLBACK_FUNCTION("\"Constructor for class '" (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_CLASSNAME(); // find class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr callback = Nan::Null(); NewInstanceBaton* baton = new NewInstanceBaton(self, clazz, method, methodArgs, callback); v8::Local<:value> result = baton->runSync(); delete baton; if(result->IsNativeError()) { return Nan::ThrowError(result); } info.GetReturnValue().Set(result); } NAN_METHOD(Java::newProxy) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; ARGS_FRONT_STRING(interfaceName); ARGS_FRONT_OBJECT(functions); DynamicProxyData* dynamicProxyData = new DynamicProxyData(); dynamicProxyData->markerStart = DYNAMIC_PROXY_DATA_MARKER_START; dynamicProxyData->markerEnd = DYNAMIC_PROXY_DATA_MARKER_END; dynamicProxyData->java = self; dynamicProxyData->interfaceName = interfaceName; dynamicProxyData->functions.Reset(functions); // find NodeDynamicProxyClass std::string className = "node.NodeDynamicProxyClass"; jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr FindClass("java/lang/Object"); jobjectArray methodArgs = env->NewObjectArray(2, objectClazz, NULL); env->SetObjectArrayElement(methodArgs, 0, v8ToJava(env, Nan::New<:string>(s_nativeBindingLocation.c_str()).ToLocalChecked())); env->SetObjectArrayElement(methodArgs, 1, longToJavaLongObj(env, (jlong)dynamicProxyData)); jobject method = javaFindConstructor(env, clazz, methodArgs); if(method == NULL) { std::ostringstream errStr; errStr FindClass("java/lang/reflect/Constructor"); jmethodID constructor_newInstance = env->GetMethodID(constructorClazz, "newInstance", "([Ljava/lang/Object;)Ljava/lang/Object;"); //printf("invoke: %s\n", javaMethodCallToString(env, m_method, constructor_newInstance, m_args).c_str()); // run constructor jobject dynamicProxy = env->CallObjectMethod(method, constructor_newInstance, methodArgs); if(env->ExceptionCheck()) { std::ostringstream errStr; errStr FindClass("java/lang/Class"); jobjectArray classArray = env->NewObjectArray(1, classClazz, NULL); if(classArray == NULL) { std::ostringstream errStr; errStr SetObjectArrayElement(classArray, 0, dynamicInterface); jmethodID class_getClassLoader = env->GetMethodID(classClazz, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject classLoader = env->CallObjectMethod(dynamicInterface, class_getClassLoader); assertNoException(env); if(classLoader == NULL) { jclass objectClazz = env->FindClass("java/lang/Object"); jmethodID object_getClass = env->GetMethodID(objectClazz, "getClass", "()Ljava/lang/Class;"); jobject jobjClass = env->CallObjectMethod(dynamicProxy, object_getClass); checkJavaException(env); classLoader = env->CallObjectMethod(jobjClass, class_getClassLoader); checkJavaException(env); } if(classLoader == NULL) { std::ostringstream errStr; errStr FindClass("java/lang/reflect/Proxy"); jmethodID proxy_newProxyInstance = env->GetStaticMethodID(proxyClass, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;"); jobject proxyInstance = env->CallStaticObjectMethod(proxyClass, proxy_newProxyInstance, classLoader, classArray, dynamicProxy); if(env->ExceptionCheck()) { std::ostringstream errStr; errStr result = javaToV8(self, env, proxyInstance, dynamicProxyData); dynamicProxyData->jsObject.Reset(result); info.GetReturnValue().Set(result); } NAN_METHOD(Java::callStaticMethod) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_CLASSNAME(); ARGS_FRONT_STRING(methodName); ARGS_BACK_CALLBACK(); // find class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { EXCEPTION_CALL_CALLBACK(self, "Could not create class " run(); END_CALLBACK_FUNCTION("\"Static method '" (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_CLASSNAME(); ARGS_FRONT_STRING(methodName); // find class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr callback = Nan::Null(); StaticMethodCallBaton* baton = new StaticMethodCallBaton(self, clazz, method, methodArgs, callback); v8::Local<:value> result = baton->runSync(); delete baton; if(result->IsNativeError()) { Nan::ThrowError(result); return; } info.GetReturnValue().Set(result); } NAN_METHOD(Java::callMethodSync) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_OBJECT(instanceObj); ARGS_FRONT_STRING(methodName); JavaObject* javaObj = Nan::ObjectWrap::Unwrap(instanceObj); // find method jclass clazz = javaObj->getClass(); jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); jobject method = javaFindMethod(env, clazz, methodName, methodArgs); if(method == NULL) { std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); return Nan::ThrowError(javaExceptionToV8(self, env, msg)); } // run v8::Local<:value> callback = Nan::Null(); InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback); v8::Local<:value> result = baton->runSync(); delete baton; if(result->IsNativeError()) { return Nan::ThrowError(result); } info.GetReturnValue().Set(result); } NAN_METHOD(Java::callMethod) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; int argsEnd = info.Length(); // arguments ARGS_FRONT_OBJECT(instanceObj); ARGS_FRONT_STRING(methodName); ARGS_BACK_CALLBACK(); JavaObject* javaObj = Nan::ObjectWrap::Unwrap(instanceObj); // find method jclass clazz = javaObj->getClass(); jobjectArray methodArgs = v8ToJava(env, info, argsStart, argsEnd); jobject method = javaFindMethod(env, clazz, methodName, methodArgs); if(method == NULL) { std::string msg = methodNotFoundToString(env, clazz, methodName, false, info, argsStart, argsEnd); EXCEPTION_CALL_CALLBACK(self, msg); info.GetReturnValue().SetUndefined(); return; } // run InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback); baton->run(); END_CALLBACK_FUNCTION("\"method '" (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; // arguments ARGS_FRONT_CLASSNAME(); // find class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr result = javaToV8(self, env, clazz); info.GetReturnValue().Set(result); } NAN_METHOD(Java::newArray) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; // arguments ARGS_FRONT_CLASSNAME(); // argument - array if(info.Length() IsArray()) { std::ostringstream errStr; errStr arrayObj = v8::Local<:array>::Cast(info[argsStart]); // find class and method jarray results; if(strcmp(className.c_str(), "byte") == 0) { results = env->NewByteArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass byteClazz = env->FindClass("java/lang/Byte"); jmethodID byte_byteValue = env->GetMethodID(byteClazz, "byteValue", "()B"); jbyte byteValues[1]; byteValues[0] = env->CallByteMethod(val, byte_byteValue); assertNoException(env); env->SetByteArrayRegion((jbyteArray)results, i, 1, byteValues); } } else if(strcmp(className.c_str(), "char") == 0) { results = env->NewCharArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass stringClazz = env->FindClass("java/lang/String"); jmethodID string_charAt = env->GetMethodID(stringClazz, "charAt", "(I)C"); jchar itemValues[1]; itemValues[0] = env->CallCharMethod(val, string_charAt, 0); checkJavaException(env); env->SetCharArrayRegion((jcharArray)results, i, 1, itemValues); } } else if(strcmp(className.c_str(), "short") == 0) { results = env->NewShortArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass shortClazz = env->FindClass("java/lang/Short"); jmethodID short_shortValue = env->GetMethodID(shortClazz, "shortValue", "()S"); jshort shortValues[1]; shortValues[0] = env->CallShortMethod(val, short_shortValue); assertNoException(env); env->SetShortArrayRegion((jshortArray)results, i, 1, shortValues); } } else if(strcmp(className.c_str(), "double") == 0) { results = env->NewDoubleArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass doubleClazz = env->FindClass("java/lang/Double"); jmethodID double_doubleValue = env->GetMethodID(doubleClazz, "doubleValue", "()D"); jdouble doubleValues[1]; doubleValues[0] = env->CallDoubleMethod(val, double_doubleValue); assertNoException(env); env->SetDoubleArrayRegion((jdoubleArray)results, i, 1, doubleValues); } } else if(strcmp(className.c_str(), "int") == 0) { results = env->NewIntArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass integerClazz = env->FindClass("java/lang/Integer"); jmethodID integer_intValue = env->GetMethodID(integerClazz, "intValue", "()I"); jint intValues[1]; intValues[0] = env->CallIntMethod(val, integer_intValue); assertNoException(env); env->SetIntArrayRegion((jintArray)results, i, 1, intValues); } } else if(strcmp(className.c_str(), "float") == 0) { results = env->NewFloatArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass floatClazz = env->FindClass("java/lang/Float"); jmethodID float_floatValue = env->GetMethodID(floatClazz, "floatValue", "()F"); jfloat floatValues[1]; floatValues[0] = env->CallFloatMethod(val, float_floatValue); checkJavaException(env); env->SetFloatArrayRegion((jfloatArray)results, i, 1, floatValues); } } else if(strcmp(className.c_str(), "boolean") == 0) { results = env->NewBooleanArray(arrayObj->Length()); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); jclass booleanClazz = env->FindClass("java/lang/Boolean"); jmethodID boolean_booleanValue = env->GetMethodID(booleanClazz, "booleanValue", "()Z"); jboolean booleanValues[1]; booleanValues[0] = env->CallBooleanMethod(val, boolean_booleanValue); checkJavaException(env); env->SetBooleanArrayRegion((jbooleanArray)results, i, 1, booleanValues); } } else { jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr NewObjectArray(arrayObj->Length(), clazz, NULL); for(uint32_t i=0; iLength(); i++) { v8::Local<:value> item = arrayObj->Get(Nan::GetCurrentContext(), i).ToLocalChecked(); jobject val = v8ToJava(env, item); env->SetObjectArrayElement((jobjectArray)results, i, val); if(env->ExceptionOccurred()) { std::ostringstream errStr; Nan::Utf8String valStr(item); errStr (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newByte only takes 1 argument")); } // argument - value if(!info[0]->IsNumber()) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); } jbyte val = Nan::To(info[0]).FromJust(); jclass clazz = env->FindClass("java/lang/Byte"); jmethodID constructor = env->GetMethodID(clazz, "", "(B)V"); jobject newObj = env->NewObject(clazz, constructor, val); info.GetReturnValue().Set(JavaObject::New(self, newObj)); return; } NAN_METHOD(Java::newShort) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newShort only takes 1 argument")); } // argument - value if(!info[0]->IsNumber()) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); } jshort val = Nan::To(info[0]).FromJust(); jclass clazz = env->FindClass("java/lang/Short"); jmethodID constructor = env->GetMethodID(clazz, "", "(S)V"); jobject newObj = env->NewObject(clazz, constructor, val); info.GetReturnValue().Set(JavaObject::New(self, newObj)); } NAN_METHOD(Java::newLong) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newLong only takes 1 argument")); } // argument - value if(!info[0]->IsNumber()) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); } jlong val = Nan::To(info[0]).FromJust(); jclass clazz = env->FindClass("java/lang/Long"); jmethodID constructor = env->GetMethodID(clazz, "", "(J)V"); jobject newObj = env->NewObject(clazz, constructor, val); info.GetReturnValue().Set(JavaObject::New(self, newObj)); } NAN_METHOD(Java::newChar) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newChar only takes 1 argument")); } // argument - value jchar charVal; if(info[0]->IsNumber()) { charVal = (jchar)Nan::To(info[0]).FromJust(); } else if(info[0]->IsString()) { v8::Local<:string> val = info[0]->ToString(Nan::GetCurrentContext()).ToLocalChecked(); if(val->Length() != 1) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a string of 1 character.")); } std::string strVal = std::string(*Nan::Utf8String(val)); charVal = (jchar)strVal[0]; } else { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number or string")); } jclass clazz = env->FindClass("java/lang/Character"); jmethodID constructor = env->GetMethodID(clazz, "", "(C)V"); jobject newObj = env->NewObject(clazz, constructor, charVal); info.GetReturnValue().Set(JavaObject::New(self, newObj)); } NAN_METHOD(Java::newFloat) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newFloat only takes 1 argument")); } else if(!info[0]->IsNumber()) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); } jfloat val = (jfloat)Nan::To(info[0]).FromJust(); jclass clazz = env->FindClass("java/lang/Float"); jmethodID constructor = env->GetMethodID(clazz, "", "(F)V"); jobject newObj = env->NewObject(clazz, constructor, val); info.GetReturnValue().Set(JavaObject::New(self, newObj)); } NAN_METHOD(Java::newDouble) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); if(info.Length() != 1) { return Nan::ThrowError(Nan::TypeError("newDouble only takes 1 argument")); } else if(!info[0]->IsNumber()) { return Nan::ThrowError(Nan::TypeError("Argument 1 must be a number")); } jdouble val = (jdouble)Nan::To(info[0]).FromJust(); jclass clazz = env->FindClass("java/lang/Double"); jmethodID constructor = env->GetMethodID(clazz, "", "(D)V"); jobject newObj = env->NewObject(clazz, constructor, val); info.GetReturnValue().Set(JavaObject::New(self, newObj)); } NAN_METHOD(Java::getStaticFieldValue) { Nan::HandleScope scope; Java* self = Nan::ObjectWrap::Unwrap(info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; // arguments ARGS_FRONT_CLASSNAME(); ARGS_FRONT_STRING(fieldName); // find the class jclass clazz = javaFindClass(env, className); if(clazz == NULL) { std::ostringstream errStr; errStr FindClass("java/lang/reflect/Field"); jmethodID field_get = env->GetMethodID(fieldClazz, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); // get field value jobject val = env->CallObjectMethod(field, field_get, NULL); if(env->ExceptionOccurred()) { std::ostringstream errStr; errStr (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; // arguments ARGS_FRONT_CLASSNAME(); ARGS_FRONT_STRING(fieldName); // argument - new value if(info.Length() FindClass("java/lang/reflect/Field"); jmethodID field_set = env->GetMethodID(fieldClazz, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); //printf("newValue: %s\n", javaObjectToString(env, newValue).c_str()); // set field value env->CallObjectMethod(field, field_set, NULL, newValue); if(env->ExceptionOccurred()) { std::ostringstream errStr; errStr (info.This()); v8::Local<:value> ensureJvmResults = self->ensureJvm(); if(!ensureJvmResults->IsNull()) { info.GetReturnValue().Set(ensureJvmResults); return; } JNIEnv* env = self->getJavaEnv(); JavaScope javaScope(env); int argsStart = 0; ARGS_FRONT_OBJECT(obj); ARGS_FRONT_STRING(className); jobject instance = v8ToJava(env, obj); if (!instance) { // not even a Java object info.GetReturnValue().Set(Nan::New<:boolean>(false)); return; } jclass clazz = javaFindClass(env, className); if(!clazz) { std::ostringstream errStr; errStr IsInstanceOf(instance, clazz); info.GetReturnValue().Set(Nan::New<:boolean>(res)); } template std::string to_string(T value) { std::ostringstream os; os dynamicProxyData; assert(callData->done == 0); if(!dynamicProxyDataVerify(dynamicProxyData)) { return; } callData->result = NULL; JNIEnv* env; int ret = dynamicProxyData->java->getJvm()->GetEnv((void**)&env, JNI_BEST_VERSION); if (ret != JNI_OK) { callData->throwableClass = "java/lang/IllegalStateException"; callData->throwableMessage = "Could not retrieve JNIEnv: jvm->GetEnv returned " + to_string(ret); callData->done = DYNAMIC_PROXY_JS_ERROR; return; } Nan::HandleScope scope; v8::Array* v8Args; v8::Local<:function> fn; v8::Local<:value>* argv; int argc; int i; v8::Local<:value> v8Result; jobject javaResult; v8::Local<:object> dynamicProxyDataFunctions = Nan::New(dynamicProxyData->functions); v8::Local<:value> fnObj = dynamicProxyDataFunctions->Get(Nan::GetCurrentContext(), Nan::New<:string>(callData->methodName.c_str()).ToLocalChecked()).ToLocalChecked(); if(fnObj->IsUndefined() || fnObj->IsNull()) { callData->throwableClass = "java/lang/NoSuchMethodError"; callData->throwableMessage = "Could not find js function " + callData->methodName; callData->done = DYNAMIC_PROXY_JS_ERROR; return; } if(!fnObj->IsFunction()) { callData->throwableClass = "java/lang/IllegalStateException"; callData->throwableMessage = callData->methodName + " is not a function"; callData->done = DYNAMIC_PROXY_JS_ERROR; return; } fn = fnObj.As<:function>(); if(callData->args) { v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, callData->args)); argc = v8Args->Length(); } else { argc = 0; } argv = new v8::Local<:value>[argc]; for(i=0; iGet(Nan::GetCurrentContext(), i).ToLocalChecked(); } Nan::TryCatch tryCatch; tryCatch.SetCaptureMessage(true); v8Result = Nan::Call(fn, dynamicProxyDataFunctions, argc, argv).FromMaybe(v8::Local<:value>()); delete[] argv; if (tryCatch.HasCaught()) { callData->throwableClass = "node/NodeJsException"; Nan::Utf8String message(tryCatch.Message()->Get()); callData->throwableMessage = std::string(*message); tryCatch.Reset(); callData->done = DYNAMIC_PROXY_JS_ERROR; return; } if(!dynamicProxyDataVerify(dynamicProxyData)) { return; } javaResult = v8ToJava(env, v8Result); if(javaResult == NULL) { callData->result = NULL; } else { callData->result = env->NewGlobalRef(javaResult); } callData->done = true; } void throwNewThrowable(JNIEnv* env, const char * excClassName, std::string msg) { jclass newExcCls = env->FindClass(excClassName); jthrowable throwable = env->ExceptionOccurred(); if (throwable != NULL) { env->Throw(throwable); // this should only be Errors, according to the docs } env->ThrowNew(newExcCls, msg.c_str()); } JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, jobjectArray args) { threadId myThreadId = my_getThreadId(); bool hasArgsGlobalRef = false; DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr; // args needs to be global, you can't send env across thread boundaries DynamicProxyJsCallData callData; callData.dynamicProxyData = dynamicProxyData; callData.args = args; callData.done = false; callData.result = NULL; callData.throwableClass = ""; callData.throwableMessage = ""; jclass methodClazz = env->FindClass("java/lang/reflect/Method"); jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;"); callData.methodName = javaObjectToString(env, env->CallObjectMethod(method, method_getName)); assertNoException(env); if(v8ThreadIdEquals(myThreadId, v8ThreadId)) { EIO_CallJs(&callData); } else { if (args) { // if args is not null and we have to kick this across the thread boundary, make it a global ref callData.args = (jobjectArray) env->NewGlobalRef(args); hasArgsGlobalRef = true; } uv_mutex_lock(&uvMutex_dynamicProxyJsCall); queue_dynamicProxyJsCallData.push(&callData); // we wait for work to finish, so ok to pass ref to local var uv_mutex_unlock(&uvMutex_dynamicProxyJsCall); uv_async_send(&uvAsync_dynamicProxyJsCall); while(!callData.done) { my_sleep(100); } } if(!dynamicProxyDataVerify(dynamicProxyData)) { throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted"); } if(hasArgsGlobalRef) { env->DeleteGlobalRef(callData.args); } if (callData.done == DYNAMIC_PROXY_JS_ERROR) { throwNewThrowable(env, callData.throwableClass.c_str(), callData.throwableMessage); } jobject result = NULL; if(callData.result) { // need to retain a local ref so that we can return it, otherwise the returned object gets corrupted result = env->NewLocalRef(callData.result); env->DeleteGlobalRef(callData.result); } return result; } JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *env, jobject src, jlong ptr) { DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr; unref(dynamicProxyData); }