js4j 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +684 -0
- package/examples/01-basic.js +75 -0
- package/examples/02-collections.js +108 -0
- package/examples/03-objects.js +99 -0
- package/examples/04-callbacks.js +97 -0
- package/examples/05-stdlib.js +123 -0
- package/examples/_gateway.js +78 -0
- package/index.d.ts +455 -0
- package/index.js +78 -0
- package/java/TestEntryPoint.java +262 -0
- package/java/py4j.jar +0 -0
- package/package.json +29 -0
- package/src/callbackServer.js +269 -0
- package/src/clientServer.js +81 -0
- package/src/collections.js +325 -0
- package/src/connection.js +253 -0
- package/src/exceptions.js +68 -0
- package/src/gateway.js +222 -0
- package/src/javaObject.js +267 -0
- package/src/jvmView.js +224 -0
- package/src/launcher.js +150 -0
- package/src/protocol.js +413 -0
- package/tests/comparison/run_comparison.js +279 -0
- package/tests/comparison/test_js4j.js +187 -0
- package/tests/comparison/test_py4j.py +171 -0
- package/tests/integration/gateway.integration.test.js +384 -0
- package/tests/unit/collections.test.js +313 -0
- package/tests/unit/exceptions.test.js +104 -0
- package/tests/unit/gateway.test.js +178 -0
- package/tests/unit/jvmView.test.js +126 -0
- package/tests/unit/launcher.test.js +27 -0
- package/tests/unit/protocol.test.js +280 -0
package/src/launcher.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const net = require('net');
|
|
5
|
+
const { JavaGateway, GatewayParameters } = require('./gateway');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Launch a Java GatewayServer process and connect a JavaGateway to it.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} options
|
|
11
|
+
* @param {string} options.classpath - Java classpath (e.g. '/path/to/py4j.jar:.')
|
|
12
|
+
* @param {string} options.mainClass - Fully-qualified main class (e.g. 'com.example.App')
|
|
13
|
+
* @param {string} [options.host='127.0.0.1'] - Gateway host
|
|
14
|
+
* @param {number} [options.port=25333] - Gateway port
|
|
15
|
+
* @param {string[]} [options.jvmArgs=[]] - Extra JVM flags (e.g. ['-Xmx512m'])
|
|
16
|
+
* @param {string[]} [options.args=[]] - Extra arguments passed to the main class
|
|
17
|
+
* @param {RegExp|string} [options.readyPattern=/GATEWAY_STARTED/] - Pattern to match in process stdout
|
|
18
|
+
* that signals the server is ready.
|
|
19
|
+
* Set to null to skip stdout check
|
|
20
|
+
* and rely only on port polling.
|
|
21
|
+
* @param {number} [options.timeout=30000] - Max ms to wait for the server to be ready
|
|
22
|
+
* @param {object} [options.gatewayOptions] - Extra options forwarded to GatewayParameters
|
|
23
|
+
*
|
|
24
|
+
* @returns {Promise<{ process: ChildProcess, gateway: JavaGateway, kill: Function }>}
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const { launchGateway } = require('js4j');
|
|
28
|
+
*
|
|
29
|
+
* const { gateway, kill } = await launchGateway({
|
|
30
|
+
* classpath: '/usr/share/py4j/py4j.jar:java/build',
|
|
31
|
+
* mainClass: 'com.example.MyApp',
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const result = await gateway.entry_point.doSomething();
|
|
35
|
+
* console.log(result);
|
|
36
|
+
*
|
|
37
|
+
* await kill();
|
|
38
|
+
*/
|
|
39
|
+
async function launchGateway(options = {}) {
|
|
40
|
+
const {
|
|
41
|
+
classpath,
|
|
42
|
+
mainClass,
|
|
43
|
+
host = '127.0.0.1',
|
|
44
|
+
port = 25333,
|
|
45
|
+
jvmArgs = [],
|
|
46
|
+
args = [],
|
|
47
|
+
readyPattern = /GATEWAY_STARTED/,
|
|
48
|
+
timeout = 30000,
|
|
49
|
+
gatewayOptions = {},
|
|
50
|
+
} = options;
|
|
51
|
+
|
|
52
|
+
if (!classpath) throw new Error('launchGateway: options.classpath is required');
|
|
53
|
+
if (!mainClass) throw new Error('launchGateway: options.mainClass is required');
|
|
54
|
+
|
|
55
|
+
// Build the java command: java [jvmArgs] -cp <classpath> <mainClass> [args]
|
|
56
|
+
const javaArgs = [...jvmArgs, '-cp', classpath, mainClass, ...args];
|
|
57
|
+
|
|
58
|
+
const child = spawn('java', javaArgs, {
|
|
59
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Forward stderr to process.stderr so errors are visible
|
|
63
|
+
child.stderr.on('data', (chunk) => process.stderr.write(chunk));
|
|
64
|
+
|
|
65
|
+
// Wait for the ready signal
|
|
66
|
+
await _waitForReady(child, host, port, readyPattern, timeout);
|
|
67
|
+
|
|
68
|
+
// Connect the gateway
|
|
69
|
+
const gateway = new JavaGateway(
|
|
70
|
+
new GatewayParameters({ host, port, ...gatewayOptions })
|
|
71
|
+
);
|
|
72
|
+
await gateway.connect();
|
|
73
|
+
|
|
74
|
+
async function kill() {
|
|
75
|
+
try { await gateway.shutdown(); } catch (_) {}
|
|
76
|
+
child.kill('SIGTERM');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { process: child, gateway, kill };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Internal helpers
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
function _waitForReady(child, host, port, readyPattern, timeout) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const deadline = setTimeout(() => {
|
|
89
|
+
reject(new Error(`launchGateway: timed out after ${timeout}ms waiting for the Java gateway to start`));
|
|
90
|
+
}, timeout);
|
|
91
|
+
|
|
92
|
+
let stdoutBuf = '';
|
|
93
|
+
let patternMatched = !readyPattern; // skip if no pattern requested
|
|
94
|
+
|
|
95
|
+
child.stdout.setEncoding('utf8');
|
|
96
|
+
child.stdout.on('data', (chunk) => {
|
|
97
|
+
process.stdout.write(chunk);
|
|
98
|
+
if (patternMatched) return;
|
|
99
|
+
stdoutBuf += chunk;
|
|
100
|
+
const re = readyPattern instanceof RegExp ? readyPattern : new RegExp(readyPattern);
|
|
101
|
+
if (re.test(stdoutBuf)) {
|
|
102
|
+
patternMatched = true;
|
|
103
|
+
_pollPort(host, port, deadline).then(resolve, reject);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
child.on('error', (err) => {
|
|
108
|
+
clearTimeout(deadline);
|
|
109
|
+
reject(new Error(`launchGateway: failed to spawn java process: ${err.message}`));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
child.on('close', (code) => {
|
|
113
|
+
if (code !== 0) {
|
|
114
|
+
clearTimeout(deadline);
|
|
115
|
+
reject(new Error(`launchGateway: java process exited with code ${code}`));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// If no readyPattern, go straight to polling
|
|
120
|
+
if (patternMatched) {
|
|
121
|
+
_pollPort(host, port, deadline).then(resolve, reject);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function _pollPort(host, port, deadline) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
function attempt() {
|
|
129
|
+
const sock = new net.Socket();
|
|
130
|
+
sock.setTimeout(500);
|
|
131
|
+
sock.on('connect', () => {
|
|
132
|
+
sock.destroy();
|
|
133
|
+
clearTimeout(deadline);
|
|
134
|
+
resolve();
|
|
135
|
+
});
|
|
136
|
+
sock.on('error', () => {
|
|
137
|
+
sock.destroy();
|
|
138
|
+
setTimeout(attempt, 200);
|
|
139
|
+
});
|
|
140
|
+
sock.on('timeout', () => {
|
|
141
|
+
sock.destroy();
|
|
142
|
+
setTimeout(attempt, 200);
|
|
143
|
+
});
|
|
144
|
+
sock.connect(port, host);
|
|
145
|
+
}
|
|
146
|
+
attempt();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { launchGateway };
|
package/src/protocol.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Js4JJavaError, Js4JError, Js4JNetworkError } = require('./exceptions');
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Command names (match py4j's protocol exactly)
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
const CALL_COMMAND_NAME = 'c\n';
|
|
9
|
+
const CONSTRUCTOR_COMMAND_NAME = 'i\n';
|
|
10
|
+
const FIELD_COMMAND_NAME = 'f\n';
|
|
11
|
+
const SHUTDOWN_GATEWAY_COMMAND_NAME = 's\n';
|
|
12
|
+
const LIST_COMMAND_NAME = 'l\n';
|
|
13
|
+
const ARRAY_COMMAND_NAME = 'a\n'; // array get/set/slice/len/create
|
|
14
|
+
const TABLE_COMMAND_NAME = 't\n';
|
|
15
|
+
const JVMVIEW_COMMAND_NAME = 'j\n';
|
|
16
|
+
const REFLECTION_COMMAND_NAME = 'r\n';
|
|
17
|
+
const MEMORY_COMMAND_NAME = 'm\n';
|
|
18
|
+
const HELP_COMMAND_NAME = 'h\n';
|
|
19
|
+
const DIR_COMMAND_NAME = 'd\n';
|
|
20
|
+
const BYTES_COMMAND_NAME = 'b\n';
|
|
21
|
+
const AUTH_COMMAND_NAME = 'A\n';
|
|
22
|
+
const STREAM_COMMAND_NAME = 'S\n';
|
|
23
|
+
|
|
24
|
+
// Sub-commands for FIELD_COMMAND
|
|
25
|
+
const FIELD_GET_SUB_COMMAND_NAME = 'g\n';
|
|
26
|
+
const FIELD_SET_SUB_COMMAND_NAME = 's\n';
|
|
27
|
+
|
|
28
|
+
// Sub-commands for JVMVIEW_COMMAND
|
|
29
|
+
const JVMVIEW_CLASS_SUB_COMMAND_NAME = 'c\n';
|
|
30
|
+
const JVMVIEW_IMPORT_SUB_COMMAND_NAME = 'i\n';
|
|
31
|
+
const JVMVIEW_SEARCH_SUB_COMMAND_NAME = 's\n';
|
|
32
|
+
const JVMVIEW_REMOVE_IMPORT_SUB_COMMAND_NAME = 'r\n';
|
|
33
|
+
|
|
34
|
+
// Sub-commands for ARRAY_COMMAND
|
|
35
|
+
const ARRAY_GET_SUB_COMMAND_NAME = 'g\n';
|
|
36
|
+
const ARRAY_SET_SUB_COMMAND_NAME = 's\n';
|
|
37
|
+
const ARRAY_SLICE_SUB_COMMAND_NAME = 'l\n';
|
|
38
|
+
const ARRAY_LEN_SUB_COMMAND_NAME = 'e\n';
|
|
39
|
+
const ARRAY_CREATE_SUB_COMMAND_NAME = 'c\n';
|
|
40
|
+
|
|
41
|
+
// Sub-commands for MEMORY_COMMAND
|
|
42
|
+
// MEMORY_DEL: Python-side GC notification — tells Java to release the object reference.
|
|
43
|
+
// MEMORY_ATTACH: mark an object as referenced again (used by FinalizerWorker).
|
|
44
|
+
const MEMORY_DEL_SUB_COMMAND_NAME = 'd\n';
|
|
45
|
+
const MEMORY_ATTACH_SUB_COMMAND_NAME = 'a\n';
|
|
46
|
+
|
|
47
|
+
// Sub-commands for LIST_COMMAND
|
|
48
|
+
const LIST_SORT_SUBCOMMAND_NAME = 's\n';
|
|
49
|
+
const LIST_REVERSE_SUBCOMMAND_NAME = 'r\n';
|
|
50
|
+
const LIST_SLICE_SUBCOMMAND_NAME = 'l\n';
|
|
51
|
+
const LIST_CONCAT_SUBCOMMAND_NAME = 'a\n';
|
|
52
|
+
const LIST_MULT_SUBCOMMAND_NAME = 'm\n';
|
|
53
|
+
const LIST_IMULT_SUBCOMMAND_NAME = 'i\n';
|
|
54
|
+
const LIST_COUNT_SUBCOMMAND_NAME = 'f\n';
|
|
55
|
+
|
|
56
|
+
// Sub-commands for REFLECTION_COMMAND
|
|
57
|
+
const REFL_GET_UNKNOWN_SUB_COMMAND_NAME = 'u\n';
|
|
58
|
+
const REFL_GET_MEMBER_SUB_COMMAND_NAME = 'm\n';
|
|
59
|
+
const REFL_GET_JAVA_LANG_STRING_SUB_COMMAND_NAME = 's\n';
|
|
60
|
+
|
|
61
|
+
// Sub-commands for DIR_COMMAND
|
|
62
|
+
const DIR_FIELDS_SUBCOMMAND_NAME = 'f\n';
|
|
63
|
+
const DIR_METHODS_SUBCOMMAND_NAME = 'm\n';
|
|
64
|
+
const DIR_STATIC_SUBCOMMAND_NAME = 's\n'; // static members of a class
|
|
65
|
+
const DIR_JVMVIEW_SUBCOMMAND_NAME = 'v\n'; // members visible in a JVMView
|
|
66
|
+
|
|
67
|
+
// Sub-commands for HELP_COMMAND
|
|
68
|
+
const HELP_OBJECT_SUBCOMMAND_NAME = 'o\n';
|
|
69
|
+
const HELP_CLASS_SUBCOMMAND_NAME = 'c\n';
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Type prefixes (verified against py4j Protocol.class bytecode)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const REFERENCE_TYPE = 'r'; // Java object reference
|
|
75
|
+
const DOUBLE_TYPE = 'd';
|
|
76
|
+
const LONG_TYPE = 'L'; // uppercase L (distinct from LIST_TYPE 'l')
|
|
77
|
+
const INTEGER_TYPE = 'i';
|
|
78
|
+
const BOOLEAN_TYPE = 'b';
|
|
79
|
+
const STRING_TYPE = 's';
|
|
80
|
+
const BYTES_TYPE = 'j';
|
|
81
|
+
const DECIMAL_TYPE = 'D';
|
|
82
|
+
const NULL_TYPE = 'n';
|
|
83
|
+
const VOID_TYPE = 'v';
|
|
84
|
+
const PYTHON_PROXY_TYPE = 'f'; // 'p' is PACKAGE_TYPE in the Java protocol
|
|
85
|
+
const LIST_TYPE = 'l'; // lowercase l
|
|
86
|
+
const SET_TYPE = 'h';
|
|
87
|
+
const ARRAY_TYPE = 't';
|
|
88
|
+
const MAP_TYPE = 'a';
|
|
89
|
+
const ITERATOR_TYPE = 'g';
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Response codes
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
const SUCCESS = 'y';
|
|
95
|
+
const ERROR = 'x';
|
|
96
|
+
const FATAL_ERROR = 'z';
|
|
97
|
+
const RETURN_MESSAGE = '!'; // Java always prefixes every response with this
|
|
98
|
+
const END = 'e';
|
|
99
|
+
const END_COMMAND_PART = '\n';
|
|
100
|
+
|
|
101
|
+
// Special object IDs
|
|
102
|
+
const ENTRY_POINT_OBJECT_ID = 't';
|
|
103
|
+
const STATIC_PREFIX = 'z:'; // prefix for static method/field calls (matches py4j's STATIC_PREFIX)
|
|
104
|
+
const DEFAULT_JVM_ID = 'rj'; // ID of the default JVMView on the Java side (py4j DEFAULT_JVM_ID)
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Encoding helpers
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Encode a JS value to a py4j protocol command part string (with trailing \n).
|
|
112
|
+
* @param {*} value
|
|
113
|
+
* @param {Map} [proxyPool] - JS proxy pool for callback objects
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function encodeCommandPart(value, proxyPool) {
|
|
117
|
+
if (value === null || value === undefined) {
|
|
118
|
+
return NULL_TYPE + END_COMMAND_PART;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof value === 'boolean') {
|
|
122
|
+
return BOOLEAN_TYPE + (value ? 'true' : 'false') + END_COMMAND_PART;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (typeof value === 'bigint') {
|
|
126
|
+
return LONG_TYPE + value.toString() + END_COMMAND_PART;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (typeof value === 'number') {
|
|
130
|
+
if (Number.isInteger(value) && value >= -2147483648 && value <= 2147483647) {
|
|
131
|
+
return INTEGER_TYPE + value.toString() + END_COMMAND_PART;
|
|
132
|
+
}
|
|
133
|
+
// Check if it's a safe long (fits in 64-bit int)
|
|
134
|
+
if (Number.isInteger(value)) {
|
|
135
|
+
return LONG_TYPE + value.toString() + END_COMMAND_PART;
|
|
136
|
+
}
|
|
137
|
+
return DOUBLE_TYPE + value.toString() + END_COMMAND_PART;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (typeof value === 'string') {
|
|
141
|
+
return STRING_TYPE + escapeNewLines(value) + END_COMMAND_PART;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
145
|
+
return BYTES_TYPE + Buffer.from(value).toString('base64') + END_COMMAND_PART;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// JavaObject / JavaClass with a _targetId (set by our wrappers)
|
|
149
|
+
if (value && typeof value === 'object' && value._targetId !== undefined) {
|
|
150
|
+
return REFERENCE_TYPE + value._targetId + END_COMMAND_PART;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// JS callback proxy (for Java interface implementations)
|
|
154
|
+
if (proxyPool && value && value._js4jProxy === true) {
|
|
155
|
+
const proxyId = proxyPool.register(value);
|
|
156
|
+
const interfaces = (value._interfaces || []).join(';');
|
|
157
|
+
return PYTHON_PROXY_TYPE + proxyId + ';' + interfaces + END_COMMAND_PART;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Auto-convert JS arrays to ArrayList
|
|
161
|
+
if (Array.isArray(value)) {
|
|
162
|
+
// Encode as reference to a temporary list — caller must handle auto_convert
|
|
163
|
+
throw new Js4JError(
|
|
164
|
+
'Cannot auto-convert JS Array to Java object without auto_convert=true'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new Js4JError(`Cannot encode value of type ${typeof value}: ${value}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Decode a py4j protocol response line to a JS value.
|
|
173
|
+
* @param {string} answer - Full response line (e.g. "yi42" or "ysHello")
|
|
174
|
+
* @param {object} gatewayClient - The GatewayClient instance for wrapping objects
|
|
175
|
+
* @returns {*}
|
|
176
|
+
*/
|
|
177
|
+
function decodeReturnValue(answer, gatewayClient) {
|
|
178
|
+
if (!answer || answer.length === 0) {
|
|
179
|
+
throw new Js4JNetworkError('Received empty response from gateway');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Java always prefixes every response with RETURN_MESSAGE ('!')
|
|
183
|
+
if (answer[0] === RETURN_MESSAGE) {
|
|
184
|
+
answer = answer.slice(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const responseCode = answer[0];
|
|
188
|
+
|
|
189
|
+
if (responseCode === FATAL_ERROR) {
|
|
190
|
+
throw new Js4JError('Fatal error from gateway: ' + answer.slice(1));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (responseCode === ERROR) {
|
|
194
|
+
// Error payload is a typed value — usually REFERENCE_TYPE + objectId for
|
|
195
|
+
// a Java Throwable registered in the gateway (e.g. "ro0").
|
|
196
|
+
const errPayload = answer.slice(1);
|
|
197
|
+
let javaException = null;
|
|
198
|
+
try {
|
|
199
|
+
const typePrefix = errPayload[0];
|
|
200
|
+
const value = errPayload.slice(1);
|
|
201
|
+
javaException = decodeTypedValue(typePrefix, value, gatewayClient);
|
|
202
|
+
} catch (_) {}
|
|
203
|
+
throw new Js4JJavaError(
|
|
204
|
+
'An error occurred while calling a Java method.',
|
|
205
|
+
errPayload,
|
|
206
|
+
javaException
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (responseCode !== SUCCESS) {
|
|
211
|
+
throw new Js4JNetworkError('Unexpected response code: ' + responseCode);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// SUCCESS path
|
|
215
|
+
if (answer.length < 2) {
|
|
216
|
+
return null; // void / empty success
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const typePrefix = answer[1];
|
|
220
|
+
const value = answer.slice(2);
|
|
221
|
+
|
|
222
|
+
return decodeTypedValue(typePrefix, value, gatewayClient);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Decode a typed value (type prefix + raw string) to a JS value.
|
|
227
|
+
*/
|
|
228
|
+
function decodeTypedValue(typePrefix, value, gatewayClient) {
|
|
229
|
+
switch (typePrefix) {
|
|
230
|
+
case VOID_TYPE:
|
|
231
|
+
return null;
|
|
232
|
+
|
|
233
|
+
case NULL_TYPE:
|
|
234
|
+
return null;
|
|
235
|
+
|
|
236
|
+
case BOOLEAN_TYPE:
|
|
237
|
+
return value.toLowerCase() === 'true';
|
|
238
|
+
|
|
239
|
+
case INTEGER_TYPE:
|
|
240
|
+
return parseInt(value, 10);
|
|
241
|
+
|
|
242
|
+
case LONG_TYPE:
|
|
243
|
+
// Return BigInt if it overflows safe integer range
|
|
244
|
+
try {
|
|
245
|
+
const n = parseInt(value, 10);
|
|
246
|
+
if (n > Number.MAX_SAFE_INTEGER || n < Number.MIN_SAFE_INTEGER) {
|
|
247
|
+
return BigInt(value);
|
|
248
|
+
}
|
|
249
|
+
return n;
|
|
250
|
+
} catch (e) {
|
|
251
|
+
return BigInt(value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
case DOUBLE_TYPE:
|
|
255
|
+
return parseFloat(value);
|
|
256
|
+
|
|
257
|
+
case DECIMAL_TYPE:
|
|
258
|
+
// Return as string to preserve precision (like Python Decimal)
|
|
259
|
+
return value;
|
|
260
|
+
|
|
261
|
+
case STRING_TYPE:
|
|
262
|
+
return unescapeNewLines(value);
|
|
263
|
+
|
|
264
|
+
case BYTES_TYPE:
|
|
265
|
+
return Buffer.from(value, 'base64');
|
|
266
|
+
|
|
267
|
+
case REFERENCE_TYPE:
|
|
268
|
+
return gatewayClient._wrapObject(value, REFERENCE_TYPE);
|
|
269
|
+
|
|
270
|
+
case LIST_TYPE:
|
|
271
|
+
return gatewayClient._wrapObject(value, LIST_TYPE);
|
|
272
|
+
|
|
273
|
+
case SET_TYPE:
|
|
274
|
+
return gatewayClient._wrapObject(value, SET_TYPE);
|
|
275
|
+
|
|
276
|
+
case MAP_TYPE:
|
|
277
|
+
return gatewayClient._wrapObject(value, MAP_TYPE);
|
|
278
|
+
|
|
279
|
+
case ARRAY_TYPE:
|
|
280
|
+
return gatewayClient._wrapObject(value, ARRAY_TYPE);
|
|
281
|
+
|
|
282
|
+
case ITERATOR_TYPE:
|
|
283
|
+
return gatewayClient._wrapObject(value, ITERATOR_TYPE);
|
|
284
|
+
|
|
285
|
+
case PYTHON_PROXY_TYPE:
|
|
286
|
+
// Java is returning a Python/JS proxy back to us — look it up
|
|
287
|
+
return gatewayClient._lookupProxy(value);
|
|
288
|
+
|
|
289
|
+
default:
|
|
290
|
+
throw new Js4JError(`Unknown type prefix '${typePrefix}' in value: ${value}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// String escaping (py4j uses \n as separator, so newlines in strings get escaped)
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Escape newlines in a string value so it can be sent as a single command part.
|
|
300
|
+
* py4j uses \n as separator; literal newlines become \\n.
|
|
301
|
+
*/
|
|
302
|
+
function escapeNewLines(s) {
|
|
303
|
+
return s.replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Unescape newlines in a received string value.
|
|
308
|
+
*/
|
|
309
|
+
function unescapeNewLines(s) {
|
|
310
|
+
return s.replace(/\\n/g, '\n').replace(/\\\\/g, '\\');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = {
|
|
314
|
+
// Command names
|
|
315
|
+
CALL_COMMAND_NAME,
|
|
316
|
+
CONSTRUCTOR_COMMAND_NAME,
|
|
317
|
+
FIELD_COMMAND_NAME,
|
|
318
|
+
SHUTDOWN_GATEWAY_COMMAND_NAME,
|
|
319
|
+
LIST_COMMAND_NAME,
|
|
320
|
+
ARRAY_COMMAND_NAME,
|
|
321
|
+
TABLE_COMMAND_NAME,
|
|
322
|
+
JVMVIEW_COMMAND_NAME,
|
|
323
|
+
REFLECTION_COMMAND_NAME,
|
|
324
|
+
MEMORY_COMMAND_NAME,
|
|
325
|
+
HELP_COMMAND_NAME,
|
|
326
|
+
DIR_COMMAND_NAME,
|
|
327
|
+
BYTES_COMMAND_NAME,
|
|
328
|
+
AUTH_COMMAND_NAME,
|
|
329
|
+
STREAM_COMMAND_NAME,
|
|
330
|
+
|
|
331
|
+
// Field sub-commands
|
|
332
|
+
FIELD_GET_SUB_COMMAND_NAME,
|
|
333
|
+
FIELD_SET_SUB_COMMAND_NAME,
|
|
334
|
+
|
|
335
|
+
// JVMView sub-commands
|
|
336
|
+
JVMVIEW_CLASS_SUB_COMMAND_NAME,
|
|
337
|
+
JVMVIEW_IMPORT_SUB_COMMAND_NAME,
|
|
338
|
+
JVMVIEW_SEARCH_SUB_COMMAND_NAME,
|
|
339
|
+
JVMVIEW_REMOVE_IMPORT_SUB_COMMAND_NAME,
|
|
340
|
+
|
|
341
|
+
// Array sub-commands
|
|
342
|
+
ARRAY_GET_SUB_COMMAND_NAME,
|
|
343
|
+
ARRAY_SET_SUB_COMMAND_NAME,
|
|
344
|
+
ARRAY_SLICE_SUB_COMMAND_NAME,
|
|
345
|
+
ARRAY_LEN_SUB_COMMAND_NAME,
|
|
346
|
+
ARRAY_CREATE_SUB_COMMAND_NAME,
|
|
347
|
+
|
|
348
|
+
// Memory sub-commands
|
|
349
|
+
MEMORY_DEL_SUB_COMMAND_NAME,
|
|
350
|
+
MEMORY_ATTACH_SUB_COMMAND_NAME,
|
|
351
|
+
|
|
352
|
+
// List sub-commands
|
|
353
|
+
LIST_SORT_SUBCOMMAND_NAME,
|
|
354
|
+
LIST_REVERSE_SUBCOMMAND_NAME,
|
|
355
|
+
LIST_SLICE_SUBCOMMAND_NAME,
|
|
356
|
+
LIST_CONCAT_SUBCOMMAND_NAME,
|
|
357
|
+
LIST_MULT_SUBCOMMAND_NAME,
|
|
358
|
+
LIST_IMULT_SUBCOMMAND_NAME,
|
|
359
|
+
LIST_COUNT_SUBCOMMAND_NAME,
|
|
360
|
+
|
|
361
|
+
// Reflection sub-commands
|
|
362
|
+
REFL_GET_UNKNOWN_SUB_COMMAND_NAME,
|
|
363
|
+
REFL_GET_MEMBER_SUB_COMMAND_NAME,
|
|
364
|
+
REFL_GET_JAVA_LANG_STRING_SUB_COMMAND_NAME,
|
|
365
|
+
|
|
366
|
+
// Dir sub-commands
|
|
367
|
+
DIR_FIELDS_SUBCOMMAND_NAME,
|
|
368
|
+
DIR_METHODS_SUBCOMMAND_NAME,
|
|
369
|
+
DIR_STATIC_SUBCOMMAND_NAME,
|
|
370
|
+
DIR_JVMVIEW_SUBCOMMAND_NAME,
|
|
371
|
+
|
|
372
|
+
// Help sub-commands
|
|
373
|
+
HELP_OBJECT_SUBCOMMAND_NAME,
|
|
374
|
+
HELP_CLASS_SUBCOMMAND_NAME,
|
|
375
|
+
|
|
376
|
+
// Type prefixes
|
|
377
|
+
REFERENCE_TYPE,
|
|
378
|
+
DOUBLE_TYPE,
|
|
379
|
+
LONG_TYPE,
|
|
380
|
+
INTEGER_TYPE,
|
|
381
|
+
BOOLEAN_TYPE,
|
|
382
|
+
STRING_TYPE,
|
|
383
|
+
BYTES_TYPE,
|
|
384
|
+
DECIMAL_TYPE,
|
|
385
|
+
NULL_TYPE,
|
|
386
|
+
VOID_TYPE,
|
|
387
|
+
PYTHON_PROXY_TYPE,
|
|
388
|
+
LIST_TYPE,
|
|
389
|
+
SET_TYPE,
|
|
390
|
+
ARRAY_TYPE,
|
|
391
|
+
MAP_TYPE,
|
|
392
|
+
ITERATOR_TYPE,
|
|
393
|
+
|
|
394
|
+
// Response codes
|
|
395
|
+
SUCCESS,
|
|
396
|
+
ERROR,
|
|
397
|
+
FATAL_ERROR,
|
|
398
|
+
RETURN_MESSAGE,
|
|
399
|
+
END,
|
|
400
|
+
END_COMMAND_PART,
|
|
401
|
+
|
|
402
|
+
// Special IDs
|
|
403
|
+
ENTRY_POINT_OBJECT_ID,
|
|
404
|
+
STATIC_PREFIX,
|
|
405
|
+
DEFAULT_JVM_ID,
|
|
406
|
+
|
|
407
|
+
// Functions
|
|
408
|
+
encodeCommandPart,
|
|
409
|
+
decodeReturnValue,
|
|
410
|
+
decodeTypedValue,
|
|
411
|
+
escapeNewLines,
|
|
412
|
+
unescapeNewLines,
|
|
413
|
+
};
|