Skip to content
Closed
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
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,48 @@ try {
}
```

### Promises

As of release 0.4.5 it is possible to create async methods that return promises by setting the asyncOptions property of the java object.

Example:

```javascript
var java = require("java");
java.asyncOptions = {
promiseSuffix: 'Promise',
promisify: require('when/node').lift
};
java.classpath.push("commons-lang3-3.1.jar");
java.classpath.push("commons-io.jar");

java.newInstancePromise("java.util.ArrayList")
.then(function(list) { return list.addPromise("item1"); })
.then(function(list) { return list.addPromise("item2"); })
.catch(function(err) { /* handle error */ });
```

* If you don't need promise-returning methods, simply leave java.asyncOptions unset.
* Sync and standard async methods are still generated as in previous releases. In the future we may provide the option to disable generation of standard async methods.
* You are free to choose whatever non-empty suffix you want for the promise-returning methods, but you must specify a value.
* asyncOptions.promisify must be a function that given a node.js style async function as input returns a function that returns a promise that is resolved (or rejected) when the async function has completed. Several Promises libraries provide such functions. This *should* just work, but at the moment one prominent promises library doesn't.
* Note that it should be possible to mix use of two different Promises/A+ conforming libraries. You may be able to use one library for installing the asyncOptions.promisify function, and then use another library everywhere else in your application.

#### Tested Promises Libraries

##### [when](https://www.npmjs.com/package/when)
We use this package in our unit tests, and it passes under all 9 cases of our [test matrix](https://travis-ci.org/joeferner/node-java).

`promisify: require('when/node').lift`

##### [bluebird](https://www.npmjs.com/package/bluebird)
Does not work with node 0.8, but works with node 0.10 and 0.11.

`promisify: require('bluebird').promisify`

##### [Q](https://www.npmjs.com/package/q)
Unfortunately, the popular Q promises library currently does **NOT** work.

# Release Notes

### v0.2.0
Expand Down
23 changes: 23 additions & 0 deletions lib/nodeJavaBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ java.classpath.push(path.resolve(__dirname, "../commons-lang3-node-java.jar"));
java.classpath.push(path.resolve(__dirname, __dirname, "../src-java"));
java.nativeBindingLocation = binaryPath;

java.onJvmCreated = function() {
if (java.asyncOptions) {
var suffix = java.asyncOptions.promiseSuffix;
var promisify = java.asyncOptions.promisify;
if (typeof suffix === 'string' && typeof promisify === 'function') {
var methods = ['newInstance', 'callMethod', 'callStaticMethod'];
methods.forEach(function (name) {
java[name + suffix] = promisify(java[name]);
});
}
}
}

var MODIFIER_PUBLIC = 1;
var MODIFIER_STATIC = 8;

Expand Down Expand Up @@ -42,6 +55,13 @@ java.import = function(name) {
}
}

var promisify = undefined;
var promiseSuffix;
if (java.asyncOptions && java.asyncOptions.promisify) {
promisify = java.asyncOptions.promisify;
promiseSuffix = java.asyncOptions.promiseSuffix;
}

// copy static methods
var methods = clazz.getDeclaredMethodsSync();
for (i = 0; i < methods.length; i++) {
Expand All @@ -50,6 +70,9 @@ java.import = function(name) {
var methodName = methods[i].getNameSync();
result[methodName + 'Sync'] = java.callStaticMethodSync.bind(java, name, methodName);
result[methodName] = java.callStaticMethod.bind(java, name, methodName);
if (promisify) {
result[methodName + promiseSuffix] = promisify(java.callStaticMethod.bind(java, name, methodName));
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
},
"devDependencies": {
"async": "~0.1.22",
"nodeunit": "0.9.0"
"nodeunit": "0.9.0",
"when": "~3.6.4"
},
"scripts": {
"test": "nodeunit test test8",
Expand Down
29 changes: 29 additions & 0 deletions src/java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ NAN_METHOD(Java::New) {
NanObjectWrapHandle(self)->Set(NanNew<v8::String>("classpath"), NanNew<v8::Array>());
NanObjectWrapHandle(self)->Set(NanNew<v8::String>("options"), NanNew<v8::Array>());
NanObjectWrapHandle(self)->Set(NanNew<v8::String>("nativeBindingLocation"), NanNew<v8::String>("Not Set"));
NanObjectWrapHandle(self)->Set(NanNew<v8::String>("asyncOptions"), NanNull());

NanReturnValue(args.This());
}
Expand All @@ -104,6 +105,20 @@ v8::Local<v8::Value> Java::createJVM(JavaVM** jvm, JNIEnv** env) {
JavaVM* jvmTemp;
JavaVMInitArgs args;

v8::Local<v8::Value> asyncOptions = NanObjectWrapHandle(this)->Get(NanNew<v8::String>("asyncOptions"));
if (asyncOptions->IsObject()) {
v8::Local<v8::Object> asyncOptionsObj = asyncOptions.As<v8::Object>();
v8::Local<v8::Value> promisify = asyncOptionsObj->Get(NanNew<v8::String>("promisify"));
if (!promisify->IsFunction()) {
return NanTypeError("asyncOptions.promisify must be a function");
}
v8::Local<v8::Value> suffix = asyncOptionsObj->Get(NanNew<v8::String>("promiseSuffix"));
if (!suffix->IsString()) {
return NanTypeError("asyncOptions.promiseSuffix must be a string");
}
NanAssignPersistent(m_asyncOptions, asyncOptionsObj);
}

// setup classpath
std::ostringstream classPath;
classPath << "-Djava.class.path=";
Expand Down Expand Up @@ -170,10 +185,20 @@ v8::Local<v8::Value> Java::createJVM(JavaVM** jvm, JNIEnv** env) {

m_classLoader = getSystemClassLoader(*env);

v8::Local<v8::Value> onJvmCreated = NanObjectWrapHandle(this)->Get(NanNew<v8::String>("onJvmCreated"));

// TODO: this handles sets put doesn't prevent modifing the underlying data. So java.classpath.push will still work which is invalid.
NanObjectWrapHandle(this)->SetAccessor(NanNew<v8::String>("classpath"), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
NanObjectWrapHandle(this)->SetAccessor(NanNew<v8::String>("options"), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
NanObjectWrapHandle(this)->SetAccessor(NanNew<v8::String>("nativeBindingLocation"), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
NanObjectWrapHandle(this)->SetAccessor(NanNew<v8::String>("asyncOptions"), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);
NanObjectWrapHandle(this)->SetAccessor(NanNew<v8::String>("onJvmCreated"), AccessorProhibitsOverwritingGetter, AccessorProhibitsOverwritingSetter);

if (onJvmCreated->IsFunction()) {
v8::Local<v8::Function> onJvmCreatedFunc = onJvmCreated.As<v8::Function>();
v8::Local<v8::Object> context = NanNew<v8::Object>();
onJvmCreatedFunc->Call(context, 0, NULL);
}

return NanNull();
}
Expand All @@ -188,6 +213,10 @@ NAN_GETTER(Java::AccessorProhibitsOverwritingGetter) {
NanReturnValue(self->m_optionsArray);
} else if(!strcmp("nativeBindingLocation", *nameStr)) {
NanReturnValue(NanNew<v8::String>(Java::s_nativeBindingLocation.c_str()));
} else if(!strcmp("asyncOptions", *nameStr)) {
NanReturnValue(self->m_asyncOptions);
} else if(!strcmp("onJvmCreated", *nameStr)) {
// There is no good reason to get onJvmCreated, so just fall through to error below.
}

std::ostringstream errStr;
Expand Down
1 change: 1 addition & 0 deletions src/java.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Java : public node::ObjectWrap {
static std::string s_nativeBindingLocation;
v8::Persistent<v8::Array> m_classPathArray;
v8::Persistent<v8::Array> m_optionsArray;
v8::Persistent<v8::Object> m_asyncOptions;
};

#endif
26 changes: 26 additions & 0 deletions src/javaObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
std::replace(className.begin(), className.end(), '[', 'a');
className = "nodeJava_" + className;

// Set up promisification
v8::Local<v8::Object> asyncOptions = NanObjectWrapHandle(java)->Get(NanNew<v8::String>("asyncOptions")).As<v8::Object>();
v8::Local<v8::Function> promisify;
std::string promiseSuffix;
bool promisifying = asyncOptions->IsObject();
if(promisifying) {
v8::Local<v8::Value> promisifyValue = asyncOptions->Get(NanNew<v8::String>("promisify"));
promisify = promisifyValue.As<v8::Function>();
v8::Local<v8::String> suffix = asyncOptions->Get(NanNew<v8::String>("promiseSuffix"))->ToString();
v8::String::Utf8Value utf8(suffix);
promiseSuffix.assign(*utf8);
}

v8::Local<v8::FunctionTemplate> funcTemplate;
if(sFunctionTemplates.find(className) != sFunctionTemplates.end()) {
//printf("existing className: %s\n", className.c_str());
Expand Down Expand Up @@ -54,6 +67,19 @@
v8::Handle<v8::String> methodNameSync = NanNew<v8::String>((methodNameStr + "Sync").c_str());
v8::Local<v8::FunctionTemplate> methodCallSyncTemplate = NanNew<v8::FunctionTemplate>(methodCallSync, methodName);
funcTemplate->PrototypeTemplate()->Set(methodNameSync, methodCallSyncTemplate->GetFunction());

if (promisifying) {
v8::Local<v8::Object> recv = NanNew<v8::Object>();
v8::Local<v8::Value> argv[] = { methodCallTemplate->GetFunction() };
v8::Local<v8::Value> result = promisify->Call(recv, 1, argv);
if (!result->IsFunction()) {
fprintf(stderr, "Promisified result is not a function -- asyncOptions.promisify must return a function.\n");
assert(result->IsFunction());
}
v8::Local<v8::Function> promFunction = result.As<v8::Function>();
v8::Handle<v8::String> methodNamePromise = NanNew<v8::String>((methodNameStr + promiseSuffix).c_str());
funcTemplate->PrototypeTemplate()->Set(methodNamePromise, promFunction);
}
}

std::list<jobject> fields;
Expand Down
Loading