-
Notifications
You must be signed in to change notification settings - Fork 6
/
java.ts
836 lines (807 loc) · 26.5 KB
/
java.ts
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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
import {
InterfaceProxyOptions,
Java,
JavaOptions,
JavaConfig,
ClassConfiguration,
} from '../native';
import {
JavaClass,
JavaClassConstructorType,
JavaVersion,
UnknownJavaClass,
UnknownJavaClassType,
} from './definitions';
import { getJavaLibPath, getNativeLibPath } from './nativeLib';
export { clearDaemonProxies, clearClassProxies, logging } from '../native';
/**
* The static java instance
*/
let javaInstance: Java | null = null;
/**
* Options for creating the Java VM.
*/
export interface JVMOptions extends JavaOptions {
/***
* The path to the native library
*/
libPath?: string | null;
/**
* The version of the jvm to request
*/
version?: string | JavaVersion | null;
/**
* Additional arguments to pass to the JVM
*/
opts?: Array<string> | null;
/**
* Whether this runs inside a packaged electron app
*/
isPackagedElectron?: boolean;
}
/**
* Ensure the java vm is created.
* If the jvm is already created, this does nothing.
* If the vm is not created yet, the jvm will be created upon this call.
* This method is also called every time with no arguments when any call
* to the jvm is done in another method.
*
* ## Examples
* Specify the path to jvm.(dylib|dll|so) manually,
* specify the java version to use and set to use daemon threads.
* ```ts
* import { ensureJvm, JavaVersion } from 'java-bridge';
*
* ensureJvm({
* libPath: 'path/to/jvm.dll',
* version: JavaVersion.VER_9,
* });
* ```
*
* Let the plugin find the jvm.(dylib|dll|so)
* ```ts
* ensureJvm({
* version: JavaVersion.VER_9,
* });
* ```
*
* Let the plugin find the jvm.(dylib|dll|so) and use the default options
* ```ts
* ensureJvm();
* ```
*
* ## Notes on the `classpath` option
*
* If you need to set the class path *before* jvm startup, for example
* when using libraries with custom class loaders, you'd need to call
* `ensureJvm` *before* making any other call to `java-bridge` as those
* methods may themselves call `ensureJvm` with no arguments
* (see comment above). Altering the startup classpath after jvm boot is
* not possible, you can only alter the runtime classpath using
* `appendClasspath` or `appendClasspathAny` which may not reflect
* in an altered classpath in your java application/library if your
* application is using a custom classpath (e.g. Spring Boot).
*
* Also, it is not possible to restart the jvm after is has been started
* once, in order to alter the startup classpath. This is due to some
* limitations with the destructor feature of the node.js native api,
* which may not call the destructor in time and having two jvm instances
* in the same application is not allowed by java. Additionally, destroying
* the jvm instance may cause *undefined behaviour*, which may or may not
* cause the application to crash. Let's not do that.
*
* @param options the options to use when creating the jvm
* @return true if the jvm was created and false if the jvm already existed and was not created
*/
export function ensureJvm(options?: JVMOptions): boolean {
if (!javaInstance) {
javaInstance = new Java(
options?.libPath,
options?.version,
options?.opts,
options,
getJavaLibPath(),
getNativeLibPath(options?.isPackagedElectron ?? false)
);
return true;
} else {
return false;
}
}
/**
* Get the addon's internal class loader.
* This may be used in combination with {@link setClassLoader}
* to create a custom class loader and load classes from it.
*
* ## Example
* ```ts
* import { getClassLoader, setClassLoader, importClass } from 'java-bridge';
*
* const classLoader = getClassLoader();
*
* const URLClassLoader = importClass('java.net.URLClassLoader');
* const URL = importClass('java.net.URL');
*
* // This actually happens internally when appendClasspath is called
* const newClassLoader = new URLClassLoader([new URL('file:///path/to/my.jar')], classLoader);
*
* setClassLoader(newClassLoader);
* ```
*/
export function getClassLoader(): UnknownJavaClass {
ensureJvm();
return javaInstance!.classLoader as UnknownJavaClass;
}
/**
* Set the internal class loader to use.
* This allows you to create a custom class loader
* and import classes using {@link importClass} or {@link importClassAsync}.
* Without setting the custom class loader, the default class loader will be used.
*
* @param classLoader the new class loader to use
*/
export function setClassLoader(classLoader: UnknownJavaClass): void {
ensureJvm();
javaInstance!.classLoader = classLoader;
}
/**
* Import a class.
* Returns the constructor of the class to be created.
* For example, import "java.util.ArrayList" for a java Array List.
*
* Define a custom class type for the imported class and pass the
* constructor type of the class as the template parameter to get
* the proper type returned. You could also just cast the result.
*
* When passing a {@link ClassConfiguration} object, the config will be applied
* to this class. This config does not apply to any other class.
* If you want to change the config for all classes, use the
* {@link config} class in order to do that. Any undefined field
* in the config will be ignored and the default value will be used.
* If you want to change the sync and async suffixes to an empty string,
* you can pass an empty string as the suffix.
*
* ## Examples
* ### Import ``java.util.ArrayList`` and create a new instance of it
* ```ts
* import { importClass } from 'java-bridge';
*
* // Import java.util.ArrayList
* const ArrayList = importClass('java.util.ArrayList');
*
* // Create a new instance of ArrayList
* const list = new ArrayList();
* ```
*
* ### Import ``java.util.ArrayList`` with types
* ```ts
* import { importClass, JavaClass, JavaType } from 'java-bridge';
*
* // Definitions for class java.util.List
* declare class List<T extends JavaType> extends JavaClass {
* size(): Promise<number>;
* sizeSync(): number;
* add(e: T): Promise<void>;
* addSync(e: T): void;
* get(index: number): Promise<T>;
* getSync(index: number): T;
* toArray(): Promise<T[]>;
* toArraySync(): T[];
* isEmpty(): Promise<boolean>;
* isEmptySync(): boolean;
* }
*
* // Definitions for class java.util.ArrayList
* declare class ArrayListClass<T extends JavaType> extends List<T> {
* public constructor(other: ArrayListClass<T>);
* public constructor();
* }
*
* // This causes the class to be imported when the module is loaded.
* class ArrayList<T> extends importClass<typeof ArrayListClass>('java.util.ArrayList')<T> {}
*
* // Create a new ArrayList instance
* const list = new ArrayList<string>();
*
* // Add some contents to the list
* list.add('Hello');
* list.add('World');
*
* // Check the list contents
* assert.equals(list.sizeSync(), 2);
* assert.equals(list.getSync(0), 'Hello');
* assert.equals(list.getSync(1), 'World');
* ```
*
* ### Import ``java.util.ArrayList`` with custom config
* ```ts
* import { importClass, config } from 'java-bridge';
*
* // Import java.util.ArrayList with custom config
* const ArrayList = importClass('java.util.ArrayList', {
* syncSuffix: '',
* asyncSuffix: 'Async',
* });
*
* // Create a new instance of ArrayList
* const list = new ArrayList();
*
* // Call the async method
* await list.addAsync('Hello World!');
*
* // Call the sync method
* list.add('Hello World!');
* ```
*
* @template T the type of the java class to import as a js type
* @param classname the name of the class to resolve
* @param config the config to use when importing the class
* @return the java class constructor
*/
export function importClass<
T extends JavaClassConstructorType = UnknownJavaClassType,
>(classname: string, config?: ClassConfiguration): T {
ensureJvm();
return javaInstance!.importClass(classname, config) as T;
}
/**
* @inheritDoc importClass
*/
export function importClassAsync<
T extends JavaClassConstructorType = UnknownJavaClassType,
>(classname: string, config?: ClassConfiguration): Promise<T> {
ensureJvm();
return javaInstance!.importClassAsync(classname, config) as Promise<T>;
}
/**
* Append a single or multiple jars to the class path.
*
* Just replaces the old internal class loader with a new one containing the new jars.
* This doesn't check if the jars are valid and/or even exist.
* The new classpath will be available to all classes imported after this call.
*
* If you want to import whole directories, you can use glob patterns.
*
* ## Example
* ### Append single files
* ```ts
* import { appendClasspath } from 'java-bridge';
*
* // Append a single jar to the class path
* appendClasspath('/path/to/jar.jar');
*
* // Append multiple jars to the class path
* appendClasspath(['/path/to/jar1.jar', '/path/to/jar2.jar']);
* ```
* or
* ```ts
* import { classpath } from 'java-bridge';
*
* // Append a single jar to the class path
* classpath.append('/path/to/jar.jar');
* ```
*
* ### Append a directory to the class path
* ```ts
* import { appendClasspath } from 'java-bridge';
*
* // Append a directory to the class path
* appendClasspath('/path/to/dir/*');
* // Append just the jar files in the directory
* appendClasspath('/path/to/dir/*.jar');
* ```
*
* @param path the path(s) to add
*/
export function appendClasspath(path: string | string[]): void {
ensureJvm();
javaInstance!.appendClasspath(path);
}
/**
* Instantly delete a java object and allow the object
* to be garbage collected by the java gc.
* Calling this method on an object that has already been
* deleted will throw an error. If an object has been deleted,
* it is not possible to use it anymore, although the object
* may still exist in the javascript process.
*
* **NOTE:** Use this method with caution, as there is no proper
* synchronization with deleting the object and other methods using
* this object in an asynchronous manner. This may cause the object
* to be deleted while another method is still using it.
* This may cause the program to crash in very rare cases.
*
* @param obj the object to delete
*/
export function deleteObject(obj: JavaClass): void {
ensureJvm();
javaInstance!.delete(obj);
}
/**
* Check if `this_obj` is instance of `other`.
* This uses the native java `instanceof` operator.
* You may want to use this if {@link JavaClass.instanceOf}
* is overridden, as that method itself does not override
* any method defined in the specific java class named 'instanceOf'.
*
* ## Example
* ```ts
* import { instanceOf, importClass } from 'java-bridge';
*
* const ArrayList = importClass('java.util.ArrayList');
* const list = new ArrayList();
*
* isInstanceOf(list, ArrayList); // true
* isInstanceOf(list, 'java.util.ArrayList'); // true
* isInstanceOf(list, 'java.util.List'); // true
* isInstanceOf(list, 'java.util.Collection'); // true
* isInstanceOf(list, 'java.lang.Object'); // true
* isInstanceOf(list, 'java.lang.String'); // false
*
* // You can also use the instanceOf method (if not overridden)
* list.instanceOf(ArrayList); // true
* list.instanceOf('java.util.ArrayList'); // true
* list.instanceOf('java.util.List'); // true
* list.instanceOf('java.util.Collection'); // true
* list.instanceOf('java.lang.Object'); // true
* list.instanceOf('java.lang.String'); // false
* ```
*
* @param this_obj the object to check
* @param other the class or class name to check against
* @return true if `this_obj` is an instance of `other`
*/
export function isInstanceOf<T extends object>(
this_obj: JavaClass,
other: string | T
): boolean {
ensureJvm();
return javaInstance!.isInstanceOf(this_obj, other);
}
/**
* Methods for altering and querying the class path.
* @example
* import { classpath } from 'java-bridge';
*
* // Append a jar to the class path
* classpath.append('/path/to/jar.jar');
*
* assert.equal(classpath.get().length, 1);
* assert.equal(classpath.get()[0], '/path/to/jar.jar');
*/
export namespace classpath {
/**
* @inheritDoc appendClasspath
*/
export function append(path: string | string[]): void {
appendClasspath(path);
}
/**
* Get the loaded files or directories in the class path
*
* @returns a list of the loaded files
*/
export function get(): string[] {
ensureJvm();
return javaInstance!.loadedJars;
}
}
/**
* A callback for any output redirected from stdout/stderr from the java process.
*
* @param err an error if the conversion of the output failed.
* This is null if the output was valid. This will probably never be set.
* @param data the data that was converted. This is unset if <code>err</code> is set.
*/
export type StdoutCallback = (err: Error | null, data?: string) => void;
/**
* The class guarding the stdout redirect.
* Keep this instance in scope to not lose the redirect.
* As soon as this gets garbage collected, the redirection
* of the stdout/stderr will be stopped. Only one instance
* of this can exist at a time. Call {@link reset} to stop
* redirecting the program output and release this class
* instance early.
*
* This can be created by calling {@link stdout.enableRedirect}.
*
* ## Example
* ```ts
* import { stdout } from 'java-bridge';
*
* const guard = stdout.enableRedirect((_, data) => {
* console.log('Stdout:', data);
* }, (_, data) => {
* console.error('Stderr:', data);
* });
*
* // Change the receiver method
* guard.on('stderr', (_, data) => {
* console.warn('Stderr:', data);
* });
*
* // Disable a receiver
* guard.on('stdout', null);
*
* // Disable stdout redirect
* guard.reset();
* ```
*
* ## See also
* * {@link stdout.enableRedirect}
*/
export interface StdoutRedirectGuard {
/**
* Set the stdout/stderr event handler.
* Pass <code>null</code> to disable this specific handler.
* Only accepts 'stdout' and 'stderr' as the <code>event</code>
* argument. Overwrites the previous handler.
*
* @param event the event to listen on
* @param callback the callback
*/
on(event: 'stdout' | 'stderr', callback: StdoutCallback | null): void;
/**
* Reset this <code>StdoutRedirectGuard</code> instance.
* After this call, the stdout/stderr will no longer
* be redirected to the specified methods and any call
* to this class will throw an error as this counts as destroyed.
*/
reset(): void;
}
/**
* A namespace containing methods for redirecting the stdout/stderr of the java process.
*
* ## See also
* * {@link StdoutRedirectGuard}
* * {@link stdout.enableRedirect}
*/
export namespace stdout {
/**
* Enable stdout/stderr redirection.
*
* Pass methods for the stdout and stderr output to be redirected to.
* These methods must accept an error as the first argument,
* although this will probably never be set and can be ignored.
* The second argument is the data that was redirected.
*
* Setting any method to ``null`` or ``undefined`` will disable the redirect for that method.
* This also allows you not set any handler which does not make any sense at all.
*
* ## Examples
* ### Redirect all data to the js console
* ```ts
* import { stdout } from 'java-bridge';
*
* const guard = stdout.enableRedirect((_, data) => {
* console.log('Stdout:', data);
* }, (_, data) => {
* console.error('Stderr:', data);
* });
* ```
*
* ### Redirect stdout to the js console
* ```ts
* const guard = stdout.enableRedirect((_, data) => {
* console.log('Stdout:', data);
* });
* ```
*
* ### Redirect stderr to the js console
* ```ts
* const guard = stdout.enableRedirect(null, (_, data) => {
* console.error('Stderr:', data);
* });
* ```
*
* ### Redirect nothing to the js console (y tho)
* This enables you to print nothing to nowhere.
* ```ts
* // Why would you do this?
* const guard = stdout.enableRedirect(null, null);
*
* // Or
* const guard = stdout.enableRedirect();
* ```
*
* @see StdoutRedirectGuard
* @see StdoutCallback
* @param stdout the callback to be called when stdout is received
* @param stderr the callback to be called when stderr is received
* @returns a <code>StdoutRedirectGuard</code> instance. Keep this instance in scope to not lose the redirect.
*/
export function enableRedirect(
stdout?: StdoutCallback | null,
stderr?: StdoutCallback | null
): StdoutRedirectGuard {
ensureJvm();
return javaInstance!.setStdoutCallbacks(stdout, stderr);
}
}
/**
* The class for implementing java interfaces.
* Keep this instance in scope to not destroy the java object.
* Call {@link reset} to instantly destroy this instance.
*
* ## Notes
* Keeping this instance alive may cause your process not to exit
* early. Thus, you must wait for the javascript garbage collector
* to destroy this instance even if you called {@link reset}.
*
* Once this instance has been destroyed, either by calling {@link reset}
* or the garbage collector, any call to any method defined earlier
* by {@link newProxy} will throw an error in the java process.
*
* ## Example
* ```ts
* import { newProxy } from 'java-bridge';
*
* const proxy = newProxy('path.to.MyInterface', {
* // Define methods...
* });
*
* // Do something with the proxy
* instance.someMethod(proxy);
*
* // Destroy the proxy
* proxy.reset();
* ```
*
* ## See also
* * {@link newProxy}
*/
export interface JavaInterfaceProxy<T extends ProxyRecord<T> = AnyProxyRecord> {
/**
* A dummy property to make sure the type is correct.
* This property will never be set.
*/
_dummy?: T;
/**
* Destroy the proxy class.
* After this call any call to any method defined by the
* interface will throw an error on the java side. This error
* may be thrown back to the node process, if you are not
* specifically implementing methods that will be called
* from another (java) thread.
* Throws an error if the proxy has already been destroyed.
*
* @param force whether to force the destruction of the proxy
* if it should be kept alive as a daemon
*/
reset(force?: boolean): void;
}
/**
* An interface proxy method.
* Any arguments passed to this method are values converted from java values.
* The return value will be converted back to a java type.
*
* @param args the arguments passed from the java process
* @return the value to pass back to the java process
*/
export type ProxyMethod = (...args: any[]) => any;
type InternalProxyRecord = Parameters<
typeof Java.prototype.createInterfaceProxy
>[1];
/**
* A record of methods to implement.
* Useful for creating a proxy for a specific interface.
*/
export type ProxyRecord<T> = Partial<Record<keyof T, ProxyMethod>>;
/**
* A generic proxy record.
*/
export type AnyProxyRecord = Record<string, ProxyMethod>;
/**
* Create a new java interface proxy.
* This allows you to implement java interfaces in javascript.
*
* Pass an object as the second argument with the names of the
* methods you want to implement as keys and the implementations
* as values in order to expose these methods to the java process.
* Any arguments will be converted to javascript values and
* return values will be converted to java values.
*
* When the java process tries to call any method which is
* not implemented by the proxy, an error will be thrown.
*
* ## Examples
* ### Implement ``java.lang.Runnable``
* ```ts
* import { newProxy, importClass } from 'java-bridge';
*
* // Define the interface
* const runnable = newProxy('java.lang.Runnable', {
* run: (): void => {
* console.log('Hello World!');
* }
* });
*
* // Note: You can't do something like this:
* // runnable.run();
*
* // Pass the proxy to a java method instead:
* const Thread = importClass('java.lang.Thread');
* const thread = new Thread(runnable); // <- Pass the proxy here
*
* // NOTE: You don't have to call this asynchronously
* // as this call instantly returns.
* thread.startSync();
* ```
*
* ### Implement ``java.util.function.Function`` to transform a string
* ```ts
* const func = newProxy('java.util.function.Function', {
* // Any parameters and return types will be automatically converted
* apply: (str: string): string => {
* return str.toUpperCase();
* }
* });
*
* // Import the string class
* const JString = java.importClass('java.lang.String');
* const str = new JString('hello');
*
* // Pass the proxy.
* // NOTE: You must call this method async otherwise your program will hang.
* // See notes for more info.
* const transformed = await str.transform(func);
*
* assert.assertEquals(transformed, 'HELLO');
* ```
*
* Which is equivalent to the following java code:
* ```java
* Function<String, String> func = new Function<>() {
* @Override
* public String apply(String str) {
* return str.toUpperCase();
* }
* };
*
* String str = "hello";
* String transformed = str.transform(func);
* assert.assertEquals(transformed, "HELLO");
* ```
*
* #### Throwing exceptions
* Any exceptions thrown by the proxy will be converted to java exceptions
* and then rethrown in the java process. This may cause the exception
* to again be rethrown in the javascript process.
* ```ts
* const func = newProxy('java.util.function.Function', {
* apply: (str: string): string => {
* throw new Error('Something went wrong');
* }
* });
*
* const JString = java.importClass('java.lang.String');
* const str = new JString('hello');
*
* // This will re-throw the above error
* const transformed: never = await str.transform(func);
* ```
*
* ## Notes
* * Keep this instance in scope to not destroy the interface proxy.
* * Call {@link JavaInterfaceProxy.reset} to instantly destroy this instance.
* Please note that calling {@link JavaInterfaceProxy.reset} is not necessary,
* the proxy instance will be automatically destroyed when it is garbage collected.
* Calling {@link JavaInterfaceProxy.reset} will just speed up the process.
* * If any method is queried by the java process and not implemented in here,
* an exception will be thrown in the java process.
* * Any errors thrown in the javascript process will be rethrown in the java process.
*
* ### Possible deadlock warning
* When calling a java method that uses an interface defined by this, you must call
* that method using the interface asynchronously as Node.js is single threaded
* and can't wait for the java method to return while calling the proxy method at the
* same time.
*
* If you still want to call everything in a synchronous manner, make sure to enable
* running the event loop while waiting for a java method to return by setting
* {@link JavaConfig.runEventLoopWhenInterfaceProxyIsActive} to true.
* **This may cause application crashes, so it is strongly recommended to just use async methods.**
*
* ### Keeping the proxy alive
* If you want to keep the proxy alive, you must keep this instance in scope.
* If that is not an option for you, you can manually keep the proxy alive
* by setting the {@link InterfaceProxyOptions}.keepAsDaemon option to true.
*
* ```ts
* const proxy = newProxy('java.lang.Runnable', {
* run: (): void => {
* console.log('Hello World!');
* }
* }, {
* keepAsDaemon: true
* });
*
* const TimeUnit = java.importClass('java.util.concurrent.TimeUnit');
* const ScheduledThreadPoolExecutor = java.importClass(
* 'java.util.concurrent.ScheduledThreadPoolExecutor'
* );
* const executor = new ScheduledThreadPoolExecutor(1);
*
* // 'proxy' will eventually be garbage collected,
* // but it will be kept alive due to this option.
* executor.scheduleAtFixedRateSync(proxy, 0, 1, TimeUnit.SECONDS);
* ```
*
* This will keep the proxy alive internally, thus the instance can be moved
* out of scope. However, this will also keep the JVM alive, so you should
* only use this if you are sure that you want to keep the JVM alive.
*
* If you want to destroy the proxy, you must call {@link clearDaemonProxies}.
* This will destroy all proxies which are kept alive by this option.
* Calling {@link JavaInterfaceProxy.reset} will not destroy a proxy
* kept alive by this option unless the force option is set to true.
*
* ## See also
* * {@link JavaInterfaceProxy}
* * {@link InterfaceProxyOptions}
*
* @param interfaceName the name of the java interface to implement
* @param methods the methods to implement.
* @param opts the options to use
* @returns a proxy class to pass back to the java process
*/
export function newProxy<T extends ProxyRecord<T> = AnyProxyRecord>(
interfaceName: string,
methods: T,
opts?: InterfaceProxyOptions
): JavaInterfaceProxy<T> {
ensureJvm();
const proxyMethods: InternalProxyRecord = Object.create(null);
for (const [name, method] of Object.entries(methods)) {
proxyMethods[name] = (
err: null | Error,
callback: (err: Error | null, data?: any | null) => void,
...args: any[]
): void => {
if (err) {
// This is extremely unlikely to happen.
// Probably out of memory or something.
throw err;
}
try {
const res = (method as ProxyMethod)(...args);
if (res instanceof Promise) {
res.then(
(res: unknown) => callback(null, res),
(e: unknown) => {
if (e instanceof Error) {
callback(e);
} else {
callback(new Error(String(e)));
}
}
);
} else {
callback(null, res);
}
} catch (e: unknown) {
if (e instanceof Error) {
callback(e);
} else {
callback(new Error(String(e)));
}
}
};
}
return javaInstance!.createInterfaceProxy(
interfaceName,
proxyMethods,
opts
) as JavaInterfaceProxy<T>;
}
/**
* Get the static java instance.
* This has no real use, all important methods are exported explicitly.
*/
export function getJavaInstance(): Java | null {
return javaInstance;
}
/**
* @inheritDoc JavaConfig
*/
export const config = new JavaConfig();