UNPKG

java

Version:

Bridge API to connect with existing Java APIs.

328 lines (287 loc) 11.4 kB
"use strict"; process.env.PATH += require("../build/jvm_dll_path.json"); const path = require("path"); const fs = require("fs"); const util = require("util"); let binaryPath = null; try { if (fs.statSync && fs.statSync(path.join(__dirname, "../build/Debug/nodejavabridge_bindings.node")).isFile()) { binaryPath = path.resolve(path.join(__dirname, "../build/Debug/nodejavabridge_bindings.node")); console.log("****** NODE-JAVA RUNNING IN DEBUG MODE ******"); } } catch (_err) { // do nothing fs.statSync just couldn't find the file } if (!binaryPath) { binaryPath = path.resolve(path.join(__dirname, "../build/Release/nodejavabridge_bindings.node")); } const bindings = require(binaryPath); const java = (module.exports = new bindings.Java()); java.promisify = util.promisify; java.classpath.push(path.resolve(__dirname, "../src-java/commons-lang3-node-java.jar")); java.classpath.push(path.resolve(__dirname, __dirname, "../src-java")); java.classpath.pushDir = function (dir) { fs.readdirSync(dir).forEach(function (file) { java.classpath.push(path.resolve(dir, file)); }); }; java.nativeBindingLocation = binaryPath; const callStaticMethod = java.callStaticMethod; const callStaticMethodSync = java.callStaticMethodSync; const newInstanceSync = java.newInstanceSync; let syncSuffix = undefined; let asyncSuffix = undefined; let ifReadOnlySuffix = "_"; const SyncCall = function (obj, method) { if (syncSuffix === undefined) { throw new Error("Sync call made before jvm created"); } const syncMethodName = method + syncSuffix; if (syncMethodName in obj) { return obj[syncMethodName].bind(obj); } else { throw new Error("Sync method not found:" + syncMethodName); } }; java.isJvmCreated = function () { return typeof java.onJvmCreated !== "function"; }; const clients = []; // We provide two methods for 'clients' of node-java to 'register' their use of java. // By registering, a client gets the opportunity to be called asynchronously just before the JVM is created, // and just after the JVM is created. The before hook function will typically be used to add to java.classpath. // The function may peform asynchronous operations, such as async [glob](https://github.com/isaacs/node-glob) // resolutions of wild-carded file system paths, and then notify when it has finished via either calling // a node-style callback function, or by resolving a promise. // A client can register function hooks to be called before and after the JVM is created. // If the client doesn't need to be called back for either function, it can pass null or undefined. // Both before and after here are assumed to be functions that accept one argument that is a node-callback function. java.registerClient = function (before, after) { if (java.isJvmCreated()) { throw new Error("java.registerClient() called after JVM already created."); } const before_ = before && before.length === 0 ? function (cb) { before(); cb(); } : before; const after_ = after && after.length === 0 ? function (cb) { after(); cb(); } : after; clients.push({ before: before_, after: after_ }); }; // A client can register function hooks to be called before and after the JVM is created. // If the client doesn't need to be called back for either function, it can pass null or undefined. // Both before and after here are assumed to be functions that return Promises/A+ `thenable` objects. java.registerClientP = function (beforeP, afterP) { if (java.isJvmCreated()) { throw new Error("java.registerClient() called after JVM already created."); } clients.push({ beforeP: beforeP, afterP: afterP }); }; async function runBeforeHooks() { for (const client of clients) { if (client.before) { await new Promise((resolve, reject) => { client.before((err) => { if (err) { return reject(err); } return resolve(); }); }); } if (client.beforeP) { await client.beforeP(); } } } function createJVMAsync() { const _ignore = java.newLong(0); // called just for the side effect that it will create the JVM } async function runAfterHooks() { for (const client of clients) { if (client.after) { await new Promise((resolve, reject) => { client.after((err) => { if (err) { return reject(err); } return resolve(); }); }); } if (client.afterP) { await client.afterP(); } } } async function initializeAll() { await runBeforeHooks(); createJVMAsync(); await runAfterHooks(); } // This function ensures that the JVM has been launched, asynchronously. The application can be notified // when the JVM is fully created via either a node callback function, or via a promise. // If the parameter `callback` is provided, it is assume be a node callback function. // This function may be called multiple times -- the 2nd and subsequent calls are no-ops. // However, once this method has been called (or the JVM is launched as a side effect of calling other java // methods), then clients can no longer use the registerClient API. java.ensureJvm = function (callback) { // First see if the promise-style API should be used. // This must be done first in order to ensure the proper API is used. if (typeof callback === "undefined") { // Create a promisified version of this function. const launchJvmPromise = util.promisify(java.ensureJvm.bind(java)); // Call the promisified function, returning its result, which should be a promise. return launchJvmPromise(); } // If we get here, callback must be a node-style callback function. If not, throw an error. else if (typeof callback !== "function") { throw new Error("java.launchJvm(cb) requires its one argument to be a callback function."); } // Now check if the JVM has already been created. If so, we assume that the jvm was already successfully // launched, and we can just implement idempotent behavior, i.e. silently notify that the JVM has been created. else if (java.isJvmCreated()) { return setImmediate(callback); } // Finally, queue the initializeAll function. else { return setImmediate(async () => { try { await initializeAll(); callback(); } catch (err) { callback(err); } }); } }; java.onJvmCreated = function () { if (java.asyncOptions) { syncSuffix = java.asyncOptions.syncSuffix; asyncSuffix = java.asyncOptions.asyncSuffix; if (typeof syncSuffix !== "string") { throw new Error("In asyncOptions, syncSuffix must be defined and must a string"); } const promiseSuffix = java.asyncOptions.promiseSuffix; if (typeof promiseSuffix === "string") { const methods = ["newInstance", "callMethod", "callStaticMethod"]; methods.forEach(function (name) { java[name + promiseSuffix] = util.promisify(java[name]); }); } if (typeof java.asyncOptions.ifReadOnlySuffix === "string" && java.asyncOptions.ifReadOnlySuffix !== "") { ifReadOnlySuffix = java.asyncOptions.ifReadOnlySuffix; } } else { syncSuffix = "Sync"; asyncSuffix = ""; } }; const MODIFIER_PUBLIC = 1; const MODIFIER_STATIC = 8; function isWritable(prop) { // If the property has no descriptor, or wasn't explicitly marked as not writable or not configurable, assume it is. // We check both desc.writable and desc.configurable, since checking desc.writable alone is not sufficient // (e.g. for either .caller or .arguments). // It may be that checking desc.configurable is sufficient, but the specification doesn't make this definitive, // and there is no harm in checking both. if (prop === "caller" || prop === "arguments") { return false; } const desc = Object.getOwnPropertyDescriptor(function () {}, prop) || {}; return desc.writable !== false && desc.configurable !== false; } function usableName(name) { if (!isWritable(name)) { name = name + ifReadOnlySuffix; } return name; } java.import = function (name) { const clazz = java.findClassSync(name); // TODO: change to Class.forName when classloader issue is resolved. const result = function javaClassConstructorProxy() { const args = [name]; for (let i = 0; i < arguments.length; i++) { args.push(arguments[i]); } return newInstanceSync.apply(java, args); }; result.class = clazz; // copy static fields const fields = SyncCall(clazz, "getDeclaredFields")(); for (let i = 0; i < fields.length; i++) { const modifiers = SyncCall(fields[i], "getModifiers")(); if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { const fieldName = SyncCall(fields[i], "getName")(); const jsfieldName = usableName(fieldName); result.__defineGetter__( jsfieldName, function (name, fieldName) { return java.getStaticFieldValue(name, fieldName); }.bind(this, name, fieldName) ); result.__defineSetter__( jsfieldName, function (name, fieldName, val) { java.setStaticFieldValue(name, fieldName, val); }.bind(this, name, fieldName) ); } } let promiseSuffix; if (java.asyncOptions) { promiseSuffix = java.asyncOptions.promiseSuffix; } // copy static methods const methods = SyncCall(clazz, "getDeclaredMethods")(); for (let i = 0; i < methods.length; i++) { const modifiers = SyncCall(methods[i], "getModifiers")(); if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { const methodName = SyncCall(methods[i], "getName")(); if (typeof syncSuffix === "string") { const syncName = usableName(methodName + syncSuffix); result[syncName] = callStaticMethodSync.bind(java, name, methodName); } if (typeof asyncSuffix === "string") { const asyncName = usableName(methodName + asyncSuffix); result[asyncName] = callStaticMethod.bind(java, name, methodName); } if (typeof promiseSuffix === "string") { const promiseName = usableName(methodName + promiseSuffix); result[promiseName] = util.promisify(callStaticMethod.bind(java, name, methodName)); } } } // copy static classes/enums const classes = SyncCall(clazz, "getDeclaredClasses")(); for (let i = 0; i < classes.length; i++) { const modifiers = SyncCall(classes[i], "getModifiers")(); if ((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC && (modifiers & MODIFIER_STATIC) === MODIFIER_STATIC) { const className = SyncCall(classes[i], "getName")(); const simpleName = SyncCall(classes[i], "getSimpleName")(); Object.defineProperty(result, simpleName, { get: function (result, simpleName, className) { const c = java.import(className); // memoize the import const d = Object.getOwnPropertyDescriptor(result, simpleName); d.get = function (c) { return c; }.bind(null, c); Object.defineProperty(result, simpleName, d); return c; }.bind(this, result, simpleName, className), enumerable: true, configurable: true, }); } } return result; };