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
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ jdk:
- oraclejdk8
- oraclejdk7
env:
- NODE_VERSION="0.11"
- NODE_VERSION="0.12"
- NODE_VERSION="0.10"
- NODE_VERSION="0.8"
before_install:
- nvm install $NODE_VERSION
before_script:
Expand Down
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,18 @@ try {
}
```

### Promises
### AsyncOptions: control over the generation of sync, async & promise method variants.

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.
As of release 0.4.5 it became possible to create async methods that return promises by setting the asyncOptions property of the java object. With release 0.4.7 this feature is extended to allow changing the suffix assigned for sync and async method variants, and to further configure this module to optionally omit generation of any of these variants.

Example:

```javascript
var java = require("java");
java.asyncOptions = {
promiseSuffix: "Promise",
asyncSuffix: undefined, // Don't generate node-style methods taking callbacks
syncSuffix: "" // Sync methods use the base name(!!)
promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise.
promisify: require("when/node").lift
};
java.classpath.push("commons-lang3-3.1.jar");
Expand All @@ -170,13 +172,18 @@ java.newInstancePromise("java.util.ArrayList")
.catch(function(err) { /* handle error */ });
```

* If you don't want 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.
* `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/A+ libraries provide such functions, but it may be necessary to provide a wrapper function. See `testHelpers.js` for an example.
* You are free to choose whatever non-empty `promiseSuffix` you want for the promise-returning methods, but you must specify a value.
* We've tested with five Promises/A+ implementations. See `testHelpers.js` for more information.
#### NOTES:
* If you want the defacto standard behavior, simply don't set java.asyncOptions.
* If you do provide asyncOptions, be aware that this module will not generate method variants of a given flavor if you don't provide a string value for the corresponding suffix (`asyncSuffix`, `syncSuffix`, `promiseSuffix`). In the example above, the application is configured to omit the method variants using node-style async callback functions.
* If you provide `asyncOptions.promiseSuffix` then you must also set `asyncOptions.promisify` to a function that *promisifies* a node-style async function. I.e. the provided function must take as input a function whose last argument is a node callback function, and it must return an equivalent promise-returning function. Several Promises/A+ libraries provide such functions, but it may be necessary to provide a wrapper function. See `testHelpers.js` for an example.
* If you provide `asyncOptions.promisify` then you must provide a *non-empty* string for `asyncOptions.promiseSuffix`.
* Either (but not both) `asyncSuffix` or `syncSuffix` can be the empty string. If you want the defacto standard behavior for no suffix on async methods, you must provide an empty string for `asyncSuffix`.
* We've tested promises with five Promises/A+ implementations. See `testHelpers.js` for more information.
* NOTE: Due to specifics of initialization order, the methods `java.newInstancePromise`, `java.callMethodPromise`, and `java.callStaticMethodPromise` are not available until some other java method is called. You may need to call some other java method such as `java.import()` to finalize java initialization.

##### Special note about the exported module functions `newInstance`, `callMethod`, and `callStaticMethod`.
These methods come in both async and sync variants. If you provide the `promisify` and `promiseSuffix` attributes in asyncOptions then you'll also get the Promises/A+ variant for these three functions. However, if you change the defacto
conventions for the `syncSuffix` (i.e. 'Sync') and/or `asyncSuffix` (i.e. '') it will not affect the naming for these three functions. I.e. no matter what you specify in asyncOptions, the async variants are named `newInstance`, `callMethod`, and `callStaticMethod`, and the sync variants are named `newInstanceSync`, `callMethodSync`, and `callStaticMethodSync`.

