Skip to content

Commit c3f157c

Browse files
committed
Make dynamic java proxy equals/hashCode methods equivalent to java.lang.Object if they are not defined on the js proxy.
1 parent 7f4a100 commit c3f157c

File tree

7 files changed

+234
-57
lines changed

7 files changed

+234
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ node_modules
66
npm-debug.log
77
.idea
88
node-java.cbp
9+
*.iml
1.46 KB
Binary file not shown.

src-java/node/NodeDynamicProxyClass.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
package node;
22

3-
import java.util.HashSet;
3+
import java.lang.reflect.Method;
4+
import java.lang.reflect.Proxy;
45

56
public class NodeDynamicProxyClass implements java.lang.reflect.InvocationHandler {
7+
private static final Method EQUALS;
8+
private static final Method HASHCODE;
9+
static {
10+
try {
11+
EQUALS = Object.class.getMethod("equals", Object.class);
12+
HASHCODE = Object.class.getMethod("hashCode");
13+
} catch (NoSuchMethodException e) {
14+
throw new ExceptionInInitializerError(e);
15+
}
16+
}
17+
618
private native Object callJs(long ptr, java.lang.reflect.Method m, Object[] args) throws Throwable;
719
private native void unref(long ptr) throws Throwable;
8-
public long ptr;
20+
public final long ptr;
921

1022
public NodeDynamicProxyClass(String path, long ptr) {
1123
try{
@@ -18,13 +30,25 @@ public NodeDynamicProxyClass(String path, long ptr) {
1830

1931
public Object invoke(Object proxy, java.lang.reflect.Method m, Object[] args) throws Throwable
2032
{
21-
Object result = callJs(this.ptr, m, args);
22-
//if(result == null) {
23-
// System.out.println("invoke: null");
24-
//} else {
25-
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
26-
//}
27-
return result;
33+
try {
34+
Object result = callJs(this.ptr, m, args);
35+
//if(result == null) {
36+
// System.out.println("invoke: null");
37+
//} else {
38+
// System.out.println("invoke: " + result + " class: " + result.getClass() + " to string: " + result.toString());
39+
//}
40+
return result;
41+
} catch (NoSuchMethodError e) {
42+
// use 'vanilla' implementations otherwise - the object that persists between multiple invocations is
43+
// 'this', not the 'proxy' argument, so we operate on this.
44+
if (EQUALS.equals(m)) {
45+
// need to check if the arg is a Proxy, and if so, if its invocation handler == this!
46+
return Proxy.isProxyClass(args[0].getClass()) && Proxy.getInvocationHandler(args[0]) == this;
47+
} else if (HASHCODE.equals(m)) {
48+
return System.identityHashCode(this);
49+
}
50+
throw e;
51+
}
2852
}
2953

3054
public void unref() throws Throwable {

src/java.cpp

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
#include <sstream>
1414
#include <nan.h>
1515

16+
#define DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR -1
17+
#define DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR -2
18+
#define DYNAMIC_PROXY_NO_ENV_ERROR -3
19+
1620
long v8ThreadId;
1721

1822
/*static*/ v8::Persistent<v8::FunctionTemplate> Java::s_ct;
@@ -1010,60 +1014,67 @@ void EIO_AfterCallJs(uv_work_t* req) {
10101014
int ret = dynamicProxyData->java->getJvm()->GetEnv((void**)&env, JNI_VERSION_1_6);
10111015
if (ret != JNI_OK) {
10121016
printf("ERROR: jvm->GetEnv returned %d\n", ret);
1013-
goto CleanUp;
1017+
dynamicProxyData->done = DYNAMIC_PROXY_NO_ENV_ERROR;
1018+
return;
10141019
}
10151020

1016-
{
1017-
NanScope();
1018-
v8::Array* v8Args;
1019-
v8::Function* fn;
1020-
v8::Handle<v8::Value>* argv;
1021-
int argc;
1022-
int i;
1023-
v8::Local<v8::Value> v8Result;
1024-
jobject javaResult;
1025-
1026-
v8::Local<v8::Object> dynamicProxyDataFunctions = NanNew(dynamicProxyData->functions);
1027-
v8::Local<v8::Value> fnObj = dynamicProxyDataFunctions->Get(NanNew<v8::String>(dynamicProxyData->methodName.c_str()));
1028-
if(fnObj->IsUndefined() || fnObj->IsNull()) {
1029-
printf("ERROR: Could not find method %s\n", dynamicProxyData->methodName.c_str());
1030-
goto CleanUp;
1031-
}
1032-
if(!fnObj->IsFunction()) {
1033-
printf("ERROR: %s is not a function.\n", dynamicProxyData->methodName.c_str());
1034-
goto CleanUp;
1035-
}
1036-
1037-
fn = v8::Function::Cast(*fnObj);
1038-
1039-
if(dynamicProxyData->args) {
1040-
v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, dynamicProxyData->args));
1041-
argc = v8Args->Length();
1042-
} else {
1043-
argc = 0;
1044-
}
1045-
argv = new v8::Handle<v8::Value>[argc];
1046-
for(i=0; i<argc; i++) {
1047-
argv[i] = v8Args->Get(i);
1048-
}
1049-
v8Result = fn->Call(dynamicProxyDataFunctions, argc, argv);
1050-
delete[] argv;
1051-
if(!dynamicProxyDataVerify(dynamicProxyData)) {
1052-
return;
1053-
}
1054-
1055-
javaResult = v8ToJava(env, v8Result);
1056-
if(javaResult == NULL) {
1057-
dynamicProxyData->result = NULL;
1058-
} else {
1059-
dynamicProxyData->result = env->NewGlobalRef(javaResult);
1060-
}
1021+
NanScope();
1022+
v8::Array* v8Args;
1023+
v8::Function* fn;
1024+
v8::Handle<v8::Value>* argv;
1025+
int argc;
1026+
int i;
1027+
v8::Local<v8::Value> v8Result;
1028+
jobject javaResult;
1029+
1030+
v8::Local<v8::Object> dynamicProxyDataFunctions = NanNew(dynamicProxyData->functions);
1031+
v8::Local<v8::Value> fnObj = dynamicProxyDataFunctions->Get(NanNew<v8::String>(dynamicProxyData->methodName.c_str()));
1032+
if(fnObj->IsUndefined() || fnObj->IsNull()) {
1033+
dynamicProxyData->done = DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR;
1034+
return;
1035+
}
1036+
if(!fnObj->IsFunction()) {
1037+
dynamicProxyData->done = DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR;
1038+
return;
1039+
}
1040+
1041+
fn = v8::Function::Cast(*fnObj);
1042+
1043+
if(dynamicProxyData->args) {
1044+
v8Args = v8::Array::Cast(*javaArrayToV8(dynamicProxyData->java, env, dynamicProxyData->args));
1045+
argc = v8Args->Length();
1046+
} else {
1047+
argc = 0;
1048+
}
1049+
argv = new v8::Handle<v8::Value>[argc];
1050+
for(i=0; i<argc; i++) {
1051+
argv[i] = v8Args->Get(i);
1052+
}
1053+
v8Result = fn->Call(dynamicProxyDataFunctions, argc, argv);
1054+
delete[] argv;
1055+
if(!dynamicProxyDataVerify(dynamicProxyData)) {
1056+
return;
1057+
}
1058+
1059+
javaResult = v8ToJava(env, v8Result);
1060+
if(javaResult == NULL) {
1061+
dynamicProxyData->result = NULL;
1062+
} else {
1063+
dynamicProxyData->result = env->NewGlobalRef(javaResult);
10611064
}
10621065

1063-
CleanUp:
10641066
dynamicProxyData->done = true;
10651067
}
10661068

1069+
void throwNewThrowable(JNIEnv* env, const char * excClassName, std::string msg) {
1070+
jclass newExcCls = env->FindClass(excClassName);
1071+
jthrowable throwable = env->ExceptionOccurred();
1072+
if (throwable != NULL) {
1073+
env->Throw(throwable); // this should only be Errors, according to the docs
1074+
}
1075+
env->ThrowNew(newExcCls, msg.c_str());
1076+
}
1077+
10671078
JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jobject src, jlong ptr, jobject method, jobjectArray args) {
10681079
long myThreadId = my_getThreadId();
10691080
bool hasArgsGlobalRef = false;
@@ -1102,12 +1113,24 @@ JNIEXPORT jobject JNICALL Java_node_NodeDynamicProxyClass_callJs(JNIEnv *env, jo
11021113
}
11031114

11041115
if(!dynamicProxyDataVerify(dynamicProxyData)) {
1105-
return NULL;
1116+
throwNewThrowable(env, "java/lang/IllegalStateException", "dynamicProxyData was corrupted");
11061117
}
11071118
if(hasArgsGlobalRef) {
11081119
env->DeleteGlobalRef(dynamicProxyData->args);
11091120
}
11101121

1122+
switch (dynamicProxyData->done) {
1123+
case DYNAMIC_PROXY_NO_SUCH_METHOD_ERROR:
1124+
throwNewThrowable(env, "java/lang/NoSuchMethodError", "Could not find js function " + dynamicProxyData->methodName);
1125+
break;
1126+
case DYNAMIC_PROXY_NOT_A_FUNCTION_ERROR:
1127+
throwNewThrowable(env, "java/lang/IllegalStateException", dynamicProxyData->methodName + " is not a function");
1128+
break;
1129+
case DYNAMIC_PROXY_NO_ENV_ERROR:
1130+
throwNewThrowable(env, "java/lang/IllegalStateException", "Could not retrieve JNIEnv");
1131+
break;
1132+
}
1133+
11111134
jobject result = NULL;
11121135
if(dynamicProxyData->result) {
11131136
// need to retain a local ref so that we can return it, otherwise the returned object gets corrupted

test/RunInterface.class

780 Bytes
Binary file not shown.

test/RunInterface.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,26 @@ public void run() {
4141
latch.await();
4242
return result[0];
4343
}
44+
45+
public boolean runEquals(final InterfaceWithReturn r) {
46+
return r.equals(Boolean.FALSE);
47+
}
48+
49+
public int runHashCode(final InterfaceWithReturn r) {
50+
return r.hashCode();
51+
}
52+
53+
private InterfaceWithReturn prev;
54+
55+
public void setInstance(final InterfaceWithReturn r) {
56+
prev = r;
57+
}
58+
59+
public boolean runEqualsInstance(final InterfaceWithReturn r) {
60+
return r.equals(prev);
61+
}
62+
63+
public String runToString(final InterfaceWithReturn r) {
64+
return r.toString();
65+
}
4466
}

test/dynamicProxy-test.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,112 @@ exports['Dynamic Proxy'] = nodeunit.testCase({
9595

9696
test.done();
9797
});
98+
},
99+
100+
"java equals()": function (test) {
101+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
102+
});
103+
104+
var runInterface = java.newInstanceSync("RunInterface");
105+
var result = runInterface.runEqualsSync(myProxy);
106+
107+
test.equals(result, false);
108+
109+
test.done();
110+
},
111+
112+
"java equals() same instance": function (test) {
113+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
114+
});
115+
116+
var runInterface = java.newInstanceSync("RunInterface");
117+
runInterface.setInstanceSync(myProxy);
118+
var result = runInterface.runEqualsInstanceSync(myProxy);
119+
120+
test.equals(result, true);
121+
122+
test.done();
123+
},
124+
125+
"java equals() different instance": function (test) {
126+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {});
127+
var myProxy2 = java.newProxy('RunInterface$InterfaceWithReturn', {});
128+
129+
var runInterface = java.newInstanceSync("RunInterface");
130+
runInterface.setInstanceSync(myProxy);
131+
var result = runInterface.runEqualsInstanceSync(myProxy2);
132+
133+
test.equals(result, false);
134+
135+
test.done();
136+
},
137+
138+
"js equals()": function (test) {
139+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
140+
equals: function (obj) {
141+
return true;
142+
}
143+
});
144+
145+
var runInterface = java.newInstanceSync("RunInterface");
146+
var result = runInterface.runEqualsSync(myProxy);
147+
148+
test.equals(result, true);
149+
150+
test.done();
151+
},
152+
153+
"java hashCode()": function (test) {
154+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
155+
});
156+
157+
var runInterface = java.newInstanceSync("RunInterface");
158+
var result = runInterface.runHashCodeSync(myProxy);
159+
var result2 = runInterface.runHashCodeSync(myProxy);
160+
161+
test.equals(result, result2);
162+
163+
test.done();
164+
},
165+
166+
"js hashCode()": function (test) {
167+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
168+
hashCode: function() {
169+
return 1234;
170+
}
171+
});
172+
173+
var runInterface = java.newInstanceSync("RunInterface");
174+
var result = runInterface.runHashCodeSync(myProxy);
175+
176+
test.equals(result, 1234);
177+
178+
test.done();
179+
},
180+
181+
"java toString()": function (test) {
182+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {});
183+
184+
var runInterface = java.newInstanceSync("RunInterface");
185+
var result = runInterface.runToStringSync(myProxy);
186+
187+
test.equals(result, "[object Object]");
188+
189+
test.done();
190+
},
191+
192+
"js toString()": function (test) {
193+
var myProxy = java.newProxy('RunInterface$InterfaceWithReturn', {
194+
toString: function() {
195+
return "myRunInterface";
196+
}
197+
});
198+
199+
var runInterface = java.newInstanceSync("RunInterface");
200+
var result = runInterface.runToStringSync(myProxy);
201+
202+
test.equals(result, "myRunInterface");
203+
204+
test.done();
98205
}
99206
});

0 commit comments

Comments
 (0)