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/src/gateway.js ADDED
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ const { ConnectionPool, GatewayConnection } = require('./connection');
4
+ const { JVMView } = require('./jvmView');
5
+ const { createJavaObject, gatewayMethods } = require('./javaObject');
6
+ const { CallbackServer, ProxyPool } = require('./callbackServer');
7
+ const {
8
+ SHUTDOWN_GATEWAY_COMMAND_NAME,
9
+ ENTRY_POINT_OBJECT_ID,
10
+ END,
11
+ END_COMMAND_PART,
12
+ decodeReturnValue,
13
+ ARRAY_COMMAND_NAME,
14
+ ARRAY_CREATE_SUB_COMMAND_NAME,
15
+ JVMVIEW_COMMAND_NAME,
16
+ JVMVIEW_IMPORT_SUB_COMMAND_NAME,
17
+ encodeCommandPart,
18
+ STRING_TYPE,
19
+ } = require('./protocol');
20
+ const { Js4JError, Js4JNetworkError } = require('./exceptions');
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // GatewayParameters — mirrors py4j's GatewayParameters
24
+ // ---------------------------------------------------------------------------
25
+
26
+ class GatewayParameters {
27
+ constructor(options = {}) {
28
+ this.host = options.host || '127.0.0.1';
29
+ this.port = options.port || 25333;
30
+ this.authToken = options.authToken || null;
31
+ this.autoField = options.autoField || false;
32
+ this.autoConvert = options.autoConvert || false;
33
+ this.enableMemoryManagement = options.enableMemoryManagement || false;
34
+ this.poolSize = options.poolSize || 4;
35
+ }
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // CallbackServerParameters — mirrors py4j's CallbackServerParameters
40
+ // ---------------------------------------------------------------------------
41
+
42
+ class CallbackServerParameters {
43
+ constructor(options = {}) {
44
+ this.host = options.host || '127.0.0.1';
45
+ this.port = options.port || 25334;
46
+ this.daemonize = options.daemonize !== undefined ? options.daemonize : true;
47
+ this.propagateException = options.propagateException || false;
48
+ }
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // GatewayClient — internal client that actually talks to the GatewayServer
53
+ // ---------------------------------------------------------------------------
54
+
55
+ class GatewayClient {
56
+ constructor(gatewayParameters, proxyPool) {
57
+ this._params = gatewayParameters;
58
+ this._proxyPool = proxyPool || new ProxyPool();
59
+ this._pool = new ConnectionPool(
60
+ {
61
+ host: gatewayParameters.host,
62
+ port: gatewayParameters.port,
63
+ authToken: gatewayParameters.authToken,
64
+ },
65
+ gatewayParameters.poolSize
66
+ );
67
+ }
68
+
69
+ async _sendCommand(command) {
70
+ return this._pool.withConnection((conn) => conn.sendCommand(command));
71
+ }
72
+
73
+ async shutdownGateway() {
74
+ const cmd = SHUTDOWN_GATEWAY_COMMAND_NAME + END + END_COMMAND_PART;
75
+ try {
76
+ await this._sendCommand(cmd);
77
+ } catch (_) {}
78
+ }
79
+
80
+ closeAll() {
81
+ this._pool.closeAll();
82
+ }
83
+ }
84
+
85
+ Object.assign(GatewayClient.prototype, gatewayMethods);
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // JavaGateway — main entry point (mirrors py4j's JavaGateway)
89
+ // ---------------------------------------------------------------------------
90
+
91
+ class JavaGateway {
92
+ constructor(gatewayParameters, callbackServerParameters) {
93
+ this._gatewayParams =
94
+ gatewayParameters instanceof GatewayParameters
95
+ ? gatewayParameters
96
+ : new GatewayParameters(gatewayParameters || {});
97
+
98
+ this._callbackParams =
99
+ callbackServerParameters instanceof CallbackServerParameters
100
+ ? callbackServerParameters
101
+ : new CallbackServerParameters(callbackServerParameters || {});
102
+
103
+ this._proxyPool = new ProxyPool();
104
+ this._client = new GatewayClient(this._gatewayParams, this._proxyPool);
105
+ this._callbackServer = null;
106
+ this._connected = false;
107
+
108
+ this.jvm = null;
109
+ this.entry_point = null;
110
+ }
111
+
112
+ async connect() {
113
+ await this._client._pool.withConnection(async () => {});
114
+ this._connected = true;
115
+ this.jvm = new JVMView(this._client);
116
+ this.entry_point = createJavaObject(ENTRY_POINT_OBJECT_ID, this._client);
117
+ return this;
118
+ }
119
+
120
+ async startCallbackServer() {
121
+ if (this._callbackServer && this._callbackServer.listening) {
122
+ return this._callbackServer;
123
+ }
124
+ this._callbackServer = new CallbackServer({
125
+ host: this._callbackParams.host,
126
+ port: this._callbackParams.port,
127
+ proxyPool: this._proxyPool,
128
+ gatewayClient: this._client,
129
+ });
130
+ await this._callbackServer.start();
131
+ return this._callbackServer;
132
+ }
133
+
134
+ async shutdownCallbackServer() {
135
+ if (this._callbackServer) {
136
+ await this._callbackServer.stop();
137
+ this._callbackServer = null;
138
+ }
139
+ }
140
+
141
+ async close() {
142
+ await this.shutdownCallbackServer();
143
+ this._client.closeAll();
144
+ this._connected = false;
145
+ }
146
+
147
+ async shutdown() {
148
+ try { await this._client.shutdownGateway(); } catch (_) {}
149
+ await this.close();
150
+ }
151
+
152
+ async getField(javaObject, fieldName) {
153
+ return this._client.getField(javaObject, fieldName);
154
+ }
155
+
156
+ async setField(javaObject, fieldName, value) {
157
+ return this._client.setField(javaObject, fieldName, value);
158
+ }
159
+
160
+ async newArray(javaClass, ...dimensions) {
161
+ if (dimensions.length === 0) {
162
+ throw new Js4JError('newArray requires at least one dimension');
163
+ }
164
+ const classFqn = (javaClass && (javaClass._fqn || javaClass._targetId)) || String(javaClass);
165
+ let command =
166
+ ARRAY_COMMAND_NAME +
167
+ ARRAY_CREATE_SUB_COMMAND_NAME +
168
+ STRING_TYPE + classFqn + END_COMMAND_PART;
169
+ for (const dim of dimensions) {
170
+ command += encodeCommandPart(dim, this._proxyPool);
171
+ }
172
+ command += END + END_COMMAND_PART;
173
+ const answer = await this._client._sendCommand(command);
174
+ return decodeReturnValue(answer, this._client);
175
+ }
176
+
177
+ async newJvmView(name = 'custom jvm') {
178
+ const command =
179
+ JVMVIEW_COMMAND_NAME +
180
+ JVMVIEW_IMPORT_SUB_COMMAND_NAME +
181
+ name + END_COMMAND_PART +
182
+ END + END_COMMAND_PART;
183
+ await this._client._sendCommand(command);
184
+ return new (require('./jvmView').JVMView)(this._client);
185
+ }
186
+
187
+ async getMethods(javaObject) { return this._client.getMethods(javaObject); }
188
+ async getFields(javaObject) { return this._client.getFields(javaObject); }
189
+
190
+ async isInstanceOf(javaObject, javaClass) {
191
+ const classFqn =
192
+ typeof javaClass === 'string' ? javaClass : javaClass._fqn || javaClass._targetId;
193
+ return this._client.callMethod(javaObject._targetId, 'getClass().isAssignableFrom', [classFqn]);
194
+ }
195
+
196
+ async javaImport(classFqn) { return this.jvm.javaImport(classFqn); }
197
+
198
+ async help(target) {
199
+ if (typeof target === 'string') return this.jvm.help(target);
200
+ return this._client.help(target);
201
+ }
202
+
203
+ async releaseObject(javaObject) {
204
+ const targetId = typeof javaObject === 'string' ? javaObject : javaObject._targetId;
205
+ return this._client.releaseObject(targetId);
206
+ }
207
+
208
+ async detach(javaObject) { return this.releaseObject(javaObject); }
209
+
210
+ async getStaticMembers(javaClass) { return this._client.getStaticMembers(javaClass); }
211
+
212
+ get connected() { return this._connected; }
213
+ get gatewayParameters() { return this._gatewayParams; }
214
+ get callbackServerParameters() { return this._callbackParams; }
215
+ }
216
+
217
+ module.exports = {
218
+ JavaGateway,
219
+ GatewayParameters,
220
+ CallbackServerParameters,
221
+ GatewayClient,
222
+ };
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ CALL_COMMAND_NAME,
5
+ CONSTRUCTOR_COMMAND_NAME,
6
+ FIELD_COMMAND_NAME,
7
+ FIELD_GET_SUB_COMMAND_NAME,
8
+ FIELD_SET_SUB_COMMAND_NAME,
9
+ MEMORY_COMMAND_NAME,
10
+ MEMORY_DEL_SUB_COMMAND_NAME,
11
+ DIR_COMMAND_NAME,
12
+ DIR_FIELDS_SUBCOMMAND_NAME,
13
+ DIR_METHODS_SUBCOMMAND_NAME,
14
+ DIR_STATIC_SUBCOMMAND_NAME,
15
+ HELP_COMMAND_NAME,
16
+ HELP_OBJECT_SUBCOMMAND_NAME,
17
+ REFLECTION_COMMAND_NAME,
18
+ REFL_GET_MEMBER_SUB_COMMAND_NAME,
19
+ STATIC_PREFIX,
20
+ END,
21
+ END_COMMAND_PART,
22
+ encodeCommandPart,
23
+ decodeReturnValue,
24
+ } = require('./protocol');
25
+
26
+ // Symbols used to expose internal state without polluting method namespace
27
+ const TARGET_ID = Symbol('targetId');
28
+ const CLIENT = Symbol('client');
29
+ const CLASS_FQN = Symbol('classFqn');
30
+
31
+ /**
32
+ * Build the argument section of a command string from a JS args array.
33
+ */
34
+ function buildArgsCommand(args, proxyPool) {
35
+ let s = '';
36
+ for (const arg of args) {
37
+ s += encodeCommandPart(arg, proxyPool);
38
+ }
39
+ return s;
40
+ }
41
+
42
+ /**
43
+ * Wrap a raw Java object ID returned by the gateway into a typed wrapper.
44
+ * The GatewayClient calls this internally via _wrapObject().
45
+ */
46
+ function wrapJavaObject(targetId, typeHint, gatewayClient) {
47
+ const {
48
+ LIST_TYPE,
49
+ SET_TYPE,
50
+ MAP_TYPE,
51
+ ARRAY_TYPE,
52
+ ITERATOR_TYPE,
53
+ } = require('./protocol');
54
+
55
+ const {
56
+ createJavaList,
57
+ createJavaSet,
58
+ createJavaMap,
59
+ createJavaArray,
60
+ createJavaIterator,
61
+ } = require('./collections');
62
+
63
+ switch (typeHint) {
64
+ case LIST_TYPE:
65
+ return createJavaList(targetId, gatewayClient);
66
+ case SET_TYPE:
67
+ return createJavaSet(targetId, gatewayClient);
68
+ case MAP_TYPE:
69
+ return createJavaMap(targetId, gatewayClient);
70
+ case ARRAY_TYPE:
71
+ return createJavaArray(targetId, gatewayClient);
72
+ case ITERATOR_TYPE:
73
+ return createJavaIterator(targetId, gatewayClient);
74
+ default:
75
+ return createJavaObject(targetId, gatewayClient);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Create a Proxy that wraps a Java object reference.
81
+ *
82
+ * Property access returns an async method caller:
83
+ * const result = await javaObj.someMethod(arg1, arg2);
84
+ *
85
+ * Mirrors py4j's JavaObject class.
86
+ */
87
+ function createJavaObject(targetId, gatewayClient) {
88
+ const internal = {
89
+ [TARGET_ID]: targetId,
90
+ [CLIENT]: gatewayClient,
91
+ _targetId: targetId,
92
+ _gatewayClient: gatewayClient,
93
+ };
94
+
95
+ const handler = {
96
+ get(target, prop, receiver) {
97
+ if (typeof prop === 'symbol') return target[prop];
98
+ if (Object.prototype.hasOwnProperty.call(target, prop)) return target[prop];
99
+ if (prop === 'then') return undefined;
100
+ return (...args) => gatewayClient.callMethod(targetId, prop, args);
101
+ },
102
+
103
+ set(target, prop, value) {
104
+ if (prop in target) {
105
+ target[prop] = value;
106
+ return true;
107
+ }
108
+ throw new Error(
109
+ `Cannot set Java field '${prop}' with assignment. Use gateway.setField(obj, '${prop}', value) instead.`
110
+ );
111
+ },
112
+
113
+ has(target, prop) {
114
+ if (prop in target) return true;
115
+ return false;
116
+ },
117
+ };
118
+
119
+ return new Proxy(internal, handler);
120
+ }
121
+
122
+ /**
123
+ * Low-level gateway client methods used by JavaObject, JavaClass, etc.
124
+ * These are mixed into the GatewayClient prototype in gateway.js.
125
+ */
126
+ const gatewayMethods = {
127
+ async callMethod(targetId, methodName, args) {
128
+ const argsStr = buildArgsCommand(args, this._proxyPool);
129
+ const command =
130
+ CALL_COMMAND_NAME +
131
+ targetId + END_COMMAND_PART +
132
+ methodName + END_COMMAND_PART +
133
+ argsStr +
134
+ END + END_COMMAND_PART;
135
+ const answer = await this._sendCommand(command);
136
+ return decodeReturnValue(answer, this);
137
+ },
138
+
139
+ async callConstructor(classFqn, args) {
140
+ const argsStr = buildArgsCommand(args, this._proxyPool);
141
+ const command =
142
+ CONSTRUCTOR_COMMAND_NAME +
143
+ classFqn + END_COMMAND_PART +
144
+ argsStr +
145
+ END + END_COMMAND_PART;
146
+ const answer = await this._sendCommand(command);
147
+ return decodeReturnValue(answer, this);
148
+ },
149
+
150
+ async getField(targetOrId, fieldName) {
151
+ const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId._targetId;
152
+
153
+ // Static fields on classes: targetId has the "z:" prefix (STATIC_PREFIX).
154
+ // The FIELD_GET command only handles registered object instances, not class FQNs.
155
+ // Use the REFLECTION GET_MEMBER command instead — this is what py4j Python does.
156
+ if (targetId.startsWith(STATIC_PREFIX)) {
157
+ const fqn = targetId.slice(STATIC_PREFIX.length);
158
+ const command =
159
+ REFLECTION_COMMAND_NAME +
160
+ REFL_GET_MEMBER_SUB_COMMAND_NAME +
161
+ fqn + END_COMMAND_PART +
162
+ fieldName + END_COMMAND_PART +
163
+ END + END_COMMAND_PART;
164
+ const answer = await this._sendCommand(command);
165
+ return decodeReturnValue(answer, this);
166
+ }
167
+
168
+ const command =
169
+ FIELD_COMMAND_NAME +
170
+ FIELD_GET_SUB_COMMAND_NAME +
171
+ targetId + END_COMMAND_PART +
172
+ fieldName + END_COMMAND_PART +
173
+ END + END_COMMAND_PART;
174
+ const answer = await this._sendCommand(command);
175
+ return decodeReturnValue(answer, this);
176
+ },
177
+
178
+ async setField(targetOrId, fieldName, value) {
179
+ const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId._targetId;
180
+ const command =
181
+ FIELD_COMMAND_NAME +
182
+ FIELD_SET_SUB_COMMAND_NAME +
183
+ targetId + END_COMMAND_PART +
184
+ fieldName + END_COMMAND_PART +
185
+ encodeCommandPart(value, this._proxyPool) +
186
+ END + END_COMMAND_PART;
187
+ const answer = await this._sendCommand(command);
188
+ return decodeReturnValue(answer, this);
189
+ },
190
+
191
+ async releaseObject(targetId) {
192
+ const command =
193
+ MEMORY_COMMAND_NAME +
194
+ MEMORY_DEL_SUB_COMMAND_NAME +
195
+ targetId + END_COMMAND_PART +
196
+ END + END_COMMAND_PART;
197
+ try {
198
+ await this._sendCommand(command);
199
+ } catch (_) {}
200
+ },
201
+
202
+ async getMethods(targetOrId) {
203
+ const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId._targetId;
204
+ const command =
205
+ DIR_COMMAND_NAME +
206
+ DIR_METHODS_SUBCOMMAND_NAME +
207
+ targetId + END_COMMAND_PART +
208
+ END + END_COMMAND_PART;
209
+ const answer = await this._sendCommand(command);
210
+ const decoded = decodeReturnValue(answer, this);
211
+ if (typeof decoded === 'string') return decoded.split('\n').filter(Boolean);
212
+ return decoded;
213
+ },
214
+
215
+ async getFields(targetOrId) {
216
+ const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId._targetId;
217
+ const command =
218
+ DIR_COMMAND_NAME +
219
+ DIR_FIELDS_SUBCOMMAND_NAME +
220
+ targetId + END_COMMAND_PART +
221
+ END + END_COMMAND_PART;
222
+ const answer = await this._sendCommand(command);
223
+ const decoded = decodeReturnValue(answer, this);
224
+ if (typeof decoded === 'string') return decoded.split('\n').filter(Boolean);
225
+ return decoded;
226
+ },
227
+
228
+ async getStaticMembers(classOrId) {
229
+ const targetId = typeof classOrId === 'string' ? classOrId : (classOrId._fqn || classOrId._targetId);
230
+ const command =
231
+ DIR_COMMAND_NAME +
232
+ DIR_STATIC_SUBCOMMAND_NAME +
233
+ targetId + END_COMMAND_PART +
234
+ END + END_COMMAND_PART;
235
+ const answer = await this._sendCommand(command);
236
+ const decoded = decodeReturnValue(answer, this);
237
+ if (typeof decoded === 'string') return decoded.split('\n').filter(Boolean);
238
+ return decoded;
239
+ },
240
+
241
+ async help(targetOrId, pattern) {
242
+ const targetId = typeof targetOrId === 'string' ? targetOrId : targetOrId._targetId;
243
+ let command =
244
+ HELP_COMMAND_NAME +
245
+ HELP_OBJECT_SUBCOMMAND_NAME +
246
+ targetId + END_COMMAND_PART;
247
+ if (pattern) {
248
+ command += pattern + END_COMMAND_PART;
249
+ }
250
+ command += END + END_COMMAND_PART;
251
+ const answer = await this._sendCommand(command);
252
+ return decodeReturnValue(answer, this);
253
+ },
254
+
255
+ _wrapObject(targetId, typeHint) {
256
+ return wrapJavaObject(targetId, typeHint, this);
257
+ },
258
+
259
+ _lookupProxy(proxyId) {
260
+ if (this._proxyPool) {
261
+ return this._proxyPool.get(proxyId);
262
+ }
263
+ return null;
264
+ },
265
+ };
266
+
267
+ module.exports = { createJavaObject, wrapJavaObject, gatewayMethods, buildArgsCommand, TARGET_ID, CLIENT };
package/src/jvmView.js ADDED
@@ -0,0 +1,224 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ JVMVIEW_COMMAND_NAME,
5
+ JVMVIEW_CLASS_SUB_COMMAND_NAME,
6
+ JVMVIEW_IMPORT_SUB_COMMAND_NAME,
7
+ JVMVIEW_REMOVE_IMPORT_SUB_COMMAND_NAME,
8
+ HELP_COMMAND_NAME,
9
+ HELP_CLASS_SUBCOMMAND_NAME,
10
+ END,
11
+ END_COMMAND_PART,
12
+ STATIC_PREFIX,
13
+ DEFAULT_JVM_ID,
14
+ decodeReturnValue,
15
+ encodeCommandPart,
16
+ } = require('./protocol');
17
+
18
+ /**
19
+ * Create a JavaClass proxy for a fully-qualified class name.
20
+ *
21
+ * The returned object behaves as:
22
+ * - A callable that invokes the Java constructor when awaited:
23
+ * const sb = await gateway.jvm.java.lang.StringBuilder('hello');
24
+ * - A source of static method accessors:
25
+ * const val = await gateway.jvm.java.lang.System.currentTimeMillis();
26
+ * - A source of static field accessors via gateway.getField(JavaClass, 'FIELD')
27
+ *
28
+ * Mirrors py4j's JavaClass.
29
+ */
30
+ function createJavaClass(fqn, gatewayClient) {
31
+ // Use a function as the Proxy target so the proxy is callable
32
+ const base = function JavaClass() {};
33
+ base._fqn = fqn;
34
+ // py4j uses a "z:" prefix for static method/field calls so Java knows to
35
+ // dispatch statically rather than looking up a registered object instance.
36
+ base._targetId = STATIC_PREFIX + fqn;
37
+ base._gatewayClient = gatewayClient;
38
+ base._isJavaClass = true;
39
+
40
+ const handler = {
41
+ // Property access → static method/field accessor
42
+ get(target, prop) {
43
+ if (typeof prop === 'symbol') return target[prop];
44
+ // Only pass through OWN properties (e.g. _fqn, _targetId, _isJavaClass).
45
+ // Inherited Function prototype methods (valueOf, toString, call, apply, …)
46
+ // must NOT shadow Java static members with the same name.
47
+ if (Object.prototype.hasOwnProperty.call(target, prop)) return target[prop];
48
+ if (prop === 'then') return undefined; // not a Promise
49
+
50
+ // Return an async function that calls a static method.
51
+ // Java's CallCommand recognises the "z:" prefix and dispatches
52
+ // the call statically on the named class.
53
+ return (...args) => gatewayClient.callMethod(STATIC_PREFIX + fqn, prop, args);
54
+ },
55
+
56
+ // Called when the JavaClass proxy itself is invoked as a function → constructor
57
+ apply(target, thisArg, args) {
58
+ return gatewayClient.callConstructor(fqn, args);
59
+ },
60
+
61
+ // Support `new JavaClass(...)` as well
62
+ construct(target, args) {
63
+ return gatewayClient.callConstructor(fqn, args);
64
+ },
65
+ };
66
+
67
+ return new Proxy(base, handler);
68
+ }
69
+
70
+ /**
71
+ * Create a JavaPackage proxy for a partial FQN (e.g. "java", "java.lang").
72
+ *
73
+ * Attribute traversal accumulates the package path until it looks like a
74
+ * class name (starts with an uppercase letter) or the value is used
75
+ * as a constructor/method target, at which point a JavaClass is returned.
76
+ *
77
+ * Mirrors py4j's JavaPackage.
78
+ */
79
+ function createJavaPackage(fqn, gatewayClient) {
80
+ const base = function JavaPackage() {
81
+ throw new Error(
82
+ `'${fqn}' looks like a package, not a class. ` +
83
+ `Traverse to a class first, e.g. gateway.jvm.java.lang.String`
84
+ );
85
+ };
86
+ base._fqn = fqn;
87
+ base._isJavaPackage = true;
88
+
89
+ const handler = {
90
+ get(target, prop) {
91
+ if (typeof prop === 'symbol') return target[prop];
92
+ if (prop in target) return target[prop];
93
+ if (prop === 'then') return undefined; // not a Promise
94
+
95
+ const childFqn = fqn ? `${fqn}.${prop}` : prop;
96
+
97
+ // Heuristic: uppercase first char → probably a class
98
+ if (prop[0] === prop[0].toUpperCase() && prop[0] !== prop[0].toLowerCase()) {
99
+ return createJavaClass(childFqn, gatewayClient);
100
+ }
101
+ // Otherwise, keep building the package chain
102
+ return createJavaPackage(childFqn, gatewayClient);
103
+ },
104
+
105
+ apply() {
106
+ throw new Error(`'${fqn}' is a Java package, not a class.`);
107
+ },
108
+ };
109
+
110
+ return new Proxy(base, handler);
111
+ }
112
+
113
+ /**
114
+ * JVMView — the root of the jvm.* namespace.
115
+ *
116
+ * Mirrors py4j's JVMView. Provides:
117
+ * gateway.jvm.java.lang.String(...) → constructor
118
+ * gateway.jvm.java.lang.System.out → static field (via getField)
119
+ * gateway.jvm.java.util.ArrayList → JavaClass
120
+ * await gateway.jvmImport('java.util.*') → shortcut imports (client-side)
121
+ */
122
+ class JVMView {
123
+ constructor(gatewayClient, id) {
124
+ this._gatewayClient = gatewayClient;
125
+ this._imports = new Map(); // shortName → fqn
126
+ // JVM view ID used in JVMVIEW protocol commands. Default is "rj" (py4j DEFAULT_JVM_ID).
127
+ this._id = id || DEFAULT_JVM_ID;
128
+
129
+ // Return a Proxy over this instance so that `jvm.java` works
130
+ return new Proxy(this, {
131
+ get(target, prop) {
132
+ if (typeof prop === 'symbol') return target[prop];
133
+ if (prop in target) return target[prop];
134
+ if (prop === 'then') return undefined;
135
+
136
+ // Check shortcut imports first
137
+ if (target._imports.has(prop)) {
138
+ const fqn = target._imports.get(prop);
139
+ return createJavaClass(fqn, target._gatewayClient);
140
+ }
141
+
142
+ // Start building a package/class chain
143
+ if (prop[0] === prop[0].toUpperCase() && prop[0] !== prop[0].toLowerCase()) {
144
+ return createJavaClass(prop, target._gatewayClient);
145
+ }
146
+ return createJavaPackage(prop, target._gatewayClient);
147
+ },
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Import a Java class or package into the JVM namespace, creating a shortcut.
153
+ * Mirrors py4j's `java_import(gateway.jvm, 'java.util.ArrayList')`.
154
+ * @param {string} fqn - Fully-qualified class name (wildcards supported for packages)
155
+ * @returns {Promise<void>}
156
+ */
157
+ async javaImport(fqn) {
158
+ const command =
159
+ JVMVIEW_COMMAND_NAME +
160
+ JVMVIEW_IMPORT_SUB_COMMAND_NAME +
161
+ this._id + END_COMMAND_PART +
162
+ fqn + END_COMMAND_PART +
163
+ END + END_COMMAND_PART;
164
+ const answer = await this._gatewayClient._sendCommand(command);
165
+ const result = decodeReturnValue(answer, this._gatewayClient);
166
+
167
+ // Register the simple name as a shortcut
168
+ const parts = fqn.split('.');
169
+ const simpleName = parts[parts.length - 1];
170
+ if (simpleName !== '*') {
171
+ this._imports.set(simpleName, fqn);
172
+ }
173
+ return result;
174
+ }
175
+
176
+ /**
177
+ * Remove a previously imported class from this JVMView's shortcut namespace.
178
+ * Mirrors py4j's remove_imports().
179
+ * @param {string} fqn - Fully-qualified class name to remove
180
+ * @returns {Promise<void>}
181
+ */
182
+ async removeImport(fqn) {
183
+ const command =
184
+ JVMVIEW_COMMAND_NAME +
185
+ JVMVIEW_REMOVE_IMPORT_SUB_COMMAND_NAME +
186
+ this._id + END_COMMAND_PART +
187
+ fqn + END_COMMAND_PART +
188
+ END + END_COMMAND_PART;
189
+ await this._gatewayClient._sendCommand(command);
190
+
191
+ // Remove from local shortcut map
192
+ const parts = fqn.split('.');
193
+ const simpleName = parts[parts.length - 1];
194
+ if (simpleName !== '*') {
195
+ this._imports.delete(simpleName);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Look up a class by FQN in the gateway's JVM.
201
+ * @param {string} classFqn
202
+ * @returns {JavaClass}
203
+ */
204
+ getClass(classFqn) {
205
+ return createJavaClass(classFqn, this._gatewayClient);
206
+ }
207
+
208
+ /**
209
+ * Get help string for a Java class.
210
+ * @param {string} classFqn
211
+ * @returns {Promise<string>}
212
+ */
213
+ async help(classFqn) {
214
+ const command =
215
+ HELP_COMMAND_NAME +
216
+ HELP_CLASS_SUBCOMMAND_NAME +
217
+ classFqn + END_COMMAND_PART +
218
+ END + END_COMMAND_PART;
219
+ const answer = await this._gatewayClient._sendCommand(command);
220
+ return decodeReturnValue(answer, this._gatewayClient);
221
+ }
222
+ }
223
+
224
+ module.exports = { JVMView, createJavaClass, createJavaPackage };