#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);
}