diff --git a/src/java.cpp b/src/java.cpp index 83fb2971..981a9d60 100644 --- a/src/java.cpp +++ b/src/java.cpp @@ -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() { @@ -95,28 +101,73 @@ Java::~Java() { v8::Local Java::ensureJvm() { if(!m_jvm) { - return createJVM(&this->m_jvm, &this->m_env); + v8::Local result = createJVM(&this->m_jvm, &this->m_env); + assert(result->IsNull()); + return result; } return NanNull(); } +void Java::configureAsync(v8::Local& asyncOptions) { + v8::Local asyncOptionsObj = asyncOptions.As(); + + m_SyncSuffix = "invalid"; + m_AsyncSuffix = "invalid"; + m_PromiseSuffix = "invalid"; + doSync = false; + doAsync = false; + doPromise = false; + + v8::Local suffixValue = asyncOptionsObj->Get(NanNew("syncSuffix")); + if (suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(); + v8::String::Utf8Value utf8(suffix); + m_SyncSuffix.assign(*utf8); + doSync = true; + } + + suffixValue = asyncOptionsObj->Get(NanNew("asyncSuffix")); + if (suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(); + v8::String::Utf8Value utf8(suffix); + m_AsyncSuffix.assign(*utf8); + doAsync = true; + } + + suffixValue = asyncOptionsObj->Get(NanNew("promiseSuffix")); + if (suffixValue->IsString()) { + v8::Local suffix = suffixValue->ToString(); + v8::String::Utf8Value utf8(suffix); + m_PromiseSuffix.assign(*utf8); + v8::Local promisify = asyncOptionsObj->Get(NanNew("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 Java::createJVM(JavaVM** jvm, JNIEnv** env) { JavaVM* jvmTemp; JavaVMInitArgs args; v8::Local asyncOptions = NanObjectWrapHandle(this)->Get(NanNew("asyncOptions")); if (asyncOptions->IsObject()) { - v8::Local asyncOptionsObj = asyncOptions.As(); - v8::Local promisify = asyncOptionsObj->Get(NanNew("promisify")); - if (!promisify->IsFunction()) { - return NanTypeError("asyncOptions.promisify must be a function"); - } - v8::Local suffix = asyncOptionsObj->Get(NanNew("promiseSuffix")); - if (!suffix->IsString()) { - return NanTypeError("asyncOptions.promiseSuffix must be a string"); - } - NanAssignPersistent(m_asyncOptions, asyncOptionsObj); + configureAsync(asyncOptions); } // setup classpath diff --git a/src/java.h b/src/java.h index 1a37abf8..f83e7fc8 100644 --- a/src/java.h +++ b/src/java.h @@ -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 createJVM(JavaVM** jvm, JNIEnv** env); void destroyJVM(JavaVM** jvm, JNIEnv** env); + void configureAsync(v8::Local& asyncOptions); static NAN_METHOD(New); static NAN_METHOD(getClassLoader); @@ -60,6 +69,14 @@ class Java : public node::ObjectWrap { v8::Persistent m_classPathArray; v8::Persistent m_optionsArray; v8::Persistent m_asyncOptions; + + std::string m_SyncSuffix; + std::string m_AsyncSuffix; + std::string m_PromiseSuffix; + + bool doSync; + bool doAsync; + bool doPromise; }; #endif diff --git a/src/javaObject.cpp b/src/javaObject.cpp index 05ed289c..ec73d075 100644 --- a/src/javaObject.cpp +++ b/src/javaObject.cpp @@ -27,17 +27,11 @@ std::replace(className.begin(), className.end(), '[', 'a'); className = "nodeJava_" + className; - // Set up promisification - v8::Local asyncOptions = NanObjectWrapHandle(java)->Get(NanNew("asyncOptions")).As(); v8::Local promisify; - std::string promiseSuffix; - bool promisifying = asyncOptions->IsObject(); - if(promisifying) { + if(java->DoPromise()) { + v8::Local asyncOptions = NanObjectWrapHandle(java)->Get(NanNew("asyncOptions")).As(); v8::Local promisifyValue = asyncOptions->Get(NanNew("promisify")); promisify = promisifyValue.As(); - v8::Local suffix = asyncOptions->Get(NanNew("promiseSuffix"))->ToString(); - v8::String::Utf8Value utf8(suffix); - promiseSuffix.assign(*utf8); } v8::Local funcTemplate; @@ -60,15 +54,17 @@ assert(!env->ExceptionCheck()); std::string methodNameStr = javaToString(env, methodNameJava); - v8::Handle methodName = NanNew(methodNameStr.c_str()); - v8::Local methodCallTemplate = NanNew(methodCall, methodName); - funcTemplate->PrototypeTemplate()->Set(methodName, methodCallTemplate->GetFunction()); + v8::Handle baseMethodName = NanNew(methodNameStr.c_str()); - v8::Handle methodNameSync = NanNew((methodNameStr + "Sync").c_str()); - v8::Local methodCallSyncTemplate = NanNew(methodCallSync, methodName); + v8::Handle methodNameAsync = NanNew((methodNameStr + java->AsyncSuffix()).c_str()); + v8::Local methodCallTemplate = NanNew(methodCall, baseMethodName); + funcTemplate->PrototypeTemplate()->Set(methodNameAsync, methodCallTemplate->GetFunction()); + + v8::Handle methodNameSync = NanNew((methodNameStr + java->SyncSuffix()).c_str()); + v8::Local methodCallSyncTemplate = NanNew(methodCallSync, baseMethodName); funcTemplate->PrototypeTemplate()->Set(methodNameSync, methodCallSyncTemplate->GetFunction()); - if (promisifying) { + if (java->DoPromise()) { v8::Local recv = NanNew(); v8::Local argv[] = { methodCallTemplate->GetFunction() }; v8::Local result = promisify->Call(recv, 1, argv); @@ -77,7 +73,7 @@ assert(result->IsFunction()); } v8::Local promFunction = result.As(); - v8::Handle methodNamePromise = NanNew((methodNameStr + promiseSuffix).c_str()); + v8::Handle methodNamePromise = NanNew((methodNameStr + java->PromiseSuffix()).c_str()); funcTemplate->PrototypeTemplate()->Set(methodNamePromise, promFunction); } } diff --git a/testAsyncOptions/testAllThreeSuffix.js b/testAsyncOptions/testAllThreeSuffix.js new file mode 100644 index 00000000..21ff7dc1 --- /dev/null +++ b/testAsyncOptions/testAllThreeSuffix.js @@ -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(); + }); + } +} diff --git a/testAsyncOptions/testAsyncSuffixSyncDefault.js b/testAsyncOptions/testAsyncSuffixSyncDefault.js new file mode 100644 index 00000000..f471edb9 --- /dev/null +++ b/testAsyncOptions/testAsyncSuffixSyncDefault.js @@ -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(); + }); + }); + }); + } +} diff --git a/testAsyncOptions/testDefacto.js b/testAsyncOptions/testDefacto.js new file mode 100644 index 00000000..f8758bbd --- /dev/null +++ b/testAsyncOptions/testDefacto.js @@ -0,0 +1,52 @@ +// testDefacto.js + +// In the defacto case, the developer sets asyncOptions, but specifies the defacto standard behavior. + +var _ = require('lodash'); +var java = require("../"); +var nodeunit = require("nodeunit"); + +java.asyncOptions = { + syncSuffix: "Sync", + asyncSuffix: "" +}; + +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, 'addSync'), 'Expected `addSync` 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.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.add("hello", function(err, result) { + test.ifError(err); + arrayList.add("world", function(err, result) { + test.ifError(err); + arrayList.size(function(err, size) { + test.ifError(err); + test.strictEqual(size, 2); + test.done(); + }); + }); + }); + } +} diff --git a/testAsyncOptions/testDefactoPlusPromise.js b/testAsyncOptions/testDefactoPlusPromise.js new file mode 100644 index 00000000..9fd52ae6 --- /dev/null +++ b/testAsyncOptions/testDefactoPlusPromise.js @@ -0,0 +1,66 @@ +// testDefactoPlusPromise.js + +// The defacto case but with promises also enabled. + +var java = require("../"); +var assert = require("assert"); +var _ = require('lodash'); + +java.asyncOptions = { + syncSuffix: "Sync", + asyncSuffix: "", + promiseSuffix: 'Promise', + promisify: require('when/node').lift // https://github.com/cujojs/when +}; + +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, 'addSync'), 'Expected `addSync` 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 be present, but it is NOT.'); + 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.add("hello", function(err, result) { + test.ifError(err); + arrayList.add("world", function(err, result) { + test.ifError(err); + arrayList.size(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(); + }); + } +} diff --git a/testAsyncOptions/testDefault.js b/testAsyncOptions/testDefault.js new file mode 100644 index 00000000..04981cc7 --- /dev/null +++ b/testAsyncOptions/testDefault.js @@ -0,0 +1,50 @@ +// testDefault.js + +// In the default case, the developer does not set asyncOptions. +// We should get the defacto standard behavior. + +var _ = require('lodash'); +var java = require("../"); +var nodeunit = require("nodeunit"); + +java.asyncOptions = undefined; + +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, 'addSync'), 'Expected `addSync` 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.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.add("hello", function(err, result) { + test.ifError(err); + arrayList.add("world", function(err, result) { + test.ifError(err); + arrayList.size(function(err, size) { + test.ifError(err); + test.strictEqual(size, 2); + test.done(); + }); + }); + }); + } +} diff --git a/testAsyncOptions/testNoAsync.js b/testAsyncOptions/testNoAsync.js new file mode 100644 index 00000000..bca0005f --- /dev/null +++ b/testAsyncOptions/testNoAsync.js @@ -0,0 +1,50 @@ +// testNoAsync.js + +// Just Sync and Promise, both with a non-empty suffix. + +var java = require("../"); +var assert = require("assert"); +var _ = require('lodash'); + +java.asyncOptions = { + syncSuffix: "Sync", + 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, '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.ok(!_.includes(api, 'addAsync'), 'Expected `addAsync` 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(); + }, + + 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(); + }); + } +} diff --git a/testAsyncOptions/testSyncDefaultPlusPromise.js b/testAsyncOptions/testSyncDefaultPlusPromise.js new file mode 100644 index 00000000..a5d4bd20 --- /dev/null +++ b/testAsyncOptions/testSyncDefaultPlusPromise.js @@ -0,0 +1,51 @@ +// testSyncDefaultPlusPromise.js + +// Just Sync and Promise, with Sync the default (i.e. no suffix). +// This is the configuration that RedSeal wants for use with Tinkerpop/Gremlin. + +var java = require("../"); +var assert = require("assert"); +var _ = require('lodash'); + +java.asyncOptions = { + syncSuffix: "", + promiseSuffix: 'P', + 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, 'add'), 'Expected `add` to be present, but it is NOT.'); + test.ok(_.includes(api, 'addP'), 'Expected `addP` to be present, but it is NOT.'); + test.ok(!_.includes(api, 'addSync'), 'Expected `addSync` to NOT be present, but it is.'); + test.ok(!_.includes(api, 'addAsync'), 'Expected `addAsync` 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(); + }, + + testPromiseCalls: function(test) { + test.expect(1); + var arrayList = java.newInstanceSync("java.util.ArrayList"); + arrayList.addP("hello") + .then(function () { return arrayList.addP("world"); }) + .then(function () { return arrayList.sizeP(); }) + .then(function (size) { + test.strictEqual(size, 2); + test.done(); + }); + } +} diff --git a/testHelpers.js b/testHelpers.js index dccc47a8..01d5b687 100644 --- a/testHelpers.js +++ b/testHelpers.js @@ -15,9 +15,12 @@ function promisifyQ(f) { } java.asyncOptions = { + syncSuffix: "Sync", + asyncSuffix: "", promiseSuffix: 'Promise', promisify: require('when/node').lift // https://github.com/cujojs/when + // We've tested with 5 different Promises/A+ implementations: // promisify: require('bluebird').promisify // https://github.com/petkaantonov/bluebird/ // promisify: require('promise').denodeify // https://github.com/then/promise