# Release Notes

Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@
"nan": "1.4.1"
},
"devDependencies": {
"async": "~0.1.22",
"async": "0.9.0",
"chalk": "^1.0.0",
"lodash": "^3.5.0",
"nodeunit": "0.9.0",
"when": "~3.6.4"
"when": "3.6.4"
},
"scripts": {
"test": "nodeunit test test8",
"test": "node testRunner.js",
"postinstall": "node postInstall.js"
},
"main": "./index.js"
Expand Down
73 changes: 62 additions & 11 deletions src/java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ NAN_METHOD(Java::New) {
Java::Java() {
this->m_jvm = NULL;
this->m_env = NULL;

m_SyncSuffix = "Sync";
m_AsyncSuffix = "";
doSync = true;
doAsync = true;
doPromise = false;
}

Java::~Java() {
Expand All @@ -95,28 +101,73 @@ Java::~Java() {

v8::Local<v8::Value> Java::ensureJvm() {
if(!m_jvm) {
return createJVM(&this->m_jvm, &this->m_env);
v8::Local<v8::Value> result = createJVM(&this->m_jvm, &this->m_env);
assert(result->IsNull());
return result;
}

return NanNull();
}

void Java::configureAsync(v8::Local<v8::Value>& asyncOptions) {
v8::Local<v8::Object> asyncOptionsObj = asyncOptions.As<v8::Object>();

m_SyncSuffix = "invalid";
m_AsyncSuffix = "invalid";
m_PromiseSuffix = "invalid";
doSync = false;
doAsync = false;
doPromise = false;

v8::Local<v8::Value> suffixValue = asyncOptionsObj->Get(NanNew<v8::String>("syncSuffix"));
if (suffixValue->IsString()) {
v8::Local<v8::String> suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_SyncSuffix.assign(*utf8);
doSync = true;
}

suffixValue = asyncOptionsObj->Get(NanNew<v8::String>("asyncSuffix"));
if (suffixValue->IsString()) {
v8::Local<v8::String> suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_AsyncSuffix.assign(*utf8);
doAsync = true;
}

suffixValue = asyncOptionsObj->Get(NanNew<v8::String>("promiseSuffix"));
if (suffixValue->IsString()) {
v8::Local<v8::String> suffix = suffixValue->ToString();
v8::String::Utf8Value utf8(suffix);
m_PromiseSuffix.assign(*utf8);
v8::Local<v8::Value> promisify = asyncOptionsObj->Get(NanNew<v8::String>("promisify"));
if (!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);
}

NanAssignPersistent(m_asyncOptions, asyncOptionsObj);
}

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);
configureAsync(asyncOptions);
}

// setup classpath
Expand Down
17 changes: 17 additions & 0 deletions src/java.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,20 @@ class Java : public node::ObjectWrap {
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; }

public:
bool DoSync() const { return doSync; }
bool DoAsync() const { return doAsync; }
bool DoPromise() const { return doPromise; }
std::string SyncSuffix() const { return m_SyncSuffix; }
std::string AsyncSuffix() const { return m_AsyncSuffix; }
std::string PromiseSuffix() const { return m_PromiseSuffix; }

private:
Java();
~Java();
v8::Local<v8::Value> createJVM(JavaVM** jvm, JNIEnv** env);
void destroyJVM(JavaVM** jvm, JNIEnv** env);
void configureAsync(v8::Local<v8::Value>& asyncOptions);

static NAN_METHOD(New);
static NAN_METHOD(getClassLoader);
Expand Down Expand Up @@ -60,6 +69,14 @@ class Java : public node::ObjectWrap {
v8::Persistent<v8::Array> m_classPathArray;
v8::Persistent<v8::Array> m_optionsArray;
v8::Persistent<v8::Object> m_asyncOptions;

std::string m_SyncSuffix;
std::string m_AsyncSuffix;
std::string m_PromiseSuffix;

bool doSync;
bool doAsync;
bool doPromise;
};

#endif
26 changes: 11 additions & 15 deletions src/javaObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,11 @@
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) {
if(java->DoPromise()) {
v8::Local<v8::Object> asyncOptions = NanObjectWrapHandle(java)->Get(NanNew<v8::String>("asyncOptions")).As<v8::Object>();
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;
Expand All @@ -60,15 +54,17 @@
assert(!env->ExceptionCheck());
std::string methodNameStr = javaToString(env, methodNameJava);

v8::Handle<v8::String> methodName = NanNew<v8::String>(methodNameStr.c_str());
v8::Local<v8::FunctionTemplate> methodCallTemplate = NanNew<v8::FunctionTemplate>(methodCall, methodName);
funcTemplate->PrototypeTemplate()->Set(methodName, methodCallTemplate->GetFunction());
v8::Handle<v8::String> baseMethodName = NanNew<v8::String>(methodNameStr.c_str());

v8::Handle<v8::String> methodNameSync = NanNew<v8::String>((methodNameStr + "Sync").c_str());
v8::Local<v8::FunctionTemplate> methodCallSyncTemplate = NanNew<v8::FunctionTemplate>(methodCallSync, methodName);
v8::Handle<v8::String> methodNameAsync = NanNew<v8::String>((methodNameStr + java->AsyncSuffix()).c_str());
v8::Local<v8::FunctionTemplate> methodCallTemplate = NanNew<v8::FunctionTemplate>(methodCall, baseMethodName);
funcTemplate->PrototypeTemplate()->Set(methodNameAsync, methodCallTemplate->GetFunction());

v8::Handle<v8::String> methodNameSync = NanNew<v8::String>((methodNameStr + java->SyncSuffix()).c_str());
v8::Local<v8::FunctionTemplate> methodCallSyncTemplate = NanNew<v8::FunctionTemplate>(methodCallSync, baseMethodName);
funcTemplate->PrototypeTemplate()->Set(methodNameSync, methodCallSyncTemplate->GetFunction());

if (promisifying) {
if (java->DoPromise()) {
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);
Expand All @@ -77,7 +73,7 @@
assert(result->IsFunction());
}
v8::Local<v8::Function> promFunction = result.As<v8::Function>();
v8::Handle<v8::String> methodNamePromise = NanNew<v8::String>((methodNameStr + promiseSuffix).c_str());
v8::Handle<v8::String> methodNamePromise = NanNew<v8::String>((methodNameStr + java->PromiseSuffix()).c_str());
funcTemplate->PrototypeTemplate()->Set(methodNamePromise, promFunction);
}
}
Expand Down
67 changes: 67 additions & 0 deletions testAsyncOptions/testAllThreeSuffix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// testAllThreeSuffix.js

