-
Notifications
You must be signed in to change notification settings - Fork 280
/
nodeJavaBridge.js
262 lines (231 loc) · 9.55 KB
/
nodeJavaBridge.js
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
'use strict';
process.env.PATH += require('../build/jvm_dll_path.json');
var _ = require('lodash');
var async = require('async');
var path = require('path');
var fs = require('fs');
var binaryPath = path.resolve(path.join(__dirname, "../build/Release/nodejavabridge_bindings.node"));
var bindings = require(binaryPath);
var java = module.exports = new bindings.Java();
java.classpath.push(path.resolve(__dirname, "../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;
var syncSuffix = undefined;
var asyncSuffix = undefined;
var SyncCall = function(obj, method) {
if (syncSuffix === undefined)
throw new Error('Sync call made before jvm created');
var 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';
}
var 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.');
}
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});
}
function runBeforeHooks(done) {
function iterator(client, cb) {
try {
if (client.before) {
client.before(cb);
}
else if (client.beforeP) {
client.beforeP().then(function(ignored) { cb(); }, function(err) { cb(err); });
}
else {
cb();
}
}
catch (err) {
cb(err);
}
}
async.each(clients, iterator, done);
}
function createJVMAsync(callback) {
var ignore = java.newLong(0); // called just for the side effect that it will create the JVM
callback();
}
function runAfterHooks(done) {
function iterator(client, cb) {
try {
if (client.after) {
client.after(cb);
}
else if (client.afterP) {
client.afterP().then(function(ignored) { cb(); }, function(err) { cb(err); });
}
else {
cb();
}
}
catch (err) {
cb(err);
}
}
async.each(clients, iterator, done);
}
function initializeAll(done) {
async.series([runBeforeHooks, createJVMAsync, runAfterHooks], done);
}
// 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.
// If the parameter is not provided, and java.asyncOptions.promisify has been specified,
// then this function will return a promise, by promisifying itself and then calling that
// promisified 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 (_.isUndefined(callback) && java.asyncOptions && _.isFunction(java.asyncOptions.promisify)) {
// Create a promisified version of this function.
var launchJvmPromise = java.asyncOptions.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 (!_.isFunction(callback)) {
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(initializeAll, callback);
}
}
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');
}
var promiseSuffix = java.asyncOptions.promiseSuffix;
var promisify = java.asyncOptions.promisify;
if (typeof promiseSuffix === 'string' && typeof promisify === 'function') {
var methods = ['newInstance', 'callMethod', 'callStaticMethod'];
methods.forEach(function (name) {
java[name + promiseSuffix] = promisify(java[name]);
});
} else if (typeof promiseSuffix === 'undefined' && typeof promisify === 'undefined') {
// no promises
} else {
throw new Error('In asyncOptions, if either promiseSuffix or promisify is defined, both most be.');
}
} else {
syncSuffix = 'Sync';
asyncSuffix = '';
}
}
var MODIFIER_PUBLIC = 1;
var MODIFIER_STATIC = 8;
java.import = function(name) {
var clazz = java.findClassSync(name); // TODO: change to Class.forName when classloader issue is resolved.
var result = function() {
var args = [name];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
return java.newInstanceSync.apply(java, args);
};
var i;
result.class = clazz;
// copy static fields
var fields = SyncCall(clazz, 'getDeclaredFields')();
for (i = 0; i < fields.length; i++) {
var modifiers = SyncCall(fields[i], 'getModifiers')();
if (((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC)
&& ((modifiers & MODIFIER_STATIC) === MODIFIER_STATIC)) {
var fieldName = SyncCall(fields[i], 'getName')();
result.__defineGetter__(fieldName, function(name, fieldName) {
return java.getStaticFieldValue(name, fieldName);
}.bind(this, name, fieldName));
result.__defineSetter__(fieldName, function(name, fieldName, val) {
java.setStaticFieldValue(name, fieldName, val);
}.bind(this, name, fieldName));
}
}
var promisify = undefined;
var promiseSuffix;
if (java.asyncOptions && java.asyncOptions.promisify) {
promisify = java.asyncOptions.promisify;
promiseSuffix = java.asyncOptions.promiseSuffix;
}
// copy static methods
var methods = SyncCall(clazz, 'getDeclaredMethods')();
for (i = 0; i < methods.length; i++) {
var modifiers = SyncCall(methods[i], 'getModifiers')();
if (((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC)
&& ((modifiers & MODIFIER_STATIC) === MODIFIER_STATIC)) {
var methodName = SyncCall(methods[i], 'getName')();
result[methodName + syncSuffix] = java.callStaticMethodSync.bind(java, name, methodName);
if (typeof asyncSuffix === 'string') {
result[methodName + asyncSuffix] = java.callStaticMethod.bind(java, name, methodName);
}
if (promisify) {
result[methodName + promiseSuffix] = promisify(java.callStaticMethod.bind(java, name, methodName));
}
}
}
// copy static classes/enums
var classes = SyncCall(clazz, 'getDeclaredClasses')();
for (i = 0; i < classes.length; i++) {
var modifiers = SyncCall(classes[i], 'getModifiers')();
if (((modifiers & MODIFIER_PUBLIC) === MODIFIER_PUBLIC)
&& ((modifiers & MODIFIER_STATIC) === MODIFIER_STATIC)) {
var className = SyncCall(classes[i], 'getName')();
var simpleName = SyncCall(classes[i], 'getSimpleName')();
Object.defineProperty(result, simpleName, {
get: function(result, simpleName, className) {
var c = java.import(className);
// memoize the import
var 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;
};