Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
npm-debug.log
.idea
node-java.cbp
*.iml
8 changes: 6 additions & 2 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
{
'variables': {
'arch%': 'amd64', # linux JVM architecture. See $(JAVA_HOME)/jre/lib/<@(arch)/server/
'uname_m': '',
'conditions': [
['target_arch=="ia32"', {
'arch%': 'i386'
}],
['"<!(uname -m)"=="s390" or "<!(uname -m)"=="s390x"', {
['OS!="win"', {
'uname_m': '<!(uname -m)'
}],
['uname_m=="s390" or uname_m=="s390x"', {
'target_arch': 's390'
}],
['"<!(uname -m)"=="ppc64" or "<!(uname -m)"=="ppc64le"', {
['uname_m=="ppc64" or uname_m=="ppc64le"', {
'target_arch': 'ppc64'
}],
['OS=="win"', {
Expand Down
Binary file modified src-java/node/NodeDynamicProxyClass.class
Binary file not shown.
42 changes: 33 additions & 9 deletions src-java/node/NodeDynamicProxyClass.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
package node;

import java.util.HashSet;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class NodeDynamicProxyClass implements java.lang.reflect.InvocationHandler {
private static final Method EQUALS;
private static final Method HASHCODE;
static {
try {
EQUALS = Object.class.getMethod("equals", Object.class);
HASHCODE = Object.class.getMethod("hashCode");
} catch (NoSuchMethodException e) {
throw new ExceptionInInitializerError(e);
}
}

private native Object callJs(long ptr, java.lang.reflect.Method m, Object[] args) throws Throwable;
private native void unref(long ptr) throws Throwable;
public long ptr;
public final long ptr;

public NodeDynamicProxyClass(String path, long ptr) {
try{
Expand All @@ -18,13 +30,25 @@ public NodeDynamicProxyClass(String path, long ptr) {

public Object invoke(Object proxy, java.lang.reflect.Method m, Object[] args) throws Throwable
{
Object result = callJs(this.ptr, m, args);
//if(result == null) {
// System.out.println("invoke: null");
//} else {
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
//}
return result;
try {
Object result = callJs(this.ptr, m, args);
//if(result == null) {
// System.out.println("invoke: null");
//} else {
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
//}
return result;
} catch (NoSuchMethodError e) {
// use 'vanilla' implementations otherwise - the object that persists between multiple invocations is
// 'this', not the 'proxy' argument, so we operate on this.
if (EQUALS.equals(m)) {
// need to check if the arg is a Proxy, and if so, if its invocation handler == this!
return Proxy.isProxyClass(args[0].getClass()) && Proxy.getInvocationHandler(args[0]) == this;
} else if (HASHCODE.equals(m)) {
return System.identityHashCode(this);
}
throw e;
}
}

public void unref() throws Throwable {
Expand Down
69 changes: 55 additions & 14 deletions src/java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <sstream>
#include <nan.h>

#define DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR -1
#define DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR -2
#define DYNAMIC_PROXY_NO_ENV_ERROR -3

long v8ThreadId;

/*static*/ v8::Persistent<v8::FunctionTemplate> Java::s_ct;
Expand Down Expand Up @@ -1006,7 +1010,13 @@ void EIO_AfterCallJs(uv_work_t* req) {
}
dynamicProxyData->result = NULL;

JNIEnv* env = dynamicProxyData->env;
JNIEnv* env;
int ret = dynamicProxyData->java->getJvm()->GetEnv((void**)&env, JNI_VERSION_1_6);
if (ret != JNI_OK) {
printf("ERROR: jvm->GetEnv returned %d\n", ret);
dynamicProxyData->done = DYNAMIC_PROXY_NO_ENV_ERROR;
return;
}

NanScope();
v8::Array* v8Args;
Expand All @@ -1020,12 +1030,12 @@ void EIO_AfterCallJs(uv_work_t* req) {
v8::Local<v8::Object> dynamicProxyDataFunctions = NanNew(dynamicProxyData->functions);
v8::Local<v8::Value> fnObj = dynamicProxyDataFunctions->Get(NanNew<v8::String>(dynamicProxyData->methodName.c_str()));
if(fnObj->IsUndefined() || fnObj->IsNull()) {
printf("ERROR: Could not find method %s\n", dynamicProxyData->methodName.c_str());
goto CleanUp;
dynamicProxyData->done = DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR;
return;
}
if(!fnObj->IsFunction()) {
printf("ERROR: %s is not a function.\n", dynamicProxyData->methodName.c_str());
goto CleanUp;
dynamicProxyData->done = DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR;
return;
}

fn = v8::Function::Cast(*fnObj);
Expand All @@ -1049,25 +1059,31 @@ void EIO_AfterCallJs(uv_work_t* req) {
javaResult = v8ToJava(env, v8Result);
if(javaResult == NULL) {
dynamicProxyData->result = NULL;
dynamicProxyData->resultGlobalRef = NULL;
} else {
dynamicProxyData->result = javaResult;
dynamicProxyData->resultGlobalRef = env->NewGlobalRef(javaResult);
dynamicProxyData->result = env->NewGlobalRef(javaResult);
}

CleanUp:
dynamicProxyData->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) {
long myThreadId = my_getThreadId();
bool hasArgsGlobalRef = false;

// args needs to be global, you can't send env across thread boundaries
DynamicProxyData* dynamicProxyData = (DynamicProxyData*)ptr;
dynamicProxyData->env = env;
dynamicProxyData->args = args;
dynamicProxyData->done = false;
dynamicProxyData->result = NULL;
dynamicProxyData->resultGlobalRef = NULL;

jclass methodClazz = env->FindClass("java/lang/reflect/Method");
jmethodID method_getName = env->GetMethodID(methodClazz, "getName", "()Ljava/lang/String;");
Expand All @@ -1083,6 +1099,12 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
EIO_AfterCallJs(req);
#endif
} else {
if (args) {
// if args is not null and we have to kick this across the thread boundary, make it a global ref
dynamicProxyData->args = (jobjectArray) env->NewGlobalRef(args);
hasArgsGlobalRef = true;
}

uv_queue_work(uv_default_loop(), req, EIO_CallJs, (uv_after_work_cb)EIO_AfterCallJs);

while(!dynamicProxyData->done) {
Expand All @@ -1091,12 +1113,31 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
}

if(!dynamicProxyDataVerify(dynamicProxyData)) {
return NULL;
throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted");
}
if(hasArgsGlobalRef) {
env->DeleteGlobalRef(dynamicProxyData->args);
}

switch (dynamicProxyData->done) {
case DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR:
throwNewThrowable(env, "java/lang/NoSuchMethodError", "Could not find js function " + dynamicProxyData->methodName);
break;
case DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR:
throwNewThrowable(env, "java/lang/IllegalStateException", dynamicProxyData->methodName + " is not a function");
break;
case DYNAMIC_PROXY_NO_ENV_ERROR:
throwNewThrowable(env, "java/lang/IllegalStateException", "Could not retrieve JNIEnv");
break;
}

jobject result = NULL;
if(dynamicProxyData->result) {
env->DeleteGlobalRef(dynamicProxyData->resultGlobalRef);
// need to retain a local ref so that we can return it, otherwise the returned object gets corrupted
result = env->NewLocalRef(dynamicProxyData->result);
env->DeleteGlobalRef(dynamicProxyData->result);
}
return dynamicProxyData->result;
return result;
}

JNIEXPORT void JNICALL Java_node_NodeDynamicProxyClass_unref(JNIEnv *env, jobject src, jlong ptr) {
Expand Down
4 changes: 2 additions & 2 deletions src/java.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Java : public node::ObjectWrap {
public:
static void Init(v8::Handle<v8::Object> target);
JavaVM* getJvm() { return m_jvm; }
JNIEnv* getJavaEnv() { return m_env; }
JNIEnv* getJavaEnv() { return m_env; } // can only be used safely by the main thread as this is the thread it belongs to
jobject getClassLoader() { return m_classLoader; }

private:
Expand Down Expand Up @@ -47,7 +47,7 @@ class Java : public node::ObjectWrap {

static v8::Persistent<v8::FunctionTemplate> s_ct;
JavaVM* m_jvm;
JNIEnv* m_env;
JNIEnv* m_env; // can only be used safely by the main thread as this is the thread it belongs to
jobject m_classLoader;
std::string m_classPath;
static std::string s_nativeBindingLocation;
Expand Down
2 changes: 0 additions & 2 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,13 @@ typedef enum _jvalueType {

struct DynamicProxyData {
unsigned int markerStart;
JNIEnv* env;
Java* java;
std::string interfaceName;
v8::Persistent<v8::Object> functions;
v8::Persistent<v8::Value> jsObject;
std::string methodName;
jobjectArray args;
jobject result;
jobject resultGlobalRef;
int done;
unsigned int markerEnd;
};
Expand Down
Binary file added test/RunInterface$1.class
Binary file not shown.
Binary file modified test/RunInterface.class
Binary file not shown.
38 changes: 38 additions & 0 deletions test/RunInterface.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import java.util.concurrent.CountDownLatch;

public class RunInterface {
public static interface Interface0Arg {
Expand Down Expand Up @@ -25,4 +26,41 @@ public void run1Args(Interface1Arg r) {
public int runWithReturn(InterfaceWithReturn r) {
return r.run(42);
}

public int runInAnotherThread(final InterfaceWithReturn r) throws InterruptedException {
final int[] result = {0};
final CountDownLatch latch = new CountDownLatch(1);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
result[0] = r.run(46);
latch.countDown();
}
});
t.start();
latch.await();
return result[0];
}

public boolean runEquals(final InterfaceWithReturn r) {
return r.equals(Boolean.FALSE);
}

public int runHashCode(final InterfaceWithReturn r) {
return r.hashCode();
}

private InterfaceWithReturn prev;

public void setInstance(final InterfaceWithReturn r) {
prev = r;
}

public boolean runEqualsInstance(final InterfaceWithReturn r) {
return r.equals(prev);
}

public String runToString(final InterfaceWithReturn r) {
return r.toString();
}
}
Loading