// All three variants have non-empty suffix, i.e a suffix is required for any variant.

var java = require("../");
var assert = require("assert");
var _ = require('lodash');

java.asyncOptions = {
syncSuffix: "Sync",
asyncSuffix: "Async",
promiseSuffix: 'Promise',
promisify: require('when/node').lift // https://github.com/cujojs/when
};

module.exports = {
testAPI: function(test) {
test.expect(6);
var arrayList = java.newInstanceSync("java.util.ArrayList");
test.ok(arrayList);
test.ok(java.instanceOf(arrayList, "java.util.ArrayList"));

var api = _.functions(arrayList);
test.ok(_.includes(api, 'addSync'), 'Expected `addSync` to be present, but it is NOT.');
test.ok(_.includes(api, 'addAsync'), 'Expected `addAsync` to be present, but it is NOT.');
test.ok(_.includes(api, 'addPromise'), 'Expected addPromise to be present, but it is NOT.');
test.ok(!_.includes(api, 'add'), 'Expected add to NOT be present, but it is.');
test.done();
},

testSyncCalls: function(test) {
test.expect(1);
var arrayList = java.newInstanceSync("java.util.ArrayList");
arrayList.addSync("hello");
arrayList.addSync("world");
test.strictEqual(arrayList.sizeSync(), 2);
test.done();
},

testAsyncCalls: function(test) {
test.expect(4);
var arrayList = java.newInstanceSync("java.util.ArrayList");
arrayList.addAsync("hello", function(err, result) {
test.ifError(err);
arrayList.addAsync("world", function(err, result) {
test.ifError(err);
arrayList.sizeAsync(function(err, size) {
test.ifError(err);
test.strictEqual(size, 2);
test.done();
});
});
});
},

testPromiseCalls: function(test) {
test.expect(1);
var arrayList = java.newInstanceSync("java.util.ArrayList");
arrayList.addPromise("hello")
.then(function () { return arrayList.addPromise("world"); })
.then(function () { return arrayList.sizePromise(); })
.then(function (size) {
test.strictEqual(size, 2);
test.done();
});
}
}
52 changes: 52 additions & 0 deletions testAsyncOptions/testAsyncSuffixSyncDefault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// testAsyncSuffixSyncDefault.js

// Use "Async" for the asyncSuffix, and "" for the syncSuffix.

var java = require("../");
var assert = require("assert");
var _ = require('lodash');

java.asyncOptions = {
syncSuffix: "",
asyncSuffix: "Async"
};

module.exports = {
testAPI: function(test) {
test.expect(5);
var arrayList = java.newInstanceSync("java.util.ArrayList");
test.ok(arrayList);
test.ok(java.instanceOf(arrayList, "java.util.ArrayList"));

var api = _.functions(arrayList);
test.ok(_.includes(api, 'addAsync'), 'Expected `addAsync` to be present, but it is NOT.');
test.ok(_.includes(api, 'add'), 'Expected `add` to be present, but it is NOT.');
test.ok(!_.includes(api, 'addPromise'), 'Expected addPromise to NOT be present, but it is.');
test.done();
},

testSyncCalls: function(test) {
test.expect(1);
var arrayList = java.newInstanceSync("java.util.ArrayList");
arrayList.add("hello");
arrayList.add("world");
test.strictEqual(arrayList.size(), 2);
test.done();
},

testAsyncCalls: function(test) {
test.expect(4);
var arrayList = java.newInstanceSync("java.util.ArrayList");
arrayList.addAsync("hello", function(err, result) {
test.ifError(err);
arrayList.addAsync("world", function(err, result) {
test.ifError(err);
arrayList.sizeAsync(function(err, size) {
test.ifError(err);
test.strictEqual(size, 2);
test.done();
});
});
});
}
}
Loading