Skip to content

Commit bed9aab

Browse files
committed
allow calling method with explict argument types [see issue joeferner#140]
1 parent cd116c5 commit bed9aab

14 files changed

Lines changed: 312 additions & 10 deletions

README.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ try {
138138
* [newInstance](#javaNewInstance)
139139
* [instanceOf](#javaInstanceOf)
140140
* [callStaticMethod](#javaCallStaticMethod)
141+
* [callMethod](#javaCallMethod)
141142
* [getStaticFieldValue](#javaGetStaticFieldValue)
142143
* [setStaticFieldValue](#javaSetStaticFieldValue)
143144
* [newArray](#javaNewArray)
@@ -252,7 +253,7 @@ otherwise it will be the first argument in the callback.
252253
__Arguments__
253254
254255
* className - The name of the class to call the method on. For subclasses seperate using a '$' (eg. com.nearinfinty.MyClass$SubClass)
255-
* methodName - The name of the method to call.
256+
* methodName - The name of the method to call. The method name can include the full signature (see [Getting the full method signature](#getFullMethodSignature)).
256257
* callback(err, item) - Callback to be called when the class is created.
257258
258259
__Example__
@@ -264,6 +265,31 @@ __Example__
264265
// results from doSomething
265266
});
266267
268+
<a name="javaCallMethod" />
269+
**java.callMethod(instance, methodName, [args...], callback)**
270+
271+
**java.callMethodSync(instance, methodName, [args...]) : result**
272+
273+
Calls a method on the specified instance. If you are using the sync method an exception will be throw if an error occures,
274+
otherwise it will be the first argument in the callback.
275+
276+
__Arguments__
277+
278+
* instance - An instance of the class from newInstance.
279+
* methodName - The name of the method to call. The method name can include the full signature (see [Getting the full method signature](#getFullMethodSignature)).
280+
* callback(err, item) - Callback to be called when the class is created.
281+
282+
__Example__
283+
284+
var instance = java.newInstanceSync("com.nearinfinty.MyClass");
285+
286+
var result = java.callMethodSync("com.nearinfinty.MyClass", "doSomething", 42, "test");
287+
288+
java.callMethodSync(instance, "doSomething", 42, "test", function(err, results) {
289+
if(err) { console.error(err); return; }
290+
// results from doSomething
291+
});
292+
267293
<a name="javaGetStaticFieldValue" />
268294
**java.getStaticFieldValue(className, fieldName)**
269295
@@ -448,6 +474,20 @@ __Example__
448474
list.data = "test";
449475
var data = list.data;
450476
477+
<a name="getFullMethodSignature" />
478+
# Getting the Full Method Signature
479+
480+
Run `javap -s -classpath <your-class-path> <your-class-name>`. Find the method name you are looking for. For example:
481+
482+
```
483+
public int methodAmbiguous(java.lang.Double);
484+
Signature: (Ljava/lang/Double;)I
485+
```
486+
487+
The full method signature would be `methodAmbiguous(Ljava/lang/Double;)I`.
488+
489+
If you have grep, a shortcut is `javap -s -classpath . my.company.MyClass | grep -A1 myMethodName`.
490+
451491
# Signal Handling
452492
453493
The JVM intercepts signals (Ctrl+C, etc.) before node/v8 gets to handle them. To fix this there are a couple options.

src-java/node/CastingUtils.class

1.35 KB
Binary file not shown.

src-java/node/CastingUtils.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package node;
2+
3+
import java.lang.reflect.Method;
4+
5+
public class CastingUtils {
6+
public static void cast(Method method, Object[] args) throws Throwable {
7+
Class[] methodParameterTypes = method.getParameterTypes();
8+
if (methodParameterTypes.length != args.length) {
9+
throw new Exception("Method argument length mismatch. Expecting " + methodParameterTypes.length + " found " + args.length);
10+
}
11+
for (int i = 0; i < methodParameterTypes.length; i++) {
12+
args[i] = cast(args[i], methodParameterTypes[0]);
13+
}
14+
}
15+
16+
public static Object cast(Object o, Class t) {
17+
if (o == null) {
18+
return null;
19+
}
20+
21+
Class oClass = o.getClass();
22+
if (oClass == Integer.class) {
23+
Integer i = (Integer) o;
24+
if (t == Double.class) {
25+
return i.doubleValue();
26+
}
27+
} else if (oClass == Double.class) {
28+
Double d = (Double) o;
29+
if (t == Integer.class) {
30+
return d.intValue();
31+
}
32+
}
33+
34+
return o;
35+
}
36+
}

src/java.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ long my_getThreadId() {
5050
NODE_SET_PROTOTYPE_METHOD(t, "newProxy", newProxy);
5151
NODE_SET_PROTOTYPE_METHOD(t, "callStaticMethod", callStaticMethod);
5252
NODE_SET_PROTOTYPE_METHOD(t, "callStaticMethodSync", callStaticMethodSync);
53+
NODE_SET_PROTOTYPE_METHOD(t, "callMethod", callMethod);
54+
NODE_SET_PROTOTYPE_METHOD(t, "callMethodSync", callMethodSync);
5355
NODE_SET_PROTOTYPE_METHOD(t, "findClassSync", findClassSync);
5456
NODE_SET_PROTOTYPE_METHOD(t, "newArray", newArray);
5557
NODE_SET_PROTOTYPE_METHOD(t, "newByte", newByte);
@@ -446,6 +448,82 @@ NAN_METHOD(Java::callStaticMethodSync) {
446448
NanReturnValue(result);
447449
}
448450

451+
NAN_METHOD(Java::callMethodSync) {
452+
NanScope();
453+
Java* self = node::ObjectWrap::Unwrap<Java>(args.This());
454+
v8::Handle<v8::Value> ensureJvmResults = self->ensureJvm();
455+
if(!ensureJvmResults->IsNull()) {
456+
NanReturnValue(ensureJvmResults);
457+
}
458+
JNIEnv* env = self->getJavaEnv();
459+
JavaScope javaScope(env);
460+
461+
int argsStart = 0;
462+
int argsEnd = args.Length();
463+
464+
// arguments
465+
ARGS_FRONT_OBJECT(instanceObj);
466+
ARGS_FRONT_STRING(methodName);
467+
468+
JavaObject* javaObj = node::ObjectWrap::Unwrap<JavaObject>(instanceObj);
469+
470+
// find method
471+
jclass clazz = javaObj->getClass();
472+
jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd);
473+
jobject method = javaFindMethod(env, clazz, methodName, methodArgs);
474+
if(method == NULL) {
475+
std::string msg = methodNotFoundToString(env, clazz, methodName, false, args, argsStart, argsEnd);
476+
return NanThrowError(javaExceptionToV8(self, env, msg));
477+
}
478+
479+
// run
480+
v8::Handle<v8::Value> callback = NanNull();
481+
InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback);
482+
v8::Handle<v8::Value> result = baton->runSync();
483+
delete baton;
484+
if(result->IsNativeError()) {
485+
return NanThrowError(result);
486+
}
487+
NanReturnValue(result);
488+
}
489+
490+
NAN_METHOD(Java::callMethod) {
491+
NanScope();
492+
Java* self = node::ObjectWrap::Unwrap<Java>(args.This());
493+
v8::Handle<v8::Value> ensureJvmResults = self->ensureJvm();
494+
if(!ensureJvmResults->IsNull()) {
495+
NanReturnValue(ensureJvmResults);
496+
}
497+
JNIEnv* env = self->getJavaEnv();
498+
JavaScope javaScope(env);
499+
500+
int argsStart = 0;
501+
int argsEnd = args.Length();
502+
503+
// arguments
504+
ARGS_FRONT_OBJECT(instanceObj);
505+
ARGS_FRONT_STRING(methodName);
506+
ARGS_BACK_CALLBACK();
507+
508+
JavaObject* javaObj = node::ObjectWrap::Unwrap<JavaObject>(instanceObj);
509+
510+
// find method
511+
jclass clazz = javaObj->getClass();
512+
jobjectArray methodArgs = v8ToJava(env, args, argsStart, argsEnd);
513+
jobject method = javaFindMethod(env, clazz, methodName, methodArgs);
514+
if(method == NULL) {
515+
std::string msg = methodNotFoundToString(env, clazz, methodName, false, args, argsStart, argsEnd);
516+
EXCEPTION_CALL_CALLBACK(self, msg);
517+
NanReturnUndefined();
518+
}
519+
520+
// run
521+
InstanceMethodCallBaton* baton = new InstanceMethodCallBaton(self, javaObj, method, methodArgs, callback);
522+
baton->run();
523+
524+
END_CALLBACK_FUNCTION("\"method '" << methodName << "' called without a callback did you mean to use the Sync version?\"");
525+
}
526+
449527
NAN_METHOD(Java::findClassSync) {
450528
NanScope();
451529
Java* self = node::ObjectWrap::Unwrap<Java>(args.This());

src/java.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Java : public node::ObjectWrap {
2828
static NAN_METHOD(newProxy);
2929
static NAN_METHOD(callStaticMethod);
3030
static NAN_METHOD(callStaticMethodSync);
31+
static NAN_METHOD(callMethod);
32+
static NAN_METHOD(callMethodSync);
3133
static NAN_METHOD(findClassSync);
3234
static NAN_METHOD(newArray);
3335
static NAN_METHOD(newByte);

src/methodCallBaton.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ void StaticMethodCallBaton::ExecuteInternal(JNIEnv* env) {
139139
printf("calling %s\n", javaObjectToString(env, m_method).c_str());
140140
printf("arguments\n");
141141
for(int i=0; i<env->GetArrayLength(m_args); i++) {
142-
printf(" %s\n", javaObjectToString(env, env->GetObjectArrayElement((jobjectArray)m_args, i)).c_str());
142+
jobject o = env->GetObjectArrayElement((jobjectArray)m_args, i);
143+
jclass c = env->GetObjectClass(o);
144+
printf(" %s (%s)\n", javaObjectToString(env, o).c_str(), javaObjectToString(env, c).c_str());
143145
}
144146
*/
145147

src/utils.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -656,14 +656,45 @@ jobjectArray javaObjectArrayToClasses(JNIEnv *env, jobjectArray objs) {
656656
}
657657

658658
jobject javaFindMethod(JNIEnv *env, jclass clazz, std::string& methodName, jobjectArray methodArgs) {
659-
jclass methodUtilsClazz = env->FindClass("com/nearinfinity/org/apache/commons/lang3/reflect/MethodUtils");
660-
jmethodID methodUtils_getMatchingAccessibleMethod = env->GetStaticMethodID(methodUtilsClazz, "getMatchingAccessibleMethod", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
661-
const char *methodNameCStr = methodName.c_str();
662-
jstring methodNameJavaStr = env->NewStringUTF(methodNameCStr);
663-
jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs);
664-
jobject method = env->CallStaticObjectMethod(methodUtilsClazz, methodUtils_getMatchingAccessibleMethod, clazz, methodNameJavaStr, methodArgClasses);
665-
checkJavaException(env);
666-
return method;
659+
std::string::size_type parenLoc = methodName.find("(");
660+
if(parenLoc != std::string::npos) {
661+
jobject method = NULL;
662+
663+
std::string methodSig = methodName.substr(parenLoc);
664+
methodName = methodName.substr(0, parenLoc);
665+
jmethodID methodID = env->GetStaticMethodID(clazz, methodName.c_str(), methodSig.c_str());
666+
if(methodID != 0) {
667+
method = env->ToReflectedMethod(clazz, methodID, true);
668+
} else {
669+
methodID = env->GetMethodID(clazz, methodName.c_str(), methodSig.c_str());
670+
if(methodID != 0) {
671+
method = env->ToReflectedMethod(clazz, methodID, true);
672+
}
673+
}
674+
env->ExceptionClear(); // If GetStaticMethodID can't find the method it throws an exception and we need to just return NULL
675+
676+
// cast arguments
677+
if(method != NULL) {
678+
javaCastArguments(env, methodArgs, method);
679+
}
680+
681+
return method;
682+
} else {
683+
jclass methodUtilsClazz = env->FindClass("com/nearinfinity/org/apache/commons/lang3/reflect/MethodUtils");
684+
jmethodID methodUtils_getMatchingAccessibleMethod = env->GetStaticMethodID(methodUtilsClazz, "getMatchingAccessibleMethod", "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
685+
const char *methodNameCStr = methodName.c_str();
686+
jstring methodNameJavaStr = env->NewStringUTF(methodNameCStr);
687+
jobjectArray methodArgClasses = javaObjectArrayToClasses(env, methodArgs);
688+
jobject method = env->CallStaticObjectMethod(methodUtilsClazz, methodUtils_getMatchingAccessibleMethod, clazz, methodNameJavaStr, methodArgClasses);
689+
checkJavaException(env);
690+
return method;
691+
}
692+
}
693+
694+
void javaCastArguments(JNIEnv *env, jobjectArray methodArgs, jobject method) {
695+
jclass castingUtilsClazz = env->FindClass("node/CastingUtils");
696+
jmethodID castingUtilsClazz_cast = env->GetStaticMethodID(castingUtilsClazz, "cast", "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)V");
697+
env->CallStaticObjectMethod(castingUtilsClazz, castingUtilsClazz_cast, method, methodArgs);
667698
}
668699

669700
jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs) {

src/utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ jclass javaFindClass(JNIEnv* env, std::string& className);
7676
jobject javaFindField(JNIEnv* env, jclass clazz, std::string& fieldName);
7777
jobject javaFindMethod(JNIEnv *env, jclass clazz, std::string& methodName, jobjectArray methodArgs);
7878
jobject javaFindConstructor(JNIEnv *env, jclass clazz, jobjectArray methodArgs);
79+
void javaCastArguments(JNIEnv *env, jobjectArray methodArgs, jobject method);
7980

8081
std::string methodNotFoundToString(JNIEnv *env, jclass clazz, std::string methodName, bool constructor, _NAN_METHOD_ARGS_TYPE args, int argStart, int argEnd);
8182

test/Test$StaticEnum.class

0 Bytes
Binary file not shown.

test/Test$SubClass.class

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)