import { InterfaceProxyOptions, Java, JavaOptions, JavaConfig, ClassConfiguration } from '../native'; import { JavaClass, JavaClassConstructorType, JavaVersion, UnknownJavaClass, UnknownJavaClassType } from './definitions'; export { clearDaemonProxies, clearClassProxies, logging } from '../native'; /** * 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 | 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 it 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 behavior*, 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 declare function ensureJvm(options?: JVMOptions): boolean; /** * 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 declare function getClassLoader(): 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 declare function setClassLoader(classLoader: UnknownJavaClass): void; /** * 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 extends JavaClass { * size(): Promise; * sizeSync(): number; * add(e: T): Promise; * addSync(e: T): void; * get(index: number): Promise; * getSync(index: number): T; * toArray(): Promise; * toArraySync(): T[]; * isEmpty(): Promise; * isEmptySync(): boolean; * } * * // Definitions for class java.util.ArrayList * declare class ArrayListClass extends List { * public constructor(other: ArrayListClass); * public constructor(); * } * * // This causes the class to be imported when the module is loaded. * class ArrayList extends importClass('java.util.ArrayList') {} * * // Create a new ArrayList instance * const list = new ArrayList(); * * // 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 declare function importClass(classname: string, config?: ClassConfiguration): T; /** * @inheritDoc importClass */ export declare function importClassAsync(classname: string, config?: ClassConfiguration): Promise; /** * 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 declare function appendClasspath(path: string | string[]): void; /** * 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 declare function deleteObject(obj: JavaClass): void; /** * 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 declare function isInstanceOf(this_obj: JavaClass, other: string | T): boolean; /** * 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 declare namespace classpath { /** * @inheritDoc appendClasspath */ function append(path: string | string[]): void; /** * Get the loaded files or directories in the class path * * @returns a list of the loaded files */ function get(): string[]; } /** * 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 err 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 null to disable this specific handler. * Only accepts 'stdout' and 'stderr' as the event * 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 StdoutRedirectGuard 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 declare 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 StdoutRedirectGuard instance. Keep this instance in scope to not lose the redirect. */ function enableRedirect(stdout?: StdoutCallback | null, stderr?: StdoutCallback | null): StdoutRedirectGuard; } /** * 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 = 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; /** * A record of methods to implement. * Useful for creating a proxy for a specific interface. */ export type ProxyRecord = Partial>; /** * A generic proxy record. */ export type AnyProxyRecord = Record; /** * 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 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 declare function newProxy = AnyProxyRecord>(interfaceName: string, methods: T, opts?: InterfaceProxyOptions): JavaInterfaceProxy; /** * Get the static java instance. * This has no real use, all important methods are exported explicitly. */ export declare function getJavaInstance(): Java | null; /** * @inheritDoc JavaConfig */ export declare const config: JavaConfig;