forked from joeferner/node-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnodeJavaBridge.js
More file actions
317 lines (277 loc) · 11.5 KB
/
nodeJavaBridge.js
File metadata and controls
317 lines (277 loc) · 11.5 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
'use strict';
process.env.PATH += require('../build/jvm_dll_path.json');
var async = require('async');
var path = require('path');
var fs = require('fs');
var 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(e) {
// do nothing fs.statSync just couldn't find the file
}
if (!binaryPath) {
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 callStaticMethod = java.callStaticMethod;
var callStaticMethodSync = java.callStaticMethodSync;
var newInstanceSync = java.newInstanceSync;
var syncSuffix = undefined;
var asyncSuffix = undefined;
var ifReadOnlySuffix = '_';
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) {
var before_, after_;
if (java.isJvmCreated()) {
throw new Error('java.registerClient() called after JVM already created.');
}
before_ = (before && before.length === 0) ?
function(cb) { before(); cb(); } :
before;
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});
}
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 (typeof callback === 'undefined' && java.asyncOptions && typeof java.asyncOptions.promisify === 'function') {
// 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 (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(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.');
}
if (typeof java.asyncOptions.ifReadOnlySuffix === 'string' && java.asyncOptions.ifReadOnlySuffix !== '') {
ifReadOnlySuffix = java.asyncOptions.ifReadOnlySuffix;
}
} else {
syncSuffix = 'Sync';
asyncSuffix = '';
}
}
var MODIFIER_PUBLIC = 1;
var 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; }
var 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) {
var clazz = java.findClassSync(name); // TODO: change to Class.forName when classloader issue is resolved.
var result = function javaClassConstructorProxy() {
var args = [name];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
return 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')();
var 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));
}
}
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')();
if (typeof syncSuffix === 'string') {
var syncName = usableName(methodName + syncSuffix);
result[syncName] = callStaticMethodSync.bind(java, name, methodName);
}
if (typeof asyncSuffix === 'string') {
var asyncName = usableName(methodName + asyncSuffix);
result[asyncName] = callStaticMethod.bind(java, name, methodName);
}
if (promisify && typeof promiseSuffix === 'string') {
var promiseName = usableName(methodName + promiseSuffix);
result[promiseName] = promisify(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;
};