-
Notifications
You must be signed in to change notification settings - Fork 290
Expand file tree
/
Copy pathnodeJavaBridge.js
More file actions
327 lines (287 loc) · 11.2 KB
/
nodeJavaBridge.js
File metadata and controls
327 lines (287 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
"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;